Animation.cpp (68913B)
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 "Animation.h" 8 9 #include "AnimationUtils.h" 10 #include "ScrollTimelineAnimationTracker.h" 11 #include "mozAutoDocUpdate.h" 12 #include "mozilla/AnimationEventDispatcher.h" 13 #include "mozilla/AnimationTarget.h" 14 #include "mozilla/AutoRestore.h" 15 #include "mozilla/CycleCollectedJSContext.h" 16 #include "mozilla/DeclarationBlock.h" 17 #include "mozilla/Likely.h" 18 #include "mozilla/Maybe.h" // For Maybe 19 #include "mozilla/StaticPrefs_dom.h" 20 #include "mozilla/dom/AnimationBinding.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/dom/DocumentInlines.h" 23 #include "mozilla/dom/DocumentTimeline.h" 24 #include "mozilla/dom/MutationObservers.h" 25 #include "mozilla/dom/Promise.h" 26 #include "nsAnimationManager.h" // For CSSAnimation 27 #include "nsComputedDOMStyle.h" 28 #include "nsDOMCSSAttrDeclaration.h" // For nsDOMCSSAttributeDeclaration 29 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch 30 #include "nsIFrame.h" 31 #include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr 32 #include "nsTransitionManager.h" // For CSSTransition 33 34 namespace mozilla::dom { 35 36 // Static members 37 uint64_t Animation::sNextAnimationIndex = 0; 38 39 NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper, mTimeline, 40 mEffect, mReady, mFinished) 41 42 NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper) 43 NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper) 44 45 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation) 46 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 47 48 JSObject* Animation::WrapObject(JSContext* aCx, 49 JS::Handle<JSObject*> aGivenProto) { 50 return dom::Animation_Binding::Wrap(aCx, this, aGivenProto); 51 } 52 53 // --------------------------------------------------------------------------- 54 // 55 // Utility methods 56 // 57 // --------------------------------------------------------------------------- 58 59 namespace { 60 // A wrapper around nsAutoAnimationMutationBatch that looks up the 61 // appropriate document from the supplied animation. 62 class MOZ_RAII AutoMutationBatchForAnimation { 63 public: 64 explicit AutoMutationBatchForAnimation(const Animation& aAnimation) { 65 NonOwningAnimationTarget target = aAnimation.GetTargetForAnimation(); 66 if (!target) { 67 return; 68 } 69 70 // For mutation observers, we use the OwnerDoc. 71 mAutoBatch.emplace(target.mElement->OwnerDoc()); 72 } 73 74 private: 75 Maybe<nsAutoAnimationMutationBatch> mAutoBatch; 76 }; 77 } // namespace 78 79 // --------------------------------------------------------------------------- 80 // 81 // Animation interface: 82 // 83 // --------------------------------------------------------------------------- 84 85 Animation::Animation(nsIGlobalObject* aGlobal) 86 : DOMEventTargetHelper(aGlobal), 87 mAnimationIndex(sNextAnimationIndex++), 88 mRTPCallerType(aGlobal->GetRTPCallerType()) {} 89 90 Animation::~Animation() = default; 91 92 /* static */ 93 already_AddRefed<Animation> Animation::ClonePausedAnimation( 94 nsIGlobalObject* aGlobal, const Animation& aOther, AnimationEffect& aEffect, 95 AnimationTimeline& aTimeline) { 96 // FIXME: Bug 1805950: Support printing for scroll-timeline once we resolve 97 // the spec issue. 98 if (aOther.UsingScrollTimeline()) { 99 return nullptr; 100 } 101 102 RefPtr<Animation> animation = new Animation(aGlobal); 103 104 // Setup the timeline. We always use document-timeline of the new document, 105 // even if the timeline of |aOther| is null. 106 animation->mTimeline = &aTimeline; 107 108 // Setup the playback rate. 109 animation->mPlaybackRate = aOther.mPlaybackRate; 110 111 // Setup the timing. 112 const Nullable<TimeDuration> currentTime = aOther.GetCurrentTimeAsDuration(); 113 if (!aOther.GetTimeline()) { 114 // This simulates what we do in SetTimelineNoUpdate(). It's possible to 115 // preserve the progress if the previous timeline is a scroll-timeline. 116 // So for null timeline, it may have a progress and the non-null current 117 // time. 118 if (!currentTime.IsNull()) { 119 animation->SilentlySetCurrentTime(currentTime.Value()); 120 } 121 animation->mPreviousCurrentTime = animation->GetCurrentTimeAsDuration(); 122 } else { 123 animation->mHoldTime = currentTime; 124 if (!currentTime.IsNull()) { 125 // FIXME: Should we use |timelineTime| as previous current time here? It 126 // seems we should use animation->GetCurrentTimeAsDuration(), per 127 // UpdateFinishedState(). 128 const Nullable<TimeDuration> timelineTime = 129 aTimeline.GetCurrentTimeAsDuration(); 130 MOZ_ASSERT(!timelineTime.IsNull(), "Timeline not yet set"); 131 animation->mPreviousCurrentTime = timelineTime; 132 } 133 } 134 135 // Setup the effect's link to this. 136 animation->mEffect = &aEffect; 137 animation->mEffect->SetAnimation(animation); 138 139 animation->mPendingState = PendingState::PausePending; 140 141 // We expect our relevance to be the same as the orginal. 142 animation->mIsRelevant = aOther.mIsRelevant; 143 144 animation->PostUpdate(); 145 animation->mTimeline->NotifyAnimationUpdated(*animation); 146 return animation.forget(); 147 } 148 149 NonOwningAnimationTarget Animation::GetTargetForAnimation() const { 150 AnimationEffect* effect = GetEffect(); 151 NonOwningAnimationTarget target; 152 if (!effect || !effect->AsKeyframeEffect()) { 153 return target; 154 } 155 return effect->AsKeyframeEffect()->GetAnimationTarget(); 156 } 157 158 /* static */ 159 already_AddRefed<Animation> Animation::Constructor( 160 const GlobalObject& aGlobal, AnimationEffect* aEffect, 161 const Optional<AnimationTimeline*>& aTimeline, ErrorResult& aRv) { 162 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 163 164 AnimationTimeline* timeline; 165 Document* document = 166 AnimationUtils::GetCurrentRealmDocument(aGlobal.Context()); 167 168 if (aTimeline.WasPassed()) { 169 timeline = aTimeline.Value(); 170 } else { 171 if (!document) { 172 aRv.Throw(NS_ERROR_FAILURE); 173 return nullptr; 174 } 175 timeline = document->Timeline(); 176 } 177 178 RefPtr<Animation> animation = new Animation(global); 179 animation->SetTimelineNoUpdate(timeline); 180 animation->SetEffectNoUpdate(aEffect); 181 182 return animation.forget(); 183 } 184 185 void Animation::SetId(const nsAString& aId) { 186 if (mId == aId) { 187 return; 188 } 189 mId = aId; 190 MutationObservers::NotifyAnimationChanged(this); 191 } 192 193 void Animation::SetEffect(AnimationEffect* aEffect) { 194 SetEffectNoUpdate(aEffect); 195 PostUpdate(); 196 } 197 198 // https://drafts.csswg.org/web-animations/#setting-the-target-effect 199 void Animation::SetEffectNoUpdate(AnimationEffect* aEffect) { 200 RefPtr<Animation> kungFuDeathGrip(this); 201 202 if (mEffect == aEffect) { 203 return; 204 } 205 206 AutoMutationBatchForAnimation mb(*this); 207 bool wasRelevant = mIsRelevant; 208 209 if (mEffect) { 210 // We need to notify observers now because once we set mEffect to null 211 // we won't be able to find the target element to notify. 212 if (mIsRelevant) { 213 MutationObservers::NotifyAnimationRemoved(this); 214 } 215 216 // Break links with the old effect and then drop it. 217 RefPtr<AnimationEffect> oldEffect = mEffect; 218 mEffect = nullptr; 219 if (IsPartialPrerendered()) { 220 if (KeyframeEffect* oldKeyframeEffect = oldEffect->AsKeyframeEffect()) { 221 oldKeyframeEffect->ResetPartialPrerendered(); 222 } 223 } 224 oldEffect->SetAnimation(nullptr); 225 226 // The following will not do any notification because mEffect is null. 227 UpdateRelevance(); 228 } 229 230 if (aEffect) { 231 // Break links from the new effect to its previous animation, if any. 232 RefPtr<AnimationEffect> newEffect = aEffect; 233 Animation* prevAnim = aEffect->GetAnimation(); 234 if (prevAnim) { 235 prevAnim->SetEffect(nullptr); 236 } 237 238 // Create links with the new effect. SetAnimation(this) will also update 239 // mIsRelevant of this animation, and then notify mutation observer if 240 // needed by calling Animation::UpdateRelevance(), so we don't need to 241 // call it again. 242 mEffect = newEffect; 243 mEffect->SetAnimation(this); 244 245 // Notify possible add or change. 246 // If the target is different, the change notification will be ignored by 247 // AutoMutationBatchForAnimation. 248 if (wasRelevant && mIsRelevant) { 249 MutationObservers::NotifyAnimationChanged(this); 250 } 251 } 252 253 MaybeScheduleReplacementCheck(); 254 255 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); 256 } 257 258 void Animation::SetTimeline(AnimationTimeline* aTimeline) { 259 SetTimelineNoUpdate(aTimeline); 260 PostUpdate(); 261 } 262 263 // https://drafts.csswg.org/web-animations/#setting-the-timeline 264 void Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline) { 265 if (mTimeline == aTimeline) { 266 return; 267 } 268 269 StickyTimeDuration activeTime = 270 mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration(); 271 272 const AnimationPlayState previousPlayState = PlayState(); 273 const Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration(); 274 // FIXME: The definition of end time in web-animation-1 is different from that 275 // in web-animation-2, which includes the start time. We are still using the 276 // definition in web-animation-1 here for now. 277 const TimeDuration endTime = TimeDuration(EffectEnd()); 278 double previousProgress = 0.0; 279 if (!previousCurrentTime.IsNull() && !endTime.IsZero()) { 280 previousProgress = 281 previousCurrentTime.Value().ToSeconds() / endTime.ToSeconds(); 282 } 283 284 RefPtr<AnimationTimeline> oldTimeline = mTimeline; 285 if (oldTimeline) { 286 oldTimeline->RemoveAnimation(this); 287 } 288 289 mTimeline = aTimeline; 290 291 mResetCurrentTimeOnResume = false; 292 293 if (mEffect) { 294 mEffect->UpdateNormalizedTiming(); 295 } 296 297 if (mTimeline && !mTimeline->IsMonotonicallyIncreasing()) { 298 // If "to finite timeline" is true. 299 300 ApplyPendingPlaybackRate(); 301 Nullable<TimeDuration> seekTime; 302 if (mPlaybackRate >= 0.0) { 303 seekTime.SetValue(TimeDuration()); 304 } else { 305 seekTime.SetValue(TimeDuration(EffectEnd())); 306 } 307 308 switch (previousPlayState) { 309 case AnimationPlayState::Running: 310 case AnimationPlayState::Finished: 311 mStartTime = seekTime; 312 break; 313 case AnimationPlayState::Paused: 314 if (!previousCurrentTime.IsNull()) { 315 mResetCurrentTimeOnResume = true; 316 mStartTime.SetNull(); 317 mHoldTime.SetValue( 318 TimeDuration(EffectEnd().MultDouble(previousProgress))); 319 } else { 320 mStartTime = seekTime; 321 } 322 break; 323 case AnimationPlayState::Idle: 324 default: 325 break; 326 } 327 } else if (oldTimeline && !oldTimeline->IsMonotonicallyIncreasing() && 328 !previousCurrentTime.IsNull()) { 329 // If "from finite timeline" and previous progress is resolved. 330 SetCurrentTimeNoUpdate( 331 TimeDuration(EffectEnd().MultDouble(previousProgress))); 332 } 333 334 if (!mStartTime.IsNull()) { 335 mHoldTime.SetNull(); 336 } 337 338 if (!aTimeline) { 339 MaybeQueueCancelEvent(activeTime); 340 } 341 342 UpdateScrollTimelineAnimationTracker(oldTimeline, aTimeline); 343 344 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); 345 346 // FIXME: Bug 1799071: Check if we need to add 347 // MutationObservers::NotifyAnimationChanged(this) here. 348 } 349 350 // https://drafts.csswg.org/web-animations/#set-the-animation-start-time 351 void Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime) { 352 // Return early if the start time will not change. However, if we 353 // are pending, then setting the start time to any value 354 // including the current value has the effect of aborting 355 // pending tasks so we should not return early in that case. 356 if (!Pending() && aNewStartTime == mStartTime) { 357 return; 358 } 359 360 AutoMutationBatchForAnimation mb(*this); 361 362 Nullable<TimeDuration> timelineTime; 363 if (mTimeline) { 364 // The spec says to check if the timeline is active (has a resolved time) 365 // before using it here, but we don't need to since it's harmless to set 366 // the already null time to null. 367 timelineTime = mTimeline->GetCurrentTimeAsDuration(); 368 } 369 if (timelineTime.IsNull() && !aNewStartTime.IsNull()) { 370 mHoldTime.SetNull(); 371 } 372 373 Nullable<TimeDuration> previousCurrentTime = GetCurrentTimeAsDuration(); 374 375 ApplyPendingPlaybackRate(); 376 mStartTime = aNewStartTime; 377 378 mResetCurrentTimeOnResume = false; 379 380 if (!aNewStartTime.IsNull()) { 381 if (mPlaybackRate != 0.0) { 382 mHoldTime.SetNull(); 383 } 384 } else { 385 mHoldTime = previousCurrentTime; 386 } 387 388 CancelPendingTasks(); 389 if (mReady) { 390 // We may have already resolved mReady, but in that case calling 391 // MaybeResolve is a no-op, so that's okay. 392 mReady->MaybeResolve(this); 393 } 394 395 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async); 396 if (IsRelevant()) { 397 MutationObservers::NotifyAnimationChanged(this); 398 } 399 PostUpdate(); 400 } 401 402 // https://drafts.csswg.org/web-animations/#current-time 403 Nullable<TimeDuration> Animation::GetCurrentTimeForHoldTime( 404 const Nullable<TimeDuration>& aHoldTime) const { 405 Nullable<TimeDuration> result; 406 if (!aHoldTime.IsNull()) { 407 result = aHoldTime; 408 return result; 409 } 410 411 if (mTimeline && !mStartTime.IsNull()) { 412 Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTimeAsDuration(); 413 if (!timelineTime.IsNull()) { 414 result = CurrentTimeFromTimelineTime(timelineTime.Value(), 415 mStartTime.Value(), mPlaybackRate); 416 } 417 } 418 return result; 419 } 420 421 // https://drafts.csswg.org/web-animations/#set-the-current-time 422 void Animation::SetCurrentTime(const TimeDuration& aSeekTime) { 423 // Return early if the current time has not changed. However, if we 424 // are pause-pending, then setting the current time to any value 425 // including the current value has the effect of aborting the 426 // pause so we should not return early in that case. 427 if (mPendingState != PendingState::PausePending && 428 Nullable<TimeDuration>(aSeekTime) == GetCurrentTimeAsDuration()) { 429 return; 430 } 431 432 AutoMutationBatchForAnimation mb(*this); 433 434 SetCurrentTimeNoUpdate(aSeekTime); 435 436 if (IsRelevant()) { 437 MutationObservers::NotifyAnimationChanged(this); 438 } 439 PostUpdate(); 440 } 441 442 void Animation::SetCurrentTimeNoUpdate(const TimeDuration& aSeekTime) { 443 SilentlySetCurrentTime(aSeekTime); 444 445 if (mPendingState == PendingState::PausePending) { 446 // Finish the pause operation 447 mHoldTime.SetValue(aSeekTime); 448 449 ApplyPendingPlaybackRate(); 450 mStartTime.SetNull(); 451 452 if (mReady) { 453 mReady->MaybeResolve(this); 454 } 455 CancelPendingTasks(); 456 } 457 458 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async); 459 } 460 461 // https://drafts.csswg.org/web-animations-2/#the-overall-progress-of-an-animation 462 Nullable<double> Animation::GetOverallProgress() const { 463 Nullable<double> result; 464 if (!mEffect) { 465 return result; 466 } 467 const Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration(); 468 if (currentTime.IsNull()) { 469 return result; 470 } 471 472 const StickyTimeDuration endTime = EffectEnd(); 473 if (endTime.IsZero()) { 474 if (currentTime.Value() < TimeDuration(0)) { 475 result.SetValue(0.0); 476 } else { 477 result.SetValue(1.0); 478 } 479 return result; 480 } 481 482 if (endTime == StickyTimeDuration::Forever()) { 483 result.SetValue(0.0); 484 return result; 485 } 486 487 auto overallProgress = 488 std::min(std::max(currentTime.Value() / endTime, 0.0), 1.0); 489 result.SetValue(overallProgress); 490 return result; 491 } 492 493 // https://drafts.csswg.org/web-animations/#set-the-playback-rate 494 void Animation::SetPlaybackRate(double aPlaybackRate) { 495 mPendingPlaybackRate.reset(); 496 497 if (aPlaybackRate == mPlaybackRate) { 498 return; 499 } 500 501 AutoMutationBatchForAnimation mb(*this); 502 503 Nullable<TimeDuration> previousTime = GetCurrentTimeAsDuration(); 504 mPlaybackRate = aPlaybackRate; 505 if (!previousTime.IsNull()) { 506 SetCurrentTime(previousTime.Value()); 507 } 508 509 // In the case where GetCurrentTimeAsDuration() returns the same result before 510 // and after updating mPlaybackRate, SetCurrentTime will return early since, 511 // as far as it can tell, nothing has changed. 512 // As a result, we need to perform the following updates here: 513 // - update timing (since, if the sign of the playback rate has changed, our 514 // finished state may have changed), 515 // - dispatch a change notification for the changed playback rate, and 516 // - update the playback rate on animations on layers. 517 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async); 518 if (IsRelevant()) { 519 MutationObservers::NotifyAnimationChanged(this); 520 } 521 PostUpdate(); 522 } 523 524 // https://drafts.csswg.org/web-animations/#seamlessly-update-the-playback-rate 525 void Animation::UpdatePlaybackRate(double aPlaybackRate) { 526 if (mPendingPlaybackRate && mPendingPlaybackRate.value() == aPlaybackRate) { 527 return; 528 } 529 530 // Calculate the play state using the existing playback rate since below we 531 // want to know if the animation is _currently_ finished or not, not whether 532 // it _will_ be finished. 533 AnimationPlayState playState = PlayState(); 534 535 mPendingPlaybackRate = Some(aPlaybackRate); 536 537 if (Pending()) { 538 // If we already have a pending task, there is nothing more to do since the 539 // playback rate will be applied then. 540 // 541 // However, as with the idle/paused case below, we still need to update the 542 // relevance (and effect set to make sure it only contains relevant 543 // animations) since the relevance is based on the Animation play state 544 // which incorporates the _pending_ playback rate. 545 UpdateEffect(PostRestyleMode::Never); 546 return; 547 } 548 549 AutoMutationBatchForAnimation mb(*this); 550 551 if (playState == AnimationPlayState::Idle || 552 playState == AnimationPlayState::Paused || 553 GetCurrentTimeAsDuration().IsNull()) { 554 // If |previous play state| is idle or paused, or the current time is 555 // unresolved, we apply any pending playback rate on animation immediately. 556 ApplyPendingPlaybackRate(); 557 558 // We don't need to update timing or post an update here because: 559 // 560 // * the current time hasn't changed -- it's either unresolved or fixed 561 // with a hold time -- so the output won't have changed 562 // * the finished state won't have changed even if the sign of the 563 // playback rate changed since we're not finished (we're paused or idle) 564 // * the playback rate on layers doesn't need to be updated since we're not 565 // moving. Once we get a start time etc. we'll update the playback rate 566 // then. 567 // 568 // However we still need to update the relevance and effect set as well as 569 // notifying observers. 570 UpdateEffect(PostRestyleMode::Never); 571 if (IsRelevant()) { 572 MutationObservers::NotifyAnimationChanged(this); 573 } 574 } else if (playState == AnimationPlayState::Finished) { 575 MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTimeAsDuration().IsNull(), 576 "If we have no active timeline, we should be idle or paused"); 577 if (aPlaybackRate != 0) { 578 // The unconstrained current time can only be unresolved if either we 579 // don't have an active timeline (and we already asserted that is not 580 // true) or we have an unresolved start time (in which case we should be 581 // paused). 582 MOZ_ASSERT(!GetUnconstrainedCurrentTime().IsNull(), 583 "Unconstrained current time should be resolved"); 584 TimeDuration unconstrainedCurrentTime = 585 GetUnconstrainedCurrentTime().Value(); 586 TimeDuration timelineTime = mTimeline->GetCurrentTimeAsDuration().Value(); 587 mStartTime = StartTimeFromTimelineTime( 588 timelineTime, unconstrainedCurrentTime, aPlaybackRate); 589 } else { 590 mStartTime = mTimeline->GetCurrentTimeAsDuration(); 591 } 592 593 ApplyPendingPlaybackRate(); 594 595 // Even though we preserve the current time, we might now leave the finished 596 // state (e.g. if the playback rate changes sign) so we need to update 597 // timing. 598 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); 599 if (IsRelevant()) { 600 MutationObservers::NotifyAnimationChanged(this); 601 } 602 PostUpdate(); 603 } else { 604 ErrorResult rv; 605 Play(rv, LimitBehavior::Continue); 606 MOZ_ASSERT(!rv.Failed(), 607 "We should only fail to play when using auto-rewind behavior"); 608 } 609 } 610 611 // https://drafts.csswg.org/web-animations/#play-state 612 AnimationPlayState Animation::PlayState() const { 613 Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration(); 614 if (currentTime.IsNull() && mStartTime.IsNull() && !Pending()) { 615 return AnimationPlayState::Idle; 616 } 617 618 if (mPendingState == PendingState::PausePending || 619 (mStartTime.IsNull() && !Pending())) { 620 return AnimationPlayState::Paused; 621 } 622 623 double playbackRate = CurrentOrPendingPlaybackRate(); 624 if (!currentTime.IsNull() && 625 ((playbackRate > 0.0 && currentTime.Value() >= EffectEnd()) || 626 (playbackRate < 0.0 && currentTime.Value() <= TimeDuration()))) { 627 return AnimationPlayState::Finished; 628 } 629 630 return AnimationPlayState::Running; 631 } 632 633 Promise* Animation::GetReady(ErrorResult& aRv) { 634 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); 635 if (!mReady && global) { 636 mReady = Promise::Create(global, aRv); // Lazily create on demand 637 } 638 if (!mReady) { 639 aRv.Throw(NS_ERROR_FAILURE); 640 return nullptr; 641 } 642 if (!Pending()) { 643 mReady->MaybeResolve(this); 644 } 645 return mReady; 646 } 647 648 Promise* Animation::GetFinished(ErrorResult& aRv) { 649 nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal(); 650 if (!mFinished && global) { 651 mFinished = Promise::Create(global, aRv); // Lazily create on demand 652 } 653 if (!mFinished) { 654 aRv.Throw(NS_ERROR_FAILURE); 655 return nullptr; 656 } 657 if (mFinishedIsResolved) { 658 MaybeResolveFinishedPromise(); 659 } 660 return mFinished; 661 } 662 663 // https://drafts.csswg.org/web-animations/#cancel-an-animation 664 void Animation::Cancel(PostRestyleMode aPostRestyle) { 665 bool newlyIdle = false; 666 667 if (PlayState() != AnimationPlayState::Idle) { 668 newlyIdle = true; 669 670 ResetPendingTasks(); 671 672 if (mFinished) { 673 mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR); 674 // mFinished can already be resolved. 675 MOZ_ALWAYS_TRUE(mFinished->SetAnyPromiseIsHandled()); 676 } 677 ResetFinishedPromise(); 678 679 QueuePlaybackEvent(nsGkAtoms::oncancel, 680 GetTimelineCurrentTimeAsTimeStamp()); 681 } 682 683 StickyTimeDuration activeTime = 684 mEffect ? mEffect->GetComputedTiming().mActiveTime : StickyTimeDuration(); 685 686 mHoldTime.SetNull(); 687 mStartTime.SetNull(); 688 689 // Allow our effect to remove itself from the its target element's EffectSet. 690 UpdateEffect(aPostRestyle); 691 692 if (mTimeline) { 693 mTimeline->RemoveAnimation(this); 694 } 695 MaybeQueueCancelEvent(activeTime); 696 697 if (newlyIdle && aPostRestyle == PostRestyleMode::IfNeeded) { 698 PostUpdate(); 699 } 700 } 701 702 // https://drafts.csswg.org/web-animations/#finish-an-animation 703 void Animation::Finish(ErrorResult& aRv) { 704 double effectivePlaybackRate = CurrentOrPendingPlaybackRate(); 705 706 if (effectivePlaybackRate == 0) { 707 return aRv.ThrowInvalidStateError( 708 "Can't finish animation with zero playback rate"); 709 } 710 if (effectivePlaybackRate > 0 && EffectEnd() == TimeDuration::Forever()) { 711 return aRv.ThrowInvalidStateError("Can't finish infinite animation"); 712 } 713 714 AutoMutationBatchForAnimation mb(*this); 715 716 ApplyPendingPlaybackRate(); 717 718 // Seek to the end 719 TimeDuration limit = 720 mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0); 721 bool didChange = GetCurrentTimeAsDuration() != Nullable<TimeDuration>(limit); 722 SilentlySetCurrentTime(limit); 723 724 // If we are paused or play-pending we need to fill in the start time in 725 // order to transition to the finished state. 726 // 727 // We only do this, however, if we have an active timeline. If we have an 728 // inactive timeline we can't transition into the finished state just like 729 // we can't transition to the running state (this finished state is really 730 // a substate of the running state). 731 if (mStartTime.IsNull() && mTimeline && 732 !mTimeline->GetCurrentTimeAsDuration().IsNull()) { 733 mStartTime = StartTimeFromTimelineTime( 734 mTimeline->GetCurrentTimeAsDuration().Value(), limit, mPlaybackRate); 735 didChange = true; 736 } 737 738 // If we just resolved the start time for a pause or play-pending 739 // animation, we need to clear the task. We don't do this as a branch of 740 // the above however since we can have a play-pending animation with a 741 // resolved start time if we aborted a pause operation. 742 if (!mStartTime.IsNull() && (mPendingState == PendingState::PlayPending || 743 mPendingState == PendingState::PausePending)) { 744 if (mPendingState == PendingState::PausePending) { 745 mHoldTime.SetNull(); 746 } 747 CancelPendingTasks(); 748 didChange = true; 749 if (mReady) { 750 mReady->MaybeResolve(this); 751 } 752 } 753 UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync); 754 if (didChange && IsRelevant()) { 755 MutationObservers::NotifyAnimationChanged(this); 756 } 757 PostUpdate(); 758 } 759 760 void Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) { 761 PlayNoUpdate(aRv, aLimitBehavior); 762 PostUpdate(); 763 } 764 765 // https://drafts.csswg.org/web-animations/#reverse-an-animation 766 void Animation::Reverse(ErrorResult& aRv) { 767 if (!mTimeline) { 768 return aRv.ThrowInvalidStateError( 769 "Can't reverse an animation with no associated timeline"); 770 } 771 if (mTimeline->GetCurrentTimeAsDuration().IsNull()) { 772 return aRv.ThrowInvalidStateError( 773 "Can't reverse an animation associated with an inactive timeline"); 774 } 775 776 double effectivePlaybackRate = CurrentOrPendingPlaybackRate(); 777 778 if (effectivePlaybackRate == 0.0) { 779 return; 780 } 781 782 Maybe<double> originalPendingPlaybackRate = mPendingPlaybackRate; 783 784 mPendingPlaybackRate = Some(-effectivePlaybackRate); 785 786 Play(aRv, LimitBehavior::AutoRewind); 787 788 // If Play() threw, restore state and don't report anything to mutation 789 // observers. 790 if (aRv.Failed()) { 791 mPendingPlaybackRate = originalPendingPlaybackRate; 792 } 793 794 // Play(), above, unconditionally calls PostUpdate so we don't need to do 795 // it here. 796 } 797 798 void Animation::Persist() { 799 if (mReplaceState == AnimationReplaceState::Persisted) { 800 return; 801 } 802 803 bool wasRemoved = mReplaceState == AnimationReplaceState::Removed; 804 805 mReplaceState = AnimationReplaceState::Persisted; 806 807 // If the animation is not (yet) removed, there should be no side effects of 808 // persisting it. 809 if (wasRemoved) { 810 UpdateEffect(PostRestyleMode::IfNeeded); 811 PostUpdate(); 812 } 813 } 814 815 DocGroup* Animation::GetDocGroup() { 816 Document* doc = GetRenderedDocument(); 817 return doc ? doc->GetDocGroup() : nullptr; 818 } 819 820 // https://drafts.csswg.org/web-animations/#dom-animation-commitstyles 821 void Animation::CommitStyles(ErrorResult& aRv) { 822 if (!mEffect) { 823 return; 824 } 825 826 // Take an owning reference to the keyframe effect. This will ensure that 827 // this Animation and the target element remain alive after flushing style. 828 RefPtr<KeyframeEffect> keyframeEffect = mEffect->AsKeyframeEffect(); 829 if (!keyframeEffect) { 830 return; 831 } 832 833 NonOwningAnimationTarget target = keyframeEffect->GetAnimationTarget(); 834 if (!target) { 835 return; 836 } 837 838 if (!target.mPseudoRequest.IsNotPseudo()) { 839 return aRv.ThrowNoModificationAllowedError( 840 "Can't commit styles of a pseudo-element"); 841 } 842 843 // Check it is an element with a style attribute 844 RefPtr<nsStyledElement> styledElement = 845 nsStyledElement::FromNodeOrNull(target.mElement); 846 if (!styledElement) { 847 return aRv.ThrowNoModificationAllowedError( 848 "Target is not capable of having a style attribute"); 849 } 850 851 // Hold onto a strong reference to the doc in case the flush destroys it. 852 RefPtr<Document> doc = target.mElement->GetComposedDoc(); 853 854 // Flush frames before checking if the target element is rendered since the 855 // result could depend on pending style changes, and IsRendered() looks at the 856 // primary frame. 857 if (doc) { 858 doc->FlushPendingNotifications(FlushType::Frames); 859 } 860 if (!target.mElement->IsRendered()) { 861 return aRv.ThrowInvalidStateError("Target is not rendered"); 862 } 863 nsPresContext* presContext = 864 nsContentUtils::GetContextForContent(target.mElement); 865 if (!presContext) { 866 return aRv.ThrowInvalidStateError("Target is not rendered"); 867 } 868 869 // Get the computed animation values 870 UniquePtr<StyleAnimationValueMap> animationValues( 871 Servo_AnimationValueMap_Create()); 872 if (!presContext->EffectCompositor()->ComposeServoAnimationRuleForEffect( 873 *keyframeEffect, CascadeLevel(), animationValues.get(), 874 StaticPrefs::dom_animations_commit_styles_endpoint_inclusive() 875 ? EndpointBehavior::Inclusive 876 : EndpointBehavior::Exclusive)) { 877 NS_WARNING("Failed to compose animation style to commit"); 878 return; 879 } 880 881 // Calling SetCSSDeclaration will trigger attribute setting code. 882 // Start the update now so that the old rule doesn't get used 883 // between when we mutate the declaration and when we set the new 884 // rule. 885 mozAutoDocUpdate autoUpdate(target.mElement->OwnerDoc(), true); 886 887 // Get the inline style to append to 888 RefPtr<DeclarationBlock> declarationBlock; 889 if (auto* existing = target.mElement->GetInlineStyleDeclaration()) { 890 declarationBlock = existing->EnsureMutable(); 891 } else { 892 declarationBlock = new DeclarationBlock(); 893 declarationBlock->SetDirty(); 894 } 895 896 // Prepare the callback 897 MutationClosureData closureData; 898 closureData.mShouldBeCalled = true; 899 closureData.mElement = target.mElement; 900 DeclarationBlockMutationClosure beforeChangeClosure = { 901 nsDOMCSSAttributeDeclaration::MutationClosureFunction, 902 &closureData, 903 }; 904 905 // Set the animated styles 906 bool changed = false; 907 const AnimatedPropertyIDSet& properties = keyframeEffect->GetPropertySet(); 908 for (const CSSPropertyId& property : properties) { 909 RefPtr<StyleAnimationValue> computedValue = 910 Servo_AnimationValueMap_GetValue(animationValues.get(), &property) 911 .Consume(); 912 if (computedValue) { 913 changed |= Servo_DeclarationBlock_SetPropertyToAnimationValue( 914 declarationBlock->Raw(), computedValue, beforeChangeClosure); 915 } 916 } 917 918 if (!changed) { 919 MOZ_ASSERT(!closureData.mWasCalled); 920 return; 921 } 922 923 MOZ_ASSERT(closureData.mWasCalled); 924 // Update inline style declaration 925 target.mElement->SetInlineStyleDeclaration(*declarationBlock, closureData); 926 } 927 928 // --------------------------------------------------------------------------- 929 // 930 // JS wrappers for Animation interface: 931 // 932 // --------------------------------------------------------------------------- 933 934 Nullable<double> Animation::GetStartTimeAsDouble() const { 935 return AnimationUtils::TimeDurationToDouble(mStartTime, mRTPCallerType); 936 } 937 938 void Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) { 939 return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime)); 940 } 941 942 Nullable<double> Animation::GetCurrentTimeAsDouble() const { 943 return AnimationUtils::TimeDurationToDouble(GetCurrentTimeAsDuration(), 944 mRTPCallerType); 945 } 946 947 void Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime, 948 ErrorResult& aRv) { 949 if (aCurrentTime.IsNull()) { 950 if (!GetCurrentTimeAsDuration().IsNull()) { 951 aRv.ThrowTypeError( 952 "Current time is resolved but trying to set it to an unresolved " 953 "time"); 954 } 955 return; 956 } 957 958 return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value())); 959 } 960 961 // --------------------------------------------------------------------------- 962 963 void Animation::Tick(AnimationTimeline::TickState& aTickState) { 964 if (Pending()) { 965 if (!mPendingReadyTime.IsNull()) { 966 TryTriggerNow(); 967 } else if (MOZ_LIKELY(mTimeline)) { 968 // Makes sure that we trigger the animation on the next tick but, 969 // importantly, with this tick's timestamp. 970 mPendingReadyTime = mTimeline->GetCurrentTimeAsTimeStamp(); 971 } 972 } 973 974 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync); 975 976 // Check for changes to whether or not this animation is replaceable. 977 bool isReplaceable = IsReplaceable(); 978 if (isReplaceable && !mWasReplaceableAtLastTick) { 979 ScheduleReplacementCheck(); 980 } 981 mWasReplaceableAtLastTick = isReplaceable; 982 983 if (!mEffect) { 984 return; 985 } 986 987 // Update layers if we are newly finished. 988 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect(); 989 if (keyframeEffect && !keyframeEffect->Properties().IsEmpty() && 990 !mFinishedAtLastComposeStyle && 991 PlayState() == AnimationPlayState::Finished) { 992 PostUpdate(); 993 } 994 } 995 996 bool Animation::TryTriggerNow() { 997 if (!Pending()) { 998 return true; 999 } 1000 // If we don't have an active timeline we can't trigger the animation. 1001 if (NS_WARN_IF(!mTimeline)) { 1002 return false; 1003 } 1004 auto currentTime = mPendingReadyTime.IsNull() 1005 ? mTimeline->GetCurrentTimeAsDuration() 1006 : mTimeline->ToTimelineTime(mPendingReadyTime); 1007 mPendingReadyTime = {}; 1008 if (NS_WARN_IF(currentTime.IsNull())) { 1009 return false; 1010 } 1011 FinishPendingAt(currentTime.Value()); 1012 return true; 1013 } 1014 1015 TimeStamp Animation::AnimationTimeToTimeStamp( 1016 const StickyTimeDuration& aTime) const { 1017 // Initializes to null. Return the same object every time to benefit from 1018 // return-value-optimization. 1019 TimeStamp result; 1020 1021 // We *don't* check for mTimeline->TracksWallclockTime() here because that 1022 // method only tells us if the timeline times can be converted to 1023 // TimeStamps that can be compared to TimeStamp::Now() or not, *not* 1024 // whether the timelines can be converted to TimeStamp values at all. 1025 // 1026 // Furthermore, we want to be able to use this method when the refresh driver 1027 // is under test control (in which case TracksWallclockTime() will return 1028 // false). 1029 // 1030 // Once we introduce timelines that are not time-based we will need to 1031 // differentiate between them here and determine how to sort their events. 1032 if (!mTimeline) { 1033 return result; 1034 } 1035 1036 // Check the time is convertible to a timestamp 1037 if (aTime == TimeDuration::Forever() || mPlaybackRate == 0.0 || 1038 mStartTime.IsNull()) { 1039 return result; 1040 } 1041 1042 // Invert the standard relation: 1043 // current time = (timeline time - start time) * playback rate 1044 TimeDuration timelineTime = 1045 TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value(); 1046 1047 result = mTimeline->ToTimeStamp(timelineTime); 1048 return result; 1049 } 1050 1051 TimeStamp Animation::ElapsedTimeToTimeStamp( 1052 const StickyTimeDuration& aElapsedTime) const { 1053 TimeDuration delay = 1054 mEffect ? mEffect->NormalizedTiming().Delay() : TimeDuration(); 1055 return AnimationTimeToTimeStamp(aElapsedTime + delay); 1056 } 1057 1058 // https://drafts.csswg.org/web-animations/#silently-set-the-current-time 1059 void Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime) { 1060 // TODO: Bug 1762238: Introduce "valid seek time" after introducing 1061 // CSSNumberish time values. 1062 // https://drafts.csswg.org/web-animations-2/#silently-set-the-current-time 1063 1064 if (!mHoldTime.IsNull() || mStartTime.IsNull() || !mTimeline || 1065 mTimeline->GetCurrentTimeAsDuration().IsNull() || mPlaybackRate == 0.0) { 1066 mHoldTime.SetValue(aSeekTime); 1067 } else { 1068 mStartTime = 1069 StartTimeFromTimelineTime(mTimeline->GetCurrentTimeAsDuration().Value(), 1070 aSeekTime, mPlaybackRate); 1071 } 1072 1073 if (!mTimeline || mTimeline->GetCurrentTimeAsDuration().IsNull()) { 1074 mStartTime.SetNull(); 1075 } 1076 1077 mPreviousCurrentTime.SetNull(); 1078 mResetCurrentTimeOnResume = false; 1079 } 1080 1081 bool Animation::ShouldBeSynchronizedWithMainThread( 1082 const nsCSSPropertyIDSet& aPropertySet, const nsIFrame* aFrame, 1083 AnimationPerformanceWarning::Type& aPerformanceWarning) const { 1084 // Only synchronize playing animations 1085 if (!IsPlaying()) { 1086 return false; 1087 } 1088 1089 // Currently only transform animations need to be synchronized 1090 if (!aPropertySet.Intersects(nsCSSPropertyIDSet::TransformLikeProperties())) { 1091 return false; 1092 } 1093 1094 KeyframeEffect* keyframeEffect = 1095 mEffect ? mEffect->AsKeyframeEffect() : nullptr; 1096 if (!keyframeEffect) { 1097 return false; 1098 } 1099 1100 return keyframeEffect->ShouldBlockAsyncTransformAnimations( 1101 aFrame, aPropertySet, aPerformanceWarning); 1102 } 1103 1104 void Animation::UpdateRelevance() { 1105 bool wasRelevant = mIsRelevant; 1106 mIsRelevant = mReplaceState != AnimationReplaceState::Removed && 1107 (HasCurrentEffect() || IsInEffect()); 1108 1109 // Notify animation observers. 1110 if (wasRelevant && !mIsRelevant) { 1111 MutationObservers::NotifyAnimationRemoved(this); 1112 } else if (!wasRelevant && mIsRelevant) { 1113 UpdateHiddenByContentVisibility(); 1114 MutationObservers::NotifyAnimationAdded(this); 1115 } 1116 } 1117 1118 template <class T> 1119 bool IsMarkupAnimation(T* aAnimation) { 1120 return aAnimation && aAnimation->IsTiedToMarkup(); 1121 } 1122 1123 // https://drafts.csswg.org/web-animations/#replaceable-animation 1124 bool Animation::IsReplaceable() const { 1125 // We never replace CSS animations or CSS transitions since they are managed 1126 // by CSS. 1127 if (IsMarkupAnimation(AsCSSAnimation()) || 1128 IsMarkupAnimation(AsCSSTransition())) { 1129 return false; 1130 } 1131 1132 // Only finished animations can be replaced. 1133 if (PlayState() != AnimationPlayState::Finished) { 1134 return false; 1135 } 1136 1137 // Already removed animations cannot be replaced. 1138 if (ReplaceState() == AnimationReplaceState::Removed) { 1139 return false; 1140 } 1141 1142 // We can only replace an animation if we know that, uninterfered, it would 1143 // never start playing again. That excludes any animations on timelines that 1144 // aren't monotonically increasing. 1145 // 1146 // If we don't have any timeline at all, then we can't be in the finished 1147 // state (since we need both a resolved start time and current time for that) 1148 // and will have already returned false above. 1149 // 1150 // (However, if it ever does become possible to be finished without a timeline 1151 // then we will want to return false here since it probably suggests an 1152 // animation being driven directly by script, in which case we can't assume 1153 // anything about how they will behave.) 1154 if (!GetTimeline() || !GetTimeline()->TracksWallclockTime()) { 1155 return false; 1156 } 1157 1158 // If the animation doesn't have an effect then we can't determine if it is 1159 // filling or not so just leave it alone. 1160 if (!GetEffect()) { 1161 return false; 1162 } 1163 1164 // At the time of writing we only know about KeyframeEffects. If we introduce 1165 // other types of effects we will need to decide if they are replaceable or 1166 // not. 1167 MOZ_ASSERT(GetEffect()->AsKeyframeEffect(), 1168 "Effect should be a keyframe effect"); 1169 1170 // We only replace animations that are filling. 1171 if (GetEffect()->GetComputedTiming().mProgress.IsNull()) { 1172 return false; 1173 } 1174 1175 // We should only replace animations with a target element (since otherwise 1176 // what other effects would we consider when determining if they are covered 1177 // or not?). 1178 if (!GetEffect()->AsKeyframeEffect()->GetAnimationTarget()) { 1179 return false; 1180 } 1181 1182 return true; 1183 } 1184 1185 bool Animation::IsRemovable() const { 1186 return ReplaceState() == AnimationReplaceState::Active && IsReplaceable(); 1187 } 1188 1189 void Animation::ScheduleReplacementCheck() { 1190 MOZ_ASSERT( 1191 IsReplaceable(), 1192 "Should only schedule a replacement check for a replaceable animation"); 1193 1194 // If IsReplaceable() is true, the following should also hold 1195 MOZ_ASSERT(GetEffect()); 1196 MOZ_ASSERT(GetEffect()->AsKeyframeEffect()); 1197 1198 NonOwningAnimationTarget target = 1199 GetEffect()->AsKeyframeEffect()->GetAnimationTarget(); 1200 1201 MOZ_ASSERT(target); 1202 1203 nsPresContext* presContext = 1204 nsContentUtils::GetContextForContent(target.mElement); 1205 if (presContext) { 1206 presContext->EffectCompositor()->NoteElementForReducing(target); 1207 } 1208 } 1209 1210 void Animation::MaybeScheduleReplacementCheck() { 1211 if (!IsReplaceable()) { 1212 return; 1213 } 1214 1215 ScheduleReplacementCheck(); 1216 } 1217 1218 void Animation::Remove() { 1219 MOZ_ASSERT(IsRemovable(), 1220 "Should not be trying to remove an effect that is not removable"); 1221 1222 mReplaceState = AnimationReplaceState::Removed; 1223 1224 UpdateEffect(PostRestyleMode::IfNeeded); 1225 PostUpdate(); 1226 1227 QueuePlaybackEvent(nsGkAtoms::onremove, GetTimelineCurrentTimeAsTimeStamp()); 1228 } 1229 1230 int32_t Animation::CompareCompositeOrder( 1231 const Maybe<EventContext>& aContext, const Animation& aOther, 1232 const Maybe<EventContext>& aOtherContext, 1233 nsContentUtils::NodeIndexCache& aCache) const { 1234 // 0. Object-equality case 1235 if (&aOther == this) { 1236 return 0; 1237 } 1238 1239 // 1. CSS Transitions sort lowest 1240 { 1241 auto asCSSTransitionForSorting = 1242 [](const Animation& anim, 1243 const Maybe<EventContext>& aContext) -> const CSSTransition* { 1244 const CSSTransition* transition = anim.AsCSSTransition(); 1245 return transition && (aContext || transition->IsTiedToMarkup()) 1246 ? transition 1247 : nullptr; 1248 }; 1249 const auto* const thisTransition = 1250 asCSSTransitionForSorting(*this, aContext); 1251 const auto* const otherTransition = 1252 asCSSTransitionForSorting(aOther, aOtherContext); 1253 if (thisTransition && otherTransition) { 1254 return thisTransition->CompareCompositeOrder(aContext, *otherTransition, 1255 aOtherContext, aCache); 1256 } 1257 if (thisTransition || otherTransition) { 1258 // Cancelled transitions no longer have an owning element. To be strictly 1259 // correct we should store a strong reference to the owning element 1260 // so that if we arrive here while sorting cancel events, we can sort 1261 // them in the correct order. 1262 // 1263 // However, given that cancel events are almost always queued 1264 // synchronously in some deterministic manner, we can be fairly sure 1265 // that cancel events will be dispatched in a deterministic order 1266 // (which is our only hard requirement until specs say otherwise). 1267 // Furthermore, we only reach here when we have events with equal 1268 // timestamps so this is an edge case we can probably ignore for now. 1269 return thisTransition ? -1 : 1; 1270 } 1271 } 1272 1273 // 2. CSS Animations sort next 1274 { 1275 auto asCSSAnimationForSorting = 1276 [](const Animation& anim) -> const CSSAnimation* { 1277 const CSSAnimation* animation = anim.AsCSSAnimation(); 1278 return animation && animation->IsTiedToMarkup() ? animation : nullptr; 1279 }; 1280 auto thisAnimation = asCSSAnimationForSorting(*this); 1281 auto otherAnimation = asCSSAnimationForSorting(aOther); 1282 if (thisAnimation && otherAnimation) { 1283 return thisAnimation->CompareCompositeOrder(*otherAnimation, aCache); 1284 } 1285 if (thisAnimation || otherAnimation) { 1286 return thisAnimation ? -1 : 1; 1287 } 1288 } 1289 1290 // Subclasses of Animation repurpose mAnimationIndex to implement their 1291 // own brand of composite ordering. However, by this point we should have 1292 // handled any such custom composite ordering so we should now have unique 1293 // animation indices. 1294 MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex, 1295 "Animation indices should be unique"); 1296 1297 // 3. Finally, generic animations sort by their position in the global 1298 // animation array. 1299 return mAnimationIndex > aOther.mAnimationIndex ? 1 : -1; 1300 } 1301 1302 void Animation::WillComposeStyle() { 1303 mFinishedAtLastComposeStyle = (PlayState() == AnimationPlayState::Finished); 1304 1305 MOZ_ASSERT(mEffect); 1306 1307 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect(); 1308 if (keyframeEffect) { 1309 keyframeEffect->WillComposeStyle(); 1310 } 1311 } 1312 1313 void Animation::ComposeStyle( 1314 StyleAnimationValueMap& aComposeResult, 1315 const InvertibleAnimatedPropertyIDSet& aPropertiesToSkip, 1316 EndpointBehavior aEndpointBehavior) { 1317 if (!mEffect) { 1318 return; 1319 } 1320 1321 // In order to prevent flicker, there are a few cases where we want to use 1322 // a different time for rendering that would otherwise be returned by 1323 // GetCurrentTimeAsDuration. These are: 1324 // 1325 // (a) For animations that are pausing but which are still running on the 1326 // compositor. In this case we send a layer transaction that removes the 1327 // animation but which also contains the animation values calculated on 1328 // the main thread. To prevent flicker when this occurs we want to ensure 1329 // the timeline time used to calculate the main thread animation values 1330 // does not lag far behind the time used on the compositor. Ideally we 1331 // would like to use the "animation ready time", but for now we just use 1332 // the current wallclock time. TODO(emilio): After bug 1864425 it seems we 1333 // could just use the ready time, or maybe we can remove this? 1334 // 1335 // (b) For animations that are pausing that we have already taken off the 1336 // compositor. In this case we record a pending ready time but we don't 1337 // apply it until the next tick. However, while waiting for the next tick, 1338 // we should still use the pending ready time as the timeline time. If we 1339 // use the regular timeline time the animation may appear jump backwards 1340 // if the main thread's timeline time lags behind the compositor. 1341 // 1342 // (c) For animations that are play-pending due to an aborted pause operation 1343 // (i.e. a pause operation that was interrupted before we entered the 1344 // paused state). When we cancel a pending pause we might momentarily take 1345 // the animation off the compositor, only to re-add it moments later. In 1346 // that case the compositor might have been ahead of the main thread so we 1347 // should use the current wallclock time to ensure the animation doesn't 1348 // temporarily jump backwards. 1349 // 1350 // To address each of these cases we temporarily tweak the hold time 1351 // immediately before updating the style rule and then restore it immediately 1352 // afterwards. This is purely to prevent visual flicker. Other behavior 1353 // such as dispatching events continues to rely on the regular timeline time. 1354 const bool pending = Pending(); 1355 { 1356 AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime); 1357 if (pending && mHoldTime.IsNull() && !mStartTime.IsNull()) { 1358 Nullable<TimeDuration> timeToUse; 1359 if (mTimeline && mTimeline->TracksWallclockTime()) { 1360 timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now()); 1361 } 1362 if (!timeToUse.IsNull()) { 1363 mHoldTime = CurrentTimeFromTimelineTime( 1364 timeToUse.Value(), mStartTime.Value(), mPlaybackRate); 1365 } 1366 } 1367 1368 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect(); 1369 if (keyframeEffect) { 1370 keyframeEffect->ComposeStyle(aComposeResult, aPropertiesToSkip, 1371 aEndpointBehavior); 1372 } 1373 } 1374 1375 MOZ_ASSERT( 1376 pending == Pending(), 1377 "Pending state should not change during the course of compositing"); 1378 } 1379 1380 void Animation::NotifyEffectTimingUpdated() { 1381 MOZ_ASSERT(mEffect, 1382 "We should only update effect timing when we have a target " 1383 "effect"); 1384 UpdateTiming(Animation::SeekFlag::NoSeek, Animation::SyncNotifyFlag::Async); 1385 } 1386 1387 void Animation::NotifyEffectPropertiesUpdated() { 1388 MOZ_ASSERT(mEffect, 1389 "We should only update effect properties when we have a target " 1390 "effect"); 1391 1392 MaybeScheduleReplacementCheck(); 1393 } 1394 1395 void Animation::NotifyEffectTargetUpdated() { 1396 MOZ_ASSERT(mEffect, 1397 "We should only update the effect target when we have a target " 1398 "effect"); 1399 1400 MaybeScheduleReplacementCheck(); 1401 } 1402 1403 static TimeStamp EnsurePaintIsScheduled(Document& aDoc) { 1404 PresShell* presShell = aDoc.GetPresShell(); 1405 if (!presShell) { 1406 return {}; 1407 } 1408 nsIFrame* rootFrame = presShell->GetRootFrame(); 1409 if (!rootFrame) { 1410 return {}; 1411 } 1412 rootFrame->SchedulePaintWithoutInvalidatingObservers(); 1413 auto* rd = rootFrame->PresContext()->RefreshDriver(); 1414 if (!rd->IsInRefresh()) { 1415 return {}; 1416 } 1417 return rd->MostRecentRefresh(); 1418 } 1419 1420 // https://drafts.csswg.org/web-animations/#play-an-animation 1421 void Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior) { 1422 AutoMutationBatchForAnimation mb(*this); 1423 1424 const bool isAutoRewind = aLimitBehavior == LimitBehavior::AutoRewind; 1425 const bool abortedPause = mPendingState == PendingState::PausePending; 1426 double effectivePlaybackRate = CurrentOrPendingPlaybackRate(); 1427 1428 Nullable<TimeDuration> currentTime = GetCurrentTimeAsDuration(); 1429 if (mResetCurrentTimeOnResume) { 1430 currentTime.SetNull(); 1431 mResetCurrentTimeOnResume = false; 1432 } 1433 1434 Nullable<TimeDuration> seekTime; 1435 if (isAutoRewind) { 1436 if (effectivePlaybackRate >= 0.0 && 1437 (currentTime.IsNull() || currentTime.Value() < TimeDuration() || 1438 currentTime.Value() >= EffectEnd())) { 1439 seekTime.SetValue(TimeDuration()); 1440 } else if (effectivePlaybackRate < 0.0 && 1441 (currentTime.IsNull() || currentTime.Value() <= TimeDuration() || 1442 currentTime.Value() > EffectEnd())) { 1443 if (EffectEnd() == TimeDuration::Forever()) { 1444 return aRv.ThrowInvalidStateError( 1445 "Can't rewind animation with infinite effect end"); 1446 } 1447 seekTime.SetValue(TimeDuration(EffectEnd())); 1448 } 1449 } 1450 1451 if (seekTime.IsNull() && mStartTime.IsNull() && currentTime.IsNull()) { 1452 seekTime.SetValue(TimeDuration()); 1453 } 1454 1455 if (!seekTime.IsNull()) { 1456 if (HasFiniteTimeline()) { 1457 mStartTime = seekTime; 1458 mHoldTime.SetNull(); 1459 ApplyPendingPlaybackRate(); 1460 } else { 1461 mHoldTime = seekTime; 1462 } 1463 } 1464 1465 bool reuseReadyPromise = false; 1466 if (mPendingState != PendingState::NotPending) { 1467 CancelPendingTasks(); 1468 reuseReadyPromise = true; 1469 } 1470 1471 // If the hold time is null then we're already playing normally and, 1472 // typically, we can bail out here. 1473 // 1474 // However, there are two cases where we can't do that: 1475 // 1476 // (a) If we just aborted a pause. In this case, for consistency, we need to 1477 // go through the motions of doing an asynchronous start. 1478 // 1479 // (b) If we have timing changes (specifically a change to the playbackRate) 1480 // that should be applied asynchronously. 1481 // 1482 if (mHoldTime.IsNull() && seekTime.IsNull() && !abortedPause && 1483 !mPendingPlaybackRate) { 1484 return; 1485 } 1486 1487 // Clear the start time until we resolve a new one. We do this except 1488 // for the case where we are aborting a pause and don't have a hold time. 1489 // 1490 // If we're aborting a pause and *do* have a hold time (e.g. because 1491 // the animation is finished or we just applied the auto-rewind behavior 1492 // above) we should respect it by clearing the start time. If we *don't* 1493 // have a hold time we should keep the current start time so that the 1494 // the animation continues moving uninterrupted by the aborted pause. 1495 // 1496 // (If we're not aborting a pause, mHoldTime must be resolved by now 1497 // or else we would have returned above.) 1498 if (!mHoldTime.IsNull()) { 1499 mStartTime.SetNull(); 1500 } 1501 1502 if (!reuseReadyPromise) { 1503 // Clear ready promise. We'll create a new one lazily. 1504 mReady = nullptr; 1505 } 1506 1507 mPendingState = PendingState::PlayPending; 1508 mPendingReadyTime = {}; 1509 if (Document* doc = GetRenderedDocument()) { 1510 if (HasFiniteTimeline()) { 1511 // Always schedule a task even if we would like to let this animation 1512 // immediately ready, per spec. 1513 // https://drafts.csswg.org/web-animations/#playing-an-animation-section 1514 // If there's no rendered document, we fail to track this animation, so 1515 // let the scroll frame to trigger it when ticking. 1516 doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this); 1517 } 1518 // Make sure to try to schedule a tick. 1519 mPendingReadyTime = EnsurePaintIsScheduled(*doc); 1520 } 1521 1522 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); 1523 if (IsRelevant()) { 1524 MutationObservers::NotifyAnimationChanged(this); 1525 } 1526 } 1527 1528 // https://drafts.csswg.org/web-animations/#pause-an-animation 1529 void Animation::Pause(ErrorResult& aRv) { 1530 if (IsPausedOrPausing()) { 1531 return; 1532 } 1533 1534 AutoMutationBatchForAnimation mb(*this); 1535 1536 Nullable<TimeDuration> seekTime; 1537 // If we are transitioning from idle, fill in the current time 1538 if (GetCurrentTimeAsDuration().IsNull()) { 1539 if (mPlaybackRate >= 0.0) { 1540 seekTime.SetValue(TimeDuration(0)); 1541 } else { 1542 if (EffectEnd() == TimeDuration::Forever()) { 1543 return aRv.ThrowInvalidStateError("Can't seek to infinite effect end"); 1544 } 1545 seekTime.SetValue(TimeDuration(EffectEnd())); 1546 } 1547 } 1548 1549 if (!seekTime.IsNull()) { 1550 if (HasFiniteTimeline()) { 1551 mStartTime = seekTime; 1552 } else { 1553 mHoldTime = seekTime; 1554 } 1555 } 1556 1557 bool reuseReadyPromise = false; 1558 if (mPendingState == PendingState::PlayPending) { 1559 CancelPendingTasks(); 1560 reuseReadyPromise = true; 1561 } 1562 1563 if (!reuseReadyPromise) { 1564 // Clear ready promise. We'll create a new one lazily. 1565 mReady = nullptr; 1566 } 1567 1568 mPendingState = PendingState::PausePending; 1569 mPendingReadyTime = {}; 1570 // See the relevant PlayPending code for comments. 1571 if (Document* doc = GetRenderedDocument()) { 1572 if (HasFiniteTimeline()) { 1573 doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this); 1574 } 1575 mPendingReadyTime = EnsurePaintIsScheduled(*doc); 1576 } 1577 1578 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); 1579 if (IsRelevant()) { 1580 MutationObservers::NotifyAnimationChanged(this); 1581 } 1582 1583 PostUpdate(); 1584 } 1585 1586 // https://drafts.csswg.org/web-animations/#play-an-animation 1587 void Animation::ResumeAt(const TimeDuration& aReadyTime) { 1588 // This method is only expected to be called for an animation that is 1589 // waiting to play. We can easily adapt it to handle other states 1590 // but it's currently not necessary. 1591 MOZ_ASSERT(mPendingState == PendingState::PlayPending, 1592 "Expected to resume a play-pending animation"); 1593 MOZ_ASSERT(!mHoldTime.IsNull() || !mStartTime.IsNull(), 1594 "An animation in the play-pending state should have either a" 1595 " resolved hold time or resolved start time"); 1596 1597 AutoMutationBatchForAnimation mb(*this); 1598 bool hadPendingPlaybackRate = mPendingPlaybackRate.isSome(); 1599 1600 if (!mHoldTime.IsNull()) { 1601 // The hold time is set, so we don't need any special handling to preserve 1602 // the current time. 1603 ApplyPendingPlaybackRate(); 1604 mStartTime = 1605 StartTimeFromTimelineTime(aReadyTime, mHoldTime.Value(), mPlaybackRate); 1606 if (mPlaybackRate != 0) { 1607 mHoldTime.SetNull(); 1608 } 1609 } else if (!mStartTime.IsNull() && mPendingPlaybackRate) { 1610 // Apply any pending playback rate, preserving the current time. 1611 TimeDuration currentTimeToMatch = CurrentTimeFromTimelineTime( 1612 aReadyTime, mStartTime.Value(), mPlaybackRate); 1613 ApplyPendingPlaybackRate(); 1614 mStartTime = StartTimeFromTimelineTime(aReadyTime, currentTimeToMatch, 1615 mPlaybackRate); 1616 if (mPlaybackRate == 0) { 1617 mHoldTime.SetValue(currentTimeToMatch); 1618 } 1619 } 1620 1621 mPendingState = PendingState::NotPending; 1622 1623 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Sync); 1624 1625 // If we had a pending playback rate, we will have now applied it so we need 1626 // to notify observers. 1627 if (hadPendingPlaybackRate && IsRelevant()) { 1628 MutationObservers::NotifyAnimationChanged(this); 1629 } 1630 1631 if (mReady) { 1632 mReady->MaybeResolve(this); 1633 } 1634 } 1635 1636 void Animation::PauseAt(const TimeDuration& aReadyTime) { 1637 MOZ_ASSERT(mPendingState == PendingState::PausePending, 1638 "Expected to pause a pause-pending animation"); 1639 1640 if (!mStartTime.IsNull() && mHoldTime.IsNull()) { 1641 mHoldTime = CurrentTimeFromTimelineTime(aReadyTime, mStartTime.Value(), 1642 mPlaybackRate); 1643 } 1644 ApplyPendingPlaybackRate(); 1645 mStartTime.SetNull(); 1646 mPendingState = PendingState::NotPending; 1647 1648 UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async); 1649 1650 if (mReady) { 1651 mReady->MaybeResolve(this); 1652 } 1653 } 1654 1655 void Animation::UpdateTiming(SeekFlag aSeekFlag, 1656 SyncNotifyFlag aSyncNotifyFlag) { 1657 // We call UpdateFinishedState before UpdateEffect because the former 1658 // can change the current time, which is used by the latter. 1659 UpdateFinishedState(aSeekFlag, aSyncNotifyFlag); 1660 UpdateEffect(PostRestyleMode::IfNeeded); 1661 1662 if (mTimeline) { 1663 mTimeline->NotifyAnimationUpdated(*this); 1664 } 1665 } 1666 1667 // https://drafts.csswg.org/web-animations/#update-an-animations-finished-state 1668 void Animation::UpdateFinishedState(SeekFlag aSeekFlag, 1669 SyncNotifyFlag aSyncNotifyFlag) { 1670 Nullable<TimeDuration> unconstrainedCurrentTime = 1671 aSeekFlag == SeekFlag::NoSeek ? GetUnconstrainedCurrentTime() 1672 : GetCurrentTimeAsDuration(); 1673 TimeDuration effectEnd = TimeDuration(EffectEnd()); 1674 1675 if (!unconstrainedCurrentTime.IsNull() && !mStartTime.IsNull() && 1676 mPendingState == PendingState::NotPending) { 1677 if (mPlaybackRate > 0.0 && unconstrainedCurrentTime.Value() >= effectEnd) { 1678 if (aSeekFlag == SeekFlag::DidSeek) { 1679 mHoldTime = unconstrainedCurrentTime; 1680 } else if (!mPreviousCurrentTime.IsNull()) { 1681 mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd)); 1682 } else { 1683 mHoldTime.SetValue(effectEnd); 1684 } 1685 } else if (mPlaybackRate < 0.0 && 1686 unconstrainedCurrentTime.Value() <= TimeDuration()) { 1687 if (aSeekFlag == SeekFlag::DidSeek) { 1688 mHoldTime = unconstrainedCurrentTime; 1689 } else if (!mPreviousCurrentTime.IsNull()) { 1690 mHoldTime.SetValue( 1691 std::min(mPreviousCurrentTime.Value(), TimeDuration(0))); 1692 } else { 1693 mHoldTime.SetValue(0); 1694 } 1695 } else if (mPlaybackRate != 0.0 && mTimeline && 1696 !mTimeline->GetCurrentTimeAsDuration().IsNull()) { 1697 if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) { 1698 mStartTime = StartTimeFromTimelineTime( 1699 mTimeline->GetCurrentTimeAsDuration().Value(), mHoldTime.Value(), 1700 mPlaybackRate); 1701 } 1702 mHoldTime.SetNull(); 1703 } 1704 } 1705 1706 // We must recalculate the current time to take account of any mHoldTime 1707 // changes the code above made. 1708 mPreviousCurrentTime = GetCurrentTimeAsDuration(); 1709 1710 bool currentFinishedState = PlayState() == AnimationPlayState::Finished; 1711 if (currentFinishedState && !mFinishedIsResolved) { 1712 DoFinishNotification(aSyncNotifyFlag); 1713 } else if (!currentFinishedState && mFinishedIsResolved) { 1714 ResetFinishedPromise(); 1715 } 1716 } 1717 1718 void Animation::UpdateEffect(PostRestyleMode aPostRestyle) { 1719 if (mEffect) { 1720 UpdateRelevance(); 1721 1722 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect(); 1723 if (keyframeEffect) { 1724 keyframeEffect->NotifyAnimationTimingUpdated(aPostRestyle); 1725 } 1726 } 1727 } 1728 1729 void Animation::FlushUnanimatedStyle() const { 1730 if (Document* doc = GetRenderedDocument()) { 1731 doc->FlushPendingNotifications( 1732 ChangesToFlush(FlushType::Style, /* aFlushAnimations = */ false, 1733 /* aUpdateRelevancy = */ false)); 1734 } 1735 } 1736 1737 void Animation::PostUpdate() { 1738 if (!mEffect) { 1739 return; 1740 } 1741 1742 KeyframeEffect* keyframeEffect = mEffect->AsKeyframeEffect(); 1743 if (!keyframeEffect) { 1744 return; 1745 } 1746 keyframeEffect->RequestRestyle(EffectCompositor::RestyleType::Layer); 1747 } 1748 1749 void Animation::CancelPendingTasks() { 1750 mPendingState = PendingState::NotPending; 1751 } 1752 1753 // https://drafts.csswg.org/web-animations/#reset-an-animations-pending-tasks 1754 void Animation::ResetPendingTasks() { 1755 if (!Pending()) { 1756 return; 1757 } 1758 1759 CancelPendingTasks(); 1760 ApplyPendingPlaybackRate(); 1761 1762 if (mReady) { 1763 mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR); 1764 MOZ_ALWAYS_TRUE(mReady->SetAnyPromiseIsHandled()); 1765 mReady = nullptr; 1766 } 1767 } 1768 1769 // https://drafts.csswg.org/web-animations-2/#at-progress-timeline-boundary 1770 /* static*/ Animation::ProgressTimelinePosition 1771 Animation::AtProgressTimelineBoundary( 1772 const Nullable<TimeDuration>& aTimelineDuration, 1773 const Nullable<TimeDuration>& aCurrentTime, 1774 const TimeDuration& aEffectStartTime, const double aPlaybackRate) { 1775 // Based on changed defined in: https://github.com/w3c/csswg-drafts/pull/6702 1776 // 1. If any of the following conditions are true: 1777 // * the associated animation's timeline is not a progress-based timeline, 1778 // or 1779 // * the associated animation's timeline duration is unresolved or zero, 1780 // or 1781 // * the animation's playback rate is zero 1782 // return false 1783 // Note: We can detect a progress-based timeline by relying on the fact that 1784 // monotonic timelines (i.e. non-progress-based timelines) have an unresolved 1785 // timeline duration. 1786 if (aTimelineDuration.IsNull() || aTimelineDuration.Value().IsZero() || 1787 aPlaybackRate == 0.0) { 1788 return ProgressTimelinePosition::NotBoundary; 1789 } 1790 1791 // 2. Let effective start time be the animation's start time if resolved, or 1792 // zero otherwise. 1793 const TimeDuration& effectiveStartTime = aEffectStartTime; 1794 1795 // 3. Let effective timeline time be (animation's current time / animation's 1796 // playback rate) + effective start time. 1797 // Note: we use zero if the current time is unresolved. See the spec issue: 1798 // https://github.com/w3c/csswg-drafts/issues/7458 1799 const TimeDuration effectiveTimelineTime = 1800 (aCurrentTime.IsNull() 1801 ? TimeDuration() 1802 : aCurrentTime.Value().MultDouble(1.0 / aPlaybackRate)) + 1803 effectiveStartTime; 1804 1805 // 4. Let effective timeline progress be (effective timeline time / timeline 1806 // duration) 1807 // 5. If effective timeline progress is 0 or 1, return true, 1808 // We avoid the division here but it is effectively the same as 4 & 5 above. 1809 return effectiveTimelineTime.IsZero() || 1810 (AnimationUtils::IsWithinAnimationTimeTolerance( 1811 effectiveTimelineTime, aTimelineDuration.Value())) 1812 ? ProgressTimelinePosition::Boundary 1813 : ProgressTimelinePosition::NotBoundary; 1814 } 1815 1816 StickyTimeDuration Animation::EffectEnd() const { 1817 if (!mEffect) { 1818 return StickyTimeDuration(0); 1819 } 1820 1821 return mEffect->NormalizedTiming().EndTime(); 1822 } 1823 1824 Document* Animation::GetRenderedDocument() const { 1825 if (!mEffect || !mEffect->AsKeyframeEffect()) { 1826 return nullptr; 1827 } 1828 1829 return mEffect->AsKeyframeEffect()->GetRenderedDocument(); 1830 } 1831 1832 Document* Animation::GetTimelineDocument() const { 1833 return mTimeline ? mTimeline->GetDocument() : nullptr; 1834 } 1835 1836 void Animation::UpdateScrollTimelineAnimationTracker( 1837 AnimationTimeline* aOldTimeline, AnimationTimeline* aNewTimeline) { 1838 // If we are still in pending, we may have to move this animation into the 1839 // correct animation tracker. 1840 Document* doc = GetRenderedDocument(); 1841 if (!doc || !Pending()) { 1842 return; 1843 } 1844 1845 const bool fromFiniteTimeline = 1846 aOldTimeline && !aOldTimeline->IsMonotonicallyIncreasing(); 1847 const bool toFiniteTimeline = 1848 aNewTimeline && !aNewTimeline->IsMonotonicallyIncreasing(); 1849 if (fromFiniteTimeline == toFiniteTimeline) { 1850 return; 1851 } 1852 1853 if (toFiniteTimeline) { 1854 doc->GetOrCreateScrollTimelineAnimationTracker()->AddPending(*this); 1855 } else { 1856 // From scroll-timeline to null/document-timeline 1857 if (auto* tracker = doc->GetScrollTimelineAnimationTracker()) { 1858 tracker->RemovePending(*this); 1859 } 1860 EnsurePaintIsScheduled(*doc); 1861 } 1862 } 1863 1864 class AsyncFinishNotification : public MicroTaskRunnable { 1865 public: 1866 explicit AsyncFinishNotification(Animation* aAnimation) 1867 : mAnimation(aAnimation) {} 1868 1869 virtual void Run(AutoSlowOperation& aAso) override { 1870 mAnimation->DoFinishNotificationImmediately(this); 1871 mAnimation = nullptr; 1872 } 1873 1874 virtual bool Suppressed() override { 1875 nsIGlobalObject* global = mAnimation->GetOwnerGlobal(); 1876 return global && global->IsInSyncOperation(); 1877 } 1878 1879 private: 1880 RefPtr<Animation> mAnimation; 1881 }; 1882 1883 void Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag) { 1884 CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); 1885 1886 if (aSyncNotifyFlag == SyncNotifyFlag::Sync) { 1887 DoFinishNotificationImmediately(); 1888 } else if (!mFinishNotificationTask) { 1889 RefPtr<MicroTaskRunnable> runnable = new AsyncFinishNotification(this); 1890 context->DispatchToMicroTask(do_AddRef(runnable)); 1891 mFinishNotificationTask = std::move(runnable); 1892 } 1893 } 1894 1895 void Animation::ResetFinishedPromise() { 1896 mFinishedIsResolved = false; 1897 mFinished = nullptr; 1898 } 1899 1900 void Animation::MaybeResolveFinishedPromise() { 1901 if (mFinished) { 1902 mFinished->MaybeResolve(this); 1903 } 1904 mFinishedIsResolved = true; 1905 } 1906 1907 void Animation::DoFinishNotificationImmediately(MicroTaskRunnable* aAsync) { 1908 if (aAsync && aAsync != mFinishNotificationTask) { 1909 return; 1910 } 1911 1912 mFinishNotificationTask = nullptr; 1913 1914 if (PlayState() != AnimationPlayState::Finished) { 1915 return; 1916 } 1917 1918 MaybeResolveFinishedPromise(); 1919 1920 QueuePlaybackEvent(nsGkAtoms::onfinish, 1921 AnimationTimeToTimeStamp(EffectEnd())); 1922 } 1923 1924 void Animation::QueuePlaybackEvent(nsAtom* aOnEvent, 1925 TimeStamp&& aScheduledEventTime) { 1926 // Use document for timing. 1927 // https://drafts.csswg.org/web-animations-1/#document-for-timing 1928 Document* doc = GetTimelineDocument(); 1929 if (!doc) { 1930 return; 1931 } 1932 1933 nsPresContext* presContext = doc->GetPresContext(); 1934 if (!presContext) { 1935 return; 1936 } 1937 1938 Nullable<double> currentTime; 1939 if (aOnEvent == nsGkAtoms::onfinish || aOnEvent == nsGkAtoms::onremove) { 1940 currentTime = GetCurrentTimeAsDouble(); 1941 } 1942 1943 Nullable<double> timelineTime; 1944 if (mTimeline) { 1945 timelineTime = mTimeline->GetCurrentTimeAsDouble(); 1946 } 1947 1948 presContext->AnimationEventDispatcher()->QueueEvent( 1949 AnimationEventInfo(aOnEvent, currentTime, timelineTime, 1950 std::move(aScheduledEventTime), this)); 1951 } 1952 1953 bool Animation::IsRunningOnCompositor() const { 1954 return mEffect && mEffect->AsKeyframeEffect() && 1955 mEffect->AsKeyframeEffect()->IsRunningOnCompositor(); 1956 } 1957 1958 bool Animation::HasCurrentEffect() const { 1959 return GetEffect() && GetEffect()->IsCurrent(); 1960 } 1961 1962 bool Animation::IsInEffect() const { 1963 return GetEffect() && GetEffect()->IsInEffect(); 1964 } 1965 1966 void Animation::SetHiddenByContentVisibility(bool hidden) { 1967 if (mHiddenByContentVisibility == hidden) { 1968 return; 1969 } 1970 1971 mHiddenByContentVisibility = hidden; 1972 1973 if (!GetTimeline()) { 1974 return; 1975 } 1976 1977 GetTimeline()->NotifyAnimationContentVisibilityChanged(this, !hidden); 1978 } 1979 1980 void Animation::UpdateHiddenByContentVisibility() { 1981 // To be consistent with nsIFrame::UpdateAnimationVisibility, here we only 1982 // deal with CSSAnimation and CSSTransition. 1983 if (!AsCSSAnimation() && !AsCSSTransition()) { 1984 return; 1985 } 1986 NonOwningAnimationTarget target = GetTargetForAnimation(); 1987 if (!target) { 1988 return; 1989 } 1990 // If a CSS animation or CSS transition is no longer associated with an owning 1991 // element, it behaves like a programmatic web animation, c-v shouldn't hide 1992 // it. 1993 bool hasOwningElement = IsMarkupAnimation(AsCSSAnimation()) || 1994 IsMarkupAnimation(AsCSSTransition()); 1995 if (auto* frame = target.mElement->GetPrimaryFrame()) { 1996 SetHiddenByContentVisibility( 1997 hasOwningElement && frame->IsHiddenByContentVisibilityOnAnyAncestor()); 1998 } 1999 } 2000 2001 StickyTimeDuration Animation::IntervalStartTime( 2002 const StickyTimeDuration& aActiveDuration) const { 2003 MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(), 2004 "Should be called for CSS animations or transitions"); 2005 static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration(); 2006 return std::max( 2007 std::min(StickyTimeDuration(-mEffect->NormalizedTiming().Delay()), 2008 aActiveDuration), 2009 zeroDuration); 2010 } 2011 2012 // Later side of the elapsed time range reported in CSS Animations and CSS 2013 // Transitions events. 2014 // 2015 // https://drafts.csswg.org/css-animations-2/#interval-end 2016 // https://drafts.csswg.org/css-transitions-2/#interval-end 2017 StickyTimeDuration Animation::IntervalEndTime( 2018 const StickyTimeDuration& aActiveDuration) const { 2019 MOZ_ASSERT(AsCSSTransition() || AsCSSAnimation(), 2020 "Should be called for CSS animations or transitions"); 2021 2022 static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration(); 2023 const StickyTimeDuration& effectEnd = EffectEnd(); 2024 2025 // If both "associated effect end" and "start delay" are Infinity, we skip it 2026 // because we will get NaN when computing "Infinity - Infinity", and 2027 // using NaN in std::min or std::max is undefined. 2028 if (MOZ_UNLIKELY(effectEnd == TimeDuration::Forever() && 2029 effectEnd == mEffect->NormalizedTiming().Delay())) { 2030 // Note: If we use TimeDuration::Forever(), within our animation event 2031 // handling, we'd end up turning that into a null TimeStamp which can causes 2032 // errors if we try to do any arithmetic with it. Given that we should never 2033 // end up _using_ the interval end time. So returning zeroDuration here is 2034 // probably fine. 2035 return zeroDuration; 2036 } 2037 2038 return std::max(std::min(effectEnd - mEffect->NormalizedTiming().Delay(), 2039 aActiveDuration), 2040 zeroDuration); 2041 } 2042 2043 } // namespace mozilla::dom