/src/mozilla-central/layout/style/nsTransitionManager.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 | | |
7 | | /* Code to start and animate CSS transitions. */ |
8 | | |
9 | | #ifndef nsTransitionManager_h_ |
10 | | #define nsTransitionManager_h_ |
11 | | |
12 | | #include "mozilla/ComputedTiming.h" |
13 | | #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel |
14 | | #include "mozilla/dom/Animation.h" |
15 | | #include "mozilla/dom/KeyframeEffect.h" |
16 | | #include "AnimationCommon.h" |
17 | | #include "nsISupportsImpl.h" |
18 | | |
19 | | class nsIGlobalObject; |
20 | | class nsPresContext; |
21 | | class nsCSSPropertyIDSet; |
22 | | |
23 | | namespace mozilla { |
24 | | class ComputedStyle; |
25 | | enum class CSSPseudoElementType : uint8_t; |
26 | | struct Keyframe; |
27 | | struct StyleTransition; |
28 | | } // namespace mozilla |
29 | | |
30 | | /***************************************************************************** |
31 | | * Per-Element data * |
32 | | *****************************************************************************/ |
33 | | |
34 | | namespace mozilla { |
35 | | |
36 | | struct ElementPropertyTransition : public dom::KeyframeEffect |
37 | | { |
38 | | ElementPropertyTransition(nsIDocument* aDocument, |
39 | | Maybe<OwningAnimationTarget>& aTarget, |
40 | | const TimingParams &aTiming, |
41 | | AnimationValue aStartForReversingTest, |
42 | | double aReversePortion, |
43 | | const KeyframeEffectParams& aEffectOptions) |
44 | | : dom::KeyframeEffect(aDocument, aTarget, aTiming, aEffectOptions) |
45 | | , mStartForReversingTest(aStartForReversingTest) |
46 | | , mReversePortion(aReversePortion) |
47 | 0 | { } |
48 | | |
49 | 0 | ElementPropertyTransition* AsTransition() override { return this; } |
50 | | const ElementPropertyTransition* AsTransition() const override |
51 | 0 | { |
52 | 0 | return this; |
53 | 0 | } |
54 | | |
55 | 0 | nsCSSPropertyID TransitionProperty() const { |
56 | 0 | MOZ_ASSERT(mKeyframes.Length() == 2, |
57 | 0 | "Transitions should have exactly two animation keyframes. " |
58 | 0 | "Perhaps we are using an un-initialized transition?"); |
59 | 0 | MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1, |
60 | 0 | "Transitions should have exactly one property in their first " |
61 | 0 | "frame"); |
62 | 0 | return mKeyframes[0].mPropertyValues[0].mProperty; |
63 | 0 | } |
64 | | |
65 | 0 | AnimationValue ToValue() const { |
66 | 0 | // If we failed to generate properties from the transition frames, |
67 | 0 | // return a null value but also show a warning since we should be |
68 | 0 | // detecting that kind of situation in advance and not generating a |
69 | 0 | // transition in the first place. |
70 | 0 | if (mProperties.Length() < 1 || |
71 | 0 | mProperties[0].mSegments.Length() < 1) { |
72 | 0 | NS_WARNING("Failed to generate transition property values"); |
73 | 0 | return AnimationValue(); |
74 | 0 | } |
75 | 0 | return mProperties[0].mSegments[0].mToValue; |
76 | 0 | } |
77 | | |
78 | | // This is the start value to be used for a check for whether a |
79 | | // transition is being reversed. Normally the same as |
80 | | // mProperties[0].mSegments[0].mFromValue, except when this transition |
81 | | // started as the reversal of another in-progress transition. |
82 | | // Needed so we can handle two reverses in a row. |
83 | | AnimationValue mStartForReversingTest; |
84 | | // Likewise, the portion (in value space) of the "full" reversed |
85 | | // transition that we're actually covering. For example, if a :hover |
86 | | // effect has a transition that moves the element 10px to the right |
87 | | // (by changing 'left' from 0px to 10px), and the mouse moves in to |
88 | | // the element (starting the transition) but then moves out after the |
89 | | // transition has advanced 4px, the second transition (from 10px/4px |
90 | | // to 0px) will have mReversePortion of 0.4. (If the mouse then moves |
91 | | // in again when the transition is back to 2px, the mReversePortion |
92 | | // for the third transition (from 0px/2px to 10px) will be 0.8. |
93 | | double mReversePortion; |
94 | | |
95 | | // Compute the portion of the *value* space that we should be through |
96 | | // at the current time. (The input to the transition timing function |
97 | | // has time units, the output has value units.) |
98 | | double CurrentValuePortion() const; |
99 | | |
100 | | // For a new transition interrupting an existing transition on the |
101 | | // compositor, update the start value to match the value of the replaced |
102 | | // transitions at the current time. |
103 | | void UpdateStartValueFromReplacedTransition(); |
104 | | |
105 | | struct ReplacedTransitionProperties { |
106 | | TimeDuration mStartTime; |
107 | | double mPlaybackRate; |
108 | | TimingParams mTiming; |
109 | | Maybe<ComputedTimingFunction> mTimingFunction; |
110 | | AnimationValue mFromValue, mToValue; |
111 | | }; |
112 | | Maybe<ReplacedTransitionProperties> mReplacedTransition; |
113 | | }; |
114 | | |
115 | | namespace dom { |
116 | | |
117 | | class CSSTransition final : public Animation |
118 | | { |
119 | | public: |
120 | | explicit CSSTransition(nsIGlobalObject* aGlobal) |
121 | | : dom::Animation(aGlobal) |
122 | | , mPreviousTransitionPhase(TransitionPhase::Idle) |
123 | | , mNeedsNewAnimationIndexWhenRun(false) |
124 | | , mTransitionProperty(eCSSProperty_UNKNOWN) |
125 | 0 | { |
126 | 0 | } |
127 | | |
128 | | JSObject* WrapObject(JSContext* aCx, |
129 | | JS::Handle<JSObject*> aGivenProto) override; |
130 | | |
131 | 0 | CSSTransition* AsCSSTransition() override { return this; } |
132 | 0 | const CSSTransition* AsCSSTransition() const override { return this; } |
133 | | |
134 | | // CSSTransition interface |
135 | | void GetTransitionProperty(nsString& aRetVal) const; |
136 | | |
137 | | // Animation interface overrides |
138 | | AnimationPlayState PlayStateFromJS() const override; |
139 | | bool PendingFromJS() const override; |
140 | | void PlayFromJS(ErrorResult& aRv) override; |
141 | | |
142 | | // A variant of Play() that avoids posting style updates since this method |
143 | | // is expected to be called whilst already updating style. |
144 | | void PlayFromStyle() |
145 | 0 | { |
146 | 0 | ErrorResult rv; |
147 | 0 | PlayNoUpdate(rv, Animation::LimitBehavior::Continue); |
148 | 0 | // play() should not throw when LimitBehavior is Continue |
149 | 0 | MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing transition"); |
150 | 0 | } |
151 | | |
152 | | void CancelFromStyle() override |
153 | 0 | { |
154 | 0 | // The animation index to use for compositing will be established when |
155 | 0 | // this transition next transitions out of the idle state but we still |
156 | 0 | // update it now so that the sort order of this transition remains |
157 | 0 | // defined until that moment. |
158 | 0 | // |
159 | 0 | // See longer explanation in CSSAnimation::CancelFromStyle. |
160 | 0 | mAnimationIndex = sNextAnimationIndex++; |
161 | 0 | mNeedsNewAnimationIndexWhenRun = true; |
162 | 0 |
|
163 | 0 | Animation::CancelFromStyle(); |
164 | 0 |
|
165 | 0 | // It is important we do this *after* calling CancelFromStyle(). |
166 | 0 | // This is because CancelFromStyle() will end up posting a restyle and |
167 | 0 | // that restyle should target the *transitions* level of the cascade. |
168 | 0 | // However, once we clear the owning element, CascadeLevel() will begin |
169 | 0 | // returning CascadeLevel::Animations. |
170 | 0 | mOwningElement = OwningElementRef(); |
171 | 0 | } |
172 | | |
173 | | void SetEffectFromStyle(AnimationEffect* aEffect); |
174 | | |
175 | | void Tick() override; |
176 | | |
177 | | nsCSSPropertyID TransitionProperty() const; |
178 | | AnimationValue ToValue() const; |
179 | | |
180 | | bool HasLowerCompositeOrderThan(const CSSTransition& aOther) const; |
181 | | EffectCompositor::CascadeLevel CascadeLevel() const override |
182 | 0 | { |
183 | 0 | return IsTiedToMarkup() ? |
184 | 0 | EffectCompositor::CascadeLevel::Transitions : |
185 | 0 | EffectCompositor::CascadeLevel::Animations; |
186 | 0 | } |
187 | | |
188 | | void SetCreationSequence(uint64_t aIndex) |
189 | 0 | { |
190 | 0 | MOZ_ASSERT(IsTiedToMarkup()); |
191 | 0 | mAnimationIndex = aIndex; |
192 | 0 | } |
193 | | |
194 | | // Sets the owning element which is used for determining the composite |
195 | | // oder of CSSTransition objects generated from CSS markup. |
196 | | // |
197 | | // @see mOwningElement |
198 | | void SetOwningElement(const OwningElementRef& aElement) |
199 | 0 | { |
200 | 0 | mOwningElement = aElement; |
201 | 0 | } |
202 | | // True for transitions that are generated from CSS markup and continue to |
203 | | // reflect changes to that markup. |
204 | 0 | bool IsTiedToMarkup() const { return mOwningElement.IsSet(); } |
205 | | |
206 | | // Return the animation current time based on a given TimeStamp, a given |
207 | | // start time and a given playbackRate on a given timeline. This is useful |
208 | | // when we estimate the current animated value running on the compositor |
209 | | // because the animation on the compositor may be running ahead while |
210 | | // main-thread is busy. |
211 | | static Nullable<TimeDuration> GetCurrentTimeAt( |
212 | | const DocumentTimeline& aTimeline, |
213 | | const TimeStamp& aBaseTime, |
214 | | const TimeDuration& aStartTime, |
215 | | double aPlaybackRate); |
216 | | |
217 | 0 | void MaybeQueueCancelEvent(const StickyTimeDuration& aActiveTime) override { |
218 | 0 | QueueEvents(aActiveTime); |
219 | 0 | } |
220 | | |
221 | | protected: |
222 | | virtual ~CSSTransition() |
223 | 0 | { |
224 | 0 | MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " |
225 | 0 | "before a CSS transition is destroyed"); |
226 | 0 | } |
227 | | |
228 | | // Animation overrides |
229 | | void UpdateTiming(SeekFlag aSeekFlag, |
230 | | SyncNotifyFlag aSyncNotifyFlag) override; |
231 | | |
232 | | void QueueEvents(const StickyTimeDuration& activeTime = StickyTimeDuration()); |
233 | | |
234 | | |
235 | | enum class TransitionPhase; |
236 | | |
237 | | // The (pseudo-)element whose computed transition-property refers to this |
238 | | // transition (if any). |
239 | | // |
240 | | // This is used for determining the relative composite order of transitions |
241 | | // generated from CSS markup. |
242 | | // |
243 | | // Typically this will be the same as the target element of the keyframe |
244 | | // effect associated with this transition. However, it can differ in the |
245 | | // following circumstances: |
246 | | // |
247 | | // a) If script removes or replaces the effect of this transition, |
248 | | // b) If this transition is cancelled (e.g. by updating the |
249 | | // transition-property or removing the owning element from the document), |
250 | | // c) If this object is generated from script using the CSSTransition |
251 | | // constructor. |
252 | | // |
253 | | // For (b) and (c) the owning element will return !IsSet(). |
254 | | OwningElementRef mOwningElement; |
255 | | |
256 | | // The 'transition phase' used to determine which transition events need |
257 | | // to be queued on this tick. |
258 | | // See: https://drafts.csswg.org/css-transitions-2/#transition-phase |
259 | | enum class TransitionPhase { |
260 | | Idle = static_cast<int>(ComputedTiming::AnimationPhase::Idle), |
261 | | Before = static_cast<int>(ComputedTiming::AnimationPhase::Before), |
262 | | Active = static_cast<int>(ComputedTiming::AnimationPhase::Active), |
263 | | After = static_cast<int>(ComputedTiming::AnimationPhase::After), |
264 | | Pending |
265 | | }; |
266 | | TransitionPhase mPreviousTransitionPhase; |
267 | | |
268 | | // When true, indicates that when this transition next leaves the idle state, |
269 | | // its animation index should be updated. |
270 | | bool mNeedsNewAnimationIndexWhenRun; |
271 | | |
272 | | // Store the transition property and to-value here since we need that |
273 | | // information in order to determine if there is an existing transition |
274 | | // for a given style change. We can't store that information on the |
275 | | // ElementPropertyTransition (effect) however since it can be replaced |
276 | | // using the Web Animations API. |
277 | | nsCSSPropertyID mTransitionProperty; |
278 | | AnimationValue mTransitionToValue; |
279 | | }; |
280 | | |
281 | | } // namespace dom |
282 | | |
283 | | template <> |
284 | | struct AnimationTypeTraits<dom::CSSTransition> |
285 | | { |
286 | | static nsAtom* ElementPropertyAtom() |
287 | 0 | { |
288 | 0 | return nsGkAtoms::transitionsProperty; |
289 | 0 | } |
290 | | static nsAtom* BeforePropertyAtom() |
291 | 0 | { |
292 | 0 | return nsGkAtoms::transitionsOfBeforeProperty; |
293 | 0 | } |
294 | | static nsAtom* AfterPropertyAtom() |
295 | 0 | { |
296 | 0 | return nsGkAtoms::transitionsOfAfterProperty; |
297 | 0 | } |
298 | | }; |
299 | | |
300 | | } // namespace mozilla |
301 | | |
302 | | class nsTransitionManager final |
303 | | : public mozilla::CommonAnimationManager<mozilla::dom::CSSTransition> |
304 | | { |
305 | | public: |
306 | | explicit nsTransitionManager(nsPresContext *aPresContext) |
307 | | : mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>(aPresContext) |
308 | 0 | { |
309 | 0 | } |
310 | | |
311 | | ~nsTransitionManager() final = default; |
312 | | |
313 | | typedef mozilla::AnimationCollection<mozilla::dom::CSSTransition> |
314 | | CSSTransitionCollection; |
315 | | |
316 | | /** |
317 | | * Update transitions for stylo. |
318 | | */ |
319 | | bool UpdateTransitions( |
320 | | mozilla::dom::Element *aElement, |
321 | | mozilla::CSSPseudoElementType aPseudoType, |
322 | | const mozilla::ComputedStyle& aOldStyle, |
323 | | const mozilla::ComputedStyle& aNewStyle); |
324 | | |
325 | | protected: |
326 | | |
327 | | typedef nsTArray<RefPtr<mozilla::dom::CSSTransition>> |
328 | | OwningCSSTransitionPtrArray; |
329 | | |
330 | | // Update transitions. This will start new transitions, |
331 | | // replace existing transitions, and stop existing transitions |
332 | | // as needed. aDisp and aElement must be non-null. |
333 | | // aElementTransitions is the collection of current transitions, and it |
334 | | // could be a nullptr if we don't have any transitions. |
335 | | bool DoUpdateTransitions(const nsStyleDisplay& aDisp, |
336 | | mozilla::dom::Element* aElement, |
337 | | mozilla::CSSPseudoElementType aPseudoType, |
338 | | CSSTransitionCollection*& aElementTransitions, |
339 | | const mozilla::ComputedStyle& aOldStyle, |
340 | | const mozilla::ComputedStyle& aNewStyle); |
341 | | |
342 | | // Returns whether the transition actually started. |
343 | | bool ConsiderInitiatingTransition(nsCSSPropertyID aProperty, |
344 | | const nsStyleDisplay& aStyleDisplay, |
345 | | uint32_t transitionIdx, |
346 | | mozilla::dom::Element* aElement, |
347 | | mozilla::CSSPseudoElementType aPseudoType, |
348 | | CSSTransitionCollection*& aElementTransitions, |
349 | | const mozilla::ComputedStyle& aOldStyle, |
350 | | const mozilla::ComputedStyle& aNewStyle, |
351 | | nsCSSPropertyIDSet& aPropertiesChecked); |
352 | | }; |
353 | | |
354 | | #endif /* !defined(nsTransitionManager_h_) */ |