mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 13:34:03 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1178664 - Part 1 - Make Animation interface EventTarget inheritance. r=smaug (0ea50ab93)
- Bug 1178664 - Part 2 - webidl for AnimationPlaybackEvent. r=smaug (3a5d86f40)
- Bug 1150808 - Implement Animation.reverse(). r=smaug r=birtles (9ce9fc4db)
- Bug 1178664 - Part 3 - Implement Animation.onfinish event. r=bbirtles, r=smaug (9139c4227)
- Bug 1178664 - Part 4 -Implement Animation.oncancel event. r=bbirtles, r=smaug (415b068e6)
- Bug 1175751 - Apply playback rate to compositor animations. r=bbirtles (0ffd9cb90)
- Bug 1181905 - Animation::IsPlaying should check playbackRate != 0 to stop playing on compositor animation. r=bbirtles (e3a2fd2f4)
- Bug 1208385 part 0 - Fix up some references to Web Animations spec; r=heycam (fca9626d4)
- Bug 1203009 part 1 - Rename sequence number to animation index; r=heycam (cd7dc513f)
- Bug 1203009 part 2 - Remove {CSSAnimation,CSSTransition}::OwningElement() getter; r=heycam (4f49c63b1)
- Bug 1203009 part 3 - Add mNeedsNewAnimationIndexWhenRun flag to CSSAnimation and CSSTransition; r=heycam (73a45b5a7)
- Bug 1195523: Use type-safe LinkedList instead of PRCList to manage AnimationCollection objects. r=birtles (35b233981)
- Bug 1194037 part 1 - Make nsAutoAnimationMutationBatch batch multiple elements at once; r=smaug (83f808043)
- Bug 1194037 part 2 - Make WillRefresh no longer call FlushAnimations; r=dholbert (449b0fbd5)
- Bug 1194037 part 3 - Add Animation::HasEndEventToQueue(); r=dholbert (b1ddc33d3)
- ug 1203009 part 4 - Implement new composite ordering; r=heycam (4c571e608)
- Bug 1203009 part 5 - Remove IsUsingCustomCompositeOrder; r=heycam (242d0142c)
- Bug 1203009 part 6 - Add tests for new composite order; r=heycam (5f8711177)
This commit is contained in:
+96
-45
@@ -7,7 +7,9 @@
|
||||
#include "Animation.h"
|
||||
#include "AnimationUtils.h"
|
||||
#include "mozilla/dom/AnimationBinding.h"
|
||||
#include "mozilla/dom/AnimationPlaybackEvent.h"
|
||||
#include "mozilla/AutoRestore.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h" //For AsyncEventDispatcher
|
||||
#include "AnimationCommon.h" // For AnimationCollection,
|
||||
// CommonAnimationManager
|
||||
#include "nsIDocument.h" // For nsIDocument
|
||||
@@ -20,16 +22,19 @@ namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// Static members
|
||||
uint64_t Animation::sNextSequenceNum = 0;
|
||||
uint64_t Animation::sNextAnimationIndex = 0;
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Animation, mGlobal, mTimeline,
|
||||
mEffect, mReady, mFinished)
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(Animation)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(Animation)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Animation)
|
||||
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
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)
|
||||
|
||||
JSObject*
|
||||
Animation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
||||
@@ -81,6 +86,7 @@ Animation::SetTimeline(AnimationTimeline* aTimeline)
|
||||
// (but *not* when this method gets called from style).
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#set-the-animation-start-time
|
||||
void
|
||||
Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
|
||||
{
|
||||
@@ -116,7 +122,7 @@ Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
|
||||
PostUpdate();
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#current-time
|
||||
// https://w3c.github.io/web-animations/#current-time
|
||||
Nullable<TimeDuration>
|
||||
Animation::GetCurrentTime() const
|
||||
{
|
||||
@@ -136,7 +142,7 @@ Animation::GetCurrentTime() const
|
||||
return result;
|
||||
}
|
||||
|
||||
// Implements http://w3c.github.io/web-animations/#set-the-current-time
|
||||
// https://w3c.github.io/web-animations/#set-the-current-time
|
||||
void
|
||||
Animation::SetCurrentTime(const TimeDuration& aSeekTime)
|
||||
{
|
||||
@@ -157,6 +163,7 @@ Animation::SetCurrentTime(const TimeDuration& aSeekTime)
|
||||
PostUpdate();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#set-the-animation-playback-rate
|
||||
void
|
||||
Animation::SetPlaybackRate(double aPlaybackRate)
|
||||
{
|
||||
@@ -169,6 +176,7 @@ Animation::SetPlaybackRate(double aPlaybackRate)
|
||||
}
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#play-state
|
||||
AnimationPlayState
|
||||
Animation::PlayState() const
|
||||
{
|
||||
@@ -196,8 +204,9 @@ Animation::PlayState() const
|
||||
Promise*
|
||||
Animation::GetReady(ErrorResult& aRv)
|
||||
{
|
||||
if (!mReady && mGlobal) {
|
||||
mReady = Promise::Create(mGlobal, aRv); // Lazily create on demand
|
||||
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
|
||||
if (!mReady && global) {
|
||||
mReady = Promise::Create(global, aRv); // Lazily create on demand
|
||||
}
|
||||
if (!mReady) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
@@ -210,8 +219,9 @@ Animation::GetReady(ErrorResult& aRv)
|
||||
Promise*
|
||||
Animation::GetFinished(ErrorResult& aRv)
|
||||
{
|
||||
if (!mFinished && mGlobal) {
|
||||
mFinished = Promise::Create(mGlobal, aRv); // Lazily create on demand
|
||||
nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
|
||||
if (!mFinished && global) {
|
||||
mFinished = Promise::Create(global, aRv); // Lazily create on demand
|
||||
}
|
||||
if (!mFinished) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
@@ -290,6 +300,23 @@ Animation::Pause(ErrorResult& aRv)
|
||||
PostUpdate();
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#reverse-an-animation
|
||||
void
|
||||
Animation::Reverse(ErrorResult& aRv)
|
||||
{
|
||||
if (!mTimeline || mTimeline->GetCurrentTime().IsNull()) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPlaybackRate == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SilentlySetPlaybackRate(-mPlaybackRate);
|
||||
Play(aRv, LimitBehavior::AutoRewind);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// JS wrappers for Animation interface:
|
||||
@@ -425,7 +452,7 @@ Animation::GetCurrentOrPendingStartTime() const
|
||||
return result;
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#silently-set-the-current-time
|
||||
// https://w3c.github.io/web-animations/#silently-set-the-current-time
|
||||
void
|
||||
Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
|
||||
{
|
||||
@@ -458,6 +485,7 @@ Animation::SilentlySetPlaybackRate(double aPlaybackRate)
|
||||
}
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#cancel-an-animation
|
||||
void
|
||||
Animation::DoCancel()
|
||||
{
|
||||
@@ -473,6 +501,8 @@ Animation::DoCancel()
|
||||
}
|
||||
ResetFinishedPromise();
|
||||
|
||||
DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
|
||||
|
||||
mHoldTime.SetNull();
|
||||
mStartTime.SetNull();
|
||||
|
||||
@@ -496,15 +526,15 @@ Animation::UpdateRelevance()
|
||||
bool
|
||||
Animation::HasLowerCompositeOrderThan(const Animation& aOther) const
|
||||
{
|
||||
// We only ever sort non-idle animations so we don't ever expect
|
||||
// mSequenceNum to be set to kUnsequenced
|
||||
MOZ_ASSERT(mSequenceNum != kUnsequenced &&
|
||||
aOther.mSequenceNum != kUnsequenced,
|
||||
"Animations to compare should not be idle");
|
||||
MOZ_ASSERT(mSequenceNum != aOther.mSequenceNum || &aOther == this,
|
||||
"Sequence numbers should be unique");
|
||||
// Due to the way subclasses of this repurpose the mAnimationIndex to
|
||||
// implement their own brand of composite ordering it is possible for
|
||||
// two animations to have an identical mAnimationIndex member.
|
||||
// However, these subclasses override this method so we shouldn't see
|
||||
// identical animation indices here.
|
||||
MOZ_ASSERT(mAnimationIndex != aOther.mAnimationIndex || &aOther == this,
|
||||
"Animation indices should be unique");
|
||||
|
||||
return mSequenceNum < aOther.mSequenceNum;
|
||||
return mAnimationIndex < aOther.mAnimationIndex;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -553,7 +583,8 @@ Animation::ComposeStyle(nsRefPtr<AnimValuesStyleRule>& aStyleRule,
|
||||
|
||||
AnimationPlayState playState = PlayState();
|
||||
if (playState == AnimationPlayState::Running ||
|
||||
playState == AnimationPlayState::Pending) {
|
||||
playState == AnimationPlayState::Pending ||
|
||||
HasEndEventToQueue()) {
|
||||
aNeedsRefreshes = true;
|
||||
}
|
||||
|
||||
@@ -639,7 +670,7 @@ Animation::NotifyEffectTimingUpdated()
|
||||
Animation::SyncNotifyFlag::Async);
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#play-an-animation
|
||||
// https://w3c.github.io/web-animations/#play-an-animation
|
||||
void
|
||||
Animation::DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
||||
{
|
||||
@@ -706,7 +737,7 @@ Animation::DoPlay(ErrorResult& aRv, LimitBehavior aLimitBehavior)
|
||||
UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#pause-an-animation
|
||||
// https://w3c.github.io/web-animations/#pause-an-animation
|
||||
void
|
||||
Animation::DoPause(ErrorResult& aRv)
|
||||
{
|
||||
@@ -812,16 +843,6 @@ Animation::PauseAt(const TimeDuration& aReadyTime)
|
||||
void
|
||||
Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
|
||||
{
|
||||
// Update the sequence number each time we transition in or out of the
|
||||
// idle state
|
||||
if (!IsUsingCustomCompositeOrder()) {
|
||||
if (PlayState() == AnimationPlayState::Idle) {
|
||||
mSequenceNum = kUnsequenced;
|
||||
} else if (mSequenceNum == kUnsequenced) {
|
||||
mSequenceNum = sNextSequenceNum++;
|
||||
}
|
||||
}
|
||||
|
||||
// We call UpdateFinishedState before UpdateEffect because the former
|
||||
// can change the current time, which is used by the latter.
|
||||
UpdateFinishedState(aSeekFlag, aSyncNotifyFlag);
|
||||
@@ -863,6 +884,7 @@ Animation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
|
||||
}
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#update-an-animations-finished-state
|
||||
void
|
||||
Animation::UpdateFinishedState(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag)
|
||||
@@ -1082,10 +1104,10 @@ void
|
||||
Animation::DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag)
|
||||
{
|
||||
if (aSyncNotifyFlag == SyncNotifyFlag::Sync) {
|
||||
MaybeResolveFinishedPromise();
|
||||
DoFinishNotificationImmediately();
|
||||
} else if (!mFinishNotificationTask.IsPending()) {
|
||||
nsRefPtr<nsRunnableMethod<Animation>> runnable =
|
||||
NS_NewRunnableMethod(this, &Animation::MaybeResolveFinishedPromise);
|
||||
NS_NewRunnableMethod(this, &Animation::DoFinishNotificationImmediately);
|
||||
Promise::DispatchToMicroTask(runnable);
|
||||
mFinishNotificationTask = runnable;
|
||||
}
|
||||
@@ -1101,17 +1123,46 @@ Animation::ResetFinishedPromise()
|
||||
void
|
||||
Animation::MaybeResolveFinishedPromise()
|
||||
{
|
||||
mFinishNotificationTask.Revoke();
|
||||
|
||||
if (PlayState() != AnimationPlayState::Finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFinished) {
|
||||
mFinished->MaybeResolve(this);
|
||||
}
|
||||
mFinishedIsResolved = true;
|
||||
}
|
||||
|
||||
void
|
||||
Animation::DoFinishNotificationImmediately()
|
||||
{
|
||||
mFinishNotificationTask.Revoke();
|
||||
|
||||
if (PlayState() != AnimationPlayState::Finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
MaybeResolveFinishedPromise();
|
||||
|
||||
DispatchPlaybackEvent(NS_LITERAL_STRING("finish"));
|
||||
}
|
||||
|
||||
void
|
||||
Animation::DispatchPlaybackEvent(const nsAString& aName)
|
||||
{
|
||||
AnimationPlaybackEventInit init;
|
||||
|
||||
if (aName.EqualsLiteral("finish")) {
|
||||
init.mCurrentTime = GetCurrentTimeAsDouble();
|
||||
}
|
||||
if (mTimeline) {
|
||||
init.mTimelineTime = mTimeline->GetCurrentTimeAsDouble();
|
||||
}
|
||||
|
||||
nsRefPtr<AnimationPlaybackEvent> event =
|
||||
AnimationPlaybackEvent::Constructor(this, aName, init);
|
||||
event->SetTrusted(true);
|
||||
|
||||
nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
|
||||
new AsyncEventDispatcher(this, event);
|
||||
asyncDispatcher->PostDOMEvent();
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
+40
-25
@@ -13,6 +13,7 @@
|
||||
#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
|
||||
#include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState
|
||||
#include "mozilla/dom/AnimationTimeline.h" // for AnimationTimeline
|
||||
#include "mozilla/DOMEventTargetHelper.h" // for DOMEventTargetHelper
|
||||
#include "mozilla/dom/KeyframeEffect.h" // for KeyframeEffectReadOnly
|
||||
#include "mozilla/dom/Promise.h" // for Promise
|
||||
#include "nsCSSProperty.h" // for nsCSSProperty
|
||||
@@ -46,18 +47,17 @@ class CSSAnimation;
|
||||
class CSSTransition;
|
||||
|
||||
class Animation
|
||||
: public nsISupports
|
||||
, public nsWrapperCache
|
||||
: public DOMEventTargetHelper
|
||||
{
|
||||
protected:
|
||||
virtual ~Animation() {}
|
||||
|
||||
public:
|
||||
explicit Animation(nsIGlobalObject* aGlobal)
|
||||
: mGlobal(aGlobal)
|
||||
: DOMEventTargetHelper(aGlobal)
|
||||
, mPlaybackRate(1.0)
|
||||
, mPendingState(PendingState::NotPending)
|
||||
, mSequenceNum(kUnsequenced)
|
||||
, mAnimationIndex(sNextAnimationIndex++)
|
||||
, mIsRunningOnCompositor(false)
|
||||
, mFinishedAtLastComposeStyle(false)
|
||||
, mIsRelevant(false)
|
||||
@@ -65,8 +65,9 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Animation)
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation,
|
||||
DOMEventTargetHelper)
|
||||
|
||||
AnimationTimeline* GetParentObject() const { return mTimeline; }
|
||||
virtual JSObject* WrapObject(JSContext* aCx,
|
||||
@@ -107,7 +108,10 @@ public:
|
||||
virtual void Finish(ErrorResult& aRv);
|
||||
virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior);
|
||||
virtual void Pause(ErrorResult& aRv);
|
||||
virtual void Reverse(ErrorResult& aRv);
|
||||
bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
|
||||
IMPL_EVENT_HANDLER(finish);
|
||||
IMPL_EVENT_HANDLER(cancel);
|
||||
|
||||
// Wrapper functions for Animation DOM methods when called
|
||||
// from script.
|
||||
@@ -245,13 +249,15 @@ public:
|
||||
* still running but we only consider it playing when it is in its active
|
||||
* interval. This definition is used for fetching the animations that are
|
||||
* candidates for running on the compositor (since we don't ship animations
|
||||
* to the compositor when they are in their delay phase or paused).
|
||||
* to the compositor when they are in their delay phase or paused including
|
||||
* being effectively paused due to having a zero playback rate).
|
||||
*/
|
||||
bool IsPlaying() const
|
||||
{
|
||||
// We need to have an effect in its active interval, and
|
||||
// be either running or waiting to run.
|
||||
// be either running or waiting to run with a non-zero playback rate.
|
||||
return HasInPlayEffect() &&
|
||||
mPlaybackRate != 0.0 &&
|
||||
(PlayState() == AnimationPlayState::Running ||
|
||||
mPendingState == PendingState::PlayPending);
|
||||
}
|
||||
@@ -262,14 +268,6 @@ public:
|
||||
* Returns true if this Animation has a lower composite order than aOther.
|
||||
*/
|
||||
virtual bool HasLowerCompositeOrderThan(const Animation& aOther) const;
|
||||
/**
|
||||
* Returns true if this Animation is involved in some sort of
|
||||
* custom composite ordering (such as the ordering defined for CSS
|
||||
* animations or CSS transitions).
|
||||
*
|
||||
* When this is true, this class will not update the sequence number.
|
||||
*/
|
||||
virtual bool IsUsingCustomCompositeOrder() const { return false; }
|
||||
|
||||
void SetIsRunningOnCompositor() { mIsRunningOnCompositor = true; }
|
||||
void ClearIsRunningOnCompositor() { mIsRunningOnCompositor = false; }
|
||||
@@ -292,6 +290,19 @@ public:
|
||||
nsCSSPropertySet& aSetProperties,
|
||||
bool& aNeedsRefreshes);
|
||||
|
||||
|
||||
// FIXME: Because we currently determine if we need refresh driver ticks
|
||||
// during restyling (specifically ComposeStyle above) and not necessarily
|
||||
// during a refresh driver tick, we can arrive at a situation where we
|
||||
// have finished running an animation but are waiting until the next tick
|
||||
// to queue the final end event. This method tells us when we are in that
|
||||
// situation so we can avoid unregistering from the refresh driver until
|
||||
// we've finished dispatching events.
|
||||
//
|
||||
// This is a temporary measure until bug 1195180 is done and we can do all
|
||||
// our registering and unregistering within a tick callback.
|
||||
virtual bool HasEndEventToQueue() const { return false; }
|
||||
|
||||
void NotifyEffectTimingUpdated();
|
||||
|
||||
protected:
|
||||
@@ -327,8 +338,8 @@ protected:
|
||||
Async
|
||||
};
|
||||
|
||||
void UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag);
|
||||
virtual void UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag);
|
||||
void UpdateFinishedState(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag);
|
||||
void UpdateEffect();
|
||||
@@ -337,6 +348,8 @@ protected:
|
||||
void ResetFinishedPromise();
|
||||
void MaybeResolveFinishedPromise();
|
||||
void DoFinishNotification(SyncNotifyFlag aSyncNotifyFlag);
|
||||
void DoFinishNotificationImmediately();
|
||||
void DispatchPlaybackEvent(const nsAString& aName);
|
||||
|
||||
/**
|
||||
* Remove this animation from the pending animation tracker and reset
|
||||
@@ -353,7 +366,6 @@ protected:
|
||||
virtual CommonAnimationManager* GetAnimationManager() const = 0;
|
||||
AnimationCollection* GetCollection() const;
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> mGlobal;
|
||||
nsRefPtr<AnimationTimeline> mTimeline;
|
||||
nsRefPtr<KeyframeEffectReadOnly> mEffect;
|
||||
// The beginning of the delay period.
|
||||
@@ -385,13 +397,16 @@ protected:
|
||||
enum class PendingState { NotPending, PlayPending, PausePending };
|
||||
PendingState mPendingState;
|
||||
|
||||
static uint64_t sNextSequenceNum;
|
||||
static const uint64_t kUnsequenced = UINT64_MAX;
|
||||
static uint64_t sNextAnimationIndex;
|
||||
|
||||
// The sequence number assigned to this animation. This is kUnsequenced
|
||||
// while the animation is in the idle state and is updated each time
|
||||
// the animation transitions out of the idle state.
|
||||
uint64_t mSequenceNum;
|
||||
// The relative position of this animation within the global animation list.
|
||||
// This is kNoIndex while the animation is in the idle state and is updated
|
||||
// each time the animation transitions out of the idle state.
|
||||
//
|
||||
// Note that subclasses such as CSSTransition and CSSAnimation may repurpose
|
||||
// this member to implement their own brand of sorting. As a result, it is
|
||||
// possible for two different objects to have the same index.
|
||||
uint64_t mAnimationIndex;
|
||||
|
||||
bool mIsRunningOnCompositor;
|
||||
bool mFinishedAtLastComposeStyle;
|
||||
|
||||
@@ -243,7 +243,7 @@ KeyframeEffectReadOnly::ActiveDuration(const AnimationTiming& aTiming)
|
||||
aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount));
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#in-play
|
||||
// https://w3c.github.io/web-animations/#in-play
|
||||
bool
|
||||
KeyframeEffectReadOnly::IsInPlay(const Animation& aAnimation) const
|
||||
{
|
||||
@@ -254,7 +254,7 @@ KeyframeEffectReadOnly::IsInPlay(const Animation& aAnimation) const
|
||||
return GetComputedTiming().mPhase == ComputedTiming::AnimationPhase_Active;
|
||||
}
|
||||
|
||||
// http://w3c.github.io/web-animations/#current
|
||||
// https://w3c.github.io/web-animations/#current
|
||||
bool
|
||||
KeyframeEffectReadOnly::IsCurrent(const Animation& aAnimation) const
|
||||
{
|
||||
@@ -267,6 +267,7 @@ KeyframeEffectReadOnly::IsCurrent(const Animation& aAnimation) const
|
||||
computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/web-animations/#in-effect
|
||||
bool
|
||||
KeyframeEffectReadOnly::IsInEffect() const
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="../testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes abc {
|
||||
to { transform: translate(10px) }
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, {'style': 'animation: abc 100s'});
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var finishedTimelineTime;
|
||||
animation.finished.then().catch(function() {
|
||||
finishedTimelineTime = animation.timeline.currentTime;
|
||||
});
|
||||
|
||||
animation.oncancel = t.step_func_done(function(event) {
|
||||
assert_equals(event.currentTime, null,
|
||||
'event.currentTime should be null');
|
||||
assert_equals(event.timelineTime, finishedTimelineTime,
|
||||
'event.timelineTime should equal to the animation timeline ' +
|
||||
'when finished promise is rejected');
|
||||
});
|
||||
|
||||
animation.cancel();
|
||||
}, 'oncancel event is fired when animation.cancel()');
|
||||
|
||||
done();
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,142 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="../testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes abc {
|
||||
to { transform: translate(10px) }
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
const ANIM_PROP_VAL = 'abc 100s';
|
||||
const ANIM_DURATION = 100000; // ms
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
div.style.animation = ANIM_PROP_VAL;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var finishedTimelineTime;
|
||||
animation.finished.then(function() {
|
||||
finishedTimelineTime = animation.timeline.currentTime;
|
||||
});
|
||||
|
||||
animation.onfinish = t.step_func_done(function(event) {
|
||||
assert_equals(event.currentTime, 0,
|
||||
'event.currentTime should be zero');
|
||||
assert_equals(event.timelineTime, finishedTimelineTime,
|
||||
'event.timelineTime should equal to the animation timeline ' +
|
||||
'when finished promise is resolved');
|
||||
});
|
||||
|
||||
animation.playbackRate = -1;
|
||||
}, 'onfinish event is fired when the currentTime < 0 and ' +
|
||||
'the playbackRate < 0');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
div.style.animation = ANIM_PROP_VAL;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var finishedTimelineTime;
|
||||
animation.finished.then(function() {
|
||||
finishedTimelineTime = animation.timeline.currentTime;
|
||||
});
|
||||
|
||||
animation.onfinish = t.step_func_done(function(event) {
|
||||
assert_equals(event.currentTime, ANIM_DURATION,
|
||||
'event.currentTime should be the effect end');
|
||||
assert_equals(event.timelineTime, finishedTimelineTime,
|
||||
'event.timelineTime should equal to the animation timeline ' +
|
||||
'when finished promise is resolved');
|
||||
});
|
||||
|
||||
animation.currentTime = ANIM_DURATION;
|
||||
}, 'onfinish event is fired when the currentTime > 0 and ' +
|
||||
'the playbackRate > 0');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, {'class': 'animated-div'});
|
||||
div.style.animation = ANIM_PROP_VAL;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
var finishedTimelineTime;
|
||||
animation.finished.then(function() {
|
||||
finishedTimelineTime = animation.timeline.currentTime;
|
||||
});
|
||||
|
||||
animation.onfinish = t.step_func_done(function(event) {
|
||||
assert_equals(event.currentTime, ANIM_DURATION,
|
||||
'event.currentTime should be the effect end');
|
||||
assert_equals(event.timelineTime, finishedTimelineTime,
|
||||
'event.timelineTime should equal to the animation timeline ' +
|
||||
'when finished promise is resolved');
|
||||
});
|
||||
|
||||
animation.finish();
|
||||
}, 'onfinish event is fired when animation.finish() is called');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
div.style.animation = ANIM_PROP_VAL;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.onfinish = t.step_func(function(event) {
|
||||
assert_unreached('onfinish event should not be fired');
|
||||
});
|
||||
|
||||
animation.currentTime = ANIM_DURATION / 2;
|
||||
animation.pause();
|
||||
|
||||
animation.ready.then(t.step_func(function() {
|
||||
animation.currentTime = ANIM_DURATION;
|
||||
return waitForAnimationFrames(2);
|
||||
})).then(t.step_func(function() {
|
||||
t.done();
|
||||
}));
|
||||
}, 'onfinish event is not fired when paused');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
div.style.animation = ANIM_PROP_VAL;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.onfinish = t.step_func(function(event) {
|
||||
assert_unreached('onfinish event should not be fired');
|
||||
});
|
||||
|
||||
animation.ready.then(function() {
|
||||
animation.playbackRate = 0;
|
||||
animation.currentTime = ANIM_DURATION;
|
||||
return waitForAnimationFrames(2);
|
||||
}).then(t.step_func(function() {
|
||||
t.done();
|
||||
}));
|
||||
|
||||
}, 'onfinish event is not fired when the playbackRate is zero');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t);
|
||||
div.style.animation = ANIM_PROP_VAL;
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.onfinish = t.step_func(function(event) {
|
||||
assert_unreached('onfinish event should not be fired');
|
||||
});
|
||||
|
||||
animation.ready.then(function() {
|
||||
animation.currentTime = ANIM_DURATION;
|
||||
animation.currentTime = ANIM_DURATION / 2;
|
||||
return waitForAnimationFrames(2);
|
||||
}).then(t.step_func(function() {
|
||||
t.done();
|
||||
}));
|
||||
|
||||
}, 'onfinish event is not fired when the animation falls out ' +
|
||||
'finished state immediately');
|
||||
|
||||
done();
|
||||
</script>
|
||||
</body>
|
||||
@@ -0,0 +1,169 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="../testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes anim {
|
||||
to { transform: translate(100px) }
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s infinite' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.ready.then(t.step_func_done(function() {
|
||||
var previousPlaybackRate = animation.playbackRate;
|
||||
animation.reverse();
|
||||
assert_equals(animation.playbackRate, -previousPlaybackRate,
|
||||
'playbackRate should be invetrted');
|
||||
}));
|
||||
}, 'reverse() inverts playbackRate');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s infinite' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.currentTime = 50000;
|
||||
animation.pause();
|
||||
animation.ready.then(t.step_func(function() {
|
||||
animation.reverse();
|
||||
return animation.ready;
|
||||
})).then(t.step_func_done(function() {
|
||||
assert_equals(animation.playState, 'running',
|
||||
'Animation.playState should be "running" after reverse()');
|
||||
}));
|
||||
}, 'reverse() starts to play when pausing animation');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
div.style.animation = "";
|
||||
flushComputedStyle(div);
|
||||
|
||||
assert_equals(animation.currentTime, null);
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 100000,
|
||||
'animation.currentTime should be its effect end');
|
||||
t.done();
|
||||
}, 'reverse() from idle state starts playing the animation');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.currentTime = 50000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 50000,
|
||||
'reverse() should not change the currentTime ' +
|
||||
'if the currentTime is in the middle of animation duration');
|
||||
t.done();
|
||||
}, 'reverse() maintains the same currentTime');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.currentTime = 200000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 100000,
|
||||
'reverse() should start playing from the animation effect end ' +
|
||||
'if the playbackRate > 0 and the currentTime > effect end');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate > 0 and currentTime > effect end');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.currentTime = -200000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 100000,
|
||||
'reverse() should start playing from the animation effect end ' +
|
||||
'if the playbackRate > 0 and the currentTime < 0');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate > 0 and currentTime < 0');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.playbackRate = -1;
|
||||
animation.currentTime = -200000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 0,
|
||||
'reverse() should start playing from the start of animation time ' +
|
||||
'if the playbackRate < 0 and the currentTime < 0');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate < 0 and currentTime < 0');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.playbackRate = -1;
|
||||
animation.currentTime = 200000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 0,
|
||||
'reverse() should start playing from the start of animation time ' +
|
||||
'if the playbackRate < 0 and the currentTime > effect end');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate < 0 and currentTime > effect end');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s infinite' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.currentTime = -200000;
|
||||
|
||||
assert_throws('InvalidStateError',
|
||||
function () { animation.reverse(); },
|
||||
'reverse() should throw InvalidStateError ' +
|
||||
'if the playbackRate > 0 and the currentTime < 0 ' +
|
||||
'and the target effect is positive infinity');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate > 0 and currentTime < 0 ' +
|
||||
'and the target effect is positive infinity');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s infinite' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.playbackRate = -1;
|
||||
animation.currentTime = -200000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.currentTime, 0,
|
||||
'reverse() should start playing from the start of animation time ' +
|
||||
'if the playbackRate < 0 and the currentTime < 0 ' +
|
||||
'and the target effect is positive infinity');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate < 0 and currentTime < 0 ' +
|
||||
'and the target effect is positive infinity');
|
||||
|
||||
async_test(function(t) {
|
||||
var div = addDiv(t, { style: 'animation: anim 100s' });
|
||||
var animation = div.getAnimations()[0];
|
||||
|
||||
animation.playbackRate = 0;
|
||||
animation.currentTime = 50000;
|
||||
animation.reverse();
|
||||
|
||||
assert_equals(animation.playbackRate, 0,
|
||||
'reverse() should preserve playbackRate if the playbackRate == 0');
|
||||
assert_equals(animation.currentTime, 50000,
|
||||
'reverse() should not affect the currentTime if the playbackRate == 0');
|
||||
t.done();
|
||||
}, 'reverse() when playbackRate == 0');
|
||||
|
||||
done();
|
||||
</script>
|
||||
</body>
|
||||
@@ -144,6 +144,54 @@ test(function(t) {
|
||||
});
|
||||
}, 'Order of CSS Animations - across and within elements');
|
||||
|
||||
test(function(t) {
|
||||
cancelAllAnimationsOnEnd(t);
|
||||
|
||||
var div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
|
||||
var animLeft = document.timeline.getAnimations()[0];
|
||||
assert_equals(animLeft.animationName, 'animLeft',
|
||||
'Originally, animLeft animation comes first');
|
||||
|
||||
// Disassociate animLeft from markup and restart
|
||||
div.style.animation = 'animTop 100s';
|
||||
animLeft.play();
|
||||
|
||||
var animations = document.timeline.getAnimations();
|
||||
assert_equals(animations.length, 2,
|
||||
'getAnimations returns markup-bound and free animations');
|
||||
assert_equals(animations[0].animationName, 'animTop',
|
||||
'Markup-bound animations come first');
|
||||
assert_equals(animations[1], animLeft, 'Free animations come last');
|
||||
}, 'Order of CSS Animations - markup-bound vs free animations');
|
||||
|
||||
test(function(t) {
|
||||
cancelAllAnimationsOnEnd(t);
|
||||
|
||||
var div = addDiv(t, { style: 'animation: animLeft 100s, animTop 100s' });
|
||||
var animLeft = document.timeline.getAnimations()[0];
|
||||
var animTop = document.timeline.getAnimations()[1];
|
||||
|
||||
// Disassociate both animations from markup and restart in opposite order
|
||||
div.style.animation = '';
|
||||
animTop.play();
|
||||
animLeft.play();
|
||||
|
||||
var animations = document.timeline.getAnimations();
|
||||
assert_equals(animations.length, 2,
|
||||
'getAnimations returns free animations');
|
||||
assert_equals(animations[0], animTop,
|
||||
'Free animations are returned in the order they are started');
|
||||
assert_equals(animations[1], animLeft,
|
||||
'Animations started later are returned later');
|
||||
|
||||
// Restarting an animation should have no effect
|
||||
animTop.cancel();
|
||||
animTop.play();
|
||||
assert_equals(document.timeline.getAnimations()[0], animTop,
|
||||
'After restarting, the ordering of free animations' +
|
||||
' does not change');
|
||||
}, 'Order of CSS Animations - free animations');
|
||||
|
||||
test(function(t) {
|
||||
// Add an animation first
|
||||
var div = addDiv(t, { style: 'animation: animLeft 100s' });
|
||||
@@ -191,6 +239,8 @@ test(function(t) {
|
||||
}, 'CSS Animations cancelled via the API are not returned');
|
||||
|
||||
test(function(t) {
|
||||
cancelAllAnimationsOnEnd(t);
|
||||
|
||||
var div = addDiv(t, { style: 'animation: animLeft 100s' });
|
||||
var anim = div.getAnimations()[0];
|
||||
anim.cancel();
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
setup({explicit_done: true});
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{ "set": [["dom.animations-api.core.enabled", true]]},
|
||||
function() {
|
||||
window.open("file_animation-oncancel.html");
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
setup({explicit_done: true});
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{ "set": [["dom.animations-api.core.enabled", true]]},
|
||||
function() {
|
||||
window.open("file_animation-onfinish.html");
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
setup({explicit_done: true});
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{ "set": [["dom.animations-api.core.enabled", true]]},
|
||||
function() {
|
||||
window.open("file_animation-reverse.html");
|
||||
});
|
||||
</script>
|
||||
</html>
|
||||
@@ -12,6 +12,10 @@ support-files = css-animations/file_animation-currenttime.html
|
||||
support-files = css-animations/file_animation-finish.html
|
||||
[css-animations/test_animation-finished.html]
|
||||
support-files = css-animations/file_animation-finished.html
|
||||
[css-animations/test_animation-oncancel.html]
|
||||
support-files = css-animations/file_animation-oncancel.html
|
||||
[css-animations/test_animation-onfinish.html]
|
||||
support-files = css-animations/file_animation-onfinish.html
|
||||
[css-animations/test_animation-pausing.html]
|
||||
support-files = css-animations/file_animation-pausing.html
|
||||
[css-animations/test_animation-play.html]
|
||||
@@ -20,6 +24,8 @@ support-files = css-animations/file_animation-play.html
|
||||
support-files = css-animations/file_animation-playstate.html
|
||||
[css-animations/test_animation-ready.html]
|
||||
support-files = css-animations/file_animation-ready.html
|
||||
[css-animations/test_animation-reverse.html]
|
||||
support-files = css-animations/file_animation-reverse.html
|
||||
[css-animations/test_animation-starttime.html]
|
||||
support-files = css-animations/file_animation-starttime.html
|
||||
[css-animations/test_cssanimation-animationname.html]
|
||||
|
||||
@@ -17,6 +17,18 @@ function addDiv(t) {
|
||||
return div;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some tests cause animations to continue to exist even after their target
|
||||
* element has been removed from the document tree. To ensure that these
|
||||
* animations do not impact other tests we should cancel them when the test
|
||||
* is complete.
|
||||
*/
|
||||
function cancelAllAnimationsOnEnd(t) {
|
||||
t.add_cleanup(function() {
|
||||
document.timeline.getAnimations().forEach(animation => animation.cancel());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise wrapper for requestAnimationFrame.
|
||||
*/
|
||||
|
||||
@@ -362,19 +362,18 @@ nsAnimationReceiver::RecordAnimationMutation(Animation* aAnimation,
|
||||
}
|
||||
|
||||
if (nsAutoAnimationMutationBatch::IsBatching()) {
|
||||
if (nsAutoAnimationMutationBatch::GetBatchTarget() != animationTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aMutationType) {
|
||||
case eAnimationMutation_Added:
|
||||
nsAutoAnimationMutationBatch::AnimationAdded(aAnimation);
|
||||
nsAutoAnimationMutationBatch::AnimationAdded(aAnimation,
|
||||
animationTarget);
|
||||
break;
|
||||
case eAnimationMutation_Changed:
|
||||
nsAutoAnimationMutationBatch::AnimationChanged(aAnimation);
|
||||
nsAutoAnimationMutationBatch::AnimationChanged(aAnimation,
|
||||
animationTarget);
|
||||
break;
|
||||
case eAnimationMutation_Removed:
|
||||
nsAutoAnimationMutationBatch::AnimationRemoved(aAnimation);
|
||||
nsAutoAnimationMutationBatch::AnimationRemoved(aAnimation,
|
||||
animationTarget);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1031,32 +1030,46 @@ nsAutoAnimationMutationBatch::Done()
|
||||
return;
|
||||
}
|
||||
|
||||
sCurrentBatch = mPreviousBatch;
|
||||
sCurrentBatch = nullptr;
|
||||
if (mObservers.IsEmpty()) {
|
||||
nsDOMMutationObserver::LeaveMutationHandling();
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
for (nsDOMMutationObserver* ob : mObservers) {
|
||||
nsRefPtr<nsDOMMutationRecord> m =
|
||||
new nsDOMMutationRecord(nsGkAtoms::animations, ob->GetParentObject());
|
||||
m->mTarget = mBatchTarget;
|
||||
mBatchTargets.Sort(TreeOrderComparator());
|
||||
|
||||
for (const Entry& e : mEntries) {
|
||||
if (e.mState == eState_Added) {
|
||||
m->mAddedAnimations.AppendElement(e.mAnimation);
|
||||
} else if (e.mState == eState_Removed) {
|
||||
m->mRemovedAnimations.AppendElement(e.mAnimation);
|
||||
} else if (e.mState == eState_RemainedPresent && e.mChanged) {
|
||||
m->mChangedAnimations.AppendElement(e.mAnimation);
|
||||
for (nsDOMMutationObserver* ob : mObservers) {
|
||||
bool didAddRecords = false;
|
||||
|
||||
for (nsINode* target : mBatchTargets) {
|
||||
EntryArray* entries = mEntryTable.Get(target);
|
||||
MOZ_ASSERT(entries,
|
||||
"Targets in entry table and targets list should match");
|
||||
|
||||
nsRefPtr<nsDOMMutationRecord> m =
|
||||
new nsDOMMutationRecord(nsGkAtoms::animations, ob->GetParentObject());
|
||||
m->mTarget = target;
|
||||
|
||||
for (const Entry& e : *entries) {
|
||||
if (e.mState == eState_Added) {
|
||||
m->mAddedAnimations.AppendElement(e.mAnimation);
|
||||
} else if (e.mState == eState_Removed) {
|
||||
m->mRemovedAnimations.AppendElement(e.mAnimation);
|
||||
} else if (e.mState == eState_RemainedPresent && e.mChanged) {
|
||||
m->mChangedAnimations.AppendElement(e.mAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m->mAddedAnimations.IsEmpty() ||
|
||||
!m->mChangedAnimations.IsEmpty() ||
|
||||
!m->mRemovedAnimations.IsEmpty()) {
|
||||
ob->AppendMutationRecord(m.forget());
|
||||
didAddRecords = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m->mAddedAnimations.IsEmpty() ||
|
||||
!m->mChangedAnimations.IsEmpty() ||
|
||||
!m->mRemovedAnimations.IsEmpty()) {
|
||||
ob->AppendMutationRecord(m.forget());
|
||||
if (didAddRecords) {
|
||||
ob->ScheduleForRun();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,20 +724,21 @@ class nsAutoAnimationMutationBatch
|
||||
struct Entry;
|
||||
|
||||
public:
|
||||
explicit nsAutoAnimationMutationBatch(nsINode* aTarget)
|
||||
: mBatchTarget(nullptr)
|
||||
explicit nsAutoAnimationMutationBatch(nsIDocument* aDocument)
|
||||
{
|
||||
Init(aTarget);
|
||||
Init(aDocument);
|
||||
}
|
||||
|
||||
void Init(nsINode* aTarget)
|
||||
void Init(nsIDocument* aDocument)
|
||||
{
|
||||
if (aTarget && aTarget->OwnerDoc()->MayHaveDOMMutationObservers()) {
|
||||
mBatchTarget = aTarget;
|
||||
mPreviousBatch = sCurrentBatch;
|
||||
sCurrentBatch = this;
|
||||
nsDOMMutationObserver::EnterMutationHandling();
|
||||
if (!aDocument ||
|
||||
!aDocument->MayHaveDOMMutationObservers() ||
|
||||
sCurrentBatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
sCurrentBatch = this;
|
||||
nsDOMMutationObserver::EnterMutationHandling();
|
||||
}
|
||||
|
||||
~nsAutoAnimationMutationBatch()
|
||||
@@ -765,18 +766,14 @@ public:
|
||||
sCurrentBatch->mObservers.AppendElement(aObserver);
|
||||
}
|
||||
|
||||
static nsINode* GetBatchTarget()
|
||||
{
|
||||
return sCurrentBatch->mBatchTarget;
|
||||
}
|
||||
|
||||
static void AnimationAdded(mozilla::dom::Animation* aAnimation)
|
||||
static void AnimationAdded(mozilla::dom::Animation* aAnimation,
|
||||
nsINode* aTarget)
|
||||
{
|
||||
if (!IsBatching()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = sCurrentBatch->FindEntry(aAnimation);
|
||||
Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
|
||||
if (entry) {
|
||||
switch (entry->mState) {
|
||||
case eState_RemainedAbsent:
|
||||
@@ -790,16 +787,16 @@ public:
|
||||
"twice");
|
||||
}
|
||||
} else {
|
||||
entry = sCurrentBatch->mEntries.AppendElement();
|
||||
entry->mAnimation = aAnimation;
|
||||
entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
|
||||
entry->mState = eState_Added;
|
||||
entry->mChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void AnimationChanged(mozilla::dom::Animation* aAnimation)
|
||||
static void AnimationChanged(mozilla::dom::Animation* aAnimation,
|
||||
nsINode* aTarget)
|
||||
{
|
||||
Entry* entry = sCurrentBatch->FindEntry(aAnimation);
|
||||
Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
|
||||
if (entry) {
|
||||
NS_ASSERTION(entry->mState == eState_RemainedPresent ||
|
||||
entry->mState == eState_Added,
|
||||
@@ -807,16 +804,16 @@ public:
|
||||
"being removed");
|
||||
entry->mChanged = true;
|
||||
} else {
|
||||
entry = sCurrentBatch->mEntries.AppendElement();
|
||||
entry->mAnimation = aAnimation;
|
||||
entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
|
||||
entry->mState = eState_RemainedPresent;
|
||||
entry->mChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void AnimationRemoved(mozilla::dom::Animation* aAnimation)
|
||||
static void AnimationRemoved(mozilla::dom::Animation* aAnimation,
|
||||
nsINode* aTarget)
|
||||
{
|
||||
Entry* entry = sCurrentBatch->FindEntry(aAnimation);
|
||||
Entry* entry = sCurrentBatch->FindEntry(aAnimation, aTarget);
|
||||
if (entry) {
|
||||
switch (entry->mState) {
|
||||
case eState_RemainedPresent:
|
||||
@@ -830,17 +827,21 @@ public:
|
||||
"twice");
|
||||
}
|
||||
} else {
|
||||
entry = sCurrentBatch->mEntries.AppendElement();
|
||||
entry->mAnimation = aAnimation;
|
||||
entry = sCurrentBatch->AddEntry(aAnimation, aTarget);
|
||||
entry->mState = eState_Removed;
|
||||
entry->mChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Entry* FindEntry(mozilla::dom::Animation* aAnimation)
|
||||
Entry* FindEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget)
|
||||
{
|
||||
for (Entry& e : mEntries) {
|
||||
EntryArray* entries = mEntryTable.Get(aTarget);
|
||||
if (!entries) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (Entry& e : *entries) {
|
||||
if (e.mAnimation == aAnimation) {
|
||||
return &e;
|
||||
}
|
||||
@@ -848,6 +849,17 @@ private:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Entry* AddEntry(mozilla::dom::Animation* aAnimation, nsINode* aTarget)
|
||||
{
|
||||
EntryArray* entries = sCurrentBatch->mEntryTable.LookupOrAdd(aTarget);
|
||||
if (entries->IsEmpty()) {
|
||||
sCurrentBatch->mBatchTargets.AppendElement(aTarget);
|
||||
}
|
||||
Entry* entry = entries->AppendElement();
|
||||
entry->mAnimation = aAnimation;
|
||||
return entry;
|
||||
}
|
||||
|
||||
enum State {
|
||||
eState_RemainedPresent,
|
||||
eState_RemainedAbsent,
|
||||
@@ -863,10 +875,11 @@ private:
|
||||
};
|
||||
|
||||
static nsAutoAnimationMutationBatch* sCurrentBatch;
|
||||
nsAutoAnimationMutationBatch* mPreviousBatch;
|
||||
nsAutoTArray<nsDOMMutationObserver*, 2> mObservers;
|
||||
nsTArray<Entry> mEntries;
|
||||
nsINode* mBatchTarget;
|
||||
typedef nsTArray<Entry> EntryArray;
|
||||
nsClassHashtable<nsPtrHashKey<nsINode>, EntryArray> mEntryTable;
|
||||
// List of nodes referred to by mEntryTable so we can sort them
|
||||
nsTArray<nsINode*> mBatchTargets;
|
||||
};
|
||||
|
||||
inline
|
||||
|
||||
@@ -699,6 +699,7 @@ GK_ATOM(onbroadcast, "onbroadcast")
|
||||
GK_ATOM(onbusy, "onbusy")
|
||||
GK_ATOM(oncached, "oncached")
|
||||
GK_ATOM(oncallschanged, "oncallschanged")
|
||||
GK_ATOM(oncancel, "oncancel")
|
||||
GK_ATOM(oncardstatechange, "oncardstatechange")
|
||||
GK_ATOM(oncfstatechange, "oncfstatechange")
|
||||
GK_ATOM(onchange, "onchange")
|
||||
@@ -773,6 +774,7 @@ GK_ATOM(onevicted, "onevicted")
|
||||
GK_ATOM(onfacesdetected, "onfacesdetected")
|
||||
GK_ATOM(onfailed, "onfailed")
|
||||
GK_ATOM(onfetch, "onfetch")
|
||||
GK_ATOM(onfinish, "onfinish")
|
||||
GK_ATOM(onfocus, "onfocus")
|
||||
GK_ATOM(onfrequencychange, "onfrequencychange")
|
||||
GK_ATOM(onspeakerforcedchange, "onspeakerforcedchange")
|
||||
|
||||
@@ -32,6 +32,10 @@ const kEventConstructors = {
|
||||
return new AnimationEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
AnimationPlaybackEvent: { create: function (aName, aProps) {
|
||||
return new AnimationPlaybackEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
AudioProcessingEvent: { create: null, // Cannot create untrusted event from JS.
|
||||
},
|
||||
BeforeAfterKeyboardEvent: { create: function (aName, aProps) {
|
||||
|
||||
@@ -139,6 +139,8 @@ var interfaceNamesInGlobalScope =
|
||||
{name: "AnimationEffectReadOnly", release: false},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"AnimationEvent",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
"AnimationPlaybackEvent",
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "AnimationTimeline", release: false},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
|
||||
|
||||
[Func="nsDocument::IsWebAnimationsEnabled"]
|
||||
interface Animation {
|
||||
interface Animation : EventTarget {
|
||||
// Bug 1049975: Make 'effect' writeable
|
||||
[Pure]
|
||||
readonly attribute AnimationEffectReadOnly? effect;
|
||||
@@ -30,6 +30,8 @@ interface Animation {
|
||||
readonly attribute Promise<Animation> ready;
|
||||
[Throws]
|
||||
readonly attribute Promise<Animation> finished;
|
||||
attribute EventHandler onfinish;
|
||||
attribute EventHandler oncancel;
|
||||
void cancel ();
|
||||
[Throws]
|
||||
void finish ();
|
||||
@@ -37,9 +39,8 @@ interface Animation {
|
||||
void play ();
|
||||
[Throws, BinaryName="pauseFromJS"]
|
||||
void pause ();
|
||||
/*
|
||||
[Throws]
|
||||
void reverse ();
|
||||
*/
|
||||
};
|
||||
|
||||
// Non-standard extensions
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* http://w3c.github.io/web-animations/#the-animationplaybackevent-interface
|
||||
*
|
||||
* Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
|
||||
* liability, trademark and document use rules apply.
|
||||
*/
|
||||
|
||||
[Func="nsDocument::IsWebAnimationsEnabled",
|
||||
Constructor(DOMString type,
|
||||
optional AnimationPlaybackEventInit eventInitDict)]
|
||||
interface AnimationPlaybackEvent : Event {
|
||||
readonly attribute double? currentTime;
|
||||
readonly attribute double? timelineTime;
|
||||
};
|
||||
|
||||
dictionary AnimationPlaybackEventInit : EventInit {
|
||||
double? currentTime = null;
|
||||
double? timelineTime = null;
|
||||
};
|
||||
@@ -738,6 +738,7 @@ if CONFIG['MOZ_B2G_FM']:
|
||||
]
|
||||
|
||||
GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
'AnimationPlaybackEvent.webidl',
|
||||
'AutocompleteErrorEvent.webidl',
|
||||
'BlobEvent.webidl',
|
||||
'CallEvent.webidl',
|
||||
|
||||
@@ -460,7 +460,8 @@ SampleAnimations(Layer* aLayer, TimeStamp aPoint)
|
||||
|
||||
MOZ_ASSERT(!animation.startTime().IsNull(),
|
||||
"Failed to resolve start time of pending animations");
|
||||
TimeDuration elapsedDuration = aPoint - animation.startTime();
|
||||
TimeDuration elapsedDuration =
|
||||
(aPoint - animation.startTime()).MultDouble(animation.playbackRate());
|
||||
// Skip animations that are yet to start.
|
||||
//
|
||||
// Currently, this should only happen when the refresh driver is under test
|
||||
|
||||
@@ -198,6 +198,7 @@ struct Animation {
|
||||
int32_t direction;
|
||||
nsCSSProperty property;
|
||||
AnimationData data;
|
||||
float playbackRate;
|
||||
};
|
||||
|
||||
// Change a layer's attributes
|
||||
|
||||
@@ -389,6 +389,7 @@ AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty,
|
||||
animation->iterationCount() = timing.mIterationCount;
|
||||
animation->direction() = timing.mDirection;
|
||||
animation->property() = aProperty.mProperty;
|
||||
animation->playbackRate() = aAnimation->PlaybackRate();
|
||||
animation->data() = aData;
|
||||
|
||||
for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) {
|
||||
|
||||
@@ -54,7 +54,6 @@ CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext)
|
||||
: mPresContext(aPresContext)
|
||||
, mIsObservingRefreshDriver(false)
|
||||
{
|
||||
PR_INIT_CLIST(&mElementCollections);
|
||||
}
|
||||
|
||||
CommonAnimationManager::~CommonAnimationManager()
|
||||
@@ -82,17 +81,15 @@ CommonAnimationManager::AddElementCollection(AnimationCollection* aCollection)
|
||||
mIsObservingRefreshDriver = true;
|
||||
}
|
||||
|
||||
PR_INSERT_BEFORE(aCollection, &mElementCollections);
|
||||
mElementCollections.insertBack(aCollection);
|
||||
}
|
||||
|
||||
void
|
||||
CommonAnimationManager::RemoveAllElementCollections()
|
||||
{
|
||||
while (!PR_CLIST_IS_EMPTY(&mElementCollections)) {
|
||||
AnimationCollection* head =
|
||||
static_cast<AnimationCollection*>(PR_LIST_HEAD(&mElementCollections));
|
||||
head->Destroy();
|
||||
}
|
||||
while (AnimationCollection* head = mElementCollections.getFirst()) {
|
||||
head->Destroy(); // Note: this removes 'head' from mElementCollections.
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -121,10 +118,9 @@ CommonAnimationManager::MaybeStartOrStopObservingRefreshDriver()
|
||||
bool
|
||||
CommonAnimationManager::NeedsRefresh() const
|
||||
{
|
||||
for (PRCList *l = PR_LIST_HEAD(&mElementCollections);
|
||||
l != &mElementCollections;
|
||||
l = PR_NEXT_LINK(l)) {
|
||||
if (static_cast<AnimationCollection*>(l)->mNeedsRefreshes) {
|
||||
for (const AnimationCollection* collection = mElementCollections.getFirst();
|
||||
collection; collection = collection->getNext()) {
|
||||
if (collection->mNeedsRefreshes) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -280,11 +276,8 @@ CommonAnimationManager::AddStyleUpdatesTo(RestyleTracker& aTracker)
|
||||
{
|
||||
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
|
||||
|
||||
PRCList* next = PR_LIST_HEAD(&mElementCollections);
|
||||
while (next != &mElementCollections) {
|
||||
AnimationCollection* collection = static_cast<AnimationCollection*>(next);
|
||||
next = PR_NEXT_LINK(next);
|
||||
|
||||
for (AnimationCollection* collection = mElementCollections.getFirst();
|
||||
collection; collection = collection->getNext()) {
|
||||
collection->EnsureStyleRuleFor(now);
|
||||
|
||||
dom::Element* elementToRestyle = collection->GetElementToRestyle();
|
||||
@@ -320,7 +313,7 @@ CommonAnimationManager::GetAnimations(dom::Element *aElement,
|
||||
nsCSSPseudoElements::Type aPseudoType,
|
||||
bool aCreateIfNeeded)
|
||||
{
|
||||
if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementCollections)) {
|
||||
if (!aCreateIfNeeded && mElementCollections.isEmpty()) {
|
||||
// Early return for the most common case.
|
||||
return nullptr;
|
||||
}
|
||||
@@ -367,11 +360,8 @@ void
|
||||
CommonAnimationManager::FlushAnimations(FlushFlags aFlags)
|
||||
{
|
||||
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
|
||||
for (PRCList *l = PR_LIST_HEAD(&mElementCollections);
|
||||
l != &mElementCollections;
|
||||
l = PR_NEXT_LINK(l)) {
|
||||
AnimationCollection* collection = static_cast<AnimationCollection*>(l);
|
||||
|
||||
for (AnimationCollection* collection = mElementCollections.getFirst();
|
||||
collection; collection = collection->getNext()) {
|
||||
if (collection->mStyleRuleRefreshTime == now) {
|
||||
continue;
|
||||
}
|
||||
@@ -380,7 +370,7 @@ CommonAnimationManager::FlushAnimations(FlushFlags aFlags)
|
||||
collection->RequestRestyle(AnimationCollection::RestyleType::Standard);
|
||||
}
|
||||
|
||||
nsAutoAnimationMutationBatch mb(collection->mElement);
|
||||
nsAutoAnimationMutationBatch mb(collection->mElement->OwnerDoc());
|
||||
collection->Tick();
|
||||
}
|
||||
|
||||
@@ -458,7 +448,14 @@ CommonAnimationManager::WillRefresh(TimeStamp aTime)
|
||||
return;
|
||||
}
|
||||
|
||||
FlushAnimations(Can_Throttle);
|
||||
nsAutoAnimationMutationBatch mb(mPresContext->Document());
|
||||
|
||||
for (AnimationCollection* collection = mElementCollections.getFirst();
|
||||
collection; collection = collection->getNext()) {
|
||||
collection->Tick();
|
||||
}
|
||||
|
||||
MaybeStartOrStopObservingRefreshDriver();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
@@ -784,7 +781,7 @@ AnimationCollection::PropertyDtor(void *aObject, nsIAtom *aPropertyName,
|
||||
collection->mCalledPropertyDtor = true;
|
||||
#endif
|
||||
{
|
||||
nsAutoAnimationMutationBatch mb(collection->mElement);
|
||||
nsAutoAnimationMutationBatch mb(collection->mElement->OwnerDoc());
|
||||
|
||||
for (size_t animIdx = collection->mAnimations.Length(); animIdx-- != 0; ) {
|
||||
collection->mAnimations[animIdx]->CancelFromStyle();
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include "nsIStyleRuleProcessor.h"
|
||||
#include "nsIStyleRule.h"
|
||||
#include "nsRefreshDriver.h"
|
||||
#include "prclist.h"
|
||||
#include "nsChangeHint.h"
|
||||
#include "nsCSSProperty.h"
|
||||
#include "nsDisplayList.h" // For nsDisplayItem::Type
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/LinkedList.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/StyleAnimationValue.h"
|
||||
#include "mozilla/dom/Animation.h"
|
||||
@@ -178,7 +178,7 @@ public:
|
||||
GetAnimationCollection(const nsIFrame* aFrame);
|
||||
|
||||
protected:
|
||||
PRCList mElementCollections;
|
||||
LinkedList<AnimationCollection> mElementCollections;
|
||||
nsPresContext *mPresContext; // weak (non-null from ctor to Disconnect)
|
||||
bool mIsObservingRefreshDriver;
|
||||
};
|
||||
@@ -233,7 +233,7 @@ private:
|
||||
|
||||
typedef InfallibleTArray<nsRefPtr<dom::Animation>> AnimationPtrArray;
|
||||
|
||||
struct AnimationCollection : public PRCList
|
||||
struct AnimationCollection : public LinkedListElement<AnimationCollection>
|
||||
{
|
||||
AnimationCollection(dom::Element *aElement, nsIAtom *aElementProperty,
|
||||
CommonAnimationManager *aManager)
|
||||
@@ -249,14 +249,13 @@ struct AnimationCollection : public PRCList
|
||||
#endif
|
||||
{
|
||||
MOZ_COUNT_CTOR(AnimationCollection);
|
||||
PR_INIT_CLIST(this);
|
||||
}
|
||||
~AnimationCollection()
|
||||
{
|
||||
MOZ_ASSERT(mCalledPropertyDtor,
|
||||
"must call destructor through element property dtor");
|
||||
MOZ_COUNT_DTOR(AnimationCollection);
|
||||
PR_REMOVE_LINK(this);
|
||||
remove();
|
||||
mManager->ElementCollectionRemoved();
|
||||
}
|
||||
|
||||
|
||||
@@ -141,28 +141,24 @@ CSSAnimation::HasLowerCompositeOrderThan(const Animation& aOther) const
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. CSS animations using custom composite ordering (i.e. those that
|
||||
// correspond to an animation-name property) sort lower than other CSS
|
||||
// animations (e.g. those created or kept-alive by script).
|
||||
if (!IsUsingCustomCompositeOrder()) {
|
||||
return !aOther.IsUsingCustomCompositeOrder() ?
|
||||
// 2. CSS animations that correspond to an animation-name property sort lower
|
||||
// than other CSS animations (e.g. those created or kept-alive by script).
|
||||
if (!IsTiedToMarkup()) {
|
||||
return !otherAnimation->IsTiedToMarkup() ?
|
||||
Animation::HasLowerCompositeOrderThan(aOther) :
|
||||
false;
|
||||
}
|
||||
if (!aOther.IsUsingCustomCompositeOrder()) {
|
||||
if (!otherAnimation->IsTiedToMarkup()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Sort by document order
|
||||
MOZ_ASSERT(mOwningElement.IsSet() && otherAnimation->OwningElement().IsSet(),
|
||||
"Animations using custom composite order should have an "
|
||||
"owning element");
|
||||
if (!mOwningElement.Equals(otherAnimation->OwningElement())) {
|
||||
return mOwningElement.LessThan(otherAnimation->OwningElement());
|
||||
if (!mOwningElement.Equals(otherAnimation->mOwningElement)) {
|
||||
return mOwningElement.LessThan(otherAnimation->mOwningElement);
|
||||
}
|
||||
|
||||
// 4. (Same element and pseudo): Sort by position in animation-name
|
||||
return mSequenceNum < otherAnimation->mSequenceNum;
|
||||
return mAnimationIndex < otherAnimation->mAnimationIndex;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -277,6 +273,21 @@ CSSAnimation::QueueEvents()
|
||||
owningPseudoType));
|
||||
}
|
||||
|
||||
bool
|
||||
CSSAnimation::HasEndEventToQueue() const
|
||||
{
|
||||
if (!mEffect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE &&
|
||||
mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER;
|
||||
bool isActive = mEffect->GetComputedTiming().mPhase ==
|
||||
ComputedTiming::AnimationPhase_Active;
|
||||
|
||||
return wasActive && !isActive;
|
||||
}
|
||||
|
||||
CommonAnimationManager*
|
||||
CSSAnimation::GetAnimationManager() const
|
||||
{
|
||||
@@ -288,6 +299,18 @@ CSSAnimation::GetAnimationManager() const
|
||||
return context->AnimationManager();
|
||||
}
|
||||
|
||||
void
|
||||
CSSAnimation::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
|
||||
{
|
||||
if (mNeedsNewAnimationIndexWhenRun &&
|
||||
PlayState() != AnimationPlayState::Idle) {
|
||||
mAnimationIndex = sNextAnimationIndex++;
|
||||
mNeedsNewAnimationIndexWhenRun = false;
|
||||
}
|
||||
|
||||
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
|
||||
}
|
||||
|
||||
////////////////////////// nsAnimationManager ////////////////////////////
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(nsAnimationManager, mEventDispatcher)
|
||||
@@ -363,7 +386,7 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsAutoAnimationMutationBatch mb(aElement);
|
||||
nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
|
||||
|
||||
// build the animations list
|
||||
dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
, mAnimationName(aAnimationName)
|
||||
, mIsStylePaused(false)
|
||||
, mPauseShouldStick(false)
|
||||
, mNeedsNewAnimationIndexWhenRun(false)
|
||||
, mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE)
|
||||
{
|
||||
// We might need to drop this assertion once we add a script-accessible
|
||||
@@ -95,59 +96,53 @@ public:
|
||||
void CancelFromStyle() override
|
||||
{
|
||||
mOwningElement = OwningElementRef();
|
||||
|
||||
// When an animation is disassociated with style it enters an odd state
|
||||
// where its composite order is undefined until it first transitions
|
||||
// out of the idle state.
|
||||
//
|
||||
// Even if the composite order isn't defined we don't want it to be random
|
||||
// in case we need to determine the order to dispatch events associated
|
||||
// with an animation in this state. To solve this we treat the animation as
|
||||
// if it had been added to the end of the global animation list so that
|
||||
// its sort order is defined. We'll update this index again once the
|
||||
// animation leaves the idle state.
|
||||
mAnimationIndex = sNextAnimationIndex++;
|
||||
mNeedsNewAnimationIndexWhenRun = true;
|
||||
|
||||
Animation::CancelFromStyle();
|
||||
MOZ_ASSERT(mSequenceNum == kUnsequenced);
|
||||
}
|
||||
|
||||
void Tick() override;
|
||||
void QueueEvents();
|
||||
bool HasEndEventToQueue() const override;
|
||||
|
||||
bool IsStylePaused() const { return mIsStylePaused; }
|
||||
|
||||
bool HasLowerCompositeOrderThan(const Animation& aOther) const override;
|
||||
bool IsUsingCustomCompositeOrder() const override
|
||||
{
|
||||
return mOwningElement.IsSet();
|
||||
}
|
||||
|
||||
void SetAnimationIndex(uint64_t aIndex)
|
||||
{
|
||||
MOZ_ASSERT(IsUsingCustomCompositeOrder());
|
||||
mSequenceNum = aIndex;
|
||||
MOZ_ASSERT(IsTiedToMarkup());
|
||||
mAnimationIndex = aIndex;
|
||||
}
|
||||
void CopyAnimationIndex(const CSSAnimation& aOther)
|
||||
{
|
||||
MOZ_ASSERT(IsUsingCustomCompositeOrder() &&
|
||||
aOther.IsUsingCustomCompositeOrder());
|
||||
mSequenceNum = aOther.mSequenceNum;
|
||||
MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup());
|
||||
mAnimationIndex = aOther.mAnimationIndex;
|
||||
}
|
||||
|
||||
// Returns the element or pseudo-element whose animation-name property
|
||||
// this CSSAnimation corresponds to (if any). This is used for determining
|
||||
// the relative composite order of animations generated from CSS markup.
|
||||
//
|
||||
// Typically this will be the same as the target element of the keyframe
|
||||
// effect associated with this animation. However, it can differ in the
|
||||
// following circumstances:
|
||||
//
|
||||
// a) If script removes or replaces the effect of this animation,
|
||||
// b) If this animation is cancelled (e.g. by updating the
|
||||
// animation-name property or removing the owning element from the
|
||||
// document),
|
||||
// c) If this object is generated from script using the CSSAnimation
|
||||
// constructor.
|
||||
//
|
||||
// For (b) and (c) the returned owning element will return !IsSet().
|
||||
const OwningElementRef& OwningElement() const { return mOwningElement; }
|
||||
|
||||
// Sets the owning element which is used for determining the composite
|
||||
// order of CSSAnimation objects generated from CSS markup.
|
||||
//
|
||||
// @see OwningElement()
|
||||
// @see mOwningElement
|
||||
void SetOwningElement(const OwningElementRef& aElement)
|
||||
{
|
||||
mOwningElement = aElement;
|
||||
}
|
||||
// True for animations that are generated from CSS markup and continue to
|
||||
// reflect changes to that markup.
|
||||
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
|
||||
|
||||
// Is this animation currently in effect for the purposes of computing
|
||||
// mWinsInCascade. (In general, this can be computed from the timing
|
||||
@@ -162,12 +157,32 @@ protected:
|
||||
MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
|
||||
"before a CSS animation is destroyed");
|
||||
}
|
||||
virtual CommonAnimationManager* GetAnimationManager() const override;
|
||||
|
||||
// Animation overrides
|
||||
CommonAnimationManager* GetAnimationManager() const override;
|
||||
void UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag) override;
|
||||
|
||||
nsString mAnimationName;
|
||||
|
||||
// The (pseudo-)element whose computed animation-name refers to this
|
||||
// animation (if any).
|
||||
//
|
||||
// This is used for determining the relative composite order of animations
|
||||
// generated from CSS markup.
|
||||
//
|
||||
// Typically this will be the same as the target element of the keyframe
|
||||
// effect associated with this animation. However, it can differ in the
|
||||
// following circumstances:
|
||||
//
|
||||
// a) If script removes or replaces the effect of this animation,
|
||||
// b) If this animation is cancelled (e.g. by updating the
|
||||
// animation-name property or removing the owning element from the
|
||||
// document),
|
||||
// c) If this object is generated from script using the CSSAnimation
|
||||
// constructor.
|
||||
//
|
||||
// For (b) and (c) the owning element will return !IsSet().
|
||||
OwningElementRef mOwningElement;
|
||||
|
||||
// When combining animation-play-state with play() / pause() the following
|
||||
@@ -222,6 +237,10 @@ protected:
|
||||
bool mIsStylePaused;
|
||||
bool mPauseShouldStick;
|
||||
|
||||
// When true, indicates that when this animation next leaves the idle state,
|
||||
// its animation index should be updated.
|
||||
bool mNeedsNewAnimationIndexWhenRun;
|
||||
|
||||
enum {
|
||||
PREVIOUS_PHASE_BEFORE = uint64_t(-1),
|
||||
PREVIOUS_PHASE_AFTER = uint64_t(-2)
|
||||
|
||||
@@ -112,6 +112,18 @@ CSSTransition::GetAnimationManager() const
|
||||
return context->TransitionManager();
|
||||
}
|
||||
|
||||
void
|
||||
CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
|
||||
{
|
||||
if (mNeedsNewAnimationIndexWhenRun &&
|
||||
PlayState() != AnimationPlayState::Idle) {
|
||||
mAnimationIndex = sNextAnimationIndex++;
|
||||
mNeedsNewAnimationIndexWhenRun = false;
|
||||
}
|
||||
|
||||
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
|
||||
}
|
||||
|
||||
void
|
||||
CSSTransition::QueueEvents()
|
||||
{
|
||||
@@ -141,6 +153,17 @@ CSSTransition::QueueEvents()
|
||||
owningPseudoType));
|
||||
}
|
||||
|
||||
bool
|
||||
CSSTransition::HasEndEventToQueue() const
|
||||
{
|
||||
if (!mEffect) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !mWasFinishedOnLastTick &&
|
||||
PlayState() == AnimationPlayState::Finished;
|
||||
}
|
||||
|
||||
void
|
||||
CSSTransition::Tick()
|
||||
{
|
||||
@@ -176,26 +199,23 @@ CSSTransition::HasLowerCompositeOrderThan(const Animation& aOther) const
|
||||
|
||||
// 2. CSS transitions that correspond to a transition-property property sort
|
||||
// lower than CSS transitions owned by script.
|
||||
if (!IsUsingCustomCompositeOrder()) {
|
||||
return !aOther.IsUsingCustomCompositeOrder() ?
|
||||
if (!IsTiedToMarkup()) {
|
||||
return !otherTransition->IsTiedToMarkup() ?
|
||||
Animation::HasLowerCompositeOrderThan(aOther) :
|
||||
false;
|
||||
}
|
||||
if (!aOther.IsUsingCustomCompositeOrder()) {
|
||||
if (!otherTransition->IsTiedToMarkup()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. Sort by document order
|
||||
MOZ_ASSERT(mOwningElement.IsSet() && otherTransition->OwningElement().IsSet(),
|
||||
"Transitions using custom composite order should have an owning "
|
||||
"element");
|
||||
if (!mOwningElement.Equals(otherTransition->OwningElement())) {
|
||||
return mOwningElement.LessThan(otherTransition->OwningElement());
|
||||
if (!mOwningElement.Equals(otherTransition->mOwningElement)) {
|
||||
return mOwningElement.LessThan(otherTransition->mOwningElement);
|
||||
}
|
||||
|
||||
// 4. (Same element and pseudo): Sort by transition generation
|
||||
if (mSequenceNum != otherTransition->mSequenceNum) {
|
||||
return mSequenceNum < otherTransition->mSequenceNum;
|
||||
if (mAnimationIndex != otherTransition->mAnimationIndex) {
|
||||
return mAnimationIndex < otherTransition->mAnimationIndex;
|
||||
}
|
||||
|
||||
// 5. (Same transition generation): Sort by transition property
|
||||
@@ -329,7 +349,7 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
|
||||
afterChangeStyle = newStyleContext;
|
||||
}
|
||||
|
||||
nsAutoAnimationMutationBatch mb(aElement);
|
||||
nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
|
||||
|
||||
// Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
|
||||
// I'll consider only the transitions from the number of items in
|
||||
|
||||
@@ -85,6 +85,7 @@ public:
|
||||
explicit CSSTransition(nsIGlobalObject* aGlobal)
|
||||
: dom::Animation(aGlobal)
|
||||
, mWasFinishedOnLastTick(false)
|
||||
, mNeedsNewAnimationIndexWhenRun(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -114,8 +115,17 @@ public:
|
||||
void CancelFromStyle() override
|
||||
{
|
||||
mOwningElement = OwningElementRef();
|
||||
|
||||
// The animation index to use for compositing will be established when
|
||||
// this transition next transitions out of the idle state but we still
|
||||
// update it now so that the sort order of this transition remains
|
||||
// defined until that moment.
|
||||
//
|
||||
// See longer explanation in CSSAnimation::CancelFromStyle.
|
||||
mAnimationIndex = sNextAnimationIndex++;
|
||||
mNeedsNewAnimationIndexWhenRun = true;
|
||||
|
||||
Animation::CancelFromStyle();
|
||||
MOZ_ASSERT(mSequenceNum == kUnsequenced);
|
||||
}
|
||||
|
||||
void Tick() override;
|
||||
@@ -123,20 +133,44 @@ public:
|
||||
nsCSSProperty TransitionProperty() const;
|
||||
|
||||
bool HasLowerCompositeOrderThan(const Animation& aOther) const override;
|
||||
bool IsUsingCustomCompositeOrder() const override
|
||||
{
|
||||
return mOwningElement.IsSet();
|
||||
}
|
||||
|
||||
void SetCreationSequence(uint64_t aIndex)
|
||||
{
|
||||
MOZ_ASSERT(IsUsingCustomCompositeOrder());
|
||||
mSequenceNum = aIndex;
|
||||
MOZ_ASSERT(IsTiedToMarkup());
|
||||
mAnimationIndex = aIndex;
|
||||
}
|
||||
|
||||
// Returns the element or pseudo-element whose transition-property property
|
||||
// this CSSTransition corresponds to (if any). This is used for determining
|
||||
// the relative composite order of transitions generated from CSS markup.
|
||||
// Sets the owning element which is used for determining the composite
|
||||
// oder of CSSTransition objects generated from CSS markup.
|
||||
//
|
||||
// @see mOwningElement
|
||||
void SetOwningElement(const OwningElementRef& aElement)
|
||||
{
|
||||
mOwningElement = aElement;
|
||||
}
|
||||
// True for transitions that are generated from CSS markup and continue to
|
||||
// reflect changes to that markup.
|
||||
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
|
||||
|
||||
protected:
|
||||
virtual ~CSSTransition()
|
||||
{
|
||||
MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
|
||||
"before a CSS transition is destroyed");
|
||||
}
|
||||
|
||||
// Animation overrides
|
||||
CommonAnimationManager* GetAnimationManager() const override;
|
||||
void UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag) override;
|
||||
|
||||
void QueueEvents();
|
||||
bool HasEndEventToQueue() const override;
|
||||
|
||||
// The (pseudo-)element whose computed transition-property refers to this
|
||||
// transition (if any).
|
||||
//
|
||||
// This is used for determining the relative composite order of transitions
|
||||
// generated from CSS markup.
|
||||
//
|
||||
// Typically this will be the same as the target element of the keyframe
|
||||
// effect associated with this transition. However, it can differ in the
|
||||
@@ -148,34 +182,14 @@ public:
|
||||
// c) If this object is generated from script using the CSSTransition
|
||||
// constructor.
|
||||
//
|
||||
// For (b) and (c) the returned owning element will return !IsSet().
|
||||
const OwningElementRef& OwningElement() const { return mOwningElement; }
|
||||
|
||||
// Sets the owning element which is used for determining the composite
|
||||
// oder of CSSTransition objects generated from CSS markup.
|
||||
//
|
||||
// @see OwningElement()
|
||||
void SetOwningElement(const OwningElementRef& aElement)
|
||||
{
|
||||
mOwningElement = aElement;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~CSSTransition()
|
||||
{
|
||||
MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
|
||||
"before a CSS transition is destroyed");
|
||||
}
|
||||
|
||||
virtual CommonAnimationManager* GetAnimationManager() const override;
|
||||
|
||||
void QueueEvents();
|
||||
|
||||
// The (pseudo-)element whose computed transition-property refers to this
|
||||
// transition (if any).
|
||||
// For (b) and (c) the owning element will return !IsSet().
|
||||
OwningElementRef mOwningElement;
|
||||
|
||||
bool mWasFinishedOnLastTick;
|
||||
|
||||
// When true, indicates that when this transition next leaves the idle state,
|
||||
// its animation index should be updated.
|
||||
bool mNeedsNewAnimationIndexWhenRun;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script type="application/javascript"
|
||||
src="/tests/SimpleTest/paint_listener.js"></script>
|
||||
<script type="application/javascript" src="animation_utils.js"></script>
|
||||
<style type="text/css">
|
||||
@keyframes anim {
|
||||
0% { transform: translate(0px) }
|
||||
100% { transform: translate(100px) }
|
||||
}
|
||||
.target {
|
||||
/* The animation target needs geometry in order to qualify for OMTA */
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var ok = opener.ok.bind(opener);
|
||||
var is = opener.is.bind(opener);
|
||||
var todo = opener.todo.bind(opener);
|
||||
function finish() {
|
||||
var o = opener;
|
||||
self.close();
|
||||
o.SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="display"></div>
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
runOMTATest(function() {
|
||||
runAllAsyncAnimTests().then(function() {
|
||||
finish();
|
||||
});
|
||||
}, finish, opener.SpecialPowers);
|
||||
|
||||
addAsyncAnimTest(function *() {
|
||||
var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
|
||||
var animation = div.getAnimations()[0];
|
||||
animation.playbackRate = 10;
|
||||
|
||||
advance_clock(300);
|
||||
|
||||
yield waitForPaints();
|
||||
omta_is(div, "transform", { tx: 30 }, RunningOn.Compositor,
|
||||
"at 300ms");
|
||||
done_div();
|
||||
});
|
||||
|
||||
addAsyncAnimTest(function *() {
|
||||
var [ div, cs ] = new_div("animation: anim 10s 1 linear forwards");
|
||||
var animation = div.getAnimations()[0];
|
||||
advance_clock(300);
|
||||
yield waitForPaints();
|
||||
|
||||
animation.playbackRate = 0;
|
||||
|
||||
yield waitForPaintsFlushed();
|
||||
|
||||
omta_is(div, "transform", { tx: 3 }, RunningOn.MainThread,
|
||||
"animation with zero playback rate should stay in the " +
|
||||
"same position and be running on the main thread");
|
||||
|
||||
done_div();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -42,6 +42,8 @@ skip-if = toolkit == 'android'
|
||||
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1041017
|
||||
[test_animations_pausing.html]
|
||||
support-files = file_animations_pausing.html
|
||||
[test_animations_playbackrate.html]
|
||||
support-files = file_animations_playbackrate.html
|
||||
[test_any_dynamic.html]
|
||||
[test_at_rule_parse_serialize.html]
|
||||
[test_bug73586.html]
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1175751
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Animation.playbackRate on compositor animations (Bug 1175751)</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175751">Mozilla Bug 1175751</a>
|
||||
<div id="display"></div>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
SpecialPowers.pushPrefEnv(
|
||||
{ "set": [[ "dom.animations-api.core.enabled", true]] },
|
||||
function() {
|
||||
window.open("file_animations_playbackrate.html");
|
||||
});
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user