/src/mozilla-central/layout/style/nsAnimationManager.h
Line | Count | Source (jump to first uncovered line) |
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 | | #ifndef nsAnimationManager_h_ |
7 | | #define nsAnimationManager_h_ |
8 | | |
9 | | #include "mozilla/Attributes.h" |
10 | | #include "mozilla/ContentEvents.h" |
11 | | #include "mozilla/EventForwards.h" |
12 | | #include "AnimationCommon.h" |
13 | | #include "mozilla/dom/Animation.h" |
14 | | #include "mozilla/Keyframe.h" |
15 | | #include "mozilla/MemoryReporting.h" |
16 | | #include "mozilla/TimeStamp.h" |
17 | | #include "nsISupportsImpl.h" |
18 | | |
19 | | class nsIGlobalObject; |
20 | | class ServoComputedData; |
21 | | struct nsStyleDisplay; |
22 | | class ServoCSSAnimationBuilder; |
23 | | |
24 | | namespace mozilla { |
25 | | class ComputedStyle; |
26 | | namespace css { |
27 | | class Declaration; |
28 | | } /* namespace css */ |
29 | | namespace dom { |
30 | | class Promise; |
31 | | } /* namespace dom */ |
32 | | |
33 | | enum class CSSPseudoElementType : uint8_t; |
34 | | struct NonOwningAnimationTarget; |
35 | | |
36 | | namespace dom { |
37 | | |
38 | | class CSSAnimation final : public Animation |
39 | | { |
40 | | public: |
41 | | explicit CSSAnimation(nsIGlobalObject* aGlobal, |
42 | | nsAtom* aAnimationName) |
43 | | : dom::Animation(aGlobal) |
44 | | , mAnimationName(aAnimationName) |
45 | | , mIsStylePaused(false) |
46 | | , mPauseShouldStick(false) |
47 | | , mNeedsNewAnimationIndexWhenRun(false) |
48 | | , mPreviousPhase(ComputedTiming::AnimationPhase::Idle) |
49 | | , mPreviousIteration(0) |
50 | 0 | { |
51 | 0 | // We might need to drop this assertion once we add a script-accessible |
52 | 0 | // constructor but for animations generated from CSS markup the |
53 | 0 | // animation-name should never be empty. |
54 | 0 | MOZ_ASSERT(mAnimationName != nsGkAtoms::_empty, |
55 | 0 | "animation-name should not be 'none'"); |
56 | 0 | } |
57 | | |
58 | | JSObject* WrapObject(JSContext* aCx, |
59 | | JS::Handle<JSObject*> aGivenProto) override; |
60 | | |
61 | 0 | CSSAnimation* AsCSSAnimation() override { return this; } |
62 | 0 | const CSSAnimation* AsCSSAnimation() const override { return this; } |
63 | | |
64 | | // CSSAnimation interface |
65 | | void GetAnimationName(nsString& aRetVal) const |
66 | 0 | { |
67 | 0 | mAnimationName->ToString(aRetVal); |
68 | 0 | } |
69 | | |
70 | 0 | nsAtom* AnimationName() const { return mAnimationName; } |
71 | | |
72 | | // Animation interface overrides |
73 | | virtual Promise* GetReady(ErrorResult& aRv) override; |
74 | | virtual void Play(ErrorResult& aRv, LimitBehavior aLimitBehavior) override; |
75 | | virtual void Pause(ErrorResult& aRv) override; |
76 | | |
77 | | // NOTE: tabbrowser.xml currently relies on the fact that reading the |
78 | | // currentTime of a CSSAnimation does *not* flush style (whereas reading the |
79 | | // playState does). If CSS Animations 2 specifies that reading currentTime |
80 | | // also flushes style we will need to find another way to detect canceled |
81 | | // animations in tabbrowser.xml. On the other hand, if CSS Animations 2 |
82 | | // specifies that reading playState does *not* flush style (and we drop the |
83 | | // following override), then we should update tabbrowser.xml to check |
84 | | // the playState instead. |
85 | | AnimationPlayState PlayStateFromJS() const override; |
86 | | bool PendingFromJS() const override; |
87 | | void PlayFromJS(ErrorResult& aRv) override; |
88 | | |
89 | | void PlayFromStyle(); |
90 | | void PauseFromStyle(); |
91 | | void CancelFromStyle() override |
92 | 0 | { |
93 | 0 | // When an animation is disassociated with style it enters an odd state |
94 | 0 | // where its composite order is undefined until it first transitions |
95 | 0 | // out of the idle state. |
96 | 0 | // |
97 | 0 | // Even if the composite order isn't defined we don't want it to be random |
98 | 0 | // in case we need to determine the order to dispatch events associated |
99 | 0 | // with an animation in this state. To solve this we treat the animation as |
100 | 0 | // if it had been added to the end of the global animation list so that |
101 | 0 | // its sort order is defined. We'll update this index again once the |
102 | 0 | // animation leaves the idle state. |
103 | 0 | mAnimationIndex = sNextAnimationIndex++; |
104 | 0 | mNeedsNewAnimationIndexWhenRun = true; |
105 | 0 |
|
106 | 0 | Animation::CancelFromStyle(); |
107 | 0 |
|
108 | 0 | // We need to do this *after* calling CancelFromStyle() since |
109 | 0 | // CancelFromStyle might synchronously trigger a cancel event for which |
110 | 0 | // we need an owning element to target the event at. |
111 | 0 | mOwningElement = OwningElementRef(); |
112 | 0 | } |
113 | | |
114 | | void Tick() override; |
115 | | void QueueEvents(const StickyTimeDuration& aActiveTime = StickyTimeDuration()); |
116 | | |
117 | 0 | bool IsStylePaused() const { return mIsStylePaused; } |
118 | | |
119 | | bool HasLowerCompositeOrderThan(const CSSAnimation& aOther) const; |
120 | | |
121 | | void SetAnimationIndex(uint64_t aIndex) |
122 | 0 | { |
123 | 0 | MOZ_ASSERT(IsTiedToMarkup()); |
124 | 0 | if (IsRelevant() && |
125 | 0 | mAnimationIndex != aIndex) { |
126 | 0 | nsNodeUtils::AnimationChanged(this); |
127 | 0 | PostUpdate(); |
128 | 0 | } |
129 | 0 | mAnimationIndex = aIndex; |
130 | 0 | } |
131 | | |
132 | | // Sets the owning element which is used for determining the composite |
133 | | // order of CSSAnimation objects generated from CSS markup. |
134 | | // |
135 | | // @see mOwningElement |
136 | | void SetOwningElement(const OwningElementRef& aElement) |
137 | 0 | { |
138 | 0 | mOwningElement = aElement; |
139 | 0 | } |
140 | | // True for animations that are generated from CSS markup and continue to |
141 | | // reflect changes to that markup. |
142 | 0 | bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } |
143 | | |
144 | 0 | void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override { |
145 | 0 | QueueEvents(aActiveTime); |
146 | 0 | } |
147 | | |
148 | | protected: |
149 | | virtual ~CSSAnimation() |
150 | 0 | { |
151 | 0 | MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " |
152 | 0 | "before a CSS animation is destroyed"); |
153 | 0 | } |
154 | | |
155 | | // Animation overrides |
156 | | void UpdateTiming(SeekFlag aSeekFlag, |
157 | | SyncNotifyFlag aSyncNotifyFlag) override; |
158 | | |
159 | | // Returns the duration from the start of the animation's source effect's |
160 | | // active interval to the point where the animation actually begins playback. |
161 | | // This is zero unless the animation's source effect has a negative delay in |
162 | | // which case it is the absolute value of that delay. |
163 | | // This is used for setting the elapsedTime member of CSS AnimationEvents. |
164 | 0 | TimeDuration InitialAdvance() const { |
165 | 0 | return mEffect ? |
166 | 0 | std::max(TimeDuration(), mEffect->SpecifiedTiming().Delay() * -1) : |
167 | 0 | TimeDuration(); |
168 | 0 | } |
169 | | |
170 | | RefPtr<nsAtom> mAnimationName; |
171 | | |
172 | | // The (pseudo-)element whose computed animation-name refers to this |
173 | | // animation (if any). |
174 | | // |
175 | | // This is used for determining the relative composite order of animations |
176 | | // generated from CSS markup. |
177 | | // |
178 | | // Typically this will be the same as the target element of the keyframe |
179 | | // effect associated with this animation. However, it can differ in the |
180 | | // following circumstances: |
181 | | // |
182 | | // a) If script removes or replaces the effect of this animation, |
183 | | // b) If this animation is cancelled (e.g. by updating the |
184 | | // animation-name property or removing the owning element from the |
185 | | // document), |
186 | | // c) If this object is generated from script using the CSSAnimation |
187 | | // constructor. |
188 | | // |
189 | | // For (b) and (c) the owning element will return !IsSet(). |
190 | | OwningElementRef mOwningElement; |
191 | | |
192 | | // When combining animation-play-state with play() / pause() the following |
193 | | // behavior applies: |
194 | | // 1. pause() is sticky and always overrides the underlying |
195 | | // animation-play-state |
196 | | // 2. If animation-play-state is 'paused', play() will temporarily override |
197 | | // it until animation-play-state next becomes 'running'. |
198 | | // 3. Calls to play() trigger finishing behavior but setting the |
199 | | // animation-play-state to 'running' does not. |
200 | | // |
201 | | // This leads to five distinct states: |
202 | | // |
203 | | // A. Running |
204 | | // B. Running and temporarily overriding animation-play-state: paused |
205 | | // C. Paused and sticky overriding animation-play-state: running |
206 | | // D. Paused and sticky overriding animation-play-state: paused |
207 | | // E. Paused by animation-play-state |
208 | | // |
209 | | // C and D may seem redundant but they differ in how to respond to the |
210 | | // sequence: call play(), set animation-play-state: paused. |
211 | | // |
212 | | // C will transition to A then E leaving the animation paused. |
213 | | // D will transition to B then B leaving the animation running. |
214 | | // |
215 | | // A state transition chart is as follows: |
216 | | // |
217 | | // A | B | C | D | E |
218 | | // --------------------------- |
219 | | // play() A | B | A | B | B |
220 | | // pause() C | D | C | D | D |
221 | | // 'running' A | A | C | C | A |
222 | | // 'paused' E | B | D | D | E |
223 | | // |
224 | | // The base class, Animation already provides a boolean value, |
225 | | // mIsPaused which gives us two states. To this we add a further two booleans |
226 | | // to represent the states as follows. |
227 | | // |
228 | | // A. Running |
229 | | // (!mIsPaused; !mIsStylePaused; !mPauseShouldStick) |
230 | | // B. Running and temporarily overriding animation-play-state: paused |
231 | | // (!mIsPaused; mIsStylePaused; !mPauseShouldStick) |
232 | | // C. Paused and sticky overriding animation-play-state: running |
233 | | // (mIsPaused; !mIsStylePaused; mPauseShouldStick) |
234 | | // D. Paused and sticky overriding animation-play-state: paused |
235 | | // (mIsPaused; mIsStylePaused; mPauseShouldStick) |
236 | | // E. Paused by animation-play-state |
237 | | // (mIsPaused; mIsStylePaused; !mPauseShouldStick) |
238 | | // |
239 | | // (That leaves 3 combinations of the boolean values that we never set because |
240 | | // they don't represent valid states.) |
241 | | bool mIsStylePaused; |
242 | | bool mPauseShouldStick; |
243 | | |
244 | | // When true, indicates that when this animation next leaves the idle state, |
245 | | // its animation index should be updated. |
246 | | bool mNeedsNewAnimationIndexWhenRun; |
247 | | |
248 | | // Phase and current iteration from the previous time we queued events. |
249 | | // This is used to determine what new events to dispatch. |
250 | | ComputedTiming::AnimationPhase mPreviousPhase; |
251 | | uint64_t mPreviousIteration; |
252 | | }; |
253 | | |
254 | | } /* namespace dom */ |
255 | | |
256 | | template <> |
257 | | struct AnimationTypeTraits<dom::CSSAnimation> |
258 | | { |
259 | | static nsAtom* ElementPropertyAtom() |
260 | 0 | { |
261 | 0 | return nsGkAtoms::animationsProperty; |
262 | 0 | } |
263 | | static nsAtom* BeforePropertyAtom() |
264 | 0 | { |
265 | 0 | return nsGkAtoms::animationsOfBeforeProperty; |
266 | 0 | } |
267 | | static nsAtom* AfterPropertyAtom() |
268 | 0 | { |
269 | 0 | return nsGkAtoms::animationsOfAfterProperty; |
270 | 0 | } |
271 | | }; |
272 | | |
273 | | } /* namespace mozilla */ |
274 | | |
275 | | class nsAnimationManager final |
276 | | : public mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation> |
277 | | { |
278 | | public: |
279 | | explicit nsAnimationManager(nsPresContext *aPresContext) |
280 | | : mozilla::CommonAnimationManager<mozilla::dom::CSSAnimation>(aPresContext) |
281 | 0 | { |
282 | 0 | } |
283 | | |
284 | | typedef mozilla::AnimationCollection<mozilla::dom::CSSAnimation> |
285 | | CSSAnimationCollection; |
286 | | typedef nsTArray<RefPtr<mozilla::dom::CSSAnimation>> |
287 | | OwningCSSAnimationPtrArray; |
288 | | |
289 | 0 | ~nsAnimationManager() override = default; |
290 | | |
291 | | /** |
292 | | * This function does the same thing as the above UpdateAnimations() |
293 | | * but with servo's computed values. |
294 | | */ |
295 | | void UpdateAnimations( |
296 | | mozilla::dom::Element* aElement, |
297 | | mozilla::CSSPseudoElementType aPseudoType, |
298 | | const mozilla::ComputedStyle* aComputedValues); |
299 | | |
300 | | |
301 | | // Utility function to walk through |aIter| to find the Keyframe with |
302 | | // matching offset and timing function but stopping as soon as the offset |
303 | | // differs from |aOffset| (i.e. it assumes a sorted iterator). |
304 | | // |
305 | | // If a matching Keyframe is found, |
306 | | // Returns true and sets |aIndex| to the index of the matching Keyframe |
307 | | // within |aIter|. |
308 | | // |
309 | | // If no matching Keyframe is found, |
310 | | // Returns false and sets |aIndex| to the index in the iterator of the |
311 | | // first Keyframe with an offset differing to |aOffset| or, if the end |
312 | | // of the iterator is reached, sets |aIndex| to the index after the last |
313 | | // Keyframe. |
314 | | template <class IterType, class TimingFunctionType> |
315 | | static bool FindMatchingKeyframe( |
316 | | IterType&& aIter, |
317 | | double aOffset, |
318 | | const TimingFunctionType& aTimingFunctionToMatch, |
319 | | size_t& aIndex) |
320 | 0 | { |
321 | 0 | aIndex = 0; |
322 | 0 | for (mozilla::Keyframe& keyframe : aIter) { |
323 | 0 | if (keyframe.mOffset.value() != aOffset) { |
324 | 0 | break; |
325 | 0 | } |
326 | 0 | if (keyframe.mTimingFunction == aTimingFunctionToMatch) { |
327 | 0 | return true; |
328 | 0 | } |
329 | 0 | ++aIndex; |
330 | 0 | } |
331 | 0 | return false; |
332 | 0 | } Unexecuted instantiation: bool nsAnimationManager::FindMatchingKeyframe<nsTArray<mozilla::Keyframe>&, nsTimingFunction>(nsTArray<mozilla::Keyframe>&, double, nsTimingFunction const&, unsigned long&) Unexecuted instantiation: bool nsAnimationManager::FindMatchingKeyframe<mozilla::detail::IteratorRange<mozilla::ReverseIterator<mozilla::ArrayIterator<mozilla::Keyframe&, nsTArray<mozilla::Keyframe> > > >, nsTimingFunction>(mozilla::detail::IteratorRange<mozilla::ReverseIterator<mozilla::ArrayIterator<mozilla::Keyframe&, nsTArray<mozilla::Keyframe> > > >&&, double, nsTimingFunction const&, unsigned long&) |
333 | | |
334 | | bool AnimationMayBeReferenced(nsAtom* aName) const |
335 | 0 | { |
336 | 0 | return mMaybeReferencedAnimations.Contains(aName); |
337 | 0 | } |
338 | | |
339 | | private: |
340 | | // This includes all animation names referenced regardless of whether a |
341 | | // corresponding `@keyframes` rule is available. |
342 | | // |
343 | | // It may contain names which are no longer referenced, but it should always |
344 | | // contain names which are currently referenced, so that it is usable for |
345 | | // style invalidation. |
346 | | nsTHashtable<nsRefPtrHashKey<nsAtom>> mMaybeReferencedAnimations; |
347 | | |
348 | | void DoUpdateAnimations( |
349 | | const mozilla::NonOwningAnimationTarget& aTarget, |
350 | | const nsStyleDisplay& aStyleDisplay, |
351 | | ServoCSSAnimationBuilder& aBuilder); |
352 | | }; |
353 | | |
354 | | #endif /* !defined(nsAnimationManager_h_) */ |