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