Backed out 8 changesets (bug 1873776) for causing vendor failures. CLOSED TREE
[gecko.git] / dom / animation / AnimationEventDispatcher.h
blob15834d25eff887423f4392829048caae04e36af8
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/. */
7 #ifndef mozilla_AnimationEventDispatcher_h
8 #define mozilla_AnimationEventDispatcher_h
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/dom/KeyframeEffect.h"
20 #include "mozilla/ProfilerMarkers.h"
21 #include "nsCSSProps.h"
22 #include "nsCycleCollectionParticipant.h"
23 #include "nsPresContext.h"
25 class nsRefreshDriver;
27 namespace geckoprofiler::markers {
29 using namespace mozilla;
31 struct CSSAnimationMarker {
32 static constexpr Span<const char> MarkerTypeName() {
33 return MakeStringSpan("CSSAnimation");
35 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
36 const nsCString& aName,
37 const nsCString& aTarget,
38 nsCSSPropertyIDSet aPropertySet) {
39 aWriter.StringProperty("Name", aName);
40 aWriter.StringProperty("Target", aTarget);
41 nsAutoCString properties;
42 nsAutoCString oncompositor;
43 for (nsCSSPropertyID property : aPropertySet) {
44 if (!properties.IsEmpty()) {
45 properties.AppendLiteral(", ");
46 oncompositor.AppendLiteral(", ");
48 properties.Append(nsCSSProps::GetStringValue(property));
49 oncompositor.Append(
50 property != eCSSPropertyExtra_variable &&
51 nsCSSProps::PropHasFlags(property,
52 CSSPropFlags::CanAnimateOnCompositor)
53 ? "true"
54 : "false");
57 aWriter.StringProperty("properties", properties);
58 aWriter.StringProperty("oncompositor", oncompositor);
60 static MarkerSchema MarkerTypeDisplay() {
61 using MS = MarkerSchema;
62 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
63 schema.AddKeyFormatSearchable("Name", MS::Format::String,
64 MS::Searchable::Searchable);
65 schema.AddKeyLabelFormat("properties", "Animated Properties",
66 MS::Format::String);
67 schema.AddKeyLabelFormat("oncompositor", "Can Run on Compositor",
68 MS::Format::String);
69 schema.AddKeyFormat("Target", MS::Format::String);
70 schema.SetChartLabel("{marker.data.Name}");
71 schema.SetTableLabel(
72 "{marker.name} - {marker.data.Name}: {marker.data.properties}");
73 return schema;
77 struct CSSTransitionMarker {
78 static constexpr Span<const char> MarkerTypeName() {
79 return MakeStringSpan("CSSTransition");
81 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
82 const nsCString& aTarget,
83 nsCSSPropertyID aProperty, bool aCanceled) {
84 aWriter.StringProperty("Target", aTarget);
85 aWriter.StringProperty("property", nsCSSProps::GetStringValue(aProperty));
86 aWriter.BoolProperty(
87 "oncompositor",
88 aProperty != eCSSPropertyExtra_variable &&
89 nsCSSProps::PropHasFlags(aProperty,
90 CSSPropFlags::CanAnimateOnCompositor));
91 if (aCanceled) {
92 aWriter.BoolProperty("Canceled", aCanceled);
95 static MarkerSchema MarkerTypeDisplay() {
96 using MS = MarkerSchema;
97 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
98 schema.AddKeyLabelFormat("property", "Animated Property",
99 MS::Format::String);
100 schema.AddKeyLabelFormat("oncompositor", "Can Run on Compositor",
101 MS::Format::String);
102 schema.AddKeyFormat("Canceled", MS::Format::String);
103 schema.AddKeyFormat("Target", MS::Format::String);
104 schema.SetChartLabel("{marker.data.property}");
105 schema.SetTableLabel("{marker.name} - {marker.data.property}");
106 return schema;
110 } // namespace geckoprofiler::markers
112 namespace mozilla {
114 struct AnimationEventInfo {
115 struct CssAnimationOrTransitionData {
116 OwningAnimationTarget mTarget;
117 const EventMessage mMessage;
118 const double mElapsedTime;
119 // FIXME(emilio): is this needed? This preserves behavior from before
120 // bug 1847200, but it's unclear what the timeStamp of the event should be.
121 // See also https://github.com/w3c/csswg-drafts/issues/9167
122 const TimeStamp mEventEnqueueTimeStamp{TimeStamp::Now()};
125 struct CssAnimationData : public CssAnimationOrTransitionData {
126 const RefPtr<nsAtom> mAnimationName;
129 struct CssTransitionData : public CssAnimationOrTransitionData {
130 // For transition events only.
131 const AnimatedPropertyID mProperty;
134 struct WebAnimationData {
135 RefPtr<dom::AnimationPlaybackEvent> mEvent;
138 using Data = Variant<CssAnimationData, CssTransitionData, WebAnimationData>;
140 RefPtr<dom::Animation> mAnimation;
141 TimeStamp mScheduledEventTimeStamp;
142 Data mData;
144 OwningAnimationTarget* GetOwningAnimationTarget() {
145 if (mData.is<CssAnimationData>()) {
146 return &mData.as<CssAnimationData>().mTarget;
148 if (mData.is<CssTransitionData>()) {
149 return &mData.as<CssTransitionData>().mTarget;
151 return nullptr;
154 void MaybeAddMarker() const;
156 // For CSS animation events
157 AnimationEventInfo(RefPtr<nsAtom> aAnimationName,
158 const NonOwningAnimationTarget& aTarget,
159 EventMessage aMessage, double aElapsedTime,
160 const TimeStamp& aScheduledEventTimeStamp,
161 dom::Animation* aAnimation)
162 : mAnimation(aAnimation),
163 mScheduledEventTimeStamp(aScheduledEventTimeStamp),
164 mData(CssAnimationData{
165 {OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoType),
166 aMessage, aElapsedTime},
167 std::move(aAnimationName)}) {
168 if (profiler_thread_is_being_profiled_for_markers()) {
169 MaybeAddMarker();
173 // For CSS transition events
174 AnimationEventInfo(const AnimatedPropertyID& aProperty,
175 const NonOwningAnimationTarget& aTarget,
176 EventMessage aMessage, double aElapsedTime,
177 const TimeStamp& aScheduledEventTimeStamp,
178 dom::Animation* aAnimation)
179 : mAnimation(aAnimation),
180 mScheduledEventTimeStamp(aScheduledEventTimeStamp),
181 mData(CssTransitionData{
182 {OwningAnimationTarget(aTarget.mElement, aTarget.mPseudoType),
183 aMessage, aElapsedTime},
184 aProperty}) {
185 if (profiler_thread_is_being_profiled_for_markers()) {
186 MaybeAddMarker();
190 // For web animation events
191 AnimationEventInfo(RefPtr<dom::AnimationPlaybackEvent>&& aEvent,
192 TimeStamp&& aScheduledEventTimeStamp,
193 dom::Animation* aAnimation)
194 : mAnimation(aAnimation),
195 mScheduledEventTimeStamp(std::move(aScheduledEventTimeStamp)),
196 mData(WebAnimationData{std::move(aEvent)}) {}
198 AnimationEventInfo(const AnimationEventInfo& aOther) = delete;
199 AnimationEventInfo& operator=(const AnimationEventInfo& aOther) = delete;
201 AnimationEventInfo(AnimationEventInfo&& aOther) = default;
202 AnimationEventInfo& operator=(AnimationEventInfo&& aOther) = default;
204 bool operator<(const AnimationEventInfo& aOther) const {
205 if (this->mScheduledEventTimeStamp != aOther.mScheduledEventTimeStamp) {
206 // Null timestamps sort first
207 if (this->mScheduledEventTimeStamp.IsNull() ||
208 aOther.mScheduledEventTimeStamp.IsNull()) {
209 return this->mScheduledEventTimeStamp.IsNull();
211 return this->mScheduledEventTimeStamp < aOther.mScheduledEventTimeStamp;
214 // Events in the Web Animations spec are prior to CSS events.
215 if (this->IsWebAnimationEvent() != aOther.IsWebAnimationEvent()) {
216 return this->IsWebAnimationEvent();
219 AnimationPtrComparator<RefPtr<dom::Animation>> comparator;
220 return comparator.LessThan(this->mAnimation, aOther.mAnimation);
223 bool IsWebAnimationEvent() const { return mData.is<WebAnimationData>(); }
225 // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
226 MOZ_CAN_RUN_SCRIPT_BOUNDARY void Dispatch(nsPresContext* aPresContext) {
227 if (mData.is<WebAnimationData>()) {
228 RefPtr playbackEvent = mData.as<WebAnimationData>().mEvent;
229 RefPtr target = mAnimation;
230 EventDispatcher::DispatchDOMEvent(target, nullptr /* WidgetEvent */,
231 playbackEvent, aPresContext,
232 nullptr /* nsEventStatus */);
233 return;
236 if (mData.is<CssTransitionData>()) {
237 const auto& data = mData.as<CssTransitionData>();
238 nsPIDOMWindowInner* win =
239 data.mTarget.mElement->OwnerDoc()->GetInnerWindow();
240 if (win && !win->HasTransitionEventListeners()) {
241 MOZ_ASSERT(data.mMessage == eTransitionStart ||
242 data.mMessage == eTransitionRun ||
243 data.mMessage == eTransitionEnd ||
244 data.mMessage == eTransitionCancel);
245 return;
248 InternalTransitionEvent event(true, data.mMessage);
249 data.mProperty.ToString(event.mPropertyName);
250 event.mElapsedTime = data.mElapsedTime;
251 event.mPseudoElement =
252 nsCSSPseudoElements::PseudoTypeAsString(data.mTarget.mPseudoType);
253 event.AssignEventTime(WidgetEventTime(data.mEventEnqueueTimeStamp));
254 RefPtr target = data.mTarget.mElement;
255 EventDispatcher::Dispatch(target, aPresContext, &event);
256 return;
259 const auto& data = mData.as<CssAnimationData>();
260 InternalAnimationEvent event(true, data.mMessage);
261 data.mAnimationName->ToString(event.mAnimationName);
262 event.mElapsedTime = data.mElapsedTime;
263 event.mPseudoElement =
264 nsCSSPseudoElements::PseudoTypeAsString(data.mTarget.mPseudoType);
265 event.AssignEventTime(WidgetEventTime(data.mEventEnqueueTimeStamp));
266 RefPtr target = data.mTarget.mElement;
267 EventDispatcher::Dispatch(target, aPresContext, &event);
271 class AnimationEventDispatcher final {
272 public:
273 explicit AnimationEventDispatcher(nsPresContext* aPresContext)
274 : mPresContext(aPresContext), mIsSorted(true), mIsObserving(false) {}
276 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEventDispatcher)
277 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnimationEventDispatcher)
279 void Disconnect();
281 void QueueEvent(AnimationEventInfo&& aEvent);
282 void QueueEvents(nsTArray<AnimationEventInfo>&& aEvents);
284 // This will call SortEvents automatically if it has not already been
285 // called.
286 void DispatchEvents() {
287 mIsObserving = false;
288 if (!mPresContext || mPendingEvents.IsEmpty()) {
289 return;
292 SortEvents();
294 EventArray events = std::move(mPendingEvents);
295 // mIsSorted will be set to true by SortEvents above, and we leave it
296 // that way since mPendingEvents is now empty
297 for (AnimationEventInfo& info : events) {
298 info.Dispatch(mPresContext);
300 // Bail out if our mPresContext was nullified due to destroying the pres
301 // context.
302 if (!mPresContext) {
303 break;
308 void ClearEventQueue() {
309 mPendingEvents.Clear();
310 mIsSorted = true;
312 bool HasQueuedEvents() const { return !mPendingEvents.IsEmpty(); }
314 private:
315 #ifndef DEBUG
316 ~AnimationEventDispatcher() = default;
317 #else
318 ~AnimationEventDispatcher() {
319 MOZ_ASSERT(!mIsObserving,
320 "AnimationEventDispatcher should have disassociated from "
321 "nsRefreshDriver");
323 #endif
325 // Sort all pending CSS animation/transition events by scheduled event time
326 // and composite order.
327 // https://drafts.csswg.org/web-animations/#update-animations-and-send-events
328 void SortEvents() {
329 if (mIsSorted) {
330 return;
333 for (auto& pending : mPendingEvents) {
334 pending.mAnimation->CachedChildIndexRef().reset();
337 mPendingEvents.StableSort();
338 mIsSorted = true;
340 void ScheduleDispatch();
342 nsPresContext* mPresContext;
343 using EventArray = nsTArray<AnimationEventInfo>;
344 EventArray mPendingEvents;
345 bool mIsSorted;
346 bool mIsObserving;
349 } // namespace mozilla
351 #endif // mozilla_AnimationEventDispatcher_h