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 <algorithm>  // For <std::stable_sort>
11 #include "mozilla/AnimationComparator.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/Attributes.h"
14 #include "mozilla/ContentEvents.h"
15 #include "mozilla/EventDispatcher.h"
16 #include "mozilla/Variant.h"
17 #include "mozilla/dom/AnimationEffect.h"
18 #include "mozilla/dom/AnimationPlaybackEvent.h"
19 #include "mozilla/ProfilerMarkers.h"
20 #include "nsCSSProps.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsPresContext.h"
23 
24 class nsRefreshDriver;
25 
26 namespace mozilla {
27 
28 struct AnimationEventInfo {
29   RefPtr<dom::EventTarget> mTarget;
30   RefPtr<dom::Animation> mAnimation;
31   TimeStamp mScheduledEventTimeStamp;
32 
33   typedef Variant<InternalTransitionEvent, InternalAnimationEvent,
34                   RefPtr<dom::AnimationPlaybackEvent>>
35       EventVariant;
36   EventVariant mEvent;
37 
38   // For CSS animation events
AnimationEventInfoAnimationEventInfo39   AnimationEventInfo(nsAtom* aAnimationName,
40                      const NonOwningAnimationTarget& aTarget,
41                      EventMessage aMessage, double aElapsedTime,
42                      const TimeStamp& aScheduledEventTimeStamp,
43                      dom::Animation* aAnimation)
44       : mTarget(aTarget.mElement),
45         mAnimation(aAnimation),
46         mScheduledEventTimeStamp(aScheduledEventTimeStamp),
47         mEvent(EventVariant(InternalAnimationEvent(true, aMessage))) {
48     InternalAnimationEvent& event = mEvent.as<InternalAnimationEvent>();
49 
50     aAnimationName->ToString(event.mAnimationName);
51     // XXX Looks like nobody initialize WidgetEvent::time
52     event.mElapsedTime = aElapsedTime;
53     event.mPseudoElement =
54         nsCSSPseudoElements::PseudoTypeAsString(aTarget.mPseudoType);
55 
56     if ((aMessage == eAnimationCancel || aMessage == eAnimationEnd ||
57          aMessage == eAnimationIteration) &&
58         profiler_thread_is_being_profiled_for_markers()) {
59       nsAutoCString markerText;
60       aAnimationName->ToUTF8String(markerText);
61 
62       const TimeStamp startTime = [&] {
63         if (aMessage == eAnimationIteration) {
64           if (auto* effect = aAnimation->GetEffect()) {
65             return aScheduledEventTimeStamp -
66                    TimeDuration(effect->GetComputedTiming().mDuration);
67           }
68         }
69         return aScheduledEventTimeStamp -
70                TimeDuration::FromSeconds(aElapsedTime);
71       }();
72 
73       PROFILER_MARKER_TEXT(
74           aMessage == eAnimationIteration
75               ? ProfilerString8View("CSS animation iteration")
76               : ProfilerString8View("CSS animation"),
77           DOM,
78           MarkerOptions(
79               MarkerTiming::Interval(startTime, aScheduledEventTimeStamp),
80               aAnimation->GetOwner()
81                   ? MarkerInnerWindowId(aAnimation->GetOwner()->WindowID())
82                   : MarkerInnerWindowId::NoId()),
83           markerText);
84     }
85   }
86 
87   // For CSS transition events
AnimationEventInfoAnimationEventInfo88   AnimationEventInfo(nsCSSPropertyID aProperty,
89                      const NonOwningAnimationTarget& aTarget,
90                      EventMessage aMessage, double aElapsedTime,
91                      const TimeStamp& aScheduledEventTimeStamp,
92                      dom::Animation* aAnimation)
93       : mTarget(aTarget.mElement),
94         mAnimation(aAnimation),
95         mScheduledEventTimeStamp(aScheduledEventTimeStamp),
96         mEvent(EventVariant(InternalTransitionEvent(true, aMessage))) {
97     InternalTransitionEvent& event = mEvent.as<InternalTransitionEvent>();
98 
99     event.mPropertyName =
100         NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
101     // XXX Looks like nobody initialize WidgetEvent::time
102     event.mElapsedTime = aElapsedTime;
103     event.mPseudoElement =
104         nsCSSPseudoElements::PseudoTypeAsString(aTarget.mPseudoType);
105 
106     if ((aMessage == eTransitionEnd || aMessage == eTransitionCancel) &&
107         profiler_thread_is_being_profiled_for_markers()) {
108       nsAutoCString markerText;
109       markerText.Assign(nsCSSProps::GetStringValue(aProperty));
110       if (aMessage == eTransitionCancel) {
111         markerText.AppendLiteral(" (canceled)");
112       }
113 
114       PROFILER_MARKER_TEXT(
115           "CSS transition", DOM,
116           MarkerOptions(
117               MarkerTiming::Interval(
118                   aScheduledEventTimeStamp -
119                       TimeDuration::FromSeconds(aElapsedTime),
120                   aScheduledEventTimeStamp),
121               aAnimation->GetOwner()
122                   ? MarkerInnerWindowId(aAnimation->GetOwner()->WindowID())
123                   : MarkerInnerWindowId::NoId()),
124           markerText);
125     }
126   }
127 
128   // For web animation events
AnimationEventInfoAnimationEventInfo129   AnimationEventInfo(const nsAString& aName,
130                      RefPtr<dom::AnimationPlaybackEvent>&& aEvent,
131                      TimeStamp&& aScheduledEventTimeStamp,
132                      dom::Animation* aAnimation)
133       : mTarget(aAnimation),
134         mAnimation(aAnimation),
135         mScheduledEventTimeStamp(std::move(aScheduledEventTimeStamp)),
136         mEvent(std::move(aEvent)) {}
137 
138   AnimationEventInfo(const AnimationEventInfo& aOther) = delete;
139   AnimationEventInfo& operator=(const AnimationEventInfo& aOther) = delete;
140   AnimationEventInfo(AnimationEventInfo&& aOther) = default;
141   AnimationEventInfo& operator=(AnimationEventInfo&& aOther) = default;
142 
IsWebAnimationEventAnimationEventInfo143   bool IsWebAnimationEvent() const {
144     return mEvent.is<RefPtr<dom::AnimationPlaybackEvent>>();
145   }
146 
147 #ifdef DEBUG
IsStaleAnimationEventInfo148   bool IsStale() const {
149     const WidgetEvent* widgetEvent = AsWidgetEvent();
150     return widgetEvent->mFlags.mIsBeingDispatched ||
151            widgetEvent->mFlags.mDispatchedAtLeastOnce;
152   }
153 
AsWidgetEventAnimationEventInfo154   const WidgetEvent* AsWidgetEvent() const {
155     return const_cast<AnimationEventInfo*>(this)->AsWidgetEvent();
156   }
157 #endif
158 
AsWidgetEventAnimationEventInfo159   WidgetEvent* AsWidgetEvent() {
160     if (mEvent.is<InternalTransitionEvent>()) {
161       return &mEvent.as<InternalTransitionEvent>();
162     }
163     if (mEvent.is<InternalAnimationEvent>()) {
164       return &mEvent.as<InternalAnimationEvent>();
165     }
166     if (mEvent.is<RefPtr<dom::AnimationPlaybackEvent>>()) {
167       return mEvent.as<RefPtr<dom::AnimationPlaybackEvent>>()->WidgetEventPtr();
168     }
169 
170     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected event type");
171     return nullptr;
172   }
173 
174   // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
DispatchAnimationEventInfo175   MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch(nsPresContext* aPresContext) {
176     RefPtr<dom::EventTarget> target = mTarget;
177     if (mEvent.is<RefPtr<dom::AnimationPlaybackEvent>>()) {
178       auto playbackEvent = mEvent.as<RefPtr<dom::AnimationPlaybackEvent>>();
179       EventDispatcher::DispatchDOMEvent(target, nullptr /* WidgetEvent */,
180                                         playbackEvent, aPresContext,
181                                         nullptr /* nsEventStatus */);
182       return;
183     }
184 
185     MOZ_ASSERT(mEvent.is<InternalTransitionEvent>() ||
186                mEvent.is<InternalAnimationEvent>());
187 
188     EventDispatcher::Dispatch(target, aPresContext, AsWidgetEvent());
189   }
190 };
191 
192 class AnimationEventDispatcher final {
193  public:
AnimationEventDispatcher(nsPresContext * aPresContext)194   explicit AnimationEventDispatcher(nsPresContext* aPresContext)
195       : mPresContext(aPresContext), mIsSorted(true), mIsObserving(false) {}
196 
197   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEventDispatcher)
198   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnimationEventDispatcher)
199 
200   void Disconnect();
201 
202   void QueueEvent(AnimationEventInfo&& aEvent);
203   void QueueEvents(nsTArray<AnimationEventInfo>&& aEvents);
204 
205   // This will call SortEvents automatically if it has not already been
206   // called.
DispatchEvents()207   void DispatchEvents() {
208     mIsObserving = false;
209     if (!mPresContext || mPendingEvents.IsEmpty()) {
210       return;
211     }
212 
213     SortEvents();
214 
215     EventArray events = std::move(mPendingEvents);
216     // mIsSorted will be set to true by SortEvents above, and we leave it
217     // that way since mPendingEvents is now empty
218     for (AnimationEventInfo& info : events) {
219       MOZ_ASSERT(!info.IsStale(), "The event shouldn't be stale");
220       info.Dispatch(mPresContext);
221 
222       // Bail out if our mPresContext was nullified due to destroying the pres
223       // context.
224       if (!mPresContext) {
225         break;
226       }
227     }
228   }
229 
ClearEventQueue()230   void ClearEventQueue() {
231     mPendingEvents.Clear();
232     mIsSorted = true;
233   }
HasQueuedEvents()234   bool HasQueuedEvents() const { return !mPendingEvents.IsEmpty(); }
235 
236  private:
237 #ifndef DEBUG
238   ~AnimationEventDispatcher() = default;
239 #else
240   ~AnimationEventDispatcher() {
241     MOZ_ASSERT(!mIsObserving,
242                "AnimationEventDispatcher should have disassociated from "
243                "nsRefreshDriver");
244   }
245 #endif
246 
247   class AnimationEventInfoLessThan {
248    public:
operator()249     bool operator()(const AnimationEventInfo& a,
250                     const AnimationEventInfo& b) const {
251       if (a.mScheduledEventTimeStamp != b.mScheduledEventTimeStamp) {
252         // Null timestamps sort first
253         if (a.mScheduledEventTimeStamp.IsNull() ||
254             b.mScheduledEventTimeStamp.IsNull()) {
255           return a.mScheduledEventTimeStamp.IsNull();
256         } else {
257           return a.mScheduledEventTimeStamp < b.mScheduledEventTimeStamp;
258         }
259       }
260 
261       // Events in the Web Animations spec are prior to CSS events.
262       if (a.IsWebAnimationEvent() != b.IsWebAnimationEvent()) {
263         return a.IsWebAnimationEvent();
264       }
265 
266       AnimationPtrComparator<RefPtr<dom::Animation>> comparator;
267       return comparator.LessThan(a.mAnimation, b.mAnimation);
268     }
269   };
270 
271   // Sort all pending CSS animation/transition events by scheduled event time
272   // and composite order.
273   // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
SortEvents()274   void SortEvents() {
275     if (mIsSorted) {
276       return;
277     }
278 
279     for (auto& pending : mPendingEvents) {
280       pending.mAnimation->CachedChildIndexRef().reset();
281     }
282 
283     // FIXME: Replace with mPendingEvents.StableSort when bug 1147091 is
284     // fixed.
285     std::stable_sort(mPendingEvents.begin(), mPendingEvents.end(),
286                      AnimationEventInfoLessThan());
287     mIsSorted = true;
288   }
289   void ScheduleDispatch();
290 
291   nsPresContext* mPresContext;
292   typedef nsTArray<AnimationEventInfo> EventArray;
293   EventArray mPendingEvents;
294   bool mIsSorted;
295   bool mIsObserving;
296 };
297 
298 }  // namespace mozilla
299 
300 #endif  // mozilla_AnimationEventDispatcher_h
301