Coverage Report

Created: 2018-09-25 14:53

/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_) */