Line data Source code
1 : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #ifndef mozilla_css_AnimationCommon_h
7 : #define mozilla_css_AnimationCommon_h
8 :
9 : #include <algorithm> // For <std::stable_sort>
10 : #include "mozilla/AnimationCollection.h"
11 : #include "mozilla/AnimationComparator.h"
12 : #include "mozilla/EventDispatcher.h"
13 : #include "mozilla/LinkedList.h"
14 : #include "mozilla/MemoryReporting.h"
15 : #include "mozilla/dom/Animation.h"
16 : #include "mozilla/AnimationTarget.h"
17 : #include "mozilla/Attributes.h" // For MOZ_NON_OWNING_REF
18 : #include "mozilla/Assertions.h"
19 : #include "mozilla/TimingParams.h"
20 : #include "nsContentUtils.h"
21 : #include "nsCSSPseudoElements.h"
22 : #include "nsCycleCollectionParticipant.h"
23 :
24 : class nsIFrame;
25 : class nsPresContext;
26 :
27 : namespace mozilla {
28 : enum class CSSPseudoElementType : uint8_t;
29 :
30 : namespace dom {
31 : class Element;
32 : }
33 :
34 : template <class AnimationType>
35 : class CommonAnimationManager {
36 : public:
37 56 : explicit CommonAnimationManager(nsPresContext *aPresContext)
38 56 : : mPresContext(aPresContext)
39 : {
40 56 : }
41 :
42 : // NOTE: This can return null after Disconnect().
43 : nsPresContext* PresContext() const { return mPresContext; }
44 :
45 : /**
46 : * Notify the manager that the pres context is going away.
47 : */
48 8 : void Disconnect()
49 : {
50 : // Content nodes might outlive the transition or animation manager.
51 8 : RemoveAllElementCollections();
52 :
53 8 : mPresContext = nullptr;
54 8 : }
55 :
56 : /**
57 : * Stop animations on the element. This method takes the real element
58 : * rather than the element for the generated content for animations on
59 : * ::before and ::after.
60 : */
61 1205 : void StopAnimationsForElement(dom::Element* aElement,
62 : CSSPseudoElementType aPseudoType)
63 : {
64 1205 : MOZ_ASSERT(aElement);
65 : AnimationCollection<AnimationType>* collection =
66 : AnimationCollection<AnimationType>::GetAnimationCollection(aElement,
67 1205 : aPseudoType);
68 1205 : if (!collection) {
69 1205 : return;
70 : }
71 :
72 0 : nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
73 0 : collection->Destroy();
74 : }
75 :
76 : protected:
77 6 : virtual ~CommonAnimationManager()
78 : {
79 6 : MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
80 12 : }
81 :
82 2 : void AddElementCollection(AnimationCollection<AnimationType>* aCollection)
83 : {
84 2 : mElementCollections.insertBack(aCollection);
85 2 : }
86 8 : void RemoveAllElementCollections()
87 : {
88 8 : while (AnimationCollection<AnimationType>* head =
89 8 : mElementCollections.getFirst()) {
90 0 : head->Destroy(); // Note: this removes 'head' from mElementCollections.
91 : }
92 8 : }
93 :
94 : LinkedList<AnimationCollection<AnimationType>> mElementCollections;
95 : nsPresContext *mPresContext; // weak (non-null from ctor to Disconnect)
96 : };
97 :
98 : /**
99 : * Utility class for referencing the element that created a CSS animation or
100 : * transition. It is non-owning (i.e. it uses a raw pointer) since it is only
101 : * expected to be set by the owned animation while it actually being managed
102 : * by the owning element.
103 : *
104 : * This class also abstracts the comparison of an element/pseudo-class pair
105 : * for the sake of composite ordering since this logic is common to both CSS
106 : * animations and transitions.
107 : *
108 : * (We call this OwningElementRef instead of just OwningElement so that we can
109 : * call the getter on CSSAnimation/CSSTransition OwningElement() without
110 : * clashing with this object's contructor.)
111 : */
112 : class OwningElementRef final
113 : {
114 : public:
115 2 : OwningElementRef() = default;
116 :
117 : explicit OwningElementRef(const NonOwningAnimationTarget& aTarget)
118 : : mTarget(aTarget)
119 : { }
120 :
121 2 : OwningElementRef(dom::Element& aElement,
122 : CSSPseudoElementType aPseudoType)
123 2 : : mTarget(&aElement, aPseudoType)
124 2 : { }
125 :
126 3 : bool Equals(const OwningElementRef& aOther) const
127 : {
128 3 : return mTarget == aOther.mTarget;
129 : }
130 :
131 3 : bool LessThan(const OwningElementRef& aOther) const
132 : {
133 3 : MOZ_ASSERT(mTarget.mElement && aOther.mTarget.mElement,
134 : "Elements to compare should not be null");
135 :
136 3 : if (mTarget.mElement != aOther.mTarget.mElement) {
137 3 : return nsContentUtils::PositionIsBefore(mTarget.mElement,
138 6 : aOther.mTarget.mElement);
139 : }
140 :
141 0 : return mTarget.mPseudoType == CSSPseudoElementType::NotPseudo ||
142 0 : (mTarget.mPseudoType == CSSPseudoElementType::before &&
143 0 : aOther.mTarget.mPseudoType == CSSPseudoElementType::after);
144 : }
145 :
146 54 : bool IsSet() const { return !!mTarget.mElement; }
147 :
148 24 : void GetElement(dom::Element*& aElement,
149 : CSSPseudoElementType& aPseudoType) const
150 : {
151 24 : aElement = mTarget.mElement;
152 24 : aPseudoType = mTarget.mPseudoType;
153 24 : }
154 :
155 : private:
156 : NonOwningAnimationTarget mTarget;
157 : };
158 :
159 : template <class EventInfo>
160 6 : class DelayedEventDispatcher
161 : {
162 : public:
163 56 : DelayedEventDispatcher() : mIsSorted(true) { }
164 :
165 6 : void QueueEvent(EventInfo&& aEventInfo)
166 : {
167 6 : mPendingEvents.AppendElement(Forward<EventInfo>(aEventInfo));
168 6 : mIsSorted = false;
169 6 : }
170 :
171 : // This is exposed as a separate method so that when we are dispatching
172 : // *both* transition events and animation events we can sort both lists
173 : // once using the current state of the document before beginning any
174 : // dispatch.
175 87 : void SortEvents()
176 : {
177 87 : if (mIsSorted) {
178 84 : return;
179 : }
180 :
181 : // FIXME: Replace with mPendingEvents.StableSort when bug 1147091 is
182 : // fixed.
183 3 : std::stable_sort(mPendingEvents.begin(), mPendingEvents.end(),
184 : EventInfoLessThan());
185 3 : mIsSorted = true;
186 : }
187 :
188 : // Takes a reference to the owning manager's pres context so it can
189 : // detect if the pres context is destroyed while dispatching one of
190 : // the events.
191 : //
192 : // This will call SortEvents automatically if it has not already been
193 : // called.
194 84 : void DispatchEvents(nsPresContext* const & aPresContext)
195 : {
196 84 : if (!aPresContext || mPendingEvents.IsEmpty()) {
197 81 : return;
198 : }
199 :
200 3 : SortEvents();
201 :
202 6 : EventArray events;
203 3 : mPendingEvents.SwapElements(events);
204 : // mIsSorted will be set to true by SortEvents above, and we leave it
205 : // that way since mPendingEvents is now empty
206 9 : for (EventInfo& info : events) {
207 6 : EventDispatcher::Dispatch(info.mElement, aPresContext, &info.mEvent);
208 :
209 6 : if (!aPresContext) {
210 0 : break;
211 : }
212 : }
213 : }
214 :
215 8 : void ClearEventQueue()
216 : {
217 8 : mPendingEvents.Clear();
218 8 : mIsSorted = true;
219 8 : }
220 : bool HasQueuedEvents() const { return !mPendingEvents.IsEmpty(); }
221 :
222 : // Methods for supporting cycle-collection
223 0 : void Traverse(nsCycleCollectionTraversalCallback* aCallback,
224 : const char* aName)
225 : {
226 0 : for (EventInfo& info : mPendingEvents) {
227 0 : ImplCycleCollectionTraverse(*aCallback, info.mElement, aName);
228 0 : ImplCycleCollectionTraverse(*aCallback, info.mAnimation, aName);
229 : }
230 0 : }
231 0 : void Unlink() { ClearEventQueue(); }
232 :
233 : protected:
234 : class EventInfoLessThan
235 : {
236 : public:
237 3 : bool operator()(const EventInfo& a, const EventInfo& b) const
238 : {
239 3 : if (a.mTimeStamp != b.mTimeStamp) {
240 : // Null timestamps sort first
241 0 : if (a.mTimeStamp.IsNull() || b.mTimeStamp.IsNull()) {
242 0 : return a.mTimeStamp.IsNull();
243 : } else {
244 0 : return a.mTimeStamp < b.mTimeStamp;
245 : }
246 : }
247 :
248 : AnimationPtrComparator<RefPtr<dom::Animation>> comparator;
249 3 : return comparator.LessThan(a.mAnimation, b.mAnimation);
250 : }
251 : };
252 :
253 : typedef nsTArray<EventInfo> EventArray;
254 : EventArray mPendingEvents;
255 : bool mIsSorted;
256 : };
257 :
258 : template <class EventInfo>
259 : inline void
260 0 : ImplCycleCollectionUnlink(DelayedEventDispatcher<EventInfo>& aField)
261 : {
262 0 : aField.Unlink();
263 0 : }
264 :
265 : template <class EventInfo>
266 : inline void
267 0 : ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
268 : DelayedEventDispatcher<EventInfo>& aField,
269 : const char* aName,
270 : uint32_t aFlags = 0)
271 : {
272 0 : aField.Traverse(&aCallback, aName);
273 0 : }
274 :
275 : // Return the TransitionPhase or AnimationPhase to use when the animation
276 : // doesn't have a target effect.
277 : template <typename PhaseType>
278 0 : PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation)
279 : {
280 0 : MOZ_ASSERT(!aAnimation.GetEffect(),
281 : "Should only be called when we do not have an effect");
282 :
283 0 : Nullable<TimeDuration> currentTime = aAnimation.GetCurrentTime();
284 0 : if (currentTime.IsNull()) {
285 0 : return PhaseType::Idle;
286 : }
287 :
288 : // If we don't have a target effect, the duration will be zero so the phase is
289 : // 'before' if the current time is less than zero.
290 0 : return currentTime.Value() < TimeDuration()
291 0 : ? PhaseType::Before
292 0 : : PhaseType::After;
293 : };
294 :
295 : inline TimingParams
296 2 : TimingParamsFromCSSParams(float aDuration, float aDelay,
297 : float aIterationCount,
298 : dom::PlaybackDirection aDirection,
299 : dom::FillMode aFillMode)
300 : {
301 2 : MOZ_ASSERT(aIterationCount >= 0.0 && !IsNaN(aIterationCount),
302 : "aIterations should be nonnegative & finite, as ensured by "
303 : "CSSParser");
304 :
305 : return TimingParams {
306 : aDuration,
307 : aDelay,
308 : aIterationCount,
309 : aDirection,
310 : aFillMode
311 2 : };
312 : }
313 :
314 : } // namespace mozilla
315 :
316 : #endif /* !defined(mozilla_css_AnimationCommon_h) */
|