tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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