tor-browser

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

AnimationEventDispatcher.h (11822B)


      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 #ifndef mozilla_AnimationEventDispatcher_h
      8 #define mozilla_AnimationEventDispatcher_h
      9 
     10 #include "mozilla/AnimationComparator.h"
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/Attributes.h"
     13 #include "mozilla/ContentEvents.h"
     14 #include "mozilla/EventDispatcher.h"
     15 #include "mozilla/EventListenerManager.h"
     16 #include "mozilla/ProfilerMarkers.h"
     17 #include "mozilla/Variant.h"
     18 #include "mozilla/dom/AnimationPlaybackEvent.h"
     19 #include "mozilla/dom/Document.h"
     20 #include "mozilla/dom/KeyframeEffect.h"
     21 #include "nsCycleCollectionParticipant.h"
     22 #include "nsPresContext.h"
     23 
     24 class nsRefreshDriver;
     25 
     26 namespace mozilla {
     27 
     28 struct AnimationEventInfo {
     29  struct CssAnimationOrTransitionData {
     30    OwningAnimationTarget mTarget;
     31    const EventMessage mMessage;
     32    const double mElapsedTime;
     33    // The transition generation or animation relative position in the global
     34    // animation list. We use this information to determine the order of
     35    // cancelled transitions or animations. (i.e. We override the animation
     36    // index of the cancelled transitions/animations because their animation
     37    // indexes have been changed.)
     38    const uint64_t mAnimationIndex;
     39    // FIXME(emilio): is this needed? This preserves behavior from before
     40    // bug 1847200, but it's unclear what the timeStamp of the event should be.
     41    // See also https://github.com/w3c/csswg-drafts/issues/9167
     42    const TimeStamp mEventEnqueueTimeStamp{TimeStamp::Now()};
     43  };
     44 
     45  struct CssAnimationData : public CssAnimationOrTransitionData {
     46    const RefPtr<nsAtom> mAnimationName;
     47  };
     48 
     49  struct CssTransitionData : public CssAnimationOrTransitionData {
     50    // For transition events only.
     51    const CSSPropertyId mProperty;
     52  };
     53 
     54  struct WebAnimationData {
     55    const RefPtr<nsAtom> mOnEvent;
     56    const dom::Nullable<double> mCurrentTime;
     57    const dom::Nullable<double> mTimelineTime;
     58    const TimeStamp mEventEnqueueTimeStamp{TimeStamp::Now()};
     59  };
     60 
     61  using Data = Variant<CssAnimationData, CssTransitionData, WebAnimationData>;
     62 
     63  RefPtr<dom::Animation> mAnimation;
     64  TimeStamp mScheduledEventTimeStamp;
     65  Data mData;
     66 
     67  OwningAnimationTarget* GetOwningAnimationTarget() {
     68    if (mData.is<CssAnimationData>()) {
     69      return &mData.as<CssAnimationData>().mTarget;
     70    }
     71    if (mData.is<CssTransitionData>()) {
     72      return &mData.as<CssTransitionData>().mTarget;
     73    }
     74    return nullptr;
     75  }
     76 
     77  // Return the event context if the event is animationcancel or
     78  // transitioncancel.
     79  Maybe<dom::Animation::EventContext> GetEventContext() const {
     80    if (mData.is<CssAnimationData>()) {
     81      const auto& data = mData.as<CssAnimationData>();
     82      return Some(dom::Animation::EventContext{
     83          NonOwningAnimationTarget(data.mTarget), data.mAnimationIndex});
     84    }
     85    if (mData.is<CssTransitionData>()) {
     86      const auto& data = mData.as<CssTransitionData>();
     87      return Some(dom::Animation::EventContext{
     88          NonOwningAnimationTarget(data.mTarget), data.mAnimationIndex});
     89    }
     90    return Nothing();
     91  }
     92 
     93  void MaybeAddMarker() const;
     94 
     95  // For CSS animation events
     96  AnimationEventInfo(RefPtr<nsAtom> aAnimationName,
     97                     const NonOwningAnimationTarget& aTarget,
     98                     EventMessage aMessage, double aElapsedTime,
     99                     uint64_t aAnimationIndex,
    100                     const TimeStamp& aScheduledEventTimeStamp,
    101                     dom::Animation* aAnimation)
    102      : mAnimation(aAnimation),
    103        mScheduledEventTimeStamp(aScheduledEventTimeStamp),
    104        mData(CssAnimationData{
    105            {OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoRequest),
    106             aMessage, aElapsedTime, aAnimationIndex},
    107            std::move(aAnimationName)}) {
    108    if (profiler_thread_is_being_profiled_for_markers()) {
    109      MaybeAddMarker();
    110    }
    111  }
    112 
    113  // For CSS transition events
    114  AnimationEventInfo(const CSSPropertyId& aProperty,
    115                     const NonOwningAnimationTarget& aTarget,
    116                     EventMessage aMessage, double aElapsedTime,
    117                     uint64_t aTransitionGeneration,
    118                     const TimeStamp& aScheduledEventTimeStamp,
    119                     dom::Animation* aAnimation)
    120      : mAnimation(aAnimation),
    121        mScheduledEventTimeStamp(aScheduledEventTimeStamp),
    122        mData(CssTransitionData{
    123            {OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoRequest),
    124             aMessage, aElapsedTime, aTransitionGeneration},
    125            aProperty}) {
    126    if (profiler_thread_is_being_profiled_for_markers()) {
    127      MaybeAddMarker();
    128    }
    129  }
    130 
    131  // For web animation events
    132  AnimationEventInfo(nsAtom* aOnEvent,
    133                     const dom::Nullable<double>& aCurrentTime,
    134                     const dom::Nullable<double>& aTimelineTime,
    135                     TimeStamp&& aScheduledEventTimeStamp,
    136                     dom::Animation* aAnimation)
    137      : mAnimation(aAnimation),
    138        mScheduledEventTimeStamp(std::move(aScheduledEventTimeStamp)),
    139        mData(WebAnimationData{RefPtr{aOnEvent}, aCurrentTime, aTimelineTime}) {
    140  }
    141 
    142  AnimationEventInfo(const AnimationEventInfo& aOther) = delete;
    143  AnimationEventInfo& operator=(const AnimationEventInfo& aOther) = delete;
    144 
    145  AnimationEventInfo(AnimationEventInfo&& aOther) = default;
    146  AnimationEventInfo& operator=(AnimationEventInfo&& aOther) = default;
    147 
    148  int32_t Compare(const AnimationEventInfo& aOther,
    149                  nsContentUtils::NodeIndexCache& aCache) const {
    150    if (mScheduledEventTimeStamp != aOther.mScheduledEventTimeStamp) {
    151      // Null timestamps sort first
    152      if (mScheduledEventTimeStamp.IsNull()) {
    153        return -1;
    154      }
    155      if (aOther.mScheduledEventTimeStamp.IsNull()) {
    156        return 1;
    157      }
    158      return mScheduledEventTimeStamp < aOther.mScheduledEventTimeStamp ? -1
    159                                                                        : 1;
    160    }
    161 
    162    // Events in the Web Animations spec are prior to CSS events.
    163    if (IsWebAnimationEvent() != aOther.IsWebAnimationEvent()) {
    164      return IsWebAnimationEvent() ? -1 : 1;
    165    }
    166 
    167    return mAnimation->CompareCompositeOrder(GetEventContext(),
    168                                             *aOther.mAnimation,
    169                                             aOther.GetEventContext(), aCache);
    170  }
    171 
    172  bool IsWebAnimationEvent() const { return mData.is<WebAnimationData>(); }
    173 
    174  // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
    175  MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch(nsPresContext* aPresContext) {
    176    if (mData.is<WebAnimationData>()) {
    177      const auto& data = mData.as<WebAnimationData>();
    178      EventListenerManager* elm = mAnimation->GetExistingListenerManager();
    179      if (!elm || !elm->HasListenersFor(data.mOnEvent)) {
    180        return;
    181      }
    182 
    183      dom::AnimationPlaybackEventInit init;
    184      init.mCurrentTime = data.mCurrentTime;
    185      init.mTimelineTime = data.mTimelineTime;
    186      MOZ_ASSERT(nsDependentAtomString(data.mOnEvent).Find(u"on"_ns) == 0,
    187                 "mOnEvent atom should start with 'on'!");
    188      RefPtr<dom::AnimationPlaybackEvent> event =
    189          dom::AnimationPlaybackEvent::Constructor(
    190              mAnimation, Substring(nsDependentAtomString(data.mOnEvent), 2),
    191              init);
    192      event->SetTrusted(true);
    193      event->WidgetEventPtr()->AssignEventTime(
    194          WidgetEventTime(data.mEventEnqueueTimeStamp));
    195      RefPtr target = mAnimation;
    196      EventDispatcher::DispatchDOMEvent(target, nullptr /* WidgetEvent */,
    197                                        event, aPresContext,
    198                                        nullptr /* nsEventStatus */);
    199      return;
    200    }
    201 
    202    if (mData.is<CssTransitionData>()) {
    203      const auto& data = mData.as<CssTransitionData>();
    204      nsPIDOMWindowInner* win =
    205          data.mTarget.mElement->OwnerDoc()->GetInnerWindow();
    206      if (win && !win->HasTransitionEventListeners()) {
    207        MOZ_ASSERT(data.mMessage == eTransitionStart ||
    208                   data.mMessage == eTransitionRun ||
    209                   data.mMessage == eTransitionEnd ||
    210                   data.mMessage == eTransitionCancel);
    211        return;
    212      }
    213 
    214      InternalTransitionEvent event(true, data.mMessage);
    215      data.mProperty.ToString(event.mPropertyName);
    216      event.mElapsedTime = data.mElapsedTime;
    217      event.mPseudoElement = nsCSSPseudoElements::PseudoRequestAsString(
    218          data.mTarget.mPseudoRequest);
    219      event.AssignEventTime(WidgetEventTime(data.mEventEnqueueTimeStamp));
    220      RefPtr target = data.mTarget.mElement;
    221      EventDispatcher::Dispatch(target, aPresContext, &event);
    222      return;
    223    }
    224 
    225    const auto& data = mData.as<CssAnimationData>();
    226    InternalAnimationEvent event(true, data.mMessage);
    227    data.mAnimationName->ToString(event.mAnimationName);
    228    event.mElapsedTime = data.mElapsedTime;
    229    event.mPseudoElement =
    230        nsCSSPseudoElements::PseudoRequestAsString(data.mTarget.mPseudoRequest);
    231    event.AssignEventTime(WidgetEventTime(data.mEventEnqueueTimeStamp));
    232    RefPtr target = data.mTarget.mElement;
    233    EventDispatcher::Dispatch(target, aPresContext, &event);
    234  }
    235 };
    236 
    237 class AnimationEventDispatcher final {
    238 public:
    239  explicit AnimationEventDispatcher(nsPresContext* aPresContext)
    240      : mPresContext(aPresContext), mIsSorted(true) {}
    241 
    242  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEventDispatcher)
    243  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnimationEventDispatcher)
    244 
    245  void Disconnect();
    246 
    247  void QueueEvent(AnimationEventInfo&& aEvent);
    248  void QueueEvents(nsTArray<AnimationEventInfo>&& aEvents);
    249 
    250  // This will call SortEvents automatically if it has not already been
    251  // called.
    252  void DispatchEvents() {
    253    if (!mPresContext || mPendingEvents.IsEmpty()) {
    254      return;
    255    }
    256 
    257    SortEvents();
    258 
    259    EventArray events = std::move(mPendingEvents);
    260    // mIsSorted will be set to true by SortEvents above, and we leave it
    261    // that way since mPendingEvents is now empty
    262    for (AnimationEventInfo& info : events) {
    263      info.Dispatch(mPresContext);
    264 
    265      // Bail out if our mPresContext was nullified due to destroying the pres
    266      // context.
    267      if (!mPresContext) {
    268        break;
    269      }
    270    }
    271  }
    272 
    273  void ClearEventQueue() {
    274    mPendingEvents.Clear();
    275    mIsSorted = true;
    276  }
    277  bool HasQueuedEvents() const { return !mPendingEvents.IsEmpty(); }
    278 
    279  // There shouldn't be a lot of events in the queue, so linear search should be
    280  // fine.
    281  bool HasQueuedEventsFor(const dom::Animation* aAnimation) const {
    282    for (const AnimationEventInfo& info : mPendingEvents) {
    283      if (info.mAnimation.get() == aAnimation) {
    284        return true;
    285      }
    286    }
    287    return false;
    288  }
    289 
    290 private:
    291  ~AnimationEventDispatcher() = default;
    292 
    293  // Sort all pending CSS animation/transition events by scheduled event time
    294  // and composite order.
    295  // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
    296  void SortEvents() {
    297    if (mIsSorted) {
    298      return;
    299    }
    300 
    301    struct AnimationEventInfoComparator {
    302      mutable nsContentUtils::NodeIndexCache mCache;
    303 
    304      bool LessThan(const AnimationEventInfo& aOne,
    305                    const AnimationEventInfo& aOther) const {
    306        return aOne.Compare(aOther, mCache) < 0;
    307      }
    308    };
    309 
    310    mPendingEvents.StableSort(AnimationEventInfoComparator());
    311    mIsSorted = true;
    312  }
    313  void ScheduleDispatch();
    314 
    315  nsPresContext* mPresContext;
    316  using EventArray = nsTArray<AnimationEventInfo>;
    317  EventArray mPendingEvents;
    318  bool mIsSorted;
    319 };
    320 
    321 }  // namespace mozilla
    322 
    323 #endif  // mozilla_AnimationEventDispatcher_h