mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 01:05:45 +00:00
Bug 1634943 - Split CSSAnimation and CSSAnimationKeyframe classes into a new file in dom/animation/. r=boris
For consistency with CSSTransition class. Differential Revision: https://phabricator.services.mozilla.com/D73572
This commit is contained in:
parent
eee4796cb1
commit
05122f238f
367
dom/animation/CSSAnimation.cpp
Normal file
367
dom/animation/CSSAnimation.cpp
Normal file
@ -0,0 +1,367 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#include "CSSAnimation.h"
|
||||
|
||||
#include "mozilla/AnimationEventDispatcher.h"
|
||||
#include "mozilla/dom/CSSAnimationBinding.h"
|
||||
#include "mozilla/dom/KeyframeEffectBinding.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsPresContext.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
using AnimationPhase = ComputedTiming::AnimationPhase;
|
||||
|
||||
JSObject* CSSAnimation::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return dom::CSSAnimation_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void CSSAnimation::SetEffect(AnimationEffect* aEffect) {
|
||||
Animation::SetEffect(aEffect);
|
||||
|
||||
AddOverriddenProperties(CSSAnimationProperties::Effect);
|
||||
}
|
||||
|
||||
void CSSAnimation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
|
||||
// Note that we always compare with the paused state since for the purposes
|
||||
// of determining if play control is being overridden or not, we want to
|
||||
// treat the finished state as running.
|
||||
bool wasPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
Animation::SetStartTimeAsDouble(aStartTime);
|
||||
|
||||
bool isPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
if (wasPaused != isPaused) {
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::dom::Promise* CSSAnimation::GetReady(ErrorResult& aRv) {
|
||||
FlushUnanimatedStyle();
|
||||
return Animation::GetReady(aRv);
|
||||
}
|
||||
|
||||
void CSSAnimation::Reverse(ErrorResult& aRv) {
|
||||
// As with CSSAnimation::SetStartTimeAsDouble, we're really only interested in
|
||||
// the paused state.
|
||||
bool wasPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
Animation::Reverse(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
if (wasPaused != isPaused) {
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationPlayState CSSAnimation::PlayStateFromJS() const {
|
||||
// Flush style to ensure that any properties controlling animation state
|
||||
// (e.g. animation-play-state) are fully updated.
|
||||
FlushUnanimatedStyle();
|
||||
return Animation::PlayStateFromJS();
|
||||
}
|
||||
|
||||
bool CSSAnimation::PendingFromJS() const {
|
||||
// Flush style since, for example, if the animation-play-state was just
|
||||
// changed its possible we should now be pending.
|
||||
FlushUnanimatedStyle();
|
||||
return Animation::PendingFromJS();
|
||||
}
|
||||
|
||||
void CSSAnimation::PlayFromJS(ErrorResult& aRv) {
|
||||
// Note that flushing style below might trigger calls to
|
||||
// PlayFromStyle()/PauseFromStyle() on this object.
|
||||
FlushUnanimatedStyle();
|
||||
Animation::PlayFromJS(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
|
||||
void CSSAnimation::PauseFromJS(ErrorResult& aRv) {
|
||||
Animation::PauseFromJS(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
|
||||
void CSSAnimation::PlayFromStyle() {
|
||||
ErrorResult rv;
|
||||
Animation::Play(rv, Animation::LimitBehavior::Continue);
|
||||
// play() should not throw when LimitBehavior is Continue
|
||||
MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
|
||||
}
|
||||
|
||||
void CSSAnimation::PauseFromStyle() {
|
||||
ErrorResult rv;
|
||||
Animation::Pause(rv);
|
||||
// pause() should only throw when *all* of the following conditions are true:
|
||||
// - we are in the idle state, and
|
||||
// - we have a negative playback rate, and
|
||||
// - we have an infinitely repeating animation
|
||||
// The first two conditions will never happen under regular style processing
|
||||
// but could happen if an author made modifications to the Animation object
|
||||
// and then updated animation-play-state. It's an unusual case and there's
|
||||
// no obvious way to pass on the exception information so we just silently
|
||||
// fail for now.
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Unexpected exception pausing animation - silently failing");
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimation::Tick() {
|
||||
Animation::Tick();
|
||||
QueueEvents();
|
||||
}
|
||||
|
||||
bool CSSAnimation::HasLowerCompositeOrderThan(
|
||||
const CSSAnimation& aOther) const {
|
||||
MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
|
||||
"Should only be called for CSS animations that are sorted "
|
||||
"as CSS animations (i.e. tied to CSS markup)");
|
||||
|
||||
// 0. Object-equality case
|
||||
if (&aOther == this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Sort by document order
|
||||
if (!mOwningElement.Equals(aOther.mOwningElement)) {
|
||||
return mOwningElement.LessThan(
|
||||
const_cast<CSSAnimation*>(this)->CachedChildIndexRef(),
|
||||
aOther.mOwningElement,
|
||||
const_cast<CSSAnimation*>(&aOther)->CachedChildIndexRef());
|
||||
}
|
||||
|
||||
// 2. (Same element and pseudo): Sort by position in animation-name
|
||||
return mAnimationIndex < aOther.mAnimationIndex;
|
||||
}
|
||||
|
||||
void CSSAnimation::QueueEvents(const StickyTimeDuration& aActiveTime) {
|
||||
// If the animation is pending, we ignore animation events until we finish
|
||||
// pending.
|
||||
if (mPendingState != PendingState::NotPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CSS animations dispatch events at their owning element. This allows
|
||||
// script to repurpose a CSS animation to target a different element,
|
||||
// to use a group effect (which has no obvious "target element"), or
|
||||
// to remove the animation effect altogether whilst still getting
|
||||
// animation events.
|
||||
//
|
||||
// It does mean, however, that for a CSS animation that has no owning
|
||||
// element (e.g. it was created using the CSSAnimation constructor or
|
||||
// disassociated from CSS) no events are fired. If it becomes desirable
|
||||
// for these animations to still fire events we should spec the concept
|
||||
// of the "original owning element" or "event target" and allow script
|
||||
// to set it when creating a CSSAnimation object.
|
||||
if (!mOwningElement.IsSet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsPresContext* presContext = mOwningElement.GetPresContext();
|
||||
if (!presContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t currentIteration = 0;
|
||||
ComputedTiming::AnimationPhase currentPhase;
|
||||
StickyTimeDuration intervalStartTime;
|
||||
StickyTimeDuration intervalEndTime;
|
||||
StickyTimeDuration iterationStartTime;
|
||||
|
||||
if (!mEffect) {
|
||||
currentPhase =
|
||||
GetAnimationPhaseWithoutEffect<ComputedTiming::AnimationPhase>(*this);
|
||||
if (currentPhase == mPreviousPhase) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ComputedTiming computedTiming = mEffect->GetComputedTiming();
|
||||
currentPhase = computedTiming.mPhase;
|
||||
currentIteration = computedTiming.mCurrentIteration;
|
||||
if (currentPhase == mPreviousPhase &&
|
||||
currentIteration == mPreviousIteration) {
|
||||
return;
|
||||
}
|
||||
intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
|
||||
intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
|
||||
|
||||
uint64_t iterationBoundary = mPreviousIteration > currentIteration
|
||||
? currentIteration + 1
|
||||
: currentIteration;
|
||||
iterationStartTime = computedTiming.mDuration.MultDouble(
|
||||
(iterationBoundary - computedTiming.mIterationStart));
|
||||
}
|
||||
|
||||
TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
|
||||
TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
|
||||
TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
|
||||
|
||||
AutoTArray<AnimationEventInfo, 2> events;
|
||||
|
||||
auto appendAnimationEvent = [&](EventMessage aMessage,
|
||||
const StickyTimeDuration& aElapsedTime,
|
||||
const TimeStamp& aScheduledEventTimeStamp) {
|
||||
double elapsedTime = aElapsedTime.ToSeconds();
|
||||
if (aMessage == eAnimationCancel) {
|
||||
// 0 is an inappropriate value for this callsite. What we need to do is
|
||||
// use a single random value for all increasing times reportable.
|
||||
// That is to say, whenever elapsedTime goes negative (because an
|
||||
// animation restarts, something rewinds the animation, or otherwise)
|
||||
// a new random value for the mix-in must be generated.
|
||||
elapsedTime =
|
||||
nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(elapsedTime, 0);
|
||||
}
|
||||
events.AppendElement(
|
||||
AnimationEventInfo(mAnimationName, mOwningElement.Target(), aMessage,
|
||||
elapsedTime, aScheduledEventTimeStamp, this));
|
||||
};
|
||||
|
||||
// Handle cancel event first
|
||||
if ((mPreviousPhase != AnimationPhase::Idle &&
|
||||
mPreviousPhase != AnimationPhase::After) &&
|
||||
currentPhase == AnimationPhase::Idle) {
|
||||
appendAnimationEvent(eAnimationCancel, aActiveTime,
|
||||
GetTimelineCurrentTimeAsTimeStamp());
|
||||
}
|
||||
|
||||
switch (mPreviousPhase) {
|
||||
case AnimationPhase::Idle:
|
||||
case AnimationPhase::Before:
|
||||
if (currentPhase == AnimationPhase::Active) {
|
||||
appendAnimationEvent(eAnimationStart, intervalStartTime,
|
||||
startTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::After) {
|
||||
appendAnimationEvent(eAnimationStart, intervalStartTime,
|
||||
startTimeStamp);
|
||||
appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
|
||||
}
|
||||
break;
|
||||
case AnimationPhase::Active:
|
||||
if (currentPhase == AnimationPhase::Before) {
|
||||
appendAnimationEvent(eAnimationEnd, intervalStartTime, startTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::Active) {
|
||||
// The currentIteration must have changed or element we would have
|
||||
// returned early above.
|
||||
MOZ_ASSERT(currentIteration != mPreviousIteration);
|
||||
appendAnimationEvent(eAnimationIteration, iterationStartTime,
|
||||
iterationTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::After) {
|
||||
appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
|
||||
}
|
||||
break;
|
||||
case AnimationPhase::After:
|
||||
if (currentPhase == AnimationPhase::Before) {
|
||||
appendAnimationEvent(eAnimationStart, intervalEndTime, startTimeStamp);
|
||||
appendAnimationEvent(eAnimationEnd, intervalStartTime, endTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::Active) {
|
||||
appendAnimationEvent(eAnimationStart, intervalEndTime, endTimeStamp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
mPreviousPhase = currentPhase;
|
||||
mPreviousIteration = currentIteration;
|
||||
|
||||
if (!events.IsEmpty()) {
|
||||
presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimation::UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag) {
|
||||
if (mNeedsNewAnimationIndexWhenRun &&
|
||||
PlayState() != AnimationPlayState::Idle) {
|
||||
mAnimationIndex = sNextAnimationIndex++;
|
||||
mNeedsNewAnimationIndexWhenRun = false;
|
||||
}
|
||||
|
||||
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
|
||||
}
|
||||
|
||||
/////////////////////// CSSAnimationKeyframeEffect ////////////////////////
|
||||
|
||||
void CSSAnimationKeyframeEffect::GetTiming(EffectTiming& aRetVal) const {
|
||||
MaybeFlushUnanimatedStyle();
|
||||
KeyframeEffect::GetTiming(aRetVal);
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::GetComputedTimingAsDict(
|
||||
ComputedEffectTiming& aRetVal) const {
|
||||
MaybeFlushUnanimatedStyle();
|
||||
KeyframeEffect::GetComputedTimingAsDict(aRetVal);
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::UpdateTiming(
|
||||
const OptionalEffectTiming& aTiming, ErrorResult& aRv) {
|
||||
KeyframeEffect::UpdateTiming(aTiming, aRv);
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
|
||||
CSSAnimationProperties updatedProperties = CSSAnimationProperties::None;
|
||||
if (aTiming.mDuration.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::Duration;
|
||||
}
|
||||
if (aTiming.mIterations.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::IterationCount;
|
||||
}
|
||||
if (aTiming.mDirection.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::Direction;
|
||||
}
|
||||
if (aTiming.mDelay.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::Delay;
|
||||
}
|
||||
if (aTiming.mFill.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::FillMode;
|
||||
}
|
||||
|
||||
cssAnimation->AddOverriddenProperties(updatedProperties);
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::SetKeyframes(JSContext* aContext,
|
||||
JS::Handle<JSObject*> aKeyframes,
|
||||
ErrorResult& aRv) {
|
||||
KeyframeEffect::SetKeyframes(aContext, aKeyframes, aRv);
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
|
||||
cssAnimation->AddOverriddenProperties(CSSAnimationProperties::Keyframes);
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::MaybeFlushUnanimatedStyle() const {
|
||||
if (!GetOwningCSSAnimation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dom::Document* doc = GetRenderedDocument()) {
|
||||
doc->FlushPendingNotifications(
|
||||
ChangesToFlush(FlushType::Style, false /* flush animations */));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
249
dom/animation/CSSAnimation.h
Normal file
249
dom/animation/CSSAnimation.h
Normal file
@ -0,0 +1,249 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_dom_CSSAnimation_h
|
||||
#define mozilla_dom_CSSAnimation_h
|
||||
|
||||
#include "mozilla/dom/Animation.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
#include "mozilla/dom/MutationObservers.h"
|
||||
#include "mozilla/StyleAnimationValue.h"
|
||||
#include "AnimationCommon.h"
|
||||
|
||||
namespace mozilla {
|
||||
// Properties of CSS Animations that can be overridden by the Web Animations API
|
||||
// in a manner that means we should ignore subsequent changes to markup for that
|
||||
// property.
|
||||
enum class CSSAnimationProperties {
|
||||
None = 0,
|
||||
Keyframes = 1 << 0,
|
||||
Duration = 1 << 1,
|
||||
IterationCount = 1 << 2,
|
||||
Direction = 1 << 3,
|
||||
Delay = 1 << 4,
|
||||
FillMode = 1 << 5,
|
||||
Effect = Keyframes | Duration | IterationCount | Direction | Delay | FillMode,
|
||||
PlayState = 1 << 6,
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSAnimationProperties)
|
||||
|
||||
namespace dom {
|
||||
|
||||
class CSSAnimation final : public Animation {
|
||||
public:
|
||||
explicit CSSAnimation(nsIGlobalObject* aGlobal, nsAtom* aAnimationName)
|
||||
: dom::Animation(aGlobal),
|
||||
mAnimationName(aAnimationName),
|
||||
mNeedsNewAnimationIndexWhenRun(false),
|
||||
mPreviousPhase(ComputedTiming::AnimationPhase::Idle),
|
||||
mPreviousIteration(0) {
|
||||
// We might need to drop this assertion once we add a script-accessible
|
||||
// constructor but for animations generated from CSS markup the
|
||||
// animation-name should never be empty.
|
||||
MOZ_ASSERT(mAnimationName != nsGkAtoms::_empty,
|
||||
"animation-name should not be 'none'");
|
||||
}
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
CSSAnimation* AsCSSAnimation() override { return this; }
|
||||
const CSSAnimation* AsCSSAnimation() const override { return this; }
|
||||
|
||||
// CSSAnimation interface
|
||||
void GetAnimationName(nsString& aRetVal) const {
|
||||
mAnimationName->ToString(aRetVal);
|
||||
}
|
||||
|
||||
nsAtom* AnimationName() const { return mAnimationName; }
|
||||
|
||||
// Animation interface overrides
|
||||
void SetEffect(AnimationEffect* aEffect) override;
|
||||
void SetStartTimeAsDouble(const Nullable<double>& aStartTime) override;
|
||||
Promise* GetReady(ErrorResult& aRv) override;
|
||||
void Reverse(ErrorResult& aRv) override;
|
||||
|
||||
// NOTE: tabbrowser.xml currently relies on the fact that reading the
|
||||
// currentTime of a CSSAnimation does *not* flush style (whereas reading the
|
||||
// playState does). If CSS Animations 2 specifies that reading currentTime
|
||||
// also flushes style we will need to find another way to detect canceled
|
||||
// animations in tabbrowser.xml. On the other hand, if CSS Animations 2
|
||||
// specifies that reading playState does *not* flush style (and we drop the
|
||||
// following override), then we should update tabbrowser.xml to check
|
||||
// the playState instead.
|
||||
AnimationPlayState PlayStateFromJS() const override;
|
||||
bool PendingFromJS() const override;
|
||||
void PlayFromJS(ErrorResult& aRv) override;
|
||||
void PauseFromJS(ErrorResult& aRv) override;
|
||||
|
||||
void PlayFromStyle();
|
||||
void PauseFromStyle();
|
||||
void CancelFromStyle(PostRestyleMode aPostRestyle) {
|
||||
// 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::Cancel(aPostRestyle);
|
||||
|
||||
// We need to do this *after* calling Cancel() since
|
||||
// Cancel() might synchronously trigger a cancel event for which
|
||||
// we need an owning element to target the event at.
|
||||
mOwningElement = OwningElementRef();
|
||||
}
|
||||
|
||||
void Tick() override;
|
||||
void QueueEvents(
|
||||
const StickyTimeDuration& aActiveTime = StickyTimeDuration());
|
||||
|
||||
bool HasLowerCompositeOrderThan(const CSSAnimation& aOther) const;
|
||||
|
||||
void SetAnimationIndex(uint64_t aIndex) {
|
||||
MOZ_ASSERT(IsTiedToMarkup());
|
||||
if (IsRelevant() && mAnimationIndex != aIndex) {
|
||||
MutationObservers::NotifyAnimationChanged(this);
|
||||
PostUpdate();
|
||||
}
|
||||
mAnimationIndex = aIndex;
|
||||
}
|
||||
|
||||
// Sets the owning element which is used for determining the composite
|
||||
// order of CSSAnimation objects generated from CSS markup.
|
||||
//
|
||||
// @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(); }
|
||||
|
||||
void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override {
|
||||
QueueEvents(aActiveTime);
|
||||
}
|
||||
|
||||
CSSAnimationProperties GetOverriddenProperties() const {
|
||||
return mOverriddenProperties;
|
||||
}
|
||||
void AddOverriddenProperties(CSSAnimationProperties aProperties) {
|
||||
mOverriddenProperties |= aProperties;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~CSSAnimation() {
|
||||
MOZ_ASSERT(!mOwningElement.IsSet(),
|
||||
"Owning element should be cleared "
|
||||
"before a CSS animation is destroyed");
|
||||
}
|
||||
|
||||
// Animation overrides
|
||||
void UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag) override;
|
||||
|
||||
// Returns the duration from the start of the animation's source effect's
|
||||
// active interval to the point where the animation actually begins playback.
|
||||
// This is zero unless the animation's source effect has a negative delay in
|
||||
// which case it is the absolute value of that delay.
|
||||
// This is used for setting the elapsedTime member of CSS AnimationEvents.
|
||||
TimeDuration InitialAdvance() const {
|
||||
return mEffect ? std::max(TimeDuration(),
|
||||
mEffect->SpecifiedTiming().Delay() * -1)
|
||||
: TimeDuration();
|
||||
}
|
||||
|
||||
RefPtr<nsAtom> 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 true, indicates that when this animation next leaves the idle state,
|
||||
// its animation index should be updated.
|
||||
bool mNeedsNewAnimationIndexWhenRun;
|
||||
|
||||
// Phase and current iteration from the previous time we queued events.
|
||||
// This is used to determine what new events to dispatch.
|
||||
ComputedTiming::AnimationPhase mPreviousPhase;
|
||||
uint64_t mPreviousIteration;
|
||||
|
||||
// Properties that would normally be defined by the cascade but which have
|
||||
// since been explicitly set via the Web Animations API.
|
||||
CSSAnimationProperties mOverriddenProperties = CSSAnimationProperties::None;
|
||||
};
|
||||
|
||||
// A subclass of KeyframeEffect that reports when specific properties have been
|
||||
// overridden via the Web Animations API.
|
||||
class CSSAnimationKeyframeEffect : public KeyframeEffect {
|
||||
public:
|
||||
CSSAnimationKeyframeEffect(Document* aDocument,
|
||||
OwningAnimationTarget&& aTarget,
|
||||
TimingParams&& aTiming,
|
||||
const KeyframeEffectParams& aOptions)
|
||||
: KeyframeEffect(aDocument, std::move(aTarget), std::move(aTiming),
|
||||
aOptions) {}
|
||||
|
||||
void GetTiming(EffectTiming& aRetVal) const override;
|
||||
void GetComputedTimingAsDict(ComputedEffectTiming& aRetVal) const override;
|
||||
void UpdateTiming(const OptionalEffectTiming& aTiming,
|
||||
ErrorResult& aRv) override;
|
||||
void SetKeyframes(JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
private:
|
||||
CSSAnimation* GetOwningCSSAnimation() {
|
||||
return mAnimation ? mAnimation->AsCSSAnimation() : nullptr;
|
||||
}
|
||||
const CSSAnimation* GetOwningCSSAnimation() const {
|
||||
return mAnimation ? mAnimation->AsCSSAnimation() : nullptr;
|
||||
}
|
||||
|
||||
// Flushes styles if our owning animation is a CSSAnimation
|
||||
void MaybeFlushUnanimatedStyle() const;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
||||
template <>
|
||||
struct AnimationTypeTraits<dom::CSSAnimation> {
|
||||
static nsAtom* ElementPropertyAtom() { return nsGkAtoms::animationsProperty; }
|
||||
static nsAtom* BeforePropertyAtom() {
|
||||
return nsGkAtoms::animationsOfBeforeProperty;
|
||||
}
|
||||
static nsAtom* AfterPropertyAtom() {
|
||||
return nsGkAtoms::animationsOfAfterProperty;
|
||||
}
|
||||
static nsAtom* MarkerPropertyAtom() {
|
||||
return nsGkAtoms::animationsOfMarkerProperty;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_CSSAnimation_h
|
@ -14,6 +14,7 @@ EXPORTS.mozilla.dom += [
|
||||
'Animation.h',
|
||||
'AnimationEffect.h',
|
||||
'AnimationTimeline.h',
|
||||
'CSSAnimation.h',
|
||||
'CSSPseudoElement.h',
|
||||
'CSSTransition.h',
|
||||
'DocumentTimeline.h',
|
||||
@ -48,6 +49,7 @@ UNIFIED_SOURCES += [
|
||||
'AnimationTimeline.cpp',
|
||||
'AnimationUtils.cpp',
|
||||
'ComputedTimingFunction.cpp',
|
||||
'CSSAnimation.cpp',
|
||||
'CSSPseudoElement.cpp',
|
||||
'CSSTransition.cpp',
|
||||
'DocumentTimeline.cpp',
|
||||
|
@ -7,8 +7,8 @@
|
||||
#include "mozilla/AnimationCollection.h"
|
||||
|
||||
#include "mozilla/RestyleManager.h"
|
||||
#include "nsAnimationManager.h" // For dom::CSSAnimation
|
||||
#include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
|
||||
#include "mozilla/dom/CSSAnimation.h" // For dom::CSSAnimation
|
||||
#include "mozilla/dom/CSSTransition.h" // For dom::CSSTransition
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -7,15 +7,11 @@
|
||||
#include "nsAnimationManager.h"
|
||||
#include "nsINode.h"
|
||||
#include "nsTransitionManager.h"
|
||||
#include "mozilla/dom/CSSAnimationBinding.h"
|
||||
|
||||
#include "mozilla/AnimationEventDispatcher.h"
|
||||
#include "mozilla/AnimationTarget.h"
|
||||
#include "mozilla/EffectCompositor.h"
|
||||
#include "mozilla/EffectSet.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/ServoStyleSet.h"
|
||||
#include "mozilla/StyleAnimationValue.h"
|
||||
#include "mozilla/dom/AnimationEffect.h"
|
||||
#include "mozilla/dom/DocumentTimeline.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
@ -53,356 +49,6 @@ using mozilla::dom::KeyframeEffect;
|
||||
using mozilla::dom::MutationObservers;
|
||||
using mozilla::dom::OptionalEffectTiming;
|
||||
|
||||
typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
|
||||
|
||||
////////////////////////// CSSAnimation ////////////////////////////
|
||||
|
||||
JSObject* CSSAnimation::WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) {
|
||||
return dom::CSSAnimation_Binding::Wrap(aCx, this, aGivenProto);
|
||||
}
|
||||
|
||||
void CSSAnimation::SetEffect(AnimationEffect* aEffect) {
|
||||
Animation::SetEffect(aEffect);
|
||||
|
||||
AddOverriddenProperties(CSSAnimationProperties::Effect);
|
||||
}
|
||||
|
||||
void CSSAnimation::SetStartTimeAsDouble(const Nullable<double>& aStartTime) {
|
||||
// Note that we always compare with the paused state since for the purposes
|
||||
// of determining if play control is being overridden or not, we want to
|
||||
// treat the finished state as running.
|
||||
bool wasPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
Animation::SetStartTimeAsDouble(aStartTime);
|
||||
|
||||
bool isPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
if (wasPaused != isPaused) {
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::dom::Promise* CSSAnimation::GetReady(ErrorResult& aRv) {
|
||||
FlushUnanimatedStyle();
|
||||
return Animation::GetReady(aRv);
|
||||
}
|
||||
|
||||
void CSSAnimation::Reverse(ErrorResult& aRv) {
|
||||
// As with CSSAnimation::SetStartTimeAsDouble, we're really only interested in
|
||||
// the paused state.
|
||||
bool wasPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
Animation::Reverse(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isPaused = PlayState() == AnimationPlayState::Paused;
|
||||
|
||||
if (wasPaused != isPaused) {
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationPlayState CSSAnimation::PlayStateFromJS() const {
|
||||
// Flush style to ensure that any properties controlling animation state
|
||||
// (e.g. animation-play-state) are fully updated.
|
||||
FlushUnanimatedStyle();
|
||||
return Animation::PlayStateFromJS();
|
||||
}
|
||||
|
||||
bool CSSAnimation::PendingFromJS() const {
|
||||
// Flush style since, for example, if the animation-play-state was just
|
||||
// changed its possible we should now be pending.
|
||||
FlushUnanimatedStyle();
|
||||
return Animation::PendingFromJS();
|
||||
}
|
||||
|
||||
void CSSAnimation::PlayFromJS(ErrorResult& aRv) {
|
||||
// Note that flushing style below might trigger calls to
|
||||
// PlayFromStyle()/PauseFromStyle() on this object.
|
||||
FlushUnanimatedStyle();
|
||||
Animation::PlayFromJS(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
|
||||
void CSSAnimation::PauseFromJS(ErrorResult& aRv) {
|
||||
Animation::PauseFromJS(aRv);
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AddOverriddenProperties(CSSAnimationProperties::PlayState);
|
||||
}
|
||||
|
||||
void CSSAnimation::PlayFromStyle() {
|
||||
ErrorResult rv;
|
||||
Animation::Play(rv, Animation::LimitBehavior::Continue);
|
||||
// play() should not throw when LimitBehavior is Continue
|
||||
MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing animation");
|
||||
}
|
||||
|
||||
void CSSAnimation::PauseFromStyle() {
|
||||
ErrorResult rv;
|
||||
Animation::Pause(rv);
|
||||
// pause() should only throw when *all* of the following conditions are true:
|
||||
// - we are in the idle state, and
|
||||
// - we have a negative playback rate, and
|
||||
// - we have an infinitely repeating animation
|
||||
// The first two conditions will never happen under regular style processing
|
||||
// but could happen if an author made modifications to the Animation object
|
||||
// and then updated animation-play-state. It's an unusual case and there's
|
||||
// no obvious way to pass on the exception information so we just silently
|
||||
// fail for now.
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Unexpected exception pausing animation - silently failing");
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimation::Tick() {
|
||||
Animation::Tick();
|
||||
QueueEvents();
|
||||
}
|
||||
|
||||
bool CSSAnimation::HasLowerCompositeOrderThan(
|
||||
const CSSAnimation& aOther) const {
|
||||
MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
|
||||
"Should only be called for CSS animations that are sorted "
|
||||
"as CSS animations (i.e. tied to CSS markup)");
|
||||
|
||||
// 0. Object-equality case
|
||||
if (&aOther == this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. Sort by document order
|
||||
if (!mOwningElement.Equals(aOther.mOwningElement)) {
|
||||
return mOwningElement.LessThan(
|
||||
const_cast<CSSAnimation*>(this)->CachedChildIndexRef(),
|
||||
aOther.mOwningElement,
|
||||
const_cast<CSSAnimation*>(&aOther)->CachedChildIndexRef());
|
||||
}
|
||||
|
||||
// 2. (Same element and pseudo): Sort by position in animation-name
|
||||
return mAnimationIndex < aOther.mAnimationIndex;
|
||||
}
|
||||
|
||||
void CSSAnimation::QueueEvents(const StickyTimeDuration& aActiveTime) {
|
||||
// If the animation is pending, we ignore animation events until we finish
|
||||
// pending.
|
||||
if (mPendingState != PendingState::NotPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
// CSS animations dispatch events at their owning element. This allows
|
||||
// script to repurpose a CSS animation to target a different element,
|
||||
// to use a group effect (which has no obvious "target element"), or
|
||||
// to remove the animation effect altogether whilst still getting
|
||||
// animation events.
|
||||
//
|
||||
// It does mean, however, that for a CSS animation that has no owning
|
||||
// element (e.g. it was created using the CSSAnimation constructor or
|
||||
// disassociated from CSS) no events are fired. If it becomes desirable
|
||||
// for these animations to still fire events we should spec the concept
|
||||
// of the "original owning element" or "event target" and allow script
|
||||
// to set it when creating a CSSAnimation object.
|
||||
if (!mOwningElement.IsSet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsPresContext* presContext = mOwningElement.GetPresContext();
|
||||
if (!presContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t currentIteration = 0;
|
||||
ComputedTiming::AnimationPhase currentPhase;
|
||||
StickyTimeDuration intervalStartTime;
|
||||
StickyTimeDuration intervalEndTime;
|
||||
StickyTimeDuration iterationStartTime;
|
||||
|
||||
if (!mEffect) {
|
||||
currentPhase =
|
||||
GetAnimationPhaseWithoutEffect<ComputedTiming::AnimationPhase>(*this);
|
||||
if (currentPhase == mPreviousPhase) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ComputedTiming computedTiming = mEffect->GetComputedTiming();
|
||||
currentPhase = computedTiming.mPhase;
|
||||
currentIteration = computedTiming.mCurrentIteration;
|
||||
if (currentPhase == mPreviousPhase &&
|
||||
currentIteration == mPreviousIteration) {
|
||||
return;
|
||||
}
|
||||
intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
|
||||
intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
|
||||
|
||||
uint64_t iterationBoundary = mPreviousIteration > currentIteration
|
||||
? currentIteration + 1
|
||||
: currentIteration;
|
||||
iterationStartTime = computedTiming.mDuration.MultDouble(
|
||||
(iterationBoundary - computedTiming.mIterationStart));
|
||||
}
|
||||
|
||||
TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
|
||||
TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
|
||||
TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
|
||||
|
||||
AutoTArray<AnimationEventInfo, 2> events;
|
||||
|
||||
auto appendAnimationEvent = [&](EventMessage aMessage,
|
||||
const StickyTimeDuration& aElapsedTime,
|
||||
const TimeStamp& aScheduledEventTimeStamp) {
|
||||
double elapsedTime = aElapsedTime.ToSeconds();
|
||||
if (aMessage == eAnimationCancel) {
|
||||
// 0 is an inappropriate value for this callsite. What we need to do is
|
||||
// use a single random value for all increasing times reportable.
|
||||
// That is to say, whenever elapsedTime goes negative (because an
|
||||
// animation restarts, something rewinds the animation, or otherwise)
|
||||
// a new random value for the mix-in must be generated.
|
||||
elapsedTime =
|
||||
nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(elapsedTime, 0);
|
||||
}
|
||||
events.AppendElement(
|
||||
AnimationEventInfo(mAnimationName, mOwningElement.Target(), aMessage,
|
||||
elapsedTime, aScheduledEventTimeStamp, this));
|
||||
};
|
||||
|
||||
// Handle cancel event first
|
||||
if ((mPreviousPhase != AnimationPhase::Idle &&
|
||||
mPreviousPhase != AnimationPhase::After) &&
|
||||
currentPhase == AnimationPhase::Idle) {
|
||||
appendAnimationEvent(eAnimationCancel, aActiveTime,
|
||||
GetTimelineCurrentTimeAsTimeStamp());
|
||||
}
|
||||
|
||||
switch (mPreviousPhase) {
|
||||
case AnimationPhase::Idle:
|
||||
case AnimationPhase::Before:
|
||||
if (currentPhase == AnimationPhase::Active) {
|
||||
appendAnimationEvent(eAnimationStart, intervalStartTime,
|
||||
startTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::After) {
|
||||
appendAnimationEvent(eAnimationStart, intervalStartTime,
|
||||
startTimeStamp);
|
||||
appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
|
||||
}
|
||||
break;
|
||||
case AnimationPhase::Active:
|
||||
if (currentPhase == AnimationPhase::Before) {
|
||||
appendAnimationEvent(eAnimationEnd, intervalStartTime, startTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::Active) {
|
||||
// The currentIteration must have changed or element we would have
|
||||
// returned early above.
|
||||
MOZ_ASSERT(currentIteration != mPreviousIteration);
|
||||
appendAnimationEvent(eAnimationIteration, iterationStartTime,
|
||||
iterationTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::After) {
|
||||
appendAnimationEvent(eAnimationEnd, intervalEndTime, endTimeStamp);
|
||||
}
|
||||
break;
|
||||
case AnimationPhase::After:
|
||||
if (currentPhase == AnimationPhase::Before) {
|
||||
appendAnimationEvent(eAnimationStart, intervalEndTime, startTimeStamp);
|
||||
appendAnimationEvent(eAnimationEnd, intervalStartTime, endTimeStamp);
|
||||
} else if (currentPhase == AnimationPhase::Active) {
|
||||
appendAnimationEvent(eAnimationStart, intervalEndTime, endTimeStamp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
mPreviousPhase = currentPhase;
|
||||
mPreviousIteration = currentIteration;
|
||||
|
||||
if (!events.IsEmpty()) {
|
||||
presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimation::UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag) {
|
||||
if (mNeedsNewAnimationIndexWhenRun &&
|
||||
PlayState() != AnimationPlayState::Idle) {
|
||||
mAnimationIndex = sNextAnimationIndex++;
|
||||
mNeedsNewAnimationIndexWhenRun = false;
|
||||
}
|
||||
|
||||
Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
|
||||
}
|
||||
|
||||
/////////////////////// CSSAnimationKeyframeEffect ////////////////////////
|
||||
|
||||
void CSSAnimationKeyframeEffect::GetTiming(EffectTiming& aRetVal) const {
|
||||
MaybeFlushUnanimatedStyle();
|
||||
KeyframeEffect::GetTiming(aRetVal);
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::GetComputedTimingAsDict(
|
||||
ComputedEffectTiming& aRetVal) const {
|
||||
MaybeFlushUnanimatedStyle();
|
||||
KeyframeEffect::GetComputedTimingAsDict(aRetVal);
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::UpdateTiming(
|
||||
const OptionalEffectTiming& aTiming, ErrorResult& aRv) {
|
||||
KeyframeEffect::UpdateTiming(aTiming, aRv);
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
|
||||
CSSAnimationProperties updatedProperties = CSSAnimationProperties::None;
|
||||
if (aTiming.mDuration.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::Duration;
|
||||
}
|
||||
if (aTiming.mIterations.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::IterationCount;
|
||||
}
|
||||
if (aTiming.mDirection.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::Direction;
|
||||
}
|
||||
if (aTiming.mDelay.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::Delay;
|
||||
}
|
||||
if (aTiming.mFill.WasPassed()) {
|
||||
updatedProperties |= CSSAnimationProperties::FillMode;
|
||||
}
|
||||
|
||||
cssAnimation->AddOverriddenProperties(updatedProperties);
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::SetKeyframes(JSContext* aContext,
|
||||
JS::Handle<JSObject*> aKeyframes,
|
||||
ErrorResult& aRv) {
|
||||
KeyframeEffect::SetKeyframes(aContext, aKeyframes, aRv);
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CSSAnimation* cssAnimation = GetOwningCSSAnimation()) {
|
||||
cssAnimation->AddOverriddenProperties(CSSAnimationProperties::Keyframes);
|
||||
}
|
||||
}
|
||||
|
||||
void CSSAnimationKeyframeEffect::MaybeFlushUnanimatedStyle() const {
|
||||
if (!GetOwningCSSAnimation()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dom::Document* doc = GetRenderedDocument()) {
|
||||
doc->FlushPendingNotifications(
|
||||
ChangesToFlush(FlushType::Style, false /* flush animations */));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////// nsAnimationManager ////////////////////////////
|
||||
|
||||
// Find the matching animation by |aName| in the old list
|
||||
@ -601,7 +247,7 @@ static already_AddRefed<CSSAnimation> BuildAnimation(
|
||||
}
|
||||
|
||||
KeyframeEffectParams effectOptions;
|
||||
RefPtr<KeyframeEffect> effect = new CSSAnimationKeyframeEffect(
|
||||
RefPtr<KeyframeEffect> effect = new dom::CSSAnimationKeyframeEffect(
|
||||
aPresContext->Document(),
|
||||
OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoType),
|
||||
std::move(timing), effectOptions);
|
||||
|
@ -7,265 +7,21 @@
|
||||
#define nsAnimationManager_h_
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ContentEvents.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "AnimationCommon.h"
|
||||
#include "mozilla/dom/Animation.h"
|
||||
#include "mozilla/dom/KeyframeEffect.h"
|
||||
#include "mozilla/dom/MutationObservers.h"
|
||||
#include "mozilla/dom/CSSAnimation.h"
|
||||
#include "mozilla/Keyframe.h"
|
||||
#include "mozilla/MemoryReporting.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
|
||||
class nsIGlobalObject;
|
||||
class ServoComputedData;
|
||||
struct nsStyleDisplay;
|
||||
class ServoCSSAnimationBuilder;
|
||||
|
||||
namespace mozilla {
|
||||
class ComputedStyle;
|
||||
namespace css {
|
||||
class Declaration;
|
||||
} /* namespace css */
|
||||
namespace dom {
|
||||
class Promise;
|
||||
} /* namespace dom */
|
||||
|
||||
enum class PseudoStyleType : uint8_t;
|
||||
struct NonOwningAnimationTarget;
|
||||
|
||||
// Properties of CSS Animations that can be overridden by the Web Animations API
|
||||
// in a manner that means we should ignore subsequent changes to markup for that
|
||||
// property.
|
||||
enum class CSSAnimationProperties {
|
||||
None = 0,
|
||||
Keyframes = 1 << 0,
|
||||
Duration = 1 << 1,
|
||||
IterationCount = 1 << 2,
|
||||
Direction = 1 << 3,
|
||||
Delay = 1 << 4,
|
||||
FillMode = 1 << 5,
|
||||
Effect = Keyframes | Duration | IterationCount | Direction | Delay | FillMode,
|
||||
PlayState = 1 << 6,
|
||||
};
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CSSAnimationProperties)
|
||||
|
||||
namespace dom {
|
||||
|
||||
class CSSAnimation final : public Animation {
|
||||
public:
|
||||
explicit CSSAnimation(nsIGlobalObject* aGlobal, nsAtom* aAnimationName)
|
||||
: dom::Animation(aGlobal),
|
||||
mAnimationName(aAnimationName),
|
||||
mNeedsNewAnimationIndexWhenRun(false),
|
||||
mPreviousPhase(ComputedTiming::AnimationPhase::Idle),
|
||||
mPreviousIteration(0) {
|
||||
// We might need to drop this assertion once we add a script-accessible
|
||||
// constructor but for animations generated from CSS markup the
|
||||
// animation-name should never be empty.
|
||||
MOZ_ASSERT(mAnimationName != nsGkAtoms::_empty,
|
||||
"animation-name should not be 'none'");
|
||||
}
|
||||
|
||||
JSObject* WrapObject(JSContext* aCx,
|
||||
JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
CSSAnimation* AsCSSAnimation() override { return this; }
|
||||
const CSSAnimation* AsCSSAnimation() const override { return this; }
|
||||
|
||||
// CSSAnimation interface
|
||||
void GetAnimationName(nsString& aRetVal) const {
|
||||
mAnimationName->ToString(aRetVal);
|
||||
}
|
||||
|
||||
nsAtom* AnimationName() const { return mAnimationName; }
|
||||
|
||||
// Animation interface overrides
|
||||
void SetEffect(AnimationEffect* aEffect) override;
|
||||
void SetStartTimeAsDouble(const Nullable<double>& aStartTime) override;
|
||||
Promise* GetReady(ErrorResult& aRv) override;
|
||||
void Reverse(ErrorResult& aRv) override;
|
||||
|
||||
// NOTE: tabbrowser.xml currently relies on the fact that reading the
|
||||
// currentTime of a CSSAnimation does *not* flush style (whereas reading the
|
||||
// playState does). If CSS Animations 2 specifies that reading currentTime
|
||||
// also flushes style we will need to find another way to detect canceled
|
||||
// animations in tabbrowser.xml. On the other hand, if CSS Animations 2
|
||||
// specifies that reading playState does *not* flush style (and we drop the
|
||||
// following override), then we should update tabbrowser.xml to check
|
||||
// the playState instead.
|
||||
AnimationPlayState PlayStateFromJS() const override;
|
||||
bool PendingFromJS() const override;
|
||||
void PlayFromJS(ErrorResult& aRv) override;
|
||||
void PauseFromJS(ErrorResult& aRv) override;
|
||||
|
||||
void PlayFromStyle();
|
||||
void PauseFromStyle();
|
||||
void CancelFromStyle(PostRestyleMode aPostRestyle) {
|
||||
// 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::Cancel(aPostRestyle);
|
||||
|
||||
// We need to do this *after* calling Cancel() since
|
||||
// Cancel() might synchronously trigger a cancel event for which
|
||||
// we need an owning element to target the event at.
|
||||
mOwningElement = OwningElementRef();
|
||||
}
|
||||
|
||||
void Tick() override;
|
||||
void QueueEvents(
|
||||
const StickyTimeDuration& aActiveTime = StickyTimeDuration());
|
||||
|
||||
bool HasLowerCompositeOrderThan(const CSSAnimation& aOther) const;
|
||||
|
||||
void SetAnimationIndex(uint64_t aIndex) {
|
||||
MOZ_ASSERT(IsTiedToMarkup());
|
||||
if (IsRelevant() && mAnimationIndex != aIndex) {
|
||||
MutationObservers::NotifyAnimationChanged(this);
|
||||
PostUpdate();
|
||||
}
|
||||
mAnimationIndex = aIndex;
|
||||
}
|
||||
|
||||
// Sets the owning element which is used for determining the composite
|
||||
// order of CSSAnimation objects generated from CSS markup.
|
||||
//
|
||||
// @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(); }
|
||||
|
||||
void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override {
|
||||
QueueEvents(aActiveTime);
|
||||
}
|
||||
|
||||
CSSAnimationProperties GetOverriddenProperties() const {
|
||||
return mOverriddenProperties;
|
||||
}
|
||||
void AddOverriddenProperties(CSSAnimationProperties aProperties) {
|
||||
mOverriddenProperties |= aProperties;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ~CSSAnimation() {
|
||||
MOZ_ASSERT(!mOwningElement.IsSet(),
|
||||
"Owning element should be cleared "
|
||||
"before a CSS animation is destroyed");
|
||||
}
|
||||
|
||||
// Animation overrides
|
||||
void UpdateTiming(SeekFlag aSeekFlag,
|
||||
SyncNotifyFlag aSyncNotifyFlag) override;
|
||||
|
||||
// Returns the duration from the start of the animation's source effect's
|
||||
// active interval to the point where the animation actually begins playback.
|
||||
// This is zero unless the animation's source effect has a negative delay in
|
||||
// which case it is the absolute value of that delay.
|
||||
// This is used for setting the elapsedTime member of CSS AnimationEvents.
|
||||
TimeDuration InitialAdvance() const {
|
||||
return mEffect ? std::max(TimeDuration(),
|
||||
mEffect->SpecifiedTiming().Delay() * -1)
|
||||
: TimeDuration();
|
||||
}
|
||||
|
||||
RefPtr<nsAtom> 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 true, indicates that when this animation next leaves the idle state,
|
||||
// its animation index should be updated.
|
||||
bool mNeedsNewAnimationIndexWhenRun;
|
||||
|
||||
// Phase and current iteration from the previous time we queued events.
|
||||
// This is used to determine what new events to dispatch.
|
||||
ComputedTiming::AnimationPhase mPreviousPhase;
|
||||
uint64_t mPreviousIteration;
|
||||
|
||||
// Properties that would normally be defined by the cascade but which have
|
||||
// since been explicitly set via the Web Animations API.
|
||||
CSSAnimationProperties mOverriddenProperties = CSSAnimationProperties::None;
|
||||
};
|
||||
|
||||
} /* namespace dom */
|
||||
|
||||
// A subclass of KeyframeEffect that reports when specific properties have been
|
||||
// overridden via the Web Animations API.
|
||||
class CSSAnimationKeyframeEffect : public dom::KeyframeEffect {
|
||||
public:
|
||||
CSSAnimationKeyframeEffect(dom::Document* aDocument,
|
||||
OwningAnimationTarget&& aTarget,
|
||||
TimingParams&& aTiming,
|
||||
const KeyframeEffectParams& aOptions)
|
||||
: KeyframeEffect(aDocument, std::move(aTarget), std::move(aTiming),
|
||||
aOptions) {}
|
||||
|
||||
void GetTiming(dom::EffectTiming& aRetVal) const override;
|
||||
void GetComputedTimingAsDict(
|
||||
dom::ComputedEffectTiming& aRetVal) const override;
|
||||
void UpdateTiming(const dom::OptionalEffectTiming& aTiming,
|
||||
ErrorResult& aRv) override;
|
||||
void SetKeyframes(JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
|
||||
ErrorResult& aRv) override;
|
||||
|
||||
private:
|
||||
dom::CSSAnimation* GetOwningCSSAnimation() {
|
||||
return mAnimation ? mAnimation->AsCSSAnimation() : nullptr;
|
||||
}
|
||||
const dom::CSSAnimation* GetOwningCSSAnimation() const {
|
||||
return mAnimation ? mAnimation->AsCSSAnimation() : nullptr;
|
||||
}
|
||||
|
||||
// Flushes styles if our owning animation is a CSSAnimation
|
||||
void MaybeFlushUnanimatedStyle() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AnimationTypeTraits<dom::CSSAnimation> {
|
||||
static nsAtom* ElementPropertyAtom() { return nsGkAtoms::animationsProperty; }
|
||||
static nsAtom* BeforePropertyAtom() {
|
||||
return nsGkAtoms::animationsOfBeforeProperty;
|
||||
}
|
||||
static nsAtom* AfterPropertyAtom() {
|
||||
return nsGkAtoms::animationsOfAfterProperty;
|
||||
}
|
||||
static nsAtom* MarkerPropertyAtom() {
|
||||
return nsGkAtoms::animationsOfMarkerProperty;
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace mozilla */
|
||||
|
||||
class nsAnimationManager final
|
||||
|
Loading…
Reference in New Issue
Block a user