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
));
50 property
!= eCSSPropertyExtra_variable
&&
51 nsCSSProps::PropHasFlags(property
,
52 CSSPropFlags::CanAnimateOnCompositor
)
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",
67 schema
.AddKeyLabelFormat("oncompositor", "Can Run on Compositor",
69 schema
.AddKeyFormat("Target", MS::Format::String
);
70 schema
.SetChartLabel("{marker.data.Name}");
72 "{marker.name} - {marker.data.Name}: {marker.data.properties}");
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
));
88 aProperty
!= eCSSPropertyExtra_variable
&&
89 nsCSSProps::PropHasFlags(aProperty
,
90 CSSPropFlags::CanAnimateOnCompositor
));
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",
100 schema
.AddKeyLabelFormat("oncompositor", "Can Run on Compositor",
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}");
110 } // namespace geckoprofiler::markers
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
;
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
;
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()) {
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
},
185 if (profiler_thread_is_being_profiled_for_markers()) {
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 */);
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
);
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
);
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
{
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
)
281 void QueueEvent(AnimationEventInfo
&& aEvent
);
282 void QueueEvents(nsTArray
<AnimationEventInfo
>&& aEvents
);
284 // This will call SortEvents automatically if it has not already been
286 void DispatchEvents() {
287 mIsObserving
= false;
288 if (!mPresContext
|| mPendingEvents
.IsEmpty()) {
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
308 void ClearEventQueue() {
309 mPendingEvents
.Clear();
312 bool HasQueuedEvents() const { return !mPendingEvents
.IsEmpty(); }
316 ~AnimationEventDispatcher() = default;
318 ~AnimationEventDispatcher() {
319 MOZ_ASSERT(!mIsObserving
,
320 "AnimationEventDispatcher should have disassociated from "
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
333 for (auto& pending
: mPendingEvents
) {
334 pending
.mAnimation
->CachedChildIndexRef().reset();
337 mPendingEvents
.StableSort();
340 void ScheduleDispatch();
342 nsPresContext
* mPresContext
;
343 using EventArray
= nsTArray
<AnimationEventInfo
>;
344 EventArray mPendingEvents
;
349 } // namespace mozilla
351 #endif // mozilla_AnimationEventDispatcher_h