AnimationEffect.cpp (14038B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/AnimationEffect.h" 8 9 #include "mozilla/AnimationUtils.h" 10 #include "mozilla/FloatingPoint.h" 11 #include "mozilla/dom/Animation.h" 12 #include "mozilla/dom/AnimationEffectBinding.h" 13 #include "mozilla/dom/KeyframeEffect.h" 14 #include "mozilla/dom/MutationObservers.h" 15 #include "nsDOMMutationObserver.h" 16 17 namespace mozilla::dom { 18 19 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AnimationEffect) 20 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AnimationEffect) 21 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument, mAnimation) 22 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 23 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 24 25 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEffect) 26 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument, mAnimation) 27 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 28 29 NS_IMPL_CYCLE_COLLECTING_ADDREF(AnimationEffect) 30 NS_IMPL_CYCLE_COLLECTING_RELEASE(AnimationEffect) 31 32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AnimationEffect) 33 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 34 NS_INTERFACE_MAP_ENTRY(nsISupports) 35 NS_INTERFACE_MAP_END 36 37 AnimationEffect::AnimationEffect(Document* aDocument, TimingParams&& aTiming) 38 : mDocument(aDocument), mTiming(std::move(aTiming)) { 39 mRTPCallerType = mDocument->GetScopeObject()->GetRTPCallerType(); 40 } 41 42 AnimationEffect::~AnimationEffect() = default; 43 44 nsISupports* AnimationEffect::GetParentObject() const { 45 return ToSupports(mDocument); 46 } 47 48 // https://drafts.csswg.org/web-animations/#current 49 bool AnimationEffect::IsCurrent() const { 50 if (!mAnimation || mAnimation->PlayState() == AnimationPlayState::Finished) { 51 return false; 52 } 53 54 ComputedTiming computedTiming = GetComputedTiming(); 55 if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Active) { 56 return true; 57 } 58 59 return (mAnimation->PlaybackRate() > 0 && 60 computedTiming.mPhase == ComputedTiming::AnimationPhase::Before) || 61 (mAnimation->PlaybackRate() < 0 && 62 computedTiming.mPhase == ComputedTiming::AnimationPhase::After); 63 } 64 65 // https://drafts.csswg.org/web-animations/#in-effect 66 bool AnimationEffect::IsInEffect() const { 67 ComputedTiming computedTiming = GetComputedTiming(); 68 return !computedTiming.mProgress.IsNull(); 69 } 70 71 void AnimationEffect::SetSpecifiedTiming(TimingParams&& aTiming) { 72 if (mTiming == aTiming) { 73 return; 74 } 75 76 mTiming = aTiming; 77 78 UpdateNormalizedTiming(); 79 80 if (mAnimation) { 81 Maybe<nsAutoAnimationMutationBatch> mb; 82 if (AsKeyframeEffect() && AsKeyframeEffect()->GetAnimationTarget()) { 83 mb.emplace(AsKeyframeEffect()->GetAnimationTarget().mElement->OwnerDoc()); 84 } 85 86 mAnimation->NotifyEffectTimingUpdated(); 87 88 if (mAnimation->IsRelevant()) { 89 MutationObservers::NotifyAnimationChanged(mAnimation); 90 } 91 92 if (AsKeyframeEffect()) { 93 AsKeyframeEffect()->RequestRestyle(EffectCompositor::RestyleType::Layer); 94 } 95 } 96 97 // For keyframe effects, NotifyEffectTimingUpdated above will eventually 98 // cause KeyframeEffect::NotifyAnimationTimingUpdated to be called so it can 99 // update its registration with the target element as necessary. 100 } 101 102 ComputedTiming AnimationEffect::GetComputedTimingAt( 103 const Nullable<TimeDuration>& aLocalTime, const TimingParams& aTiming, 104 double aPlaybackRate, 105 Animation::ProgressTimelinePosition aProgressTimelinePosition, 106 EndpointBehavior aEndpointBehavior) { 107 static const StickyTimeDuration zeroDuration; 108 109 // Always return the same object to benefit from return-value optimization. 110 ComputedTiming result; 111 112 if (aTiming.Duration()) { 113 MOZ_ASSERT(aTiming.Duration().ref() >= zeroDuration, 114 "Iteration duration should be positive"); 115 result.mDuration = aTiming.Duration().ref(); 116 } 117 118 MOZ_ASSERT(aTiming.Iterations() >= 0.0 && !std::isnan(aTiming.Iterations()), 119 "mIterations should be nonnegative & finite, as ensured by " 120 "ValidateIterations or CSSParser"); 121 result.mIterations = aTiming.Iterations(); 122 123 MOZ_ASSERT(aTiming.IterationStart() >= 0.0, 124 "mIterationStart should be nonnegative, as ensured by " 125 "ValidateIterationStart"); 126 result.mIterationStart = aTiming.IterationStart(); 127 128 result.mActiveDuration = aTiming.ActiveDuration(); 129 result.mEndTime = aTiming.EndTime(); 130 result.mFill = aTiming.Fill() == dom::FillMode::Auto ? dom::FillMode::None 131 : aTiming.Fill(); 132 133 // The default constructor for ComputedTiming sets all other members to 134 // values consistent with an animation that has not been sampled. 135 if (aLocalTime.IsNull()) { 136 return result; 137 } 138 const TimeDuration& localTime = aLocalTime.Value(); 139 const bool atProgressTimelineBoundary = 140 aProgressTimelinePosition == 141 Animation::ProgressTimelinePosition::Boundary; 142 143 StickyTimeDuration beforeActiveBoundary = aTiming.CalcBeforeActiveBoundary(); 144 StickyTimeDuration activeAfterBoundary = aTiming.CalcActiveAfterBoundary(); 145 146 if (localTime > activeAfterBoundary || 147 (aEndpointBehavior == EndpointBehavior::Exclusive && aPlaybackRate >= 0 && 148 localTime == activeAfterBoundary && !atProgressTimelineBoundary)) { 149 result.mPhase = ComputedTiming::AnimationPhase::After; 150 if (!result.FillsForwards()) { 151 // The animation isn't active or filling at this time. 152 return result; 153 } 154 result.mActiveTime = 155 std::max(std::min(StickyTimeDuration(localTime - aTiming.Delay()), 156 result.mActiveDuration), 157 zeroDuration); 158 } else if (localTime < beforeActiveBoundary || 159 (aEndpointBehavior == EndpointBehavior::Exclusive && 160 aPlaybackRate < 0 && localTime == beforeActiveBoundary && 161 !atProgressTimelineBoundary)) { 162 result.mPhase = ComputedTiming::AnimationPhase::Before; 163 if (!result.FillsBackwards()) { 164 // The animation isn't active or filling at this time. 165 return result; 166 } 167 result.mActiveTime = 168 std::max(StickyTimeDuration(localTime - aTiming.Delay()), zeroDuration); 169 } else { 170 // Note: For progress-based timeline, it's possible to have a zero active 171 // duration with active phase. 172 result.mPhase = ComputedTiming::AnimationPhase::Active; 173 result.mActiveTime = localTime - aTiming.Delay(); 174 } 175 176 // Convert active time to a multiple of iterations. 177 // https://drafts.csswg.org/web-animations/#overall-progress 178 double overallProgress; 179 if (!result.mDuration) { 180 overallProgress = result.mPhase == ComputedTiming::AnimationPhase::Before 181 ? 0.0 182 : result.mIterations; 183 } else { 184 overallProgress = result.mActiveTime / result.mDuration; 185 } 186 187 // Factor in iteration start offset. 188 if (std::isfinite(overallProgress)) { 189 overallProgress += result.mIterationStart; 190 } 191 192 // Determine the 0-based index of the current iteration. 193 // https://drafts.csswg.org/web-animations/#current-iteration 194 result.mCurrentIteration = 195 (result.mIterations >= double(UINT64_MAX) && 196 result.mPhase == ComputedTiming::AnimationPhase::After) || 197 overallProgress >= double(UINT64_MAX) 198 ? UINT64_MAX // In GetComputedTimingDictionary(), 199 // we will convert this into Infinity 200 : static_cast<uint64_t>(std::max(overallProgress, 0.0)); 201 202 // Convert the overall progress to a fraction of a single iteration--the 203 // simply iteration progress. 204 // https://drafts.csswg.org/web-animations/#simple-iteration-progress 205 double progress = std::isfinite(overallProgress) 206 ? fmod(overallProgress, 1.0) 207 : fmod(result.mIterationStart, 1.0); 208 209 // When we are at the end of the active interval and the end of an iteration 210 // we need to report the end of the final iteration and not the start of the 211 // next iteration. We *don't* want to do this, however, when we have 212 // a zero-iteration animation. 213 if (progress == 0.0 && 214 (result.mPhase == ComputedTiming::AnimationPhase::After || 215 result.mPhase == ComputedTiming::AnimationPhase::Active) && 216 result.mActiveTime == result.mActiveDuration && 217 result.mIterations != 0.0) { 218 // The only way we can reach the end of the active interval and have 219 // a progress of zero and a current iteration of zero, is if we have a 220 // zero iteration count -- something we should have detected above. 221 MOZ_ASSERT(result.mCurrentIteration != 0, 222 "Should not have zero current iteration"); 223 progress = 1.0; 224 if (result.mCurrentIteration != UINT64_MAX) { 225 result.mCurrentIteration--; 226 } 227 } 228 229 // Factor in the direction. 230 bool thisIterationReverse = false; 231 switch (aTiming.Direction()) { 232 case PlaybackDirection::Normal: 233 thisIterationReverse = false; 234 break; 235 case PlaybackDirection::Reverse: 236 thisIterationReverse = true; 237 break; 238 case PlaybackDirection::Alternate: 239 thisIterationReverse = (result.mCurrentIteration & 1) == 1; 240 break; 241 case PlaybackDirection::Alternate_reverse: 242 thisIterationReverse = (result.mCurrentIteration & 1) == 0; 243 break; 244 default: 245 MOZ_ASSERT_UNREACHABLE("Unknown PlaybackDirection type"); 246 } 247 if (thisIterationReverse) { 248 progress = 1.0 - progress; 249 } 250 251 // Calculate the 'before flag' which we use when applying step timing 252 // functions. 253 if ((result.mPhase == ComputedTiming::AnimationPhase::After && 254 thisIterationReverse) || 255 (result.mPhase == ComputedTiming::AnimationPhase::Before && 256 !thisIterationReverse)) { 257 result.mBeforeFlag = true; 258 } 259 260 // Apply the easing. 261 if (const auto& fn = aTiming.TimingFunction()) { 262 progress = fn->At(progress, result.mBeforeFlag); 263 } 264 265 MOZ_ASSERT(std::isfinite(progress), "Progress value should be finite"); 266 result.mProgress.SetValue(progress); 267 return result; 268 } 269 270 ComputedTiming AnimationEffect::GetComputedTiming( 271 const TimingParams* aTiming, EndpointBehavior aEndpointBehavior) const { 272 const double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1; 273 const auto progressTimelinePosition = 274 mAnimation ? mAnimation->AtProgressTimelineBoundary() 275 : Animation::ProgressTimelinePosition::NotBoundary; 276 return GetComputedTimingAt( 277 GetLocalTime(), aTiming ? *aTiming : NormalizedTiming(), playbackRate, 278 progressTimelinePosition, aEndpointBehavior); 279 } 280 281 // Helper function for generating an (Computed)EffectTiming dictionary 282 static void GetEffectTimingDictionary(const TimingParams& aTiming, 283 EffectTiming& aRetVal) { 284 aRetVal.mDelay = aTiming.Delay().ToMilliseconds(); 285 aRetVal.mEndDelay = aTiming.EndDelay().ToMilliseconds(); 286 aRetVal.mFill = aTiming.Fill(); 287 aRetVal.mIterationStart = aTiming.IterationStart(); 288 aRetVal.mIterations = aTiming.Iterations(); 289 if (aTiming.Duration()) { 290 aRetVal.mDuration.SetAsUnrestrictedDouble() = 291 aTiming.Duration()->ToMilliseconds(); 292 } 293 aRetVal.mDirection = aTiming.Direction(); 294 if (aTiming.TimingFunction()) { 295 aRetVal.mEasing.Truncate(); 296 aTiming.TimingFunction()->AppendToString(aRetVal.mEasing); 297 } 298 } 299 300 void AnimationEffect::GetTiming(EffectTiming& aRetVal) const { 301 GetEffectTimingDictionary(SpecifiedTiming(), aRetVal); 302 } 303 304 void AnimationEffect::GetComputedTimingAsDict( 305 ComputedEffectTiming& aRetVal) const { 306 // Specified timing 307 GetEffectTimingDictionary(SpecifiedTiming(), aRetVal); 308 309 // Computed timing 310 double playbackRate = mAnimation ? mAnimation->PlaybackRate() : 1; 311 const Nullable<TimeDuration> currentTime = GetLocalTime(); 312 const auto progressTimelinePosition = 313 mAnimation ? mAnimation->AtProgressTimelineBoundary() 314 : Animation::ProgressTimelinePosition::NotBoundary; 315 ComputedTiming computedTiming = GetComputedTimingAt( 316 currentTime, SpecifiedTiming(), playbackRate, progressTimelinePosition); 317 318 aRetVal.mDuration.SetAsUnrestrictedDouble() = 319 computedTiming.mDuration.ToMilliseconds(); 320 aRetVal.mFill = computedTiming.mFill; 321 aRetVal.mActiveDuration = computedTiming.mActiveDuration.ToMilliseconds(); 322 aRetVal.mEndTime = computedTiming.mEndTime.ToMilliseconds(); 323 aRetVal.mLocalTime = 324 AnimationUtils::TimeDurationToDouble(currentTime, mRTPCallerType); 325 aRetVal.mProgress = computedTiming.mProgress; 326 327 if (!aRetVal.mProgress.IsNull()) { 328 // Convert the returned currentIteration into Infinity if we set 329 // (uint64_t) computedTiming.mCurrentIteration to UINT64_MAX 330 double iteration = 331 computedTiming.mCurrentIteration == UINT64_MAX 332 ? PositiveInfinity<double>() 333 : static_cast<double>(computedTiming.mCurrentIteration); 334 aRetVal.mCurrentIteration.SetValue(iteration); 335 } 336 } 337 338 void AnimationEffect::UpdateTiming(const OptionalEffectTiming& aTiming, 339 ErrorResult& aRv) { 340 TimingParams timing = 341 TimingParams::MergeOptionalEffectTiming(mTiming, aTiming, aRv); 342 if (aRv.Failed()) { 343 return; 344 } 345 346 SetSpecifiedTiming(std::move(timing)); 347 } 348 349 void AnimationEffect::UpdateNormalizedTiming() { 350 mNormalizedTiming.reset(); 351 352 if (!mAnimation || !mAnimation->UsingScrollTimeline()) { 353 return; 354 } 355 356 // Since `mAnimation` has a scroll timeline, we can be sure `GetTimeline()` 357 // and `TimelineDuration()` will not return null. 358 mNormalizedTiming.emplace( 359 mTiming.Normalize(mAnimation->GetTimeline()->TimelineDuration().Value())); 360 } 361 362 Nullable<TimeDuration> AnimationEffect::GetLocalTime() const { 363 // Since the *animation* start time is currently always zero, the local 364 // time is equal to the parent time. 365 Nullable<TimeDuration> result; 366 if (mAnimation) { 367 result = mAnimation->GetCurrentTimeAsDuration(); 368 } 369 return result; 370 } 371 372 } // namespace mozilla::dom