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