tor-browser

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

CSSTransition.cpp (15005B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "CSSTransition.h"
      8 
      9 #include "mozilla/AnimationEventDispatcher.h"
     10 #include "mozilla/TimeStamp.h"
     11 #include "mozilla/dom/CSSTransitionBinding.h"
     12 #include "mozilla/dom/KeyframeEffect.h"
     13 #include "mozilla/dom/KeyframeEffectBinding.h"
     14 #include "nsPresContext.h"
     15 
     16 namespace mozilla::dom {
     17 
     18 JSObject* CSSTransition::WrapObject(JSContext* aCx,
     19                                    JS::Handle<JSObject*> aGivenProto) {
     20  return dom::CSSTransition_Binding::Wrap(aCx, this, aGivenProto);
     21 }
     22 
     23 void CSSTransition::GetTransitionProperty(nsString& aRetVal) const {
     24  MOZ_ASSERT(mTransitionProperty.IsValid(),
     25             "Transition Property should be initialized");
     26  mTransitionProperty.ToString(aRetVal);
     27 }
     28 
     29 AnimationPlayState CSSTransition::PlayStateFromJS() const {
     30  FlushUnanimatedStyle();
     31  return Animation::PlayStateFromJS();
     32 }
     33 
     34 bool CSSTransition::PendingFromJS() const {
     35  // Transitions don't become pending again after they start running but, if
     36  // while the transition is still pending, style is updated in such a way
     37  // that the transition will be canceled, we need to report false here.
     38  // Hence we need to flush, but only when we're pending.
     39  if (Pending()) {
     40    FlushUnanimatedStyle();
     41  }
     42  return Animation::PendingFromJS();
     43 }
     44 
     45 void CSSTransition::PlayFromJS(ErrorResult& aRv) {
     46  FlushUnanimatedStyle();
     47  Animation::PlayFromJS(aRv);
     48 }
     49 
     50 void CSSTransition::UpdateTiming(SeekFlag aSeekFlag,
     51                                 SyncNotifyFlag aSyncNotifyFlag) {
     52  if (mNeedsNewAnimationIndexWhenRun &&
     53      PlayState() != AnimationPlayState::Idle) {
     54    mAnimationIndex = sNextAnimationIndex++;
     55    mNeedsNewAnimationIndexWhenRun = false;
     56  }
     57 
     58  Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
     59 }
     60 
     61 void CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime) {
     62  if (!mOwningElement.ShouldFireEvents()) {
     63    return;
     64  }
     65 
     66  nsPresContext* presContext = mOwningElement.GetPresContext();
     67  if (!presContext) {
     68    return;
     69  }
     70 
     71  static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
     72 
     73  TransitionPhase currentPhase;
     74  StickyTimeDuration intervalStartTime;
     75  StickyTimeDuration intervalEndTime;
     76 
     77  if (!mEffect) {
     78    currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
     79  } else {
     80    ComputedTiming computedTiming = mEffect->GetComputedTiming();
     81 
     82    currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
     83    intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
     84    intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
     85  }
     86 
     87  if (mPendingState != PendingState::NotPending &&
     88      (mPreviousTransitionPhase == TransitionPhase::Idle ||
     89       mPreviousTransitionPhase == TransitionPhase::Pending)) {
     90    currentPhase = TransitionPhase::Pending;
     91  }
     92 
     93  if (currentPhase == mPreviousTransitionPhase) {
     94    return;
     95  }
     96 
     97  // TimeStamps to use for ordering the events when they are dispatched. We
     98  // use a TimeStamp so we can compare events produced by different elements,
     99  // perhaps even with different timelines.
    100  // The zero timestamp is for transitionrun events where we ignore the delay
    101  // for the purpose of ordering events.
    102  TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
    103  TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
    104  TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
    105 
    106  AutoTArray<AnimationEventInfo, 3> events;
    107 
    108  auto appendTransitionEvent = [&](EventMessage aMessage,
    109                                   const StickyTimeDuration& aElapsedTime,
    110                                   const TimeStamp& aScheduledEventTimeStamp) {
    111    double elapsedTime = aElapsedTime.ToSeconds();
    112    if (aMessage == eTransitionCancel) {
    113      // 0 is an inappropriate value for this callsite. What we need to do is
    114      // use a single random value for all increasing times reportable.
    115      // That is to say, whenever elapsedTime goes negative (because an
    116      // animation restarts, something rewinds the animation, or otherwise)
    117      // a new random value for the mix-in must be generated.
    118      elapsedTime = nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(
    119          elapsedTime, 0, mRTPCallerType);
    120    }
    121    events.AppendElement(AnimationEventInfo(
    122        TransitionProperty(), mOwningElement.Target(), aMessage, elapsedTime,
    123        mAnimationIndex, aScheduledEventTimeStamp, this));
    124  };
    125 
    126  // Handle cancel events first
    127  if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
    128       mPreviousTransitionPhase != TransitionPhase::After) &&
    129      currentPhase == TransitionPhase::Idle) {
    130    appendTransitionEvent(eTransitionCancel, aActiveTime,
    131                          GetTimelineCurrentTimeAsTimeStamp());
    132  }
    133 
    134  // All other events
    135  switch (mPreviousTransitionPhase) {
    136    case TransitionPhase::Idle:
    137      if (currentPhase == TransitionPhase::Pending ||
    138          currentPhase == TransitionPhase::Before) {
    139        // When we are replacing a transition and flushing the style in the
    140        // meantime, after a timeout, we may tick this transition without a
    141        // proper |mPendingReadyTime| because the refresh driver is not in
    142        // refresh, i.e. mInRefresh is false. So in the current tick we queue
    143        // this event but the transition would be triggered in the next tick.
    144        //
    145        // In general, we use Animation::EnsurePaintIsScheduled() to assign a
    146        // valid time to |mPendingReadyTime| of this transition, and then we
    147        // could trigger this transition if this value is set. When triggering,
    148        // we set a proper |mStartTime|, which could be used to calculate the
    149        // animation time, i.e. |zeroTimeStamp|.
    150        //
    151        // However, due to this race condition (i.e. the transition hasn't been
    152        // triggered yet but we are enqueuing this event), it's posssible to
    153        // have a null |zeroTimeStamp|, which breaks the sorting of transition
    154        // events. So we use the current time as a fallback way to make sure we
    155        // have a reasonable schedule time for sorting.
    156        appendTransitionEvent(eTransitionRun, intervalStartTime,
    157                              zeroTimeStamp.IsNull()
    158                                  ? GetTimelineCurrentTimeAsTimeStamp()
    159                                  : zeroTimeStamp);
    160      } else if (currentPhase == TransitionPhase::Active) {
    161        appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
    162        appendTransitionEvent(eTransitionStart, intervalStartTime,
    163                              startTimeStamp);
    164      } else if (currentPhase == TransitionPhase::After) {
    165        appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
    166        appendTransitionEvent(eTransitionStart, intervalStartTime,
    167                              startTimeStamp);
    168        appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
    169      }
    170      break;
    171 
    172    case TransitionPhase::Pending:
    173    case TransitionPhase::Before:
    174      if (currentPhase == TransitionPhase::Active) {
    175        appendTransitionEvent(eTransitionStart, intervalStartTime,
    176                              startTimeStamp);
    177      } else if (currentPhase == TransitionPhase::After) {
    178        appendTransitionEvent(eTransitionStart, intervalStartTime,
    179                              startTimeStamp);
    180        appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
    181      }
    182      break;
    183 
    184    case TransitionPhase::Active:
    185      if (currentPhase == TransitionPhase::After) {
    186        appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
    187      } else if (currentPhase == TransitionPhase::Before) {
    188        appendTransitionEvent(eTransitionEnd, intervalStartTime,
    189                              startTimeStamp);
    190      }
    191      break;
    192 
    193    case TransitionPhase::After:
    194      if (currentPhase == TransitionPhase::Active) {
    195        appendTransitionEvent(eTransitionStart, intervalEndTime,
    196                              startTimeStamp);
    197      } else if (currentPhase == TransitionPhase::Before) {
    198        appendTransitionEvent(eTransitionStart, intervalEndTime,
    199                              startTimeStamp);
    200        appendTransitionEvent(eTransitionEnd, intervalStartTime, endTimeStamp);
    201      }
    202      break;
    203  }
    204  mPreviousTransitionPhase = currentPhase;
    205 
    206  if (!events.IsEmpty()) {
    207    presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
    208  }
    209 }
    210 
    211 void CSSTransition::Tick(TickState& aState) {
    212  Animation::Tick(aState);
    213  QueueEvents();
    214 }
    215 
    216 const CSSPropertyId& CSSTransition::TransitionProperty() const {
    217  MOZ_ASSERT(mTransitionProperty.IsValid(),
    218             "Transition property should be initialized");
    219  return mTransitionProperty;
    220 }
    221 
    222 AnimationValue CSSTransition::ToValue() const {
    223  MOZ_ASSERT(!mTransitionToValue.IsNull(),
    224             "Transition ToValue should be initialized");
    225  return mTransitionToValue;
    226 }
    227 
    228 int32_t CSSTransition::CompareCompositeOrder(
    229    const Maybe<EventContext>& aContext, const CSSTransition& aOther,
    230    const Maybe<EventContext>& aOtherContext,
    231    nsContentUtils::NodeIndexCache& aCache) const {
    232  MOZ_ASSERT((IsTiedToMarkup() || aContext) &&
    233                 (aOther.IsTiedToMarkup() || aOtherContext),
    234             "Should only be called for CSS transitions that are sorted "
    235             "as CSS transitions (i.e. tied to CSS markup) or with overridden "
    236             "target and animation index");
    237 
    238  // 0. Object-equality case
    239  if (&aOther == this) {
    240    return 0;
    241  }
    242 
    243  // 1. Sort by document order
    244  const OwningElementRef& owningElement1 =
    245      IsTiedToMarkup() ? mOwningElement : OwningElementRef(aContext->mTarget);
    246  const OwningElementRef& owningElement2 =
    247      aOther.IsTiedToMarkup() ? aOther.mOwningElement
    248                              : OwningElementRef(aOtherContext->mTarget);
    249  if (!owningElement1.Equals(owningElement2)) {
    250    return owningElement1.Compare(owningElement2, aCache);
    251  }
    252 
    253  // 2. (Same element and pseudo): Sort by transition generation
    254  const uint64_t index1 = IsTiedToMarkup() ? mAnimationIndex : aContext->mIndex;
    255  const uint64_t index2 =
    256      aOther.IsTiedToMarkup() ? aOther.mAnimationIndex : aOtherContext->mIndex;
    257  if (index1 != index2) {
    258    return index1 < index2 ? -1 : 1;
    259  }
    260 
    261  // 3. (Same transition generation): Sort by transition property
    262  if (mTransitionProperty == aOther.mTransitionProperty) {
    263    return 0;
    264  }
    265  nsAutoString name, otherName;
    266  GetTransitionProperty(name);
    267  aOther.GetTransitionProperty(otherName);
    268  return name < otherName ? -1 : 1;
    269 }
    270 
    271 /* static */
    272 Nullable<TimeDuration> CSSTransition::GetCurrentTimeAt(
    273    const AnimationTimeline& aTimeline, const TimeStamp& aBaseTime,
    274    const TimeDuration& aStartTime, double aPlaybackRate) {
    275  Nullable<TimeDuration> result;
    276 
    277  Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
    278  if (!timelineTime.IsNull()) {
    279    result.SetValue(
    280        (timelineTime.Value() - aStartTime).MultDouble(aPlaybackRate));
    281  }
    282 
    283  return result;
    284 }
    285 
    286 double CSSTransition::CurrentValuePortion() const {
    287  if (!GetEffect()) {
    288    return 0.0;
    289  }
    290 
    291  // Transitions use a fill mode of 'backwards' so GetComputedTiming will
    292  // never return a null time progress due to being *before* the animation
    293  // interval. However, it might be possible that we're behind on flushing
    294  // causing us to get called *after* the animation interval. So, just in
    295  // case, we override the fill mode to 'both' to ensure the progress
    296  // is never null.
    297  TimingParams timingToUse = GetEffect()->SpecifiedTiming();
    298  timingToUse.SetFill(dom::FillMode::Both);
    299  ComputedTiming computedTiming = GetEffect()->GetComputedTiming(&timingToUse);
    300 
    301  if (computedTiming.mProgress.IsNull()) {
    302    return 0.0;
    303  }
    304 
    305  // 'transition-timing-function' corresponds to the effect timing while
    306  // the transition keyframes have a linear timing function so we can ignore
    307  // them for the purposes of calculating the value portion.
    308  return computedTiming.mProgress.Value();
    309 }
    310 
    311 bool CSSTransition::UpdateStartValueFromReplacedTransition() {
    312  MOZ_ASSERT(mEffect && mEffect->AsKeyframeEffect() &&
    313                 mEffect->AsKeyframeEffect()->HasAnimationOfPropertySet(
    314                     nsCSSPropertyIDSet::CompositorAnimatables()),
    315             "Should be called for compositor-runnable transitions");
    316 
    317  if (!mReplacedTransition) {
    318    return false;
    319  }
    320 
    321  // We don't set |mReplacedTransition| if the timeline of this transition is
    322  // different from the document timeline. The timeline of Animation may be
    323  // null via script, so if it's null, it must be different from the document
    324  // timeline (because document timeline is readonly so we cannot change it by
    325  // script). Therefore, we check this assertion if mReplacedTransition is
    326  // valid.
    327  MOZ_ASSERT(mTimeline,
    328             "Should have a timeline if we are replacing transition start "
    329             "values");
    330 
    331  if (Maybe<double> valuePosition =
    332          ComputeTransformedProgress(*mTimeline, *mReplacedTransition)) {
    333    // FIXME: Bug 1634945. We may have to use the last value on the compositor
    334    // to replace the start value.
    335    const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
    336    const AnimationValue& replacedTo = mReplacedTransition->mToValue;
    337    AnimationValue startValue;
    338    startValue.mServo =
    339        Servo_AnimationValues_Interpolate(replacedFrom.mServo,
    340                                          replacedTo.mServo, *valuePosition)
    341            .Consume();
    342 
    343    mEffect->AsKeyframeEffect()->ReplaceTransitionStartValue(
    344        std::move(startValue));
    345  }
    346 
    347  mReplacedTransition.reset();
    348 
    349  return true;
    350 }
    351 
    352 /* static*/
    353 Maybe<double> CSSTransition::ComputeTransformedProgress(
    354    const AnimationTimeline& aTimeline,
    355    const ReplacedTransitionProperties& aProperties) {
    356  ComputedTiming computedTiming = AnimationEffect::GetComputedTimingAt(
    357      CSSTransition::GetCurrentTimeAt(aTimeline, TimeStamp::Now(),
    358                                      aProperties.mStartTime,
    359                                      aProperties.mPlaybackRate),
    360      aProperties.mTiming, aProperties.mPlaybackRate,
    361      Animation::ProgressTimelinePosition::NotBoundary);
    362  if (computedTiming.mProgress.IsNull()) {
    363    return Nothing();
    364  }
    365 
    366  return Some(StyleComputedTimingFunction::GetPortion(
    367      aProperties.mTimingFunction, computedTiming.mProgress.Value(),
    368      computedTiming.mBeforeFlag));
    369 }
    370 
    371 void CSSTransition::SetEffectFromStyle(KeyframeEffect* aEffect) {
    372  MOZ_ASSERT(aEffect->IsValidTransition());
    373 
    374  Animation::SetEffectNoUpdate(aEffect);
    375  mTransitionProperty = aEffect->Properties()[0].mProperty;
    376  mTransitionToValue = aEffect->Properties()[0].mSegments[0].mToValue;
    377 }
    378 
    379 }  // namespace mozilla::dom