2015-05-03 22:32:37 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
2015-04-15 02:48:21 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
2015-04-30 16:06:43 +03:00
|
|
|
#include "mozilla/dom/AnimationEffectReadOnly.h"
|
|
|
|
#include "mozilla/dom/AnimationEffectReadOnlyBinding.h"
|
2017-10-12 15:50:23 +03:00
|
|
|
|
|
|
|
#include "mozilla/dom/Animation.h"
|
2017-10-26 13:55:28 +03:00
|
|
|
#include "mozilla/dom/KeyframeEffectReadOnly.h"
|
2016-07-25 13:27:33 +03:00
|
|
|
#include "mozilla/AnimationUtils.h"
|
|
|
|
#include "mozilla/FloatingPoint.h"
|
2015-04-15 02:48:21 +03:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
|
2016-07-25 13:27:33 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(AnimationEffectReadOnly)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AnimationEffectReadOnly)
|
|
|
|
if (tmp->mTiming) {
|
|
|
|
tmp->mTiming->Unlink();
|
|
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument, mTiming, mAnimation)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEffectReadOnly)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument, mTiming, mAnimation)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(AnimationEffectReadOnly)
|
2015-04-15 02:48:21 +03:00
|
|
|
|
2015-04-30 16:06:43 +03:00
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationEffectReadOnly)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationEffectReadOnly)
|
2015-04-15 02:48:21 +03:00
|
|
|
|
2015-04-30 16:06:43 +03:00
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationEffectReadOnly)
|
2015-04-15 02:48:21 +03:00
|
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
|
|
NS_INTERFACE_MAP_END
|
|
|
|
|
2016-07-25 13:27:33 +03:00
|
|
|
AnimationEffectReadOnly::AnimationEffectReadOnly(
|
|
|
|
nsIDocument* aDocument, AnimationEffectTimingReadOnly* aTiming)
|
|
|
|
: mDocument(aDocument)
|
|
|
|
, mTiming(aTiming)
|
|
|
|
{
|
|
|
|
MOZ_ASSERT(aTiming);
|
|
|
|
}
|
|
|
|
|
2017-12-15 23:55:08 +03:00
|
|
|
// https://drafts.csswg.org/web-animations/#current
|
2016-07-25 13:27:33 +03:00
|
|
|
bool
|
|
|
|
AnimationEffectReadOnly::IsCurrent() const
|
|
|
|
{
|
|
|
|
if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
|
|
return computedTiming.mPhase == ComputedTiming::AnimationPhase::Before ||
|
|
|
|
computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
|
|
|
|
}
|
|
|
|
|
2017-12-15 23:55:08 +03:00
|
|
|
// https://drafts.csswg.org/web-animations/#in-effect
|
2016-07-25 13:27:33 +03:00
|
|
|
bool
|
|
|
|
AnimationEffectReadOnly::IsInEffect() const
|
|
|
|
{
|
|
|
|
ComputedTiming computedTiming = GetComputedTiming();
|
|
|
|
return !computedTiming.mProgress.IsNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
already_AddRefed<AnimationEffectTimingReadOnly>
|
|
|
|
AnimationEffectReadOnly::Timing()
|
|
|
|
{
|
|
|
|
RefPtr<AnimationEffectTimingReadOnly> temp(mTiming);
|
|
|
|
return temp.forget();
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
AnimationEffectReadOnly::SetSpecifiedTiming(const TimingParams& aTiming)
|
|
|
|
{
|
|
|
|
if (mTiming->AsTimingParams() == aTiming) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
mTiming->SetTimingParams(aTiming);
|
|
|
|
if (mAnimation) {
|
|
|
|
mAnimation->NotifyEffectTimingUpdated();
|
2017-08-03 00:34:38 +03:00
|
|
|
if (AsKeyframeEffect()) {
|
|
|
|
AsKeyframeEffect()->RequestRestyle(EffectCompositor::RestyleType::Layer);
|
|
|
|
}
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
2016-07-13 13:44:19 +03:00
|
|
|
// For keyframe effects, NotifyEffectTimingUpdated above will eventually cause
|
|
|
|
// KeyframeEffectReadOnly::NotifyAnimationTimingUpdated to be called so it can
|
|
|
|
// update its registration with the target element as necessary.
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
ComputedTiming
|
|
|
|
AnimationEffectReadOnly::GetComputedTimingAt(
|
|
|
|
const Nullable<TimeDuration>& aLocalTime,
|
|
|
|
const TimingParams& aTiming,
|
|
|
|
double aPlaybackRate)
|
|
|
|
{
|
2017-07-10 10:36:28 +03:00
|
|
|
static const StickyTimeDuration zeroDuration;
|
2016-07-25 13:27:33 +03:00
|
|
|
|
|
|
|
// Always return the same object to benefit from return-value optimization.
|
|
|
|
ComputedTiming result;
|
|
|
|
|
2017-06-22 06:12:43 +03:00
|
|
|
if (aTiming.Duration()) {
|
|
|
|
MOZ_ASSERT(aTiming.Duration().ref() >= zeroDuration,
|
2016-07-25 13:27:33 +03:00
|
|
|
"Iteration duration should be positive");
|
2017-06-22 06:12:43 +03:00
|
|
|
result.mDuration = aTiming.Duration().ref();
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
|
|
|
|
2017-06-22 06:12:43 +03:00
|
|
|
MOZ_ASSERT(aTiming.Iterations() >= 0.0 && !IsNaN(aTiming.Iterations()),
|
2016-07-25 13:27:33 +03:00
|
|
|
"mIterations should be nonnegative & finite, as ensured by "
|
|
|
|
"ValidateIterations or CSSParser");
|
2017-06-22 06:12:43 +03:00
|
|
|
result.mIterations = aTiming.Iterations();
|
2016-07-25 13:27:33 +03:00
|
|
|
|
2017-06-22 06:12:43 +03:00
|
|
|
MOZ_ASSERT(aTiming.IterationStart() >= 0.0,
|
2016-07-25 13:27:33 +03:00
|
|
|
"mIterationStart should be nonnegative, as ensured by "
|
|
|
|
"ValidateIterationStart");
|
2017-06-22 06:12:43 +03:00
|
|
|
result.mIterationStart = aTiming.IterationStart();
|
2016-07-25 13:27:33 +03:00
|
|
|
|
|
|
|
result.mActiveDuration = aTiming.ActiveDuration();
|
|
|
|
result.mEndTime = aTiming.EndTime();
|
2017-06-22 06:12:43 +03:00
|
|
|
result.mFill = aTiming.Fill() == dom::FillMode::Auto ?
|
2016-07-25 13:27:33 +03:00
|
|
|
dom::FillMode::None :
|
2017-06-22 06:12:43 +03:00
|
|
|
aTiming.Fill();
|
2016-07-25 13:27:33 +03:00
|
|
|
|
|
|
|
// The default constructor for ComputedTiming sets all other members to
|
|
|
|
// values consistent with an animation that has not been sampled.
|
|
|
|
if (aLocalTime.IsNull()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
const TimeDuration& localTime = aLocalTime.Value();
|
|
|
|
|
|
|
|
StickyTimeDuration beforeActiveBoundary =
|
2017-06-22 06:12:43 +03:00
|
|
|
std::max(std::min(StickyTimeDuration(aTiming.Delay()), result.mEndTime),
|
2016-09-01 04:57:55 +03:00
|
|
|
zeroDuration);
|
|
|
|
|
2016-07-25 13:27:33 +03:00
|
|
|
StickyTimeDuration activeAfterBoundary =
|
2017-06-22 06:12:43 +03:00
|
|
|
std::max(std::min(StickyTimeDuration(aTiming.Delay() +
|
2016-09-01 04:57:55 +03:00
|
|
|
result.mActiveDuration),
|
|
|
|
result.mEndTime),
|
|
|
|
zeroDuration);
|
2016-07-25 13:27:33 +03:00
|
|
|
|
|
|
|
if (localTime > activeAfterBoundary ||
|
|
|
|
(aPlaybackRate >= 0 && localTime == activeAfterBoundary)) {
|
|
|
|
result.mPhase = ComputedTiming::AnimationPhase::After;
|
|
|
|
if (!result.FillsForwards()) {
|
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
return result;
|
|
|
|
}
|
2016-12-20 10:03:29 +03:00
|
|
|
result.mActiveTime =
|
2017-06-22 06:12:43 +03:00
|
|
|
std::max(std::min(StickyTimeDuration(localTime - aTiming.Delay()),
|
2016-09-01 04:57:55 +03:00
|
|
|
result.mActiveDuration),
|
|
|
|
zeroDuration);
|
2016-07-25 13:27:33 +03:00
|
|
|
} else if (localTime < beforeActiveBoundary ||
|
|
|
|
(aPlaybackRate < 0 && localTime == beforeActiveBoundary)) {
|
|
|
|
result.mPhase = ComputedTiming::AnimationPhase::Before;
|
|
|
|
if (!result.FillsBackwards()) {
|
|
|
|
// The animation isn't active or filling at this time.
|
|
|
|
return result;
|
|
|
|
}
|
2016-12-20 10:03:29 +03:00
|
|
|
result.mActiveTime
|
2017-06-22 06:12:43 +03:00
|
|
|
= std::max(StickyTimeDuration(localTime - aTiming.Delay()),
|
2016-12-20 10:03:29 +03:00
|
|
|
zeroDuration);
|
2016-07-25 13:27:33 +03:00
|
|
|
} else {
|
2017-07-10 10:21:30 +03:00
|
|
|
MOZ_ASSERT(result.mActiveDuration,
|
2016-07-25 13:27:33 +03:00
|
|
|
"How can we be in the middle of a zero-duration interval?");
|
|
|
|
result.mPhase = ComputedTiming::AnimationPhase::Active;
|
2017-06-22 06:12:43 +03:00
|
|
|
result.mActiveTime = localTime - aTiming.Delay();
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert active time to a multiple of iterations.
|
2017-12-15 23:55:08 +03:00
|
|
|
// https://drafts.csswg.org/web-animations/#overall-progress
|
2016-07-25 13:27:33 +03:00
|
|
|
double overallProgress;
|
2017-07-10 10:21:30 +03:00
|
|
|
if (!result.mDuration) {
|
2016-07-25 13:27:33 +03:00
|
|
|
overallProgress = result.mPhase == ComputedTiming::AnimationPhase::Before
|
|
|
|
? 0.0
|
|
|
|
: result.mIterations;
|
|
|
|
} else {
|
2016-12-20 10:03:29 +03:00
|
|
|
overallProgress = result.mActiveTime / result.mDuration;
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Factor in iteration start offset.
|
|
|
|
if (IsFinite(overallProgress)) {
|
|
|
|
overallProgress += result.mIterationStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the 0-based index of the current iteration.
|
2017-12-15 23:55:08 +03:00
|
|
|
// https://drafts.csswg.org/web-animations/#current-iteration
|
2016-07-25 13:27:33 +03:00
|
|
|
result.mCurrentIteration =
|
2017-09-12 02:42:54 +03:00
|
|
|
(result.mIterations >= UINT64_MAX
|
|
|
|
&& result.mPhase == ComputedTiming::AnimationPhase::After)
|
|
|
|
|| overallProgress >= UINT64_MAX
|
2016-07-25 13:27:33 +03:00
|
|
|
? UINT64_MAX // In GetComputedTimingDictionary(),
|
|
|
|
// we will convert this into Infinity
|
|
|
|
: static_cast<uint64_t>(overallProgress);
|
|
|
|
|
|
|
|
// Convert the overall progress to a fraction of a single iteration--the
|
|
|
|
// simply iteration progress.
|
2017-12-15 23:55:08 +03:00
|
|
|
// https://drafts.csswg.org/web-animations/#simple-iteration-progress
|
2016-07-25 13:27:33 +03:00
|
|
|
double progress = IsFinite(overallProgress)
|
|
|
|
? fmod(overallProgress, 1.0)
|
|
|
|
: fmod(result.mIterationStart, 1.0);
|
|
|
|
|
2017-10-13 06:31:21 +03:00
|
|
|
// When we are at the end of the active interval and the end of an iteration
|
|
|
|
// we need to report the end of the final iteration and not the start of the
|
|
|
|
// next iteration. We *don't* want to do this, however, when we have
|
|
|
|
// a zero-iteration animation.
|
|
|
|
if (progress == 0.0 &&
|
|
|
|
(result.mPhase == ComputedTiming::AnimationPhase::After ||
|
|
|
|
result.mPhase == ComputedTiming::AnimationPhase::Active) &&
|
|
|
|
result.mActiveTime == result.mActiveDuration &&
|
|
|
|
result.mIterations != 0.0) {
|
|
|
|
// The only way we can reach the end of the active interval and have
|
|
|
|
// a progress of zero and a current iteration of zero, is if we have a zero
|
|
|
|
// iteration count -- something we should have detected above.
|
2016-07-25 13:27:33 +03:00
|
|
|
MOZ_ASSERT(result.mCurrentIteration != 0,
|
|
|
|
"Should not have zero current iteration");
|
|
|
|
progress = 1.0;
|
|
|
|
if (result.mCurrentIteration != UINT64_MAX) {
|
|
|
|
result.mCurrentIteration--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Factor in the direction.
|
|
|
|
bool thisIterationReverse = false;
|
2017-06-22 06:12:43 +03:00
|
|
|
switch (aTiming.Direction()) {
|
2016-07-25 13:27:33 +03:00
|
|
|
case PlaybackDirection::Normal:
|
|
|
|
thisIterationReverse = false;
|
|
|
|
break;
|
|
|
|
case PlaybackDirection::Reverse:
|
|
|
|
thisIterationReverse = true;
|
|
|
|
break;
|
|
|
|
case PlaybackDirection::Alternate:
|
|
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 1;
|
|
|
|
break;
|
|
|
|
case PlaybackDirection::Alternate_reverse:
|
|
|
|
thisIterationReverse = (result.mCurrentIteration & 1) == 0;
|
|
|
|
break;
|
|
|
|
default:
|
2017-02-27 20:38:52 +03:00
|
|
|
MOZ_ASSERT_UNREACHABLE("Unknown PlaybackDirection type");
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
|
|
|
if (thisIterationReverse) {
|
|
|
|
progress = 1.0 - progress;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate the 'before flag' which we use when applying step timing
|
|
|
|
// functions.
|
|
|
|
if ((result.mPhase == ComputedTiming::AnimationPhase::After &&
|
|
|
|
thisIterationReverse) ||
|
|
|
|
(result.mPhase == ComputedTiming::AnimationPhase::Before &&
|
|
|
|
!thisIterationReverse)) {
|
|
|
|
result.mBeforeFlag = ComputedTimingFunction::BeforeFlag::Set;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the easing.
|
2017-06-22 06:12:43 +03:00
|
|
|
if (aTiming.TimingFunction()) {
|
|
|
|
progress = aTiming.TimingFunction()->GetValue(progress, result.mBeforeFlag);
|
2016-07-25 13:27:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
MOZ_ASSERT(IsFinite(progress), "Progress value should be finite");
|
|
|
|
result.mProgress.SetValue(progress);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
ComputedTiming
|
|
|
|
AnimationEffectReadOnly::GetComputedTiming(const TimingParams* aTiming) const
|
|
|
|
{
|
|
|
|
double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
|
|
|
|
return GetComputedTimingAt(GetLocalTime(),
|
|
|
|
aTiming ? *aTiming : SpecifiedTiming(),
|
|
|
|
playbackRate);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper functions for generating a ComputedTimingProperties dictionary
|
|
|
|
static void
|
|
|
|
GetComputedTimingDictionary(const ComputedTiming& aComputedTiming,
|
|
|
|
const Nullable<TimeDuration>& aLocalTime,
|
|
|
|
const TimingParams& aTiming,
|
|
|
|
ComputedTimingProperties& aRetVal)
|
|
|
|
{
|
|
|
|
// AnimationEffectTimingProperties
|
2017-06-22 06:12:43 +03:00
|
|
|
aRetVal.mDelay = aTiming.Delay().ToMilliseconds();
|
|
|
|
aRetVal.mEndDelay = aTiming.EndDelay().ToMilliseconds();
|
2016-07-25 13:27:33 +03:00
|
|
|
aRetVal.mFill = aComputedTiming.mFill;
|
|
|
|
aRetVal.mIterations = aComputedTiming.mIterations;
|
|
|
|
aRetVal.mIterationStart = aComputedTiming.mIterationStart;
|
|
|
|
aRetVal.mDuration.SetAsUnrestrictedDouble() =
|
|
|
|
aComputedTiming.mDuration.ToMilliseconds();
|
2017-06-22 06:12:43 +03:00
|
|
|
aRetVal.mDirection = aTiming.Direction();
|
2016-07-25 13:27:33 +03:00
|
|
|
|
|
|
|
// ComputedTimingProperties
|
|
|
|
aRetVal.mActiveDuration = aComputedTiming.mActiveDuration.ToMilliseconds();
|
|
|
|
aRetVal.mEndTime = aComputedTiming.mEndTime.ToMilliseconds();
|
|
|
|
aRetVal.mLocalTime = AnimationUtils::TimeDurationToDouble(aLocalTime);
|
|
|
|
aRetVal.mProgress = aComputedTiming.mProgress;
|
|
|
|
|
|
|
|
if (!aRetVal.mProgress.IsNull()) {
|
|
|
|
// Convert the returned currentIteration into Infinity if we set
|
|
|
|
// (uint64_t) aComputedTiming.mCurrentIteration to UINT64_MAX
|
|
|
|
double iteration = aComputedTiming.mCurrentIteration == UINT64_MAX
|
|
|
|
? PositiveInfinity<double>()
|
|
|
|
: static_cast<double>(aComputedTiming.mCurrentIteration);
|
|
|
|
aRetVal.mCurrentIteration.SetValue(iteration);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
AnimationEffectReadOnly::GetComputedTimingAsDict(
|
|
|
|
ComputedTimingProperties& aRetVal) const
|
|
|
|
{
|
|
|
|
double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1;
|
|
|
|
const Nullable<TimeDuration> currentTime = GetLocalTime();
|
|
|
|
GetComputedTimingDictionary(GetComputedTimingAt(currentTime,
|
|
|
|
SpecifiedTiming(),
|
|
|
|
playbackRate),
|
|
|
|
currentTime,
|
|
|
|
SpecifiedTiming(),
|
|
|
|
aRetVal);
|
|
|
|
}
|
|
|
|
|
|
|
|
AnimationEffectReadOnly::~AnimationEffectReadOnly()
|
|
|
|
{
|
|
|
|
// mTiming is cycle collected, so we have to do null check first even though
|
|
|
|
// mTiming shouldn't be null during the lifetime of KeyframeEffect.
|
|
|
|
if (mTiming) {
|
|
|
|
mTiming->Unlink();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Nullable<TimeDuration>
|
|
|
|
AnimationEffectReadOnly::GetLocalTime() const
|
|
|
|
{
|
|
|
|
// Since the *animation* start time is currently always zero, the local
|
|
|
|
// time is equal to the parent time.
|
|
|
|
Nullable<TimeDuration> result;
|
|
|
|
if (mAnimation) {
|
|
|
|
result = mAnimation->GetCurrentTime();
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-04-15 02:48:21 +03:00
|
|
|
} // namespace dom
|
|
|
|
} // namespace mozilla
|