AnimationEventDispatcher.cpp (8677B)
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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/AnimationEventDispatcher.h" 8 9 #include "mozilla/EventDispatcher.h" 10 #include "mozilla/dom/AnimationEffect.h" 11 #include "nsCSSProps.h" 12 #include "nsGlobalWindowInner.h" 13 #include "nsPresContext.h" 14 #include "nsRefreshDriver.h" 15 16 using namespace mozilla; 17 18 namespace geckoprofiler::markers { 19 20 struct CSSAnimationMarker { 21 static constexpr Span<const char> MarkerTypeName() { 22 return MakeStringSpan("CSSAnimation"); 23 } 24 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, 25 const nsCString& aName, 26 const nsCString& aTarget, 27 const nsCString& aProperties, 28 const nsCString& aOnCompositor) { 29 aWriter.StringProperty("Name", aName); 30 aWriter.StringProperty("Target", aTarget); 31 aWriter.StringProperty("properties", aProperties); 32 aWriter.StringProperty("oncompositor", aOnCompositor); 33 } 34 static MarkerSchema MarkerTypeDisplay() { 35 using MS = MarkerSchema; 36 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; 37 schema.AddKeyFormat("Name", MS::Format::String, 38 MS::PayloadFlags::Searchable); 39 schema.AddKeyLabelFormat("properties", "Animated Properties", 40 MS::Format::String); 41 schema.AddKeyLabelFormat("oncompositor", "Can Run on Compositor", 42 MS::Format::String); 43 schema.AddKeyFormat("Target", MS::Format::String); 44 schema.SetChartLabel("{marker.data.Name}"); 45 schema.SetTableLabel("{marker.data.Name}: {marker.data.properties}"); 46 return schema; 47 } 48 }; 49 50 struct CSSTransitionMarker { 51 static constexpr Span<const char> MarkerTypeName() { 52 return MakeStringSpan("CSSTransition"); 53 } 54 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, 55 const nsCString& aTarget, 56 const nsCString& aProperty, 57 bool aOnCompositor, bool aCanceled) { 58 aWriter.StringProperty("Target", aTarget); 59 aWriter.StringProperty("property", aProperty); 60 aWriter.BoolProperty("oncompositor", aOnCompositor); 61 if (aCanceled) { 62 aWriter.BoolProperty("Canceled", aCanceled); 63 } 64 } 65 static MarkerSchema MarkerTypeDisplay() { 66 using MS = MarkerSchema; 67 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; 68 schema.AddKeyLabelFormat("property", "Animated Property", 69 MS::Format::String); 70 schema.AddKeyLabelFormat("oncompositor", "Can Run on Compositor", 71 MS::Format::String); 72 schema.AddKeyFormat("Canceled", MS::Format::String); 73 schema.AddKeyFormat("Target", MS::Format::String); 74 schema.SetChartLabel("{marker.data.property}"); 75 schema.SetTableLabel("{marker.data.property}"); 76 return schema; 77 } 78 }; 79 80 } // namespace geckoprofiler::markers 81 82 namespace mozilla { 83 84 NS_IMPL_CYCLE_COLLECTION_CLASS(AnimationEventDispatcher) 85 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AnimationEventDispatcher) 86 tmp->ClearEventQueue(); 87 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 88 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEventDispatcher) 89 for (auto& info : tmp->mPendingEvents) { 90 if (OwningAnimationTarget* target = info.GetOwningAnimationTarget()) { 91 ImplCycleCollectionTraverse( 92 cb, target->mElement, 93 "mozilla::AnimationEventDispatcher.mPendingEvents.mTarget"); 94 } 95 ImplCycleCollectionTraverse( 96 cb, info.mAnimation, 97 "mozilla::AnimationEventDispatcher.mPendingEvents.mAnimation"); 98 } 99 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 100 101 void AnimationEventDispatcher::Disconnect() { 102 ClearEventQueue(); 103 mPresContext = nullptr; 104 } 105 106 void AnimationEventDispatcher::QueueEvent(AnimationEventInfo&& aEvent) { 107 const bool wasEmpty = mPendingEvents.IsEmpty(); 108 mPendingEvents.AppendElement(std::move(aEvent)); 109 mIsSorted = !wasEmpty; 110 if (wasEmpty) { 111 ScheduleDispatch(); 112 } 113 } 114 115 void AnimationEventDispatcher::QueueEvents( 116 nsTArray<AnimationEventInfo>&& aEvents) { 117 if (aEvents.IsEmpty()) { 118 return; 119 } 120 const bool wasEmpty = mPendingEvents.IsEmpty(); 121 mPendingEvents.AppendElements(std::move(aEvents)); 122 mIsSorted = false; 123 if (wasEmpty) { 124 ScheduleDispatch(); 125 } 126 } 127 128 void AnimationEventDispatcher::ScheduleDispatch() { 129 MOZ_ASSERT(mPresContext, "The pres context should be valid"); 130 mPresContext->RefreshDriver()->ScheduleRenderingPhase( 131 RenderingPhase::UpdateAnimationsAndSendEvents); 132 } 133 134 void AnimationEventInfo::MaybeAddMarker() const { 135 if (mData.is<CssAnimationData>()) { 136 const auto& data = mData.as<CssAnimationData>(); 137 const EventMessage message = data.mMessage; 138 if (message != eAnimationCancel && message != eAnimationEnd && 139 message != eAnimationIteration) { 140 return; 141 } 142 nsAutoCString name; 143 data.mAnimationName->ToUTF8String(name); 144 const TimeStamp startTime = [&] { 145 if (message == eAnimationIteration) { 146 if (auto* effect = mAnimation->GetEffect()) { 147 return mScheduledEventTimeStamp - 148 TimeDuration(effect->GetComputedTiming().mDuration); 149 } 150 } 151 return mScheduledEventTimeStamp - 152 TimeDuration::FromSeconds(data.mElapsedTime); 153 }(); 154 155 AnimatedPropertyIDSet propertySet; 156 nsAutoString target; 157 if (dom::AnimationEffect* effect = mAnimation->GetEffect()) { 158 if (dom::KeyframeEffect* keyFrameEffect = effect->AsKeyframeEffect()) { 159 keyFrameEffect->GetTarget()->Describe(target, true); 160 for (const AnimationProperty& property : keyFrameEffect->Properties()) { 161 propertySet.AddProperty(property.mProperty); 162 } 163 } 164 } 165 nsAutoCString properties; 166 nsAutoCString oncompositor; 167 for (const CSSPropertyId& property : propertySet) { 168 if (!properties.IsEmpty()) { 169 properties.AppendLiteral(", "); 170 oncompositor.AppendLiteral(", "); 171 } 172 nsAutoCString prop; 173 property.ToString(prop); 174 properties.Append(prop); 175 oncompositor.Append( 176 !property.IsCustom() && 177 nsCSSProps::PropHasFlags(property.mId, 178 CSSPropFlags::CanAnimateOnCompositor) 179 ? "true" 180 : "false"); 181 } 182 PROFILER_MARKER( 183 message == eAnimationIteration 184 ? ProfilerString8View("CSS animation iteration") 185 : ProfilerString8View("CSS animation"), 186 DOM, 187 MarkerOptions( 188 MarkerTiming::Interval(startTime, mScheduledEventTimeStamp), 189 mAnimation->GetOwnerWindow() 190 ? MarkerInnerWindowId(mAnimation->GetOwnerWindow()->WindowID()) 191 : MarkerInnerWindowId::NoId()), 192 CSSAnimationMarker, name, NS_ConvertUTF16toUTF8(target), properties, 193 oncompositor); 194 return; 195 } 196 197 if (!mData.is<CssTransitionData>()) { 198 return; 199 } 200 201 const auto& data = mData.as<CssTransitionData>(); 202 const EventMessage message = data.mMessage; 203 if (message != eTransitionEnd && message != eTransitionCancel) { 204 return; 205 } 206 207 nsAutoString target; 208 if (dom::AnimationEffect* effect = mAnimation->GetEffect()) { 209 if (dom::KeyframeEffect* keyFrameEffect = effect->AsKeyframeEffect()) { 210 keyFrameEffect->GetTarget()->Describe(target, true); 211 } 212 } 213 nsAutoCString property; 214 data.mProperty.ToString(property); 215 216 // FIXME: This doesn't _really_ reflect whether the animation is actually run 217 // in the compositor. The effect has that information and we should use it 218 // probably. 219 const bool onCompositor = 220 !data.mProperty.IsCustom() && 221 nsCSSProps::PropHasFlags(data.mProperty.mId, 222 CSSPropFlags::CanAnimateOnCompositor); 223 PROFILER_MARKER( 224 "CSS transition", DOM, 225 MarkerOptions( 226 MarkerTiming::Interval( 227 mScheduledEventTimeStamp - 228 TimeDuration::FromSeconds(data.mElapsedTime), 229 mScheduledEventTimeStamp), 230 mAnimation->GetOwnerWindow() 231 ? MarkerInnerWindowId(mAnimation->GetOwnerWindow()->WindowID()) 232 : MarkerInnerWindowId::NoId()), 233 CSSTransitionMarker, NS_ConvertUTF16toUTF8(target), property, 234 onCompositor, message == eTransitionCancel); 235 } 236 237 } // namespace mozilla