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 : /* Code to start and animate CSS transitions. */
7 :
8 : #ifndef nsTransitionManager_h_
9 : #define nsTransitionManager_h_
10 :
11 : #include "mozilla/ComputedTiming.h"
12 : #include "mozilla/ContentEvents.h"
13 : #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
14 : #include "mozilla/MemoryReporting.h"
15 : #include "mozilla/dom/Animation.h"
16 : #include "mozilla/dom/KeyframeEffectReadOnly.h"
17 : #include "AnimationCommon.h"
18 : #include "nsCSSProps.h"
19 :
20 : class nsIGlobalObject;
21 : class nsStyleContext;
22 : class nsPresContext;
23 : class nsCSSPropertyIDSet;
24 :
25 : namespace mozilla {
26 : enum class CSSPseudoElementType : uint8_t;
27 : struct Keyframe;
28 : struct StyleTransition;
29 : } // namespace mozilla
30 :
31 : /*****************************************************************************
32 : * Per-Element data *
33 : *****************************************************************************/
34 :
35 : namespace mozilla {
36 :
37 0 : struct ElementPropertyTransition : public dom::KeyframeEffectReadOnly
38 : {
39 2 : ElementPropertyTransition(nsIDocument* aDocument,
40 : Maybe<OwningAnimationTarget>& aTarget,
41 : const TimingParams &aTiming,
42 : AnimationValue aStartForReversingTest,
43 : double aReversePortion,
44 : const KeyframeEffectParams& aEffectOptions)
45 2 : : dom::KeyframeEffectReadOnly(aDocument, aTarget, aTiming, aEffectOptions)
46 : , mStartForReversingTest(aStartForReversingTest)
47 2 : , mReversePortion(aReversePortion)
48 2 : { }
49 :
50 10 : ElementPropertyTransition* AsTransition() override { return this; }
51 0 : const ElementPropertyTransition* AsTransition() const override
52 : {
53 0 : return this;
54 : }
55 :
56 2 : nsCSSPropertyID TransitionProperty() const {
57 2 : MOZ_ASSERT(mKeyframes.Length() == 2,
58 : "Transitions should have exactly two animation keyframes. "
59 : "Perhaps we are using an un-initialized transition?");
60 2 : MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
61 : "Transitions should have exactly one property in their first "
62 : "frame");
63 2 : return mKeyframes[0].mPropertyValues[0].mProperty;
64 : }
65 :
66 2 : AnimationValue ToValue() const {
67 : // If we failed to generate properties from the transition frames,
68 : // return a null value but also show a warning since we should be
69 : // detecting that kind of situation in advance and not generating a
70 : // transition in the first place.
71 4 : if (mProperties.Length() < 1 ||
72 2 : mProperties[0].mSegments.Length() < 1) {
73 0 : NS_WARNING("Failed to generate transition property values");
74 0 : return AnimationValue();
75 : }
76 2 : return mProperties[0].mSegments[0].mToValue;
77 : }
78 :
79 : // This is the start value to be used for a check for whether a
80 : // transition is being reversed. Normally the same as
81 : // mProperties[0].mSegments[0].mFromValue, except when this transition
82 : // started as the reversal of another in-progress transition.
83 : // Needed so we can handle two reverses in a row.
84 : AnimationValue mStartForReversingTest;
85 : // Likewise, the portion (in value space) of the "full" reversed
86 : // transition that we're actually covering. For example, if a :hover
87 : // effect has a transition that moves the element 10px to the right
88 : // (by changing 'left' from 0px to 10px), and the mouse moves in to
89 : // the element (starting the transition) but then moves out after the
90 : // transition has advanced 4px, the second transition (from 10px/4px
91 : // to 0px) will have mReversePortion of 0.4. (If the mouse then moves
92 : // in again when the transition is back to 2px, the mReversePortion
93 : // for the third transition (from 0px/2px to 10px) will be 0.8.
94 : double mReversePortion;
95 :
96 : // Compute the portion of the *value* space that we should be through
97 : // at the current time. (The input to the transition timing function
98 : // has time units, the output has value units.)
99 : double CurrentValuePortion() const;
100 :
101 : // For a new transition interrupting an existing transition on the
102 : // compositor, update the start value to match the value of the replaced
103 : // transitions at the current time.
104 : void UpdateStartValueFromReplacedTransition();
105 :
106 0 : struct ReplacedTransitionProperties {
107 : TimeDuration mStartTime;
108 : double mPlaybackRate;
109 : TimingParams mTiming;
110 : Maybe<ComputedTimingFunction> mTimingFunction;
111 : AnimationValue mFromValue, mToValue;
112 : };
113 : Maybe<ReplacedTransitionProperties> mReplacedTransition;
114 : };
115 :
116 : namespace dom {
117 :
118 : class CSSTransition final : public Animation
119 : {
120 : public:
121 2 : explicit CSSTransition(nsIGlobalObject* aGlobal)
122 2 : : dom::Animation(aGlobal)
123 : , mPreviousTransitionPhase(TransitionPhase::Idle)
124 : , mNeedsNewAnimationIndexWhenRun(false)
125 2 : , mTransitionProperty(eCSSProperty_UNKNOWN)
126 : {
127 2 : }
128 :
129 : JSObject* WrapObject(JSContext* aCx,
130 : JS::Handle<JSObject*> aGivenProto) override;
131 :
132 0 : CSSTransition* AsCSSTransition() override { return this; }
133 6 : const CSSTransition* AsCSSTransition() const override { return this; }
134 :
135 : // CSSTransition interface
136 : void GetTransitionProperty(nsString& aRetVal) const;
137 :
138 : // Animation interface overrides
139 : virtual AnimationPlayState PlayStateFromJS() const override;
140 : virtual 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 2 : void PlayFromStyle()
145 : {
146 4 : ErrorResult rv;
147 2 : PlayNoUpdate(rv, Animation::LimitBehavior::Continue);
148 : // play() should not throw when LimitBehavior is Continue
149 2 : MOZ_ASSERT(!rv.Failed(), "Unexpected exception playing transition");
150 2 : }
151 :
152 0 : void CancelFromStyle() override
153 : {
154 : // The animation index to use for compositing will be established when
155 : // this transition next transitions out of the idle state but we still
156 : // update it now so that the sort order of this transition remains
157 : // defined until that moment.
158 : //
159 : // See longer explanation in CSSAnimation::CancelFromStyle.
160 0 : mAnimationIndex = sNextAnimationIndex++;
161 0 : mNeedsNewAnimationIndexWhenRun = true;
162 :
163 0 : Animation::CancelFromStyle();
164 :
165 : // It is important we do this *after* calling CancelFromStyle().
166 : // This is because CancelFromStyle() will end up posting a restyle and
167 : // that restyle should target the *transitions* level of the cascade.
168 : // However, once we clear the owning element, CascadeLevel() will begin
169 : // returning CascadeLevel::Animations.
170 0 : mOwningElement = OwningElementRef();
171 0 : }
172 :
173 : void SetEffectFromStyle(AnimationEffectReadOnly* aEffect);
174 :
175 : void Tick() override;
176 :
177 : nsCSSPropertyID TransitionProperty() const;
178 : AnimationValue ToValue() const;
179 :
180 : bool HasLowerCompositeOrderThan(const CSSTransition& aOther) const;
181 16 : EffectCompositor::CascadeLevel CascadeLevel() const override
182 : {
183 16 : return IsTiedToMarkup() ?
184 : EffectCompositor::CascadeLevel::Transitions :
185 16 : EffectCompositor::CascadeLevel::Animations;
186 : }
187 :
188 2 : void SetCreationSequence(uint64_t aIndex)
189 : {
190 2 : MOZ_ASSERT(IsTiedToMarkup());
191 2 : mAnimationIndex = aIndex;
192 2 : }
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 2 : void SetOwningElement(const OwningElementRef& aElement)
199 : {
200 2 : mOwningElement = aElement;
201 2 : }
202 : // True for transitions that are generated from CSS markup and continue to
203 : // reflect changes to that markup.
204 30 : 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(StickyTimeDuration aActiveTime) override {
218 0 : QueueEvents(aActiveTime);
219 0 : }
220 :
221 : protected:
222 0 : virtual ~CSSTransition()
223 0 : {
224 0 : MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
225 : "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(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 18 : static nsIAtom* ElementPropertyAtom()
287 : {
288 18 : return nsGkAtoms::transitionsProperty;
289 : }
290 0 : static nsIAtom* BeforePropertyAtom()
291 : {
292 0 : return nsGkAtoms::transitionsOfBeforeProperty;
293 : }
294 0 : static nsIAtom* AfterPropertyAtom()
295 : {
296 0 : return nsGkAtoms::transitionsOfAfterProperty;
297 : }
298 : };
299 :
300 27 : struct TransitionEventInfo {
301 : RefPtr<dom::Element> mElement;
302 : RefPtr<dom::Animation> mAnimation;
303 : InternalTransitionEvent mEvent;
304 : TimeStamp mTimeStamp;
305 :
306 6 : TransitionEventInfo(dom::Element* aElement,
307 : CSSPseudoElementType aPseudoType,
308 : EventMessage aMessage,
309 : nsCSSPropertyID aProperty,
310 : StickyTimeDuration aDuration,
311 : const TimeStamp& aTimeStamp,
312 : dom::Animation* aAnimation)
313 6 : : mElement(aElement)
314 : , mAnimation(aAnimation)
315 : , mEvent(true, aMessage)
316 6 : , mTimeStamp(aTimeStamp)
317 : {
318 : // XXX Looks like nobody initialize WidgetEvent::time
319 : mEvent.mPropertyName =
320 6 : NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
321 6 : mEvent.mElapsedTime = aDuration.ToSeconds();
322 : mEvent.mPseudoElement =
323 6 : AnimationCollection<dom::CSSTransition>::PseudoTypeAsString(aPseudoType);
324 6 : }
325 :
326 : // InternalTransitionEvent doesn't support copy-construction, so we need
327 : // to ourselves in order to work with nsTArray
328 12 : TransitionEventInfo(const TransitionEventInfo& aOther)
329 12 : : mElement(aOther.mElement)
330 : , mAnimation(aOther.mAnimation)
331 : , mEvent(aOther.mEvent)
332 12 : , mTimeStamp(aOther.mTimeStamp)
333 : {
334 12 : mEvent.AssignTransitionEventData(aOther.mEvent, false);
335 12 : }
336 : };
337 :
338 : } // namespace mozilla
339 :
340 : class nsTransitionManager final
341 : : public mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>
342 : {
343 : public:
344 28 : explicit nsTransitionManager(nsPresContext *aPresContext)
345 28 : : mozilla::CommonAnimationManager<mozilla::dom::CSSTransition>(aPresContext)
346 28 : , mInAnimationOnlyStyleUpdate(false)
347 : {
348 28 : }
349 :
350 116 : NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsTransitionManager)
351 122 : NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTransitionManager)
352 :
353 : typedef mozilla::AnimationCollection<mozilla::dom::CSSTransition>
354 : CSSTransitionCollection;
355 :
356 : /**
357 : * StyleContextChanged
358 : *
359 : * To be called from RestyleManager::TryInitiatingTransition when the
360 : * style of an element has changed, to initiate transitions from
361 : * that style change. For style contexts with :before and :after
362 : * pseudos, aElement is expected to be the generated before/after
363 : * element.
364 : *
365 : * It may modify the new style context (by replacing
366 : * *aNewStyleContext) to cover up some of the changes for the duration
367 : * of the restyling of descendants. If it does, this function will
368 : * take care of causing the necessary restyle afterwards.
369 : */
370 : void StyleContextChanged(mozilla::dom::Element *aElement,
371 : nsStyleContext *aOldStyleContext,
372 : RefPtr<nsStyleContext>* aNewStyleContext /* inout */);
373 :
374 : /**
375 : * Update transitions for stylo.
376 : */
377 : bool UpdateTransitions(
378 : mozilla::dom::Element *aElement,
379 : mozilla::CSSPseudoElementType aPseudoType,
380 : const ServoComputedValues* aOldStyle,
381 : const ServoComputedValues* aNewStyle);
382 :
383 : /**
384 : * When we're resolving style for an element that previously didn't have
385 : * style, we might have some old finished transitions for it, if,
386 : * say, it was display:none for a while, but previously displayed.
387 : *
388 : * This method removes any finished transitions that don't match the
389 : * new style.
390 : */
391 : void PruneCompletedTransitions(mozilla::dom::Element* aElement,
392 : mozilla::CSSPseudoElementType aPseudoType,
393 : nsStyleContext* aNewStyleContext);
394 :
395 488 : void SetInAnimationOnlyStyleUpdate(bool aInAnimationOnlyUpdate) {
396 488 : mInAnimationOnlyStyleUpdate = aInAnimationOnlyUpdate;
397 488 : }
398 :
399 0 : bool InAnimationOnlyStyleUpdate() const {
400 0 : return mInAnimationOnlyStyleUpdate;
401 : }
402 :
403 6 : void QueueEvent(mozilla::TransitionEventInfo&& aEventInfo)
404 : {
405 6 : mEventDispatcher.QueueEvent(
406 6 : mozilla::Forward<mozilla::TransitionEventInfo>(aEventInfo));
407 6 : }
408 :
409 42 : void DispatchEvents()
410 : {
411 84 : RefPtr<nsTransitionManager> kungFuDeathGrip(this);
412 42 : mEventDispatcher.DispatchEvents(mPresContext);
413 42 : }
414 42 : void SortEvents() { mEventDispatcher.SortEvents(); }
415 4 : void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); }
416 :
417 : protected:
418 9 : virtual ~nsTransitionManager() {}
419 :
420 : typedef nsTArray<RefPtr<mozilla::dom::CSSTransition>>
421 : OwningCSSTransitionPtrArray;
422 :
423 : // Update transitions. This will start new transitions,
424 : // replace existing transitions, and stop existing transitions
425 : // as needed. aDisp and aElement must be non-null.
426 : // aElementTransitions is the collection of current transitions, and it
427 : // could be a nullptr if we don't have any transitions.
428 : template<typename StyleType> bool
429 : DoUpdateTransitions(const nsStyleDisplay* aDisp,
430 : mozilla::dom::Element* aElement,
431 : mozilla::CSSPseudoElementType aPseudoType,
432 : CSSTransitionCollection*& aElementTransitions,
433 : StyleType aOldStyle,
434 : StyleType aNewStyle);
435 :
436 : template<typename StyleType> void
437 : ConsiderInitiatingTransition(nsCSSPropertyID aProperty,
438 : const mozilla::StyleTransition& aTransition,
439 : mozilla::dom::Element* aElement,
440 : mozilla::CSSPseudoElementType aPseudoType,
441 : CSSTransitionCollection*& aElementTransitions,
442 : StyleType aOldStyle,
443 : StyleType aNewStyle,
444 : bool* aStartedAny,
445 : nsCSSPropertyIDSet* aWhichStarted);
446 :
447 : bool mInAnimationOnlyStyleUpdate;
448 :
449 : mozilla::DelayedEventDispatcher<mozilla::TransitionEventInfo>
450 : mEventDispatcher;
451 : };
452 :
453 : #endif /* !defined(nsTransitionManager_h_) */
|