2015-05-03 22:32:37 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2014-08-10 11:06:44 +04:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
2015-04-21 04:22:09 +03:00
|
|
|
#include "Animation.h"
|
2014-08-30 10:11:57 +04:00
|
|
|
#include "AnimationUtils.h"
|
2015-04-21 04:22:09 +03:00
|
|
|
#include "mozilla/dom/AnimationBinding.h"
|
2015-07-31 00:25:00 +03:00
|
|
|
#include "mozilla/dom/AnimationPlaybackEvent.h"
|
2016-05-30 03:01:11 +03:00
|
|
|
#include "mozilla/dom/DocumentTimeline.h"
|
2016-04-28 18:22:43 +03:00
|
|
|
#include "mozilla/AnimationTarget.h"
|
2015-04-01 06:23:25 +03:00
|
|
|
#include "mozilla/AutoRestore.h"
|
2015-10-22 09:16:18 +03:00
|
|
|
#include "mozilla/AsyncEventDispatcher.h" // For AsyncEventDispatcher
|
|
|
|
#include "mozilla/Maybe.h" // For Maybe
|
2016-01-14 04:24:24 +03:00
|
|
|
#include "nsAnimationManager.h" // For CSSAnimation
|
2015-10-22 09:16:18 +03:00
|
|
|
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
|
2014-11-17 07:45:58 +03:00
|
|
|
#include "nsIDocument.h" // For nsIDocument
|
2014-11-17 07:45:58 +03:00
|
|
|
#include "nsIPresShell.h" // For nsIPresShell
|
2015-07-30 00:21:00 +03:00
|
|
|
#include "nsThreadUtils.h" // For nsRunnableMethod and nsRevocableEventPtr
|
2016-01-14 04:24:24 +03:00
|
|
|
#include "nsTransitionManager.h" // For CSSTransition
|
2015-04-21 04:22:09 +03:00
|
|
|
#include "PendingAnimationTracker.h" // For PendingAnimationTracker
|
2014-08-10 11:06:44 +04:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
|
2015-06-09 05:13:53 +03:00
|
|
|
// Static members
|
2015-09-15 05:20:26 +03:00
|
|
|
uint64_t Animation::sNextAnimationIndex = 0;
|
2015-06-09 05:13:53 +03:00
|
|
|
|
2015-07-31 00:23:00 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(Animation, DOMEventTargetHelper,
|
|
|
|
mTimeline,
|
|
|
|
mEffect,
|
|
|
|
mReady,
|
|
|
|
mFinished)
|
|
|
|
|
|
|
|
NS_IMPL_ADDREF_INHERITED(Animation, DOMEventTargetHelper)
|
|
|
|
NS_IMPL_RELEASE_INHERITED(Animation, DOMEventTargetHelper)
|
|
|
|
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Animation)
|
|
|
|
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
2014-08-10 11:06:44 +04:00
|
|
|
|
|
|
|
JSObject*
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
2014-08-10 11:06:44 +04:00
|
|
|
{
|
2015-04-21 04:22:09 +03:00
|
|
|
return dom::AnimationBinding::Wrap(aCx, this, aGivenProto);
|
2014-08-10 11:06:44 +04:00
|
|
|
}
|
|
|
|
|
2015-10-22 09:16:18 +03:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// Utility methods
|
|
|
|
//
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
// A wrapper around nsAutoAnimationMutationBatch that looks up the
|
|
|
|
// appropriate document from the supplied animation.
|
|
|
|
class MOZ_RAII AutoMutationBatchForAnimation {
|
|
|
|
public:
|
|
|
|
explicit AutoMutationBatchForAnimation(const Animation& aAnimation
|
|
|
|
MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
|
|
|
|
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
|
2016-03-21 11:49:50 +03:00
|
|
|
Maybe<NonOwningAnimationTarget> target =
|
|
|
|
nsNodeUtils::GetTargetForAnimation(&aAnimation);
|
|
|
|
if (!target) {
|
2015-10-22 09:16:18 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For mutation observers, we use the OwnerDoc.
|
2016-03-21 11:49:50 +03:00
|
|
|
nsIDocument* doc = target->mElement->OwnerDoc();
|
2015-10-22 09:16:18 +03:00
|
|
|
if (!doc) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mAutoBatch.emplace(doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
|
|
|
|
Maybe<nsAutoAnimationMutationBatch> mAutoBatch;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-04-27 04:05:46 +03:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// Animation interface:
|
|
|
|
//
|
|
|
|
// ---------------------------------------------------------------------------
|
2016-01-17 03:02:00 +03:00
|
|
|
/* static */ already_AddRefed<Animation>
|
|
|
|
Animation::Constructor(const GlobalObject& aGlobal,
|
2016-08-16 11:48:55 +03:00
|
|
|
AnimationEffectReadOnly* aEffect,
|
2016-05-30 03:01:11 +03:00
|
|
|
const Optional<AnimationTimeline*>& aTimeline,
|
2016-01-17 03:02:00 +03:00
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
|
|
|
|
RefPtr<Animation> animation = new Animation(global);
|
|
|
|
|
2016-05-30 03:01:11 +03:00
|
|
|
AnimationTimeline* timeline;
|
|
|
|
if (aTimeline.WasPassed()) {
|
|
|
|
timeline = aTimeline.Value();
|
|
|
|
} else {
|
|
|
|
nsIDocument* document =
|
|
|
|
AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
|
|
|
|
if (!document) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
timeline = document->Timeline();
|
|
|
|
}
|
|
|
|
|
2016-05-31 03:42:37 +03:00
|
|
|
animation->SetTimelineNoUpdate(timeline);
|
2016-08-24 09:36:14 +03:00
|
|
|
animation->SetEffectNoUpdate(aEffect);
|
2016-01-17 03:02:00 +03:00
|
|
|
|
|
|
|
return animation.forget();
|
|
|
|
}
|
|
|
|
|
2016-01-08 11:17:00 +03:00
|
|
|
void
|
|
|
|
Animation::SetId(const nsAString& aId)
|
|
|
|
{
|
|
|
|
if (mId == aId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mId = aId;
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2015-04-27 04:05:46 +03:00
|
|
|
|
|
|
|
void
|
2016-07-25 11:56:34 +03:00
|
|
|
Animation::SetEffect(AnimationEffectReadOnly* aEffect)
|
2016-08-24 09:36:14 +03:00
|
|
|
{
|
|
|
|
SetEffectNoUpdate(aEffect);
|
|
|
|
PostUpdate();
|
|
|
|
}
|
|
|
|
|
2016-07-13 13:44:19 +03:00
|
|
|
// https://w3c.github.io/web-animations/#setting-the-target-effect
|
2016-08-24 09:36:14 +03:00
|
|
|
void
|
|
|
|
Animation::SetEffectNoUpdate(AnimationEffectReadOnly* aEffect)
|
2015-04-27 04:05:46 +03:00
|
|
|
{
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<Animation> kungFuDeathGrip(this);
|
2015-10-07 08:30:27 +03:00
|
|
|
|
2015-08-18 10:57:00 +03:00
|
|
|
if (mEffect == aEffect) {
|
|
|
|
return;
|
|
|
|
}
|
2016-07-13 13:44:19 +03:00
|
|
|
|
2016-08-16 15:00:35 +03:00
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
bool wasRelevant = mIsRelevant;
|
|
|
|
|
2015-04-27 04:05:46 +03:00
|
|
|
if (mEffect) {
|
2016-07-13 13:44:19 +03:00
|
|
|
if (!aEffect) {
|
|
|
|
// If the new effect is null, call ResetPendingTasks before clearing
|
|
|
|
// mEffect since ResetPendingTasks needs it to get the appropriate
|
|
|
|
// PendingAnimationTracker.
|
|
|
|
ResetPendingTasks();
|
|
|
|
}
|
|
|
|
|
2016-08-16 15:00:35 +03:00
|
|
|
// We need to notify observers now because once we set mEffect to null
|
|
|
|
// we won't be able to find the target element to notify.
|
|
|
|
if (mIsRelevant) {
|
|
|
|
nsNodeUtils::AnimationRemoved(this);
|
|
|
|
}
|
|
|
|
|
2016-07-13 13:44:19 +03:00
|
|
|
// Break links with the old effect and then drop it.
|
|
|
|
RefPtr<AnimationEffectReadOnly> oldEffect = mEffect;
|
|
|
|
mEffect = nullptr;
|
|
|
|
oldEffect->SetAnimation(nullptr);
|
2016-08-16 15:00:35 +03:00
|
|
|
|
|
|
|
// The following will not do any notification because mEffect is null.
|
|
|
|
UpdateRelevance();
|
2015-04-27 04:05:46 +03:00
|
|
|
}
|
2016-07-13 13:44:19 +03:00
|
|
|
|
|
|
|
if (aEffect) {
|
|
|
|
// Break links from the new effect to its previous animation, if any.
|
|
|
|
RefPtr<AnimationEffectReadOnly> newEffect = aEffect;
|
|
|
|
Animation* prevAnim = aEffect->GetAnimation();
|
|
|
|
if (prevAnim) {
|
|
|
|
prevAnim->SetEffect(nullptr);
|
|
|
|
}
|
|
|
|
|
2016-08-29 11:22:46 +03:00
|
|
|
// Create links with the new effect. SetAnimation(this) will also update
|
|
|
|
// mIsRelevant of this animation, and then notify mutation observer if
|
|
|
|
// needed by calling Animation::UpdateRelevance(), so we don't need to
|
|
|
|
// call it again.
|
2016-07-13 13:44:19 +03:00
|
|
|
mEffect = newEffect;
|
2015-10-07 08:30:27 +03:00
|
|
|
mEffect->SetAnimation(this);
|
2016-07-13 13:44:19 +03:00
|
|
|
|
2016-08-29 11:22:46 +03:00
|
|
|
// Notify possible add or change.
|
2016-08-16 15:00:35 +03:00
|
|
|
// If the target is different, the change notification will be ignored by
|
|
|
|
// AutoMutationBatchForAnimation.
|
|
|
|
if (wasRelevant && mIsRelevant) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
|
|
|
|
2016-07-13 13:44:19 +03:00
|
|
|
// Reschedule pending pause or pending play tasks.
|
|
|
|
// If we have a pending animation, it will either be registered
|
|
|
|
// in the pending animation tracker and have a null pending ready time,
|
|
|
|
// or, after it has been painted, it will be removed from the tracker
|
|
|
|
// and assigned a pending ready time.
|
|
|
|
// After updating the effect we'll typically need to repaint so if we've
|
|
|
|
// already been assigned a pending ready time, we should clear it and put
|
|
|
|
// the animation back in the tracker.
|
|
|
|
if (!mPendingReadyTime.IsNull()) {
|
|
|
|
mPendingReadyTime.SetNull();
|
|
|
|
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
|
|
if (doc) {
|
|
|
|
PendingAnimationTracker* tracker =
|
|
|
|
doc->GetOrCreatePendingAnimationTracker();
|
|
|
|
if (mPendingState == PendingState::PlayPending) {
|
|
|
|
tracker->AddPlayPending(*this);
|
|
|
|
} else {
|
|
|
|
tracker->AddPausePending(*this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-04-27 04:05:46 +03:00
|
|
|
}
|
2015-08-17 07:59:45 +03:00
|
|
|
|
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-04-27 04:05:46 +03:00
|
|
|
}
|
|
|
|
|
2015-04-28 11:21:58 +03:00
|
|
|
void
|
|
|
|
Animation::SetTimeline(AnimationTimeline* aTimeline)
|
2016-05-31 03:42:37 +03:00
|
|
|
{
|
|
|
|
SetTimelineNoUpdate(aTimeline);
|
|
|
|
PostUpdate();
|
|
|
|
}
|
|
|
|
|
2016-05-31 03:42:37 +03:00
|
|
|
// https://w3c.github.io/web-animations/#setting-the-timeline
|
2016-05-31 03:42:37 +03:00
|
|
|
void
|
|
|
|
Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline)
|
2015-04-28 11:21:58 +03:00
|
|
|
{
|
|
|
|
if (mTimeline == aTimeline) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-24 21:12:09 +03:00
|
|
|
RefPtr<AnimationTimeline> oldTimeline = mTimeline;
|
|
|
|
if (oldTimeline) {
|
|
|
|
oldTimeline->RemoveAnimation(this);
|
2016-05-31 03:42:38 +03:00
|
|
|
}
|
|
|
|
|
2015-04-28 11:21:58 +03:00
|
|
|
mTimeline = aTimeline;
|
2016-05-31 03:42:37 +03:00
|
|
|
if (!mStartTime.IsNull()) {
|
|
|
|
mHoldTime.SetNull();
|
|
|
|
}
|
2015-04-28 11:21:58 +03:00
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-04-28 11:21:58 +03:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#set-the-animation-start-time
|
2015-02-09 13:26:27 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
|
2015-02-09 13:26:27 +03:00
|
|
|
{
|
2015-10-22 09:16:18 +03:00
|
|
|
if (aNewStartTime == mStartTime) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
2015-04-28 06:49:12 +03:00
|
|
|
Nullable<TimeDuration> timelineTime;
|
2015-02-09 13:26:27 +03:00
|
|
|
if (mTimeline) {
|
|
|
|
// The spec says to check if the timeline is active (has a resolved time)
|
|
|
|
// before using it here, but we don't need to since it's harmless to set
|
|
|
|
// the already null time to null.
|
|
|
|
timelineTime = mTimeline->GetCurrentTime();
|
|
|
|
}
|
|
|
|
if (timelineTime.IsNull() && !aNewStartTime.IsNull()) {
|
|
|
|
mHoldTime.SetNull();
|
|
|
|
}
|
2015-04-28 06:49:12 +03:00
|
|
|
|
2015-02-09 13:26:27 +03:00
|
|
|
Nullable<TimeDuration> previousCurrentTime = GetCurrentTime();
|
|
|
|
mStartTime = aNewStartTime;
|
|
|
|
if (!aNewStartTime.IsNull()) {
|
2015-03-13 23:10:45 +03:00
|
|
|
if (mPlaybackRate != 0.0) {
|
|
|
|
mHoldTime.SetNull();
|
|
|
|
}
|
2015-02-09 13:26:27 +03:00
|
|
|
} else {
|
|
|
|
mHoldTime = previousCurrentTime;
|
|
|
|
}
|
|
|
|
|
2015-03-27 09:56:45 +03:00
|
|
|
CancelPendingTasks();
|
2015-02-09 13:26:27 +03:00
|
|
|
if (mReady) {
|
|
|
|
// We may have already resolved mReady, but in that case calling
|
|
|
|
// MaybeResolve is a no-op, so that's okay.
|
|
|
|
mReady->MaybeResolve(this);
|
|
|
|
}
|
|
|
|
|
2016-04-27 05:34:05 +03:00
|
|
|
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
|
2015-10-22 09:16:18 +03:00
|
|
|
if (IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2015-02-12 20:56:57 +03:00
|
|
|
PostUpdate();
|
2015-02-09 13:26:27 +03:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#current-time
|
2014-10-20 08:55:45 +04:00
|
|
|
Nullable<TimeDuration>
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetCurrentTime() const
|
2014-08-10 11:06:44 +04:00
|
|
|
{
|
2014-10-20 08:55:45 +04:00
|
|
|
Nullable<TimeDuration> result;
|
|
|
|
if (!mHoldTime.IsNull()) {
|
|
|
|
result = mHoldTime;
|
2014-12-04 23:13:38 +03:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-04-28 06:49:12 +03:00
|
|
|
if (mTimeline && !mStartTime.IsNull()) {
|
2014-10-20 08:55:45 +04:00
|
|
|
Nullable<TimeDuration> timelineTime = mTimeline->GetCurrentTime();
|
2014-12-04 23:13:38 +03:00
|
|
|
if (!timelineTime.IsNull()) {
|
2015-03-13 23:10:45 +03:00
|
|
|
result.SetValue((timelineTime.Value() - mStartTime.Value())
|
|
|
|
.MultDouble(mPlaybackRate));
|
2014-10-20 08:55:45 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2014-08-10 11:06:44 +04:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#set-the-current-time
|
2015-03-09 19:50:39 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::SetCurrentTime(const TimeDuration& aSeekTime)
|
2015-03-09 19:50:39 +03:00
|
|
|
{
|
2015-10-22 09:16:18 +03:00
|
|
|
// Return early if the current time has not changed. However, if we
|
|
|
|
// are pause-pending, then setting the current time to any value
|
|
|
|
// including the current value has the effect of aborting the
|
|
|
|
// pause so we should not return early in that case.
|
|
|
|
if (mPendingState != PendingState::PausePending &&
|
|
|
|
Nullable<TimeDuration>(aSeekTime) == GetCurrentTime()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
2015-03-09 19:50:39 +03:00
|
|
|
SilentlySetCurrentTime(aSeekTime);
|
|
|
|
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState == PendingState::PausePending) {
|
2015-05-18 05:41:19 +03:00
|
|
|
// Finish the pause operation
|
|
|
|
mHoldTime.SetValue(aSeekTime);
|
|
|
|
mStartTime.SetNull();
|
|
|
|
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mReady) {
|
|
|
|
mReady->MaybeResolve(this);
|
|
|
|
}
|
2015-05-18 05:41:19 +03:00
|
|
|
CancelPendingTasks();
|
2015-03-27 09:56:45 +03:00
|
|
|
}
|
2015-03-09 19:50:39 +03:00
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
|
2015-10-22 09:16:18 +03:00
|
|
|
if (IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2015-03-09 19:50:39 +03:00
|
|
|
PostUpdate();
|
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#set-the-animation-playback-rate
|
2015-03-13 23:10:45 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::SetPlaybackRate(double aPlaybackRate)
|
2015-03-13 23:10:45 +03:00
|
|
|
{
|
2015-10-22 09:16:18 +03:00
|
|
|
if (aPlaybackRate == mPlaybackRate) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
2015-03-13 23:10:45 +03:00
|
|
|
Nullable<TimeDuration> previousTime = GetCurrentTime();
|
|
|
|
mPlaybackRate = aPlaybackRate;
|
|
|
|
if (!previousTime.IsNull()) {
|
|
|
|
SetCurrentTime(previousTime.Value());
|
|
|
|
}
|
2015-10-22 09:16:18 +03:00
|
|
|
|
2015-10-22 09:16:18 +03:00
|
|
|
// In the case where GetCurrentTime() returns the same result before and
|
|
|
|
// after updating mPlaybackRate, SetCurrentTime will return early since,
|
|
|
|
// as far as it can tell, nothing has changed.
|
|
|
|
// As a result, we need to perform the following updates here:
|
|
|
|
// - update timing (since, if the sign of the playback rate has changed, our
|
|
|
|
// finished state may have changed),
|
|
|
|
// - dispatch a change notification for the changed playback rate, and
|
|
|
|
// - update the playback rate on animations on layers.
|
|
|
|
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Async);
|
2015-10-22 09:16:18 +03:00
|
|
|
if (IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2015-10-22 09:16:18 +03:00
|
|
|
PostUpdate();
|
2015-03-13 23:10:45 +03:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#play-state
|
2014-10-20 08:55:45 +04:00
|
|
|
AnimationPlayState
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::PlayState() const
|
2014-10-20 08:55:45 +04:00
|
|
|
{
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState != PendingState::NotPending) {
|
2014-12-18 02:42:41 +03:00
|
|
|
return AnimationPlayState::Pending;
|
|
|
|
}
|
|
|
|
|
2014-10-20 08:55:45 +04:00
|
|
|
Nullable<TimeDuration> currentTime = GetCurrentTime();
|
2014-10-20 08:55:45 +04:00
|
|
|
if (currentTime.IsNull()) {
|
|
|
|
return AnimationPlayState::Idle;
|
|
|
|
}
|
|
|
|
|
2014-12-04 19:28:37 +03:00
|
|
|
if (mStartTime.IsNull()) {
|
2014-10-20 08:55:45 +04:00
|
|
|
return AnimationPlayState::Paused;
|
|
|
|
}
|
|
|
|
|
2015-04-15 02:48:21 +03:00
|
|
|
if ((mPlaybackRate > 0.0 && currentTime.Value() >= EffectEnd()) ||
|
2016-08-27 10:53:33 +03:00
|
|
|
(mPlaybackRate < 0.0 && currentTime.Value() <= TimeDuration())) {
|
2014-10-20 08:55:45 +04:00
|
|
|
return AnimationPlayState::Finished;
|
|
|
|
}
|
|
|
|
|
|
|
|
return AnimationPlayState::Running;
|
|
|
|
}
|
|
|
|
|
2014-12-18 02:42:40 +03:00
|
|
|
Promise*
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetReady(ErrorResult& aRv)
|
2014-12-18 02:42:40 +03:00
|
|
|
{
|
2015-07-31 00:23:00 +03:00
|
|
|
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
|
|
|
|
if (!mReady && global) {
|
|
|
|
mReady = Promise::Create(global, aRv); // Lazily create on demand
|
2014-12-18 02:42:40 +03:00
|
|
|
}
|
|
|
|
if (!mReady) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
2015-03-18 16:22:11 +03:00
|
|
|
} else if (PlayState() != AnimationPlayState::Pending) {
|
|
|
|
mReady->MaybeResolve(this);
|
2014-12-18 02:42:40 +03:00
|
|
|
}
|
|
|
|
return mReady;
|
|
|
|
}
|
|
|
|
|
2015-03-18 16:22:11 +03:00
|
|
|
Promise*
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetFinished(ErrorResult& aRv)
|
2015-03-18 16:22:11 +03:00
|
|
|
{
|
2015-07-31 00:23:00 +03:00
|
|
|
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
|
|
|
|
if (!mFinished && global) {
|
|
|
|
mFinished = Promise::Create(global, aRv); // Lazily create on demand
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
|
|
|
if (!mFinished) {
|
|
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
2015-07-30 00:21:00 +03:00
|
|
|
} else if (mFinishedIsResolved) {
|
|
|
|
MaybeResolveFinishedPromise();
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
|
|
|
return mFinished;
|
|
|
|
}
|
|
|
|
|
2015-04-27 02:53:19 +03:00
|
|
|
void
|
|
|
|
Animation::Cancel()
|
|
|
|
{
|
2016-05-31 03:42:37 +03:00
|
|
|
CancelNoUpdate();
|
2015-04-27 02:53:19 +03:00
|
|
|
PostUpdate();
|
|
|
|
}
|
|
|
|
|
2015-04-27 04:05:46 +03:00
|
|
|
// https://w3c.github.io/web-animations/#finish-an-animation
|
2015-04-16 19:15:20 +03:00
|
|
|
void
|
|
|
|
Animation::Finish(ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
if (mPlaybackRate == 0 ||
|
|
|
|
(mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
|
|
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-22 09:16:18 +03:00
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
|
|
|
// Seek to the end
|
2015-04-16 19:15:20 +03:00
|
|
|
TimeDuration limit =
|
|
|
|
mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
|
2015-10-22 09:16:18 +03:00
|
|
|
bool didChange = GetCurrentTime() != Nullable<TimeDuration>(limit);
|
2015-07-14 22:45:00 +03:00
|
|
|
SilentlySetCurrentTime(limit);
|
2015-04-16 19:15:20 +03:00
|
|
|
|
2015-05-19 04:08:46 +03:00
|
|
|
// If we are paused or play-pending we need to fill in the start time in
|
|
|
|
// order to transition to the finished state.
|
|
|
|
//
|
|
|
|
// We only do this, however, if we have an active timeline. If we have an
|
|
|
|
// inactive timeline we can't transition into the finished state just like
|
|
|
|
// we can't transition to the running state (this finished state is really
|
|
|
|
// a substate of the running state).
|
|
|
|
if (mStartTime.IsNull() &&
|
|
|
|
mTimeline &&
|
|
|
|
!mTimeline->GetCurrentTime().IsNull()) {
|
|
|
|
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
|
|
|
|
limit.MultDouble(1.0 / mPlaybackRate));
|
2015-10-22 09:16:18 +03:00
|
|
|
didChange = true;
|
2015-05-19 04:08:46 +03:00
|
|
|
}
|
|
|
|
|
2015-07-14 22:45:00 +03:00
|
|
|
// If we just resolved the start time for a pause or play-pending
|
|
|
|
// animation, we need to clear the task. We don't do this as a branch of
|
|
|
|
// the above however since we can have a play-pending animation with a
|
|
|
|
// resolved start time if we aborted a pause operation.
|
|
|
|
if (!mStartTime.IsNull() &&
|
|
|
|
(mPendingState == PendingState::PlayPending ||
|
|
|
|
mPendingState == PendingState::PausePending)) {
|
|
|
|
if (mPendingState == PendingState::PausePending) {
|
|
|
|
mHoldTime.SetNull();
|
|
|
|
}
|
2015-04-16 19:15:20 +03:00
|
|
|
CancelPendingTasks();
|
2015-10-22 09:16:18 +03:00
|
|
|
didChange = true;
|
2015-04-16 19:15:20 +03:00
|
|
|
if (mReady) {
|
|
|
|
mReady->MaybeResolve(this);
|
|
|
|
}
|
|
|
|
}
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::DidSeek, SyncNotifyFlag::Sync);
|
2015-10-22 09:16:18 +03:00
|
|
|
if (didChange && IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2015-04-16 19:15:20 +03:00
|
|
|
PostUpdate();
|
|
|
|
}
|
|
|
|
|
2014-10-20 08:55:43 +04:00
|
|
|
void
|
2015-05-19 08:00:48 +03:00
|
|
|
Animation::Play(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
2014-10-20 08:55:43 +04:00
|
|
|
{
|
2016-05-31 03:42:37 +03:00
|
|
|
PlayNoUpdate(aRv, aLimitBehavior);
|
2014-11-17 07:46:01 +03:00
|
|
|
PostUpdate();
|
2014-10-20 08:55:43 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2015-05-19 08:55:26 +03:00
|
|
|
Animation::Pause(ErrorResult& aRv)
|
2014-10-20 08:55:43 +04:00
|
|
|
{
|
2016-05-31 03:42:37 +03:00
|
|
|
PauseNoUpdate(aRv);
|
2014-11-17 07:46:01 +03:00
|
|
|
PostUpdate();
|
2014-10-20 08:55:43 +04:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#reverse-an-animation
|
2015-07-09 23:54:00 +03:00
|
|
|
void
|
|
|
|
Animation::Reverse(ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPlaybackRate == 0.0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-22 09:16:18 +03:00
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
2015-07-09 23:54:00 +03:00
|
|
|
SilentlySetPlaybackRate(-mPlaybackRate);
|
|
|
|
Play(aRv, LimitBehavior::AutoRewind);
|
2015-10-22 09:16:18 +03:00
|
|
|
|
|
|
|
if (IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2015-07-09 23:54:00 +03:00
|
|
|
}
|
|
|
|
|
2015-04-27 04:05:46 +03:00
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
//
|
|
|
|
// JS wrappers for Animation interface:
|
|
|
|
//
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
2014-12-04 19:28:38 +03:00
|
|
|
Nullable<double>
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetStartTimeAsDouble() const
|
2014-12-04 19:28:38 +03:00
|
|
|
{
|
|
|
|
return AnimationUtils::TimeDurationToDouble(mStartTime);
|
|
|
|
}
|
|
|
|
|
2015-02-09 13:26:27 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::SetStartTimeAsDouble(const Nullable<double>& aStartTime)
|
2015-02-09 13:26:27 +03:00
|
|
|
{
|
|
|
|
return SetStartTime(AnimationUtils::DoubleToTimeDuration(aStartTime));
|
|
|
|
}
|
2015-04-21 04:22:09 +03:00
|
|
|
|
2014-10-20 08:55:45 +04:00
|
|
|
Nullable<double>
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetCurrentTimeAsDouble() const
|
2014-10-20 08:55:45 +04:00
|
|
|
{
|
|
|
|
return AnimationUtils::TimeDurationToDouble(GetCurrentTime());
|
|
|
|
}
|
|
|
|
|
2015-03-09 19:50:39 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::SetCurrentTimeAsDouble(const Nullable<double>& aCurrentTime,
|
2015-03-09 19:50:39 +03:00
|
|
|
ErrorResult& aRv)
|
|
|
|
{
|
|
|
|
if (aCurrentTime.IsNull()) {
|
|
|
|
if (!GetCurrentTime().IsNull()) {
|
|
|
|
aRv.Throw(NS_ERROR_DOM_TYPE_ERR);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SetCurrentTime(TimeDuration::FromMilliseconds(aCurrentTime.Value()));
|
|
|
|
}
|
|
|
|
|
2015-04-27 04:05:46 +03:00
|
|
|
// ---------------------------------------------------------------------------
|
2014-08-10 11:06:48 +04:00
|
|
|
|
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::Tick()
|
2014-08-10 11:06:48 +04:00
|
|
|
{
|
Bug 1208938 part 3 - Update pending finishing handling; r=heycam
Animation::Tick contains special handling to cope with pending ready times
that are in the future. This was originally introduced to cope with the
situation where we are called multiple times per refresh-driver tick.
As of bug 1195180, Animation::Tick should no longer be called multiple
times per refresh driver tick. It would seem, therefore, that we no longer
need to check for a future time. However, since introducing this check, the
vsync refresh driver timer has been added which means that we can still have
a recorded time from TimeStamp::Now that is ahead of the vsync time used to
update the refresh driver. In that case, however, rather than waiting for the
next tick, we should simply clamp that pending ready time to the refresh driver
time and finish pending immediately.
This patch also updates one of the tests for reversing. With this updated
behavior we can sometimes arrive at a situation where when an Animation starts
and its ready promise resolves, its currentTime is still 0. If we call
reverse() at this point on an animation with an infinite active duration it
should throw an InvalidStateError. To avoid this situation, this test makes
sure we wait an extra frame before calling reverse().
2015-10-07 08:30:28 +03:00
|
|
|
// Finish pending if we have a pending ready time, but only if we also
|
|
|
|
// have an active timeline.
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState != PendingState::NotPending &&
|
2015-01-09 01:57:58 +03:00
|
|
|
!mPendingReadyTime.IsNull() &&
|
2015-04-28 06:49:12 +03:00
|
|
|
mTimeline &&
|
Bug 1208938 part 3 - Update pending finishing handling; r=heycam
Animation::Tick contains special handling to cope with pending ready times
that are in the future. This was originally introduced to cope with the
situation where we are called multiple times per refresh-driver tick.
As of bug 1195180, Animation::Tick should no longer be called multiple
times per refresh driver tick. It would seem, therefore, that we no longer
need to check for a future time. However, since introducing this check, the
vsync refresh driver timer has been added which means that we can still have
a recorded time from TimeStamp::Now that is ahead of the vsync time used to
update the refresh driver. In that case, however, rather than waiting for the
next tick, we should simply clamp that pending ready time to the refresh driver
time and finish pending immediately.
This patch also updates one of the tests for reversing. With this updated
behavior we can sometimes arrive at a situation where when an Animation starts
and its ready promise resolves, its currentTime is still 0. If we call
reverse() at this point on an animation with an infinite active duration it
should throw an InvalidStateError. To avoid this situation, this test makes
sure we wait an extra frame before calling reverse().
2015-10-07 08:30:28 +03:00
|
|
|
!mTimeline->GetCurrentTime().IsNull()) {
|
|
|
|
// Even though mPendingReadyTime is initialized using TimeStamp::Now()
|
|
|
|
// during the *previous* tick of the refresh driver, it can still be
|
|
|
|
// ahead of the *current* timeline time when we are using the
|
|
|
|
// vsync timer so we need to clamp it to the timeline time.
|
|
|
|
mPendingReadyTime.SetValue(std::min(mTimeline->GetCurrentTime().Value(),
|
|
|
|
mPendingReadyTime.Value()));
|
2015-04-01 06:23:24 +03:00
|
|
|
FinishPendingAt(mPendingReadyTime.Value());
|
2015-01-09 01:57:58 +03:00
|
|
|
mPendingReadyTime.SetNull();
|
|
|
|
}
|
|
|
|
|
2015-04-21 04:22:09 +03:00
|
|
|
if (IsPossiblyOrphanedPendingAnimation()) {
|
2015-01-09 01:57:58 +03:00
|
|
|
MOZ_ASSERT(mTimeline && !mTimeline->GetCurrentTime().IsNull(),
|
2016-07-25 11:56:34 +03:00
|
|
|
"Orphaned pending animations should have an active timeline");
|
2015-04-01 06:23:24 +03:00
|
|
|
FinishPendingAt(mTimeline->GetCurrentTime().Value());
|
2015-01-09 01:57:58 +03:00
|
|
|
}
|
2014-12-25 10:28:24 +03:00
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-08-18 10:11:55 +03:00
|
|
|
|
2016-07-25 11:56:34 +03:00
|
|
|
if (!mEffect) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-01-06 05:04:06 +03:00
|
|
|
// Update layers if we are newly finished.
|
2016-07-25 11:56:34 +03:00
|
|
|
KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
|
|
|
|
if (keyframeEffect &&
|
|
|
|
!keyframeEffect->Properties().IsEmpty() &&
|
2016-01-06 05:04:06 +03:00
|
|
|
!mFinishedAtLastComposeStyle &&
|
|
|
|
PlayState() == AnimationPlayState::Finished) {
|
|
|
|
PostUpdate();
|
|
|
|
}
|
2014-08-10 11:06:47 +04:00
|
|
|
}
|
|
|
|
|
2015-01-09 01:57:58 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::TriggerOnNextTick(const Nullable<TimeDuration>& aReadyTime)
|
2015-01-09 01:57:58 +03:00
|
|
|
{
|
|
|
|
// Normally we expect the play state to be pending but it's possible that,
|
2015-04-21 04:22:09 +03:00
|
|
|
// due to the handling of possibly orphaned animations in Tick(), this
|
|
|
|
// animation got started whilst still being in another document's pending
|
|
|
|
// animation map.
|
2015-01-09 01:57:58 +03:00
|
|
|
if (PlayState() != AnimationPlayState::Pending) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-01-09 01:57:58 +03:00
|
|
|
// If aReadyTime.IsNull() we'll detect this in Tick() where we check for
|
2015-04-21 04:22:09 +03:00
|
|
|
// orphaned animations and trigger this animation anyway
|
2015-01-09 01:57:58 +03:00
|
|
|
mPendingReadyTime = aReadyTime;
|
|
|
|
}
|
|
|
|
|
2014-12-04 19:28:38 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::TriggerNow()
|
2014-12-04 19:28:38 +03:00
|
|
|
{
|
2015-06-09 05:13:53 +03:00
|
|
|
// Normally we expect the play state to be pending but when an animation
|
|
|
|
// is cancelled and its rendered document can't be reached, we can end up
|
|
|
|
// with the animation still in a pending player tracker even after it is
|
|
|
|
// no longer pending.
|
|
|
|
if (PlayState() != AnimationPlayState::Pending) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-28 06:49:12 +03:00
|
|
|
|
|
|
|
// If we don't have an active timeline we can't trigger the animation.
|
|
|
|
// However, this is a test-only method that we don't expect to be used in
|
|
|
|
// conjunction with animations without an active timeline so generate
|
|
|
|
// a warning if we do find ourselves in that situation.
|
|
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
|
|
NS_WARNING("Failed to trigger an animation with an active timeline");
|
|
|
|
return;
|
|
|
|
}
|
2014-12-18 02:42:40 +03:00
|
|
|
|
2015-04-01 06:23:24 +03:00
|
|
|
FinishPendingAt(mTimeline->GetCurrentTime().Value());
|
2014-12-04 19:28:38 +03:00
|
|
|
}
|
|
|
|
|
2015-02-03 08:08:37 +03:00
|
|
|
Nullable<TimeDuration>
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetCurrentOrPendingStartTime() const
|
2015-02-03 08:08:37 +03:00
|
|
|
{
|
|
|
|
Nullable<TimeDuration> result;
|
|
|
|
|
|
|
|
if (!mStartTime.IsNull()) {
|
|
|
|
result = mStartTime;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the equivalent start time from the pending ready time.
|
|
|
|
// This is the same as the calculation performed in ResumeAt and will
|
|
|
|
// need to incorporate the playbackRate when implemented (bug 1127380).
|
|
|
|
result.SetValue(mPendingReadyTime.Value() - mHoldTime.Value());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-11-18 03:50:00 +03:00
|
|
|
TimeStamp
|
|
|
|
Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const
|
|
|
|
{
|
|
|
|
// Initializes to null. Return the same object every time to benefit from
|
|
|
|
// return-value-optimization.
|
|
|
|
TimeStamp result;
|
|
|
|
|
|
|
|
// We *don't* check for mTimeline->TracksWallclockTime() here because that
|
|
|
|
// method only tells us if the timeline times can be converted to
|
|
|
|
// TimeStamps that can be compared to TimeStamp::Now() or not, *not*
|
|
|
|
// whether the timelines can be converted to TimeStamp values at all.
|
|
|
|
//
|
|
|
|
// Furthermore, we want to be able to use this method when the refresh driver
|
|
|
|
// is under test control (in which case TracksWallclockTime() will return
|
|
|
|
// false).
|
|
|
|
//
|
|
|
|
// Once we introduce timelines that are not time-based we will need to
|
|
|
|
// differentiate between them here and determine how to sort their events.
|
|
|
|
if (!mTimeline) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the time is convertible to a timestamp
|
|
|
|
if (aTime == TimeDuration::Forever() ||
|
|
|
|
mPlaybackRate == 0.0 ||
|
|
|
|
mStartTime.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invert the standard relation:
|
|
|
|
// animation time = (timeline time - start time) * playback rate
|
|
|
|
TimeDuration timelineTime =
|
|
|
|
TimeDuration(aTime).MultDouble(1.0 / mPlaybackRate) + mStartTime.Value();
|
|
|
|
|
|
|
|
result = mTimeline->ToTimeStamp(timelineTime);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#silently-set-the-current-time
|
2015-04-27 04:05:46 +03:00
|
|
|
void
|
|
|
|
Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
|
|
|
|
{
|
|
|
|
if (!mHoldTime.IsNull() ||
|
|
|
|
mStartTime.IsNull() ||
|
|
|
|
!mTimeline ||
|
|
|
|
mTimeline->GetCurrentTime().IsNull() ||
|
|
|
|
mPlaybackRate == 0.0) {
|
|
|
|
mHoldTime.SetValue(aSeekTime);
|
|
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
|
|
mStartTime.SetNull();
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
|
|
|
|
(aSeekTime.MultDouble(1 / mPlaybackRate)));
|
|
|
|
}
|
|
|
|
|
|
|
|
mPreviousCurrentTime.SetNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Animation::SilentlySetPlaybackRate(double aPlaybackRate)
|
|
|
|
{
|
|
|
|
Nullable<TimeDuration> previousTime = GetCurrentTime();
|
|
|
|
mPlaybackRate = aPlaybackRate;
|
|
|
|
if (!previousTime.IsNull()) {
|
|
|
|
SilentlySetCurrentTime(previousTime.Value());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#cancel-an-animation
|
2014-12-18 02:42:41 +03:00
|
|
|
void
|
2016-05-31 03:42:37 +03:00
|
|
|
Animation::CancelNoUpdate()
|
2014-12-18 02:42:41 +03:00
|
|
|
{
|
2016-07-15 12:17:25 +03:00
|
|
|
ResetPendingTasks();
|
2014-12-18 02:42:41 +03:00
|
|
|
|
2015-03-18 16:22:11 +03:00
|
|
|
if (mFinished) {
|
|
|
|
mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
|
|
}
|
2015-07-30 00:21:00 +03:00
|
|
|
ResetFinishedPromise();
|
2015-03-18 16:22:11 +03:00
|
|
|
|
2015-07-31 00:26:00 +03:00
|
|
|
DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
|
|
|
|
|
2014-12-18 02:42:41 +03:00
|
|
|
mHoldTime.SetNull();
|
|
|
|
mStartTime.SetNull();
|
2015-03-14 08:34:40 +03:00
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-11-16 20:44:55 +03:00
|
|
|
|
|
|
|
if (mTimeline) {
|
|
|
|
mTimeline->RemoveAnimation(this);
|
|
|
|
}
|
2014-12-18 02:42:41 +03:00
|
|
|
}
|
|
|
|
|
2015-03-14 08:34:40 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::UpdateRelevance()
|
2015-03-14 08:34:40 +03:00
|
|
|
{
|
2015-03-14 08:34:40 +03:00
|
|
|
bool wasRelevant = mIsRelevant;
|
2015-04-15 02:48:21 +03:00
|
|
|
mIsRelevant = HasCurrentEffect() || IsInEffect();
|
2015-03-14 08:34:40 +03:00
|
|
|
|
|
|
|
// Notify animation observers.
|
|
|
|
if (wasRelevant && !mIsRelevant) {
|
|
|
|
nsNodeUtils::AnimationRemoved(this);
|
|
|
|
} else if (!wasRelevant && mIsRelevant) {
|
|
|
|
nsNodeUtils::AnimationAdded(this);
|
|
|
|
}
|
2015-03-14 08:34:40 +03:00
|
|
|
}
|
|
|
|
|
2015-06-09 05:13:53 +03:00
|
|
|
bool
|
|
|
|
Animation::HasLowerCompositeOrderThan(const Animation& aOther) const
|
|
|
|
{
|
2016-01-14 04:24:24 +03:00
|
|
|
// 0. Object-equality case
|
|
|
|
if (&aOther == this) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 1. CSS Transitions sort lowest
|
|
|
|
{
|
|
|
|
auto asCSSTransitionForSorting =
|
|
|
|
[] (const Animation& anim) -> const CSSTransition*
|
|
|
|
{
|
|
|
|
const CSSTransition* transition = anim.AsCSSTransition();
|
|
|
|
return transition && transition->IsTiedToMarkup() ?
|
|
|
|
transition :
|
|
|
|
nullptr;
|
|
|
|
};
|
|
|
|
auto thisTransition = asCSSTransitionForSorting(*this);
|
|
|
|
auto otherTransition = asCSSTransitionForSorting(aOther);
|
|
|
|
if (thisTransition && otherTransition) {
|
|
|
|
return thisTransition->HasLowerCompositeOrderThan(*otherTransition);
|
|
|
|
}
|
|
|
|
if (thisTransition || otherTransition) {
|
|
|
|
return thisTransition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. CSS Animations sort next
|
|
|
|
{
|
|
|
|
auto asCSSAnimationForSorting =
|
|
|
|
[] (const Animation& anim) -> const CSSAnimation*
|
|
|
|
{
|
|
|
|
const CSSAnimation* animation = anim.AsCSSAnimation();
|
|
|
|
return animation && animation->IsTiedToMarkup() ? animation : nullptr;
|
|
|
|
};
|
|
|
|
auto thisAnimation = asCSSAnimationForSorting(*this);
|
|
|
|
auto otherAnimation = asCSSAnimationForSorting(aOther);
|
|
|
|
if (thisAnimation && otherAnimation) {
|
|
|
|
return thisAnimation->HasLowerCompositeOrderThan(*otherAnimation);
|
|
|
|
}
|
|
|
|
if (thisAnimation || otherAnimation) {
|
|
|
|
return thisAnimation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subclasses of Animation repurpose mAnimationIndex to implement their
|
|
|
|
// own brand of composite ordering. However, by this point we should have
|
|
|
|
// handled any such custom composite ordering so we should now have unique
|
|
|
|
// animation indices.
|
|
|
|
MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex,
|
2015-09-15 05:20:26 +03:00
|
|
|
"Animation indices should be unique");
|
2015-06-09 05:13:53 +03:00
|
|
|
|
2016-01-14 04:24:24 +03:00
|
|
|
// 3. Finally, generic animations sort by their position in the global
|
|
|
|
// animation array.
|
2015-09-15 05:20:26 +03:00
|
|
|
return mAnimationIndex < aOther.mAnimationIndex;
|
2015-06-09 05:13:53 +03:00
|
|
|
}
|
|
|
|
|
2014-10-20 08:55:46 +04:00
|
|
|
void
|
2015-10-18 08:24:48 +03:00
|
|
|
Animation::ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
|
2016-10-05 08:42:56 +03:00
|
|
|
const nsCSSPropertyIDSet& aPropertiesToSkip)
|
2014-10-20 08:55:46 +04:00
|
|
|
{
|
2015-08-07 06:29:35 +03:00
|
|
|
if (!mEffect) {
|
2014-10-20 08:55:46 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-08-07 06:29:35 +03:00
|
|
|
if (!IsInEffect()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-01 06:23:25 +03:00
|
|
|
// In order to prevent flicker, there are a few cases where we want to use
|
|
|
|
// a different time for rendering that would otherwise be returned by
|
|
|
|
// GetCurrentTime. These are:
|
|
|
|
//
|
|
|
|
// (a) For animations that are pausing but which are still running on the
|
|
|
|
// compositor. In this case we send a layer transaction that removes the
|
|
|
|
// animation but which also contains the animation values calculated on
|
|
|
|
// the main thread. To prevent flicker when this occurs we want to ensure
|
|
|
|
// the timeline time used to calculate the main thread animation values
|
|
|
|
// does not lag far behind the time used on the compositor. Ideally we
|
|
|
|
// would like to use the "animation ready time" calculated at the end of
|
|
|
|
// the layer transaction as the timeline time but it will be too late to
|
|
|
|
// update the style rule at that point so instead we just use the current
|
|
|
|
// wallclock time.
|
|
|
|
//
|
|
|
|
// (b) For animations that are pausing that we have already taken off the
|
|
|
|
// compositor. In this case we record a pending ready time but we don't
|
|
|
|
// apply it until the next tick. However, while waiting for the next tick,
|
|
|
|
// we should still use the pending ready time as the timeline time. If we
|
|
|
|
// use the regular timeline time the animation may appear jump backwards
|
|
|
|
// if the main thread's timeline time lags behind the compositor.
|
|
|
|
//
|
|
|
|
// (c) For animations that are play-pending due to an aborted pause operation
|
|
|
|
// (i.e. a pause operation that was interrupted before we entered the
|
|
|
|
// paused state). When we cancel a pending pause we might momentarily take
|
|
|
|
// the animation off the compositor, only to re-add it moments later. In
|
|
|
|
// that case the compositor might have been ahead of the main thread so we
|
|
|
|
// should use the current wallclock time to ensure the animation doesn't
|
|
|
|
// temporarily jump backwards.
|
|
|
|
//
|
|
|
|
// To address each of these cases we temporarily tweak the hold time
|
|
|
|
// immediately before updating the style rule and then restore it immediately
|
|
|
|
// afterwards. This is purely to prevent visual flicker. Other behavior
|
|
|
|
// such as dispatching events continues to rely on the regular timeline time.
|
2016-01-13 01:54:54 +03:00
|
|
|
AnimationPlayState playState = PlayState();
|
2015-04-01 06:23:25 +03:00
|
|
|
{
|
|
|
|
AutoRestore<Nullable<TimeDuration>> restoreHoldTime(mHoldTime);
|
2015-04-02 06:30:17 +03:00
|
|
|
|
|
|
|
if (playState == AnimationPlayState::Pending &&
|
2015-04-01 06:23:25 +03:00
|
|
|
mHoldTime.IsNull() &&
|
|
|
|
!mStartTime.IsNull()) {
|
|
|
|
Nullable<TimeDuration> timeToUse = mPendingReadyTime;
|
|
|
|
if (timeToUse.IsNull() &&
|
|
|
|
mTimeline &&
|
2015-04-28 05:17:10 +03:00
|
|
|
mTimeline->TracksWallclockTime()) {
|
2015-04-01 06:23:25 +03:00
|
|
|
timeToUse = mTimeline->ToTimelineTime(TimeStamp::Now());
|
|
|
|
}
|
|
|
|
if (!timeToUse.IsNull()) {
|
|
|
|
mHoldTime.SetValue((timeToUse.Value() - mStartTime.Value())
|
|
|
|
.MultDouble(mPlaybackRate));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-25 11:56:34 +03:00
|
|
|
KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
|
|
|
|
if (keyframeEffect) {
|
2016-10-05 08:42:56 +03:00
|
|
|
keyframeEffect->ComposeStyle(aStyleRule, aPropertiesToSkip);
|
2016-07-25 11:56:34 +03:00
|
|
|
}
|
2015-09-28 06:38:41 +03:00
|
|
|
}
|
2015-04-01 06:23:25 +03:00
|
|
|
|
2015-09-28 06:38:41 +03:00
|
|
|
MOZ_ASSERT(playState == PlayState(),
|
|
|
|
"Play state should not change during the course of compositing");
|
|
|
|
mFinishedAtLastComposeStyle = (playState == AnimationPlayState::Finished);
|
2014-10-20 08:55:46 +04:00
|
|
|
}
|
|
|
|
|
2015-08-17 21:28:00 +03:00
|
|
|
void
|
|
|
|
Animation::NotifyEffectTimingUpdated()
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(mEffect,
|
|
|
|
"We should only update timing effect when we have a target "
|
|
|
|
"effect");
|
|
|
|
UpdateTiming(Animation::SeekFlag::NoSeek,
|
|
|
|
Animation::SyncNotifyFlag::Async);
|
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#play-an-animation
|
2014-11-17 07:46:01 +03:00
|
|
|
void
|
2016-05-31 03:42:37 +03:00
|
|
|
Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
2014-11-17 07:46:01 +03:00
|
|
|
{
|
2015-10-22 09:16:18 +03:00
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
2015-04-01 06:23:25 +03:00
|
|
|
bool abortedPause = mPendingState == PendingState::PausePending;
|
|
|
|
|
2014-12-04 19:28:37 +03:00
|
|
|
Nullable<TimeDuration> currentTime = GetCurrentTime();
|
2015-03-13 23:10:45 +03:00
|
|
|
if (mPlaybackRate > 0.0 &&
|
2015-03-18 16:22:11 +03:00
|
|
|
(currentTime.IsNull() ||
|
|
|
|
(aLimitBehavior == LimitBehavior::AutoRewind &&
|
2016-08-27 10:53:33 +03:00
|
|
|
(currentTime.Value() < TimeDuration() ||
|
2015-04-15 02:48:21 +03:00
|
|
|
currentTime.Value() >= EffectEnd())))) {
|
2014-12-04 19:28:37 +03:00
|
|
|
mHoldTime.SetValue(TimeDuration(0));
|
2015-03-13 23:10:45 +03:00
|
|
|
} else if (mPlaybackRate < 0.0 &&
|
2015-03-18 16:22:11 +03:00
|
|
|
(currentTime.IsNull() ||
|
|
|
|
(aLimitBehavior == LimitBehavior::AutoRewind &&
|
2016-08-27 10:53:33 +03:00
|
|
|
(currentTime.Value() <= TimeDuration() ||
|
2015-04-15 02:48:21 +03:00
|
|
|
currentTime.Value() > EffectEnd())))) {
|
2015-05-19 08:00:48 +03:00
|
|
|
if (EffectEnd() == TimeDuration::Forever()) {
|
|
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
2015-04-15 02:48:21 +03:00
|
|
|
mHoldTime.SetValue(TimeDuration(EffectEnd()));
|
2015-03-13 23:10:45 +03:00
|
|
|
} else if (mPlaybackRate == 0.0 && currentTime.IsNull()) {
|
|
|
|
mHoldTime.SetValue(TimeDuration(0));
|
|
|
|
}
|
|
|
|
|
2015-05-19 08:00:48 +03:00
|
|
|
bool reuseReadyPromise = false;
|
|
|
|
if (mPendingState != PendingState::NotPending) {
|
|
|
|
CancelPendingTasks();
|
|
|
|
reuseReadyPromise = true;
|
|
|
|
}
|
|
|
|
|
2015-04-01 06:23:25 +03:00
|
|
|
// If the hold time is null then we're either already playing normally (and
|
|
|
|
// we can ignore this call) or we aborted a pending pause operation (in which
|
|
|
|
// case, for consistency, we need to go through the motions of doing an
|
|
|
|
// asynchronous start even though we already have a resolved start time).
|
|
|
|
if (mHoldTime.IsNull() && !abortedPause) {
|
2014-11-17 07:46:01 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-08 10:17:13 +03:00
|
|
|
// Clear the start time until we resolve a new one. We do this except
|
|
|
|
// for the case where we are aborting a pause and don't have a hold time.
|
|
|
|
//
|
|
|
|
// If we're aborting a pause and *do* have a hold time (e.g. because
|
|
|
|
// the animation is finished or we just applied the auto-rewind behavior
|
|
|
|
// above) we should respect it by clearing the start time. If we *don't*
|
|
|
|
// have a hold time we should keep the current start time so that the
|
|
|
|
// the animation continues moving uninterrupted by the aborted pause.
|
|
|
|
//
|
|
|
|
// (If we're not aborting a pause, mHoldTime must be resolved by now
|
|
|
|
// or else we would have returned above.)
|
|
|
|
if (!mHoldTime.IsNull()) {
|
2015-04-01 06:23:25 +03:00
|
|
|
mStartTime.SetNull();
|
|
|
|
}
|
2015-03-24 03:21:08 +03:00
|
|
|
|
2015-03-27 09:56:45 +03:00
|
|
|
if (!reuseReadyPromise) {
|
|
|
|
// Clear ready promise. We'll create a new one lazily.
|
|
|
|
mReady = nullptr;
|
|
|
|
}
|
|
|
|
|
2015-03-27 09:56:45 +03:00
|
|
|
mPendingState = PendingState::PlayPending;
|
2014-12-25 10:28:24 +03:00
|
|
|
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
2015-06-09 05:13:53 +03:00
|
|
|
if (doc) {
|
|
|
|
PendingAnimationTracker* tracker =
|
|
|
|
doc->GetOrCreatePendingAnimationTracker();
|
|
|
|
tracker->AddPlayPending(*this);
|
|
|
|
} else {
|
2015-03-27 09:56:45 +03:00
|
|
|
TriggerOnNextTick(Nullable<TimeDuration>());
|
2014-12-25 10:28:24 +03:00
|
|
|
}
|
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-10-22 09:16:18 +03:00
|
|
|
if (IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2014-11-17 07:46:01 +03:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#pause-an-animation
|
2014-11-17 07:46:01 +03:00
|
|
|
void
|
2016-05-31 03:42:37 +03:00
|
|
|
Animation::PauseNoUpdate(ErrorResult& aRv)
|
2014-11-17 07:46:01 +03:00
|
|
|
{
|
2015-04-14 03:07:41 +03:00
|
|
|
if (IsPausedOrPausing()) {
|
2015-04-01 06:23:25 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-10-22 09:16:18 +03:00
|
|
|
AutoMutationBatchForAnimation mb(*this);
|
|
|
|
|
2015-05-19 08:55:26 +03:00
|
|
|
// If we are transitioning from idle, fill in the current time
|
|
|
|
if (GetCurrentTime().IsNull()) {
|
|
|
|
if (mPlaybackRate >= 0.0) {
|
|
|
|
mHoldTime.SetValue(TimeDuration(0));
|
|
|
|
} else {
|
|
|
|
if (EffectEnd() == TimeDuration::Forever()) {
|
|
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mHoldTime.SetValue(TimeDuration(EffectEnd()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-01 06:23:25 +03:00
|
|
|
bool reuseReadyPromise = false;
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState == PendingState::PlayPending) {
|
2015-03-27 09:56:45 +03:00
|
|
|
CancelPendingTasks();
|
2015-04-01 06:23:25 +03:00
|
|
|
reuseReadyPromise = true;
|
2014-11-17 07:46:01 +03:00
|
|
|
}
|
2014-12-18 02:42:41 +03:00
|
|
|
|
2015-04-01 06:23:25 +03:00
|
|
|
if (!reuseReadyPromise) {
|
|
|
|
// Clear ready promise. We'll create a new one lazily.
|
|
|
|
mReady = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
mPendingState = PendingState::PausePending;
|
|
|
|
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
2015-06-09 05:13:53 +03:00
|
|
|
if (doc) {
|
|
|
|
PendingAnimationTracker* tracker =
|
|
|
|
doc->GetOrCreatePendingAnimationTracker();
|
|
|
|
tracker->AddPausePending(*this);
|
|
|
|
} else {
|
2015-04-01 06:23:25 +03:00
|
|
|
TriggerOnNextTick(Nullable<TimeDuration>());
|
|
|
|
}
|
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-10-22 09:16:18 +03:00
|
|
|
if (IsRelevant()) {
|
|
|
|
nsNodeUtils::AnimationChanged(this);
|
|
|
|
}
|
2014-11-17 07:46:01 +03:00
|
|
|
}
|
|
|
|
|
2015-01-09 01:57:58 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::ResumeAt(const TimeDuration& aReadyTime)
|
2015-01-09 01:57:58 +03:00
|
|
|
{
|
2015-04-21 04:22:09 +03:00
|
|
|
// This method is only expected to be called for an animation that is
|
2015-01-09 01:57:58 +03:00
|
|
|
// waiting to play. We can easily adapt it to handle other states
|
|
|
|
// but it's currently not necessary.
|
2015-03-27 09:56:45 +03:00
|
|
|
MOZ_ASSERT(mPendingState == PendingState::PlayPending,
|
2015-04-21 04:22:09 +03:00
|
|
|
"Expected to resume a play-pending animation");
|
2015-04-01 06:23:25 +03:00
|
|
|
MOZ_ASSERT(mHoldTime.IsNull() != mStartTime.IsNull(),
|
2015-04-21 04:22:09 +03:00
|
|
|
"An animation in the play-pending state should have either a"
|
2015-04-01 06:23:25 +03:00
|
|
|
" resolved hold time or resolved start time (but not both)");
|
2015-01-09 01:57:58 +03:00
|
|
|
|
2015-04-01 06:23:25 +03:00
|
|
|
// If we aborted a pending pause operation we will already have a start time
|
|
|
|
// we should use. In all other cases, we resolve it from the ready time.
|
|
|
|
if (mStartTime.IsNull()) {
|
|
|
|
if (mPlaybackRate != 0) {
|
2015-03-30 18:27:05 +03:00
|
|
|
mStartTime.SetValue(aReadyTime -
|
|
|
|
(mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
|
2015-04-01 06:23:25 +03:00
|
|
|
mHoldTime.SetNull();
|
|
|
|
} else {
|
|
|
|
mStartTime.SetValue(aReadyTime);
|
|
|
|
}
|
2015-03-13 23:10:45 +03:00
|
|
|
}
|
2015-03-27 09:56:45 +03:00
|
|
|
mPendingState = PendingState::NotPending;
|
2015-01-09 01:57:58 +03:00
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-01-09 01:57:58 +03:00
|
|
|
|
|
|
|
if (mReady) {
|
|
|
|
mReady->MaybeResolve(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-01 06:23:24 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::PauseAt(const TimeDuration& aReadyTime)
|
2015-04-01 06:23:24 +03:00
|
|
|
{
|
|
|
|
MOZ_ASSERT(mPendingState == PendingState::PausePending,
|
2015-04-21 04:22:09 +03:00
|
|
|
"Expected to pause a pause-pending animation");
|
2015-04-01 06:23:24 +03:00
|
|
|
|
2015-11-02 02:33:58 +03:00
|
|
|
if (!mStartTime.IsNull() && mHoldTime.IsNull()) {
|
2015-04-01 06:23:24 +03:00
|
|
|
mHoldTime.SetValue((aReadyTime - mStartTime.Value())
|
|
|
|
.MultDouble(mPlaybackRate));
|
|
|
|
}
|
|
|
|
mStartTime.SetNull();
|
|
|
|
mPendingState = PendingState::NotPending;
|
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
2015-04-01 06:23:24 +03:00
|
|
|
|
|
|
|
if (mReady) {
|
|
|
|
mReady->MaybeResolve(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-18 16:22:11 +03:00
|
|
|
void
|
2015-07-30 00:21:00 +03:00
|
|
|
Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
|
2015-03-18 16:22:11 +03:00
|
|
|
{
|
2015-04-15 02:48:21 +03:00
|
|
|
// We call UpdateFinishedState before UpdateEffect because the former
|
2015-03-18 16:22:11 +03:00
|
|
|
// can change the current time, which is used by the latter.
|
2015-07-30 00:21:00 +03:00
|
|
|
UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
|
2015-04-15 02:48:21 +03:00
|
|
|
UpdateEffect();
|
2015-06-15 05:05:43 +03:00
|
|
|
|
|
|
|
if (mTimeline) {
|
Bug 1195180 part 6 - Lazily remove animations from timelines; r=heycam
Now that DocumentTimeline observes the refresh driver we can use regular
ticks to remove unnecessary animations.
We do this because in a subsequent patch, in order to provide deterministic
enumeration order when ticking animations, we will store animations in an array.
Removing an arbitrary element from an nsTArray is O(n) since we have to search
for the array index first, or O(log n) if we keep the array sorted. If we
destroy a subtree containing n animations, the operation effectively becomes
O(n^2), or, if we keep the array sorted, O(n log n). By destroying during a
tick when we are already iterating over the array, however, we will be able
to do this much more efficiently.
Whether an animation is newly associated with a timeline, or is disassociated
from a timeline, or if it merely has its timing updated, the behavior
implemented in this patch is to simply make sure we are observing the refresh
driver and deal with the animation on the next tick.
It might seem that we could be a lot more clever about this and, for example, if
an animation reports NeedsTicks() == false, not start observing the refresh
driver. There are various edge cases however that need to be taken into account.
For example, if a CSS animation is finished (IsRelevant() == false so that
animation will have been removed from the timeline), and paused
(NeedsTicks() == false), and we seek it back to the point where it is relevant
again, we actually need to observe the refresh driver so that it can dispatch an
animationstart event on the next tick. A test case in a subsequent patch tests
this specific situation.
We could possibly add logic to detect if we need to fire events on the next tick
but the complexity does not seem warranted given that even if we unnecessarily
start observing the refresh driver, we will stop watching it on the next tick.
This patch removes some rather lengthy comments from
AnimationTiming::UpdateTiming. This is, in part, because of the behavior
described above that makes these comments no longer relevant. Other parts are
removed because the Web Animations specification has been updated such that a
timeline becoming inactive now pauses the animation[1] so that the issue
regarding detecting timelines becoming active/inactive no longer applies
since animations attached to an inactive timeline remain "relevant".
[1] https://w3c.github.io/web-animations/#responding-to-a-newly-inactive-timeline
2015-09-28 06:38:41 +03:00
|
|
|
mTimeline->NotifyAnimationUpdated(*this);
|
2015-06-15 05:05:43 +03:00
|
|
|
}
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
|
|
|
|
2015-10-07 08:30:27 +03:00
|
|
|
// https://w3c.github.io/web-animations/#update-an-animations-finished-state
|
2015-03-18 16:22:11 +03:00
|
|
|
void
|
2015-07-30 00:21:00 +03:00
|
|
|
Animation::UpdateFinishedState(SeekFlag aSeekFlag,
|
|
|
|
SyncNotifyFlag aSyncNotifyFlag)
|
2015-03-18 16:22:11 +03:00
|
|
|
{
|
|
|
|
Nullable<TimeDuration> currentTime = GetCurrentTime();
|
2015-04-15 02:48:21 +03:00
|
|
|
TimeDuration effectEnd = TimeDuration(EffectEnd());
|
2015-03-18 16:22:11 +03:00
|
|
|
|
|
|
|
if (!mStartTime.IsNull() &&
|
|
|
|
mPendingState == PendingState::NotPending) {
|
|
|
|
if (mPlaybackRate > 0.0 &&
|
|
|
|
!currentTime.IsNull() &&
|
2015-04-15 02:48:21 +03:00
|
|
|
currentTime.Value() >= effectEnd) {
|
2015-05-11 11:17:06 +03:00
|
|
|
if (aSeekFlag == SeekFlag::DidSeek) {
|
2015-03-18 16:22:11 +03:00
|
|
|
mHoldTime = currentTime;
|
|
|
|
} else if (!mPreviousCurrentTime.IsNull()) {
|
2015-04-15 02:48:21 +03:00
|
|
|
mHoldTime.SetValue(std::max(mPreviousCurrentTime.Value(), effectEnd));
|
2015-03-18 16:22:11 +03:00
|
|
|
} else {
|
2015-04-15 02:48:21 +03:00
|
|
|
mHoldTime.SetValue(effectEnd);
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
|
|
|
} else if (mPlaybackRate < 0.0 &&
|
|
|
|
!currentTime.IsNull() &&
|
2016-08-27 10:53:33 +03:00
|
|
|
currentTime.Value() <= TimeDuration()) {
|
2015-05-11 11:17:06 +03:00
|
|
|
if (aSeekFlag == SeekFlag::DidSeek) {
|
2015-03-18 16:22:11 +03:00
|
|
|
mHoldTime = currentTime;
|
2016-08-19 05:35:16 +03:00
|
|
|
} else if (!mPreviousCurrentTime.IsNull()) {
|
|
|
|
mHoldTime.SetValue(std::min(mPreviousCurrentTime.Value(),
|
|
|
|
TimeDuration(0)));
|
2015-03-18 16:22:11 +03:00
|
|
|
} else {
|
|
|
|
mHoldTime.SetValue(0);
|
|
|
|
}
|
|
|
|
} else if (mPlaybackRate != 0.0 &&
|
2015-04-28 06:49:12 +03:00
|
|
|
!currentTime.IsNull() &&
|
|
|
|
mTimeline &&
|
|
|
|
!mTimeline->GetCurrentTime().IsNull()) {
|
2015-05-11 11:17:06 +03:00
|
|
|
if (aSeekFlag == SeekFlag::DidSeek && !mHoldTime.IsNull()) {
|
2015-03-18 16:22:11 +03:00
|
|
|
mStartTime.SetValue(mTimeline->GetCurrentTime().Value() -
|
2015-04-28 06:49:12 +03:00
|
|
|
(mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
|
|
|
mHoldTime.SetNull();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-19 02:51:13 +03:00
|
|
|
bool currentFinishedState = PlayState() == AnimationPlayState::Finished;
|
2015-07-30 00:21:00 +03:00
|
|
|
if (currentFinishedState && !mFinishedIsResolved) {
|
|
|
|
DoFinishNotification(aSyncNotifyFlag);
|
|
|
|
} else if (!currentFinishedState && mFinishedIsResolved) {
|
|
|
|
ResetFinishedPromise();
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
2015-03-23 16:23:19 +03:00
|
|
|
// We must recalculate the current time to take account of any mHoldTime
|
|
|
|
// changes the code above made.
|
|
|
|
mPreviousCurrentTime = GetCurrentTime();
|
2015-03-18 16:22:11 +03:00
|
|
|
}
|
|
|
|
|
2014-12-22 03:35:42 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::UpdateEffect()
|
2014-12-22 03:35:42 +03:00
|
|
|
{
|
2015-04-15 02:48:21 +03:00
|
|
|
if (mEffect) {
|
2015-03-14 08:34:40 +03:00
|
|
|
UpdateRelevance();
|
2016-07-13 13:44:19 +03:00
|
|
|
|
|
|
|
KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
|
|
|
|
if (keyframeEffect) {
|
|
|
|
keyframeEffect->NotifyAnimationTimingUpdated();
|
|
|
|
}
|
2014-12-22 03:35:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-20 08:55:43 +04:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::FlushStyle() const
|
2014-10-20 08:55:43 +04:00
|
|
|
{
|
2014-11-17 07:45:58 +03:00
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
2014-10-20 08:55:47 +04:00
|
|
|
if (doc) {
|
|
|
|
doc->FlushPendingNotifications(Flush_Style);
|
2014-10-20 08:55:43 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-17 07:45:59 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::PostUpdate()
|
2014-11-17 07:45:59 +03:00
|
|
|
{
|
2016-07-25 11:56:34 +03:00
|
|
|
if (!mEffect) {
|
2016-01-13 01:54:54 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-25 11:56:34 +03:00
|
|
|
KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
|
|
|
|
if (!keyframeEffect) {
|
2016-07-12 06:42:49 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-07-25 11:56:34 +03:00
|
|
|
Maybe<NonOwningAnimationTarget> target = keyframeEffect->GetTarget();
|
2016-03-21 11:49:50 +03:00
|
|
|
if (!target) {
|
2016-01-13 01:54:54 +03:00
|
|
|
return;
|
2014-11-17 07:45:59 +03:00
|
|
|
}
|
2016-01-13 01:54:54 +03:00
|
|
|
|
2016-07-25 11:56:34 +03:00
|
|
|
nsPresContext* presContext = keyframeEffect->GetPresContext();
|
|
|
|
if (!presContext) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-01-13 01:54:54 +03:00
|
|
|
presContext->EffectCompositor()
|
2016-03-21 11:49:50 +03:00
|
|
|
->RequestRestyle(target->mElement,
|
|
|
|
target->mPseudoType,
|
2016-01-13 01:54:54 +03:00
|
|
|
EffectCompositor::RestyleType::Layer,
|
|
|
|
CascadeLevel());
|
2014-11-17 07:45:59 +03:00
|
|
|
}
|
|
|
|
|
2014-12-18 02:42:41 +03:00
|
|
|
void
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::CancelPendingTasks()
|
2014-12-18 02:42:41 +03:00
|
|
|
{
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState == PendingState::NotPending) {
|
2014-12-18 02:42:41 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
|
|
|
if (doc) {
|
2015-04-21 04:22:09 +03:00
|
|
|
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
2014-12-18 02:42:41 +03:00
|
|
|
if (tracker) {
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState == PendingState::PlayPending) {
|
|
|
|
tracker->RemovePlayPending(*this);
|
|
|
|
} else {
|
|
|
|
tracker->RemovePausePending(*this);
|
|
|
|
}
|
2014-12-18 02:42:41 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-27 09:56:45 +03:00
|
|
|
mPendingState = PendingState::NotPending;
|
2015-01-09 01:57:58 +03:00
|
|
|
mPendingReadyTime.SetNull();
|
2014-12-18 02:42:41 +03:00
|
|
|
}
|
|
|
|
|
2016-07-15 12:17:25 +03:00
|
|
|
// https://w3c.github.io/web-animations/#reset-an-animations-pending-tasks
|
|
|
|
void
|
|
|
|
Animation::ResetPendingTasks()
|
|
|
|
{
|
|
|
|
if (mPendingState == PendingState::NotPending) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CancelPendingTasks();
|
|
|
|
if (mReady) {
|
|
|
|
mReady->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-09 01:57:58 +03:00
|
|
|
bool
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::IsPossiblyOrphanedPendingAnimation() const
|
2015-01-09 01:57:58 +03:00
|
|
|
{
|
|
|
|
// Check if we are pending but might never start because we are not being
|
|
|
|
// tracked.
|
|
|
|
//
|
|
|
|
// This covers the following cases:
|
|
|
|
//
|
2015-04-15 02:48:21 +03:00
|
|
|
// * We started playing but our effect's target element was orphaned
|
2015-01-09 01:57:58 +03:00
|
|
|
// or bound to a different document.
|
2015-04-15 02:48:21 +03:00
|
|
|
// (note that for the case of our effect changing we should handle
|
|
|
|
// that in SetEffect)
|
2015-01-09 01:57:58 +03:00
|
|
|
// * We started playing but our timeline became inactive.
|
2015-04-21 04:22:09 +03:00
|
|
|
// In this case the pending animation tracker will drop us from its hashmap
|
2015-01-09 01:57:58 +03:00
|
|
|
// when we have been painted.
|
2015-04-21 04:22:09 +03:00
|
|
|
// * When we started playing we couldn't find a PendingAnimationTracker to
|
2015-04-15 02:48:21 +03:00
|
|
|
// register with (perhaps the effect had no document) so we simply
|
2016-05-31 03:42:37 +03:00
|
|
|
// set mPendingState in PlayNoUpdate and relied on this method to catch us
|
|
|
|
// on the next tick.
|
2015-01-09 01:57:58 +03:00
|
|
|
|
|
|
|
// If we're not pending we're ok.
|
2015-03-27 09:56:45 +03:00
|
|
|
if (mPendingState == PendingState::NotPending) {
|
2015-01-09 01:57:58 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have a pending ready time then we will be started on the next
|
|
|
|
// tick.
|
|
|
|
if (!mPendingReadyTime.IsNull()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we don't have an active timeline then we shouldn't start until
|
|
|
|
// we do.
|
|
|
|
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have no rendered document, or we're not in our rendered document's
|
2015-04-21 04:22:09 +03:00
|
|
|
// PendingAnimationTracker then there's a good chance no one is tracking us.
|
2015-01-09 01:57:58 +03:00
|
|
|
//
|
|
|
|
// If we're wrong and another document is tracking us then, at worst, we'll
|
2015-03-27 09:56:45 +03:00
|
|
|
// simply start/pause the animation one tick too soon. That's better than
|
|
|
|
// never starting/pausing the animation and is unlikely.
|
2015-01-09 01:57:58 +03:00
|
|
|
nsIDocument* doc = GetRenderedDocument();
|
2015-03-27 09:56:45 +03:00
|
|
|
if (!doc) {
|
2015-04-28 09:47:45 +03:00
|
|
|
return true;
|
2015-03-27 09:56:45 +03:00
|
|
|
}
|
|
|
|
|
2015-04-21 04:22:09 +03:00
|
|
|
PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
|
2015-03-27 09:56:45 +03:00
|
|
|
return !tracker ||
|
|
|
|
(!tracker->IsWaitingToPlay(*this) &&
|
|
|
|
!tracker->IsWaitingToPause(*this));
|
2015-01-09 01:57:58 +03:00
|
|
|
}
|
|
|
|
|
2014-10-20 08:55:45 +04:00
|
|
|
StickyTimeDuration
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::EffectEnd() const
|
2014-10-20 08:55:45 +04:00
|
|
|
{
|
2015-04-15 02:48:21 +03:00
|
|
|
if (!mEffect) {
|
2014-10-20 08:55:45 +04:00
|
|
|
return StickyTimeDuration(0);
|
|
|
|
}
|
|
|
|
|
2016-04-14 13:39:44 +03:00
|
|
|
return mEffect->SpecifiedTiming().EndTime();
|
2014-10-20 08:55:45 +04:00
|
|
|
}
|
|
|
|
|
2014-11-17 07:45:58 +03:00
|
|
|
nsIDocument*
|
2015-04-21 04:22:09 +03:00
|
|
|
Animation::GetRenderedDocument() const
|
2014-11-17 07:45:58 +03:00
|
|
|
{
|
2016-07-25 11:56:34 +03:00
|
|
|
if (!mEffect || !mEffect->AsKeyframeEffect()) {
|
2014-11-17 07:45:58 +03:00
|
|
|
return nullptr;
|
|
|
|
}
|
2015-11-06 04:51:00 +03:00
|
|
|
|
2016-07-25 11:56:34 +03:00
|
|
|
return mEffect->AsKeyframeEffect()->GetRenderedDocument();
|
2014-11-17 07:45:58 +03:00
|
|
|
}
|
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
void
|
|
|
|
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
|
|
|
|
{
|
2016-09-14 16:47:32 +03:00
|
|
|
CycleCollectedJSContext* context = CycleCollectedJSContext::Get();
|
2016-03-24 18:12:00 +03:00
|
|
|
|
2015-07-30 00:21:00 +03:00
|
|
|
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
|
2015-07-31 00:25:00 +03:00
|
|
|
DoFinishNotificationImmediately();
|
2015-07-30 00:21:00 +03:00
|
|
|
} else if (!mFinishNotificationTask.IsPending()) {
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<nsRunnableMethod<Animation>> runnable =
|
2016-05-05 11:45:00 +03:00
|
|
|
NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
|
2016-09-14 16:47:32 +03:00
|
|
|
context->DispatchToMicroTask(do_AddRef(runnable));
|
2016-07-06 01:49:06 +03:00
|
|
|
mFinishNotificationTask = runnable.forget();
|
2015-07-30 00:21:00 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Animation::ResetFinishedPromise()
|
|
|
|
{
|
|
|
|
mFinishedIsResolved = false;
|
|
|
|
mFinished = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Animation::MaybeResolveFinishedPromise()
|
2015-07-31 00:25:00 +03:00
|
|
|
{
|
|
|
|
if (mFinished) {
|
|
|
|
mFinished->MaybeResolve(this);
|
|
|
|
}
|
|
|
|
mFinishedIsResolved = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Animation::DoFinishNotificationImmediately()
|
2015-07-30 00:21:00 +03:00
|
|
|
{
|
|
|
|
mFinishNotificationTask.Revoke();
|
|
|
|
|
|
|
|
if (PlayState() != AnimationPlayState::Finished) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-31 00:25:00 +03:00
|
|
|
MaybeResolveFinishedPromise();
|
|
|
|
|
|
|
|
DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Animation::DispatchPlaybackEvent(const nsAString& aName)
|
|
|
|
{
|
|
|
|
AnimationPlaybackEventInit init;
|
|
|
|
|
2015-07-31 00:26:00 +03:00
|
|
|
if (aName.EqualsLiteral("finish")) {
|
|
|
|
init.mCurrentTime = GetCurrentTimeAsDouble();
|
|
|
|
}
|
2015-07-31 00:25:00 +03:00
|
|
|
if (mTimeline) {
|
|
|
|
init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
|
2015-07-30 00:21:00 +03:00
|
|
|
}
|
2015-07-31 00:25:00 +03:00
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AnimationPlaybackEvent> event =
|
2015-07-31 00:25:00 +03:00
|
|
|
AnimationPlaybackEvent::Constructor(this, aName, init);
|
|
|
|
event->SetTrusted(true);
|
|
|
|
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<AsyncEventDispatcher> asyncDispatcher =
|
2015-07-31 00:25:00 +03:00
|
|
|
new AsyncEventDispatcher(this, event);
|
|
|
|
asyncDispatcher->PostDOMEvent();
|
2015-07-30 00:21:00 +03:00
|
|
|
}
|
|
|
|
|
2015-09-16 17:05:00 +03:00
|
|
|
bool
|
|
|
|
Animation::IsRunningOnCompositor() const
|
|
|
|
{
|
2016-07-25 11:56:34 +03:00
|
|
|
return mEffect &&
|
|
|
|
mEffect->AsKeyframeEffect() &&
|
|
|
|
mEffect->AsKeyframeEffect()->IsRunningOnCompositor();
|
2015-09-16 17:05:00 +03:00
|
|
|
}
|
|
|
|
|
2014-08-10 11:06:44 +04:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|