Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/style/nsTransitionManager.cpp
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
#include "nsTransitionManager.h"
10
#include "nsAnimationManager.h"
11
#include "mozilla/dom/CSSTransitionBinding.h"
12
13
#include "nsIContent.h"
14
#include "mozilla/ComputedStyle.h"
15
#include "mozilla/MemoryReporting.h"
16
#include "mozilla/TimeStamp.h"
17
#include "nsRefreshDriver.h"
18
#include "nsCSSPropertyIDSet.h"
19
#include "mozilla/AnimationEventDispatcher.h"
20
#include "mozilla/EffectCompositor.h"
21
#include "mozilla/EffectSet.h"
22
#include "mozilla/EventDispatcher.h"
23
#include "mozilla/ServoBindings.h"
24
#include "mozilla/StyleAnimationValue.h"
25
#include "mozilla/dom/DocumentTimeline.h"
26
#include "mozilla/dom/Element.h"
27
#include "nsIFrame.h"
28
#include "Layers.h"
29
#include "FrameLayerBuilder.h"
30
#include "nsCSSProps.h"
31
#include "nsCSSPseudoElements.h"
32
#include "nsDisplayList.h"
33
#include "nsRFPService.h"
34
#include "nsStyleChangeList.h"
35
#include "mozilla/RestyleManager.h"
36
#include "nsDOMMutationObserver.h"
37
38
using mozilla::TimeStamp;
39
using mozilla::TimeDuration;
40
using mozilla::dom::Animation;
41
using mozilla::dom::AnimationPlayState;
42
using mozilla::dom::CSSTransition;
43
using mozilla::dom::Nullable;
44
45
using namespace mozilla;
46
using namespace mozilla::css;
47
48
double
49
ElementPropertyTransition::CurrentValuePortion() const
50
0
{
51
0
  MOZ_ASSERT(!GetLocalTime().IsNull(),
52
0
             "Getting the value portion of an animation that's not being "
53
0
             "sampled");
54
0
55
0
  // Transitions use a fill mode of 'backwards' so GetComputedTiming will
56
0
  // never return a null time progress due to being *before* the animation
57
0
  // interval. However, it might be possible that we're behind on flushing
58
0
  // causing us to get called *after* the animation interval. So, just in
59
0
  // case, we override the fill mode to 'both' to ensure the progress
60
0
  // is never null.
61
0
  TimingParams timingToUse = SpecifiedTiming();
62
0
  timingToUse.SetFill(dom::FillMode::Both);
63
0
  ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
64
0
65
0
  MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
66
0
             "Got a null progress for a fill mode of 'both'");
67
0
  MOZ_ASSERT(mKeyframes.Length() == 2,
68
0
             "Should have two animation keyframes for a transition");
69
0
  return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
70
0
                                            computedTiming.mProgress.Value(),
71
0
                                            computedTiming.mBeforeFlag);
72
0
}
73
74
void
75
ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
76
0
{
77
0
  if (!mReplacedTransition) {
78
0
    return;
79
0
  }
80
0
  MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
81
0
                                      CSSPropFlags::CanAnimateOnCompositor),
82
0
             "The transition property should be able to be run on the "
83
0
             "compositor");
84
0
  MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
85
0
             "We should have a valid document at this moment");
86
0
87
0
  dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
88
0
  ComputedTiming computedTiming = GetComputedTimingAt(
89
0
    dom::CSSTransition::GetCurrentTimeAt(*timeline,
90
0
                                         TimeStamp::Now(),
91
0
                                         mReplacedTransition->mStartTime,
92
0
                                         mReplacedTransition->mPlaybackRate),
93
0
    mReplacedTransition->mTiming,
94
0
    mReplacedTransition->mPlaybackRate);
95
0
96
0
  if (!computedTiming.mProgress.IsNull()) {
97
0
    double valuePosition =
98
0
      ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
99
0
                                         computedTiming.mProgress.Value(),
100
0
                                         computedTiming.mBeforeFlag);
101
0
102
0
    MOZ_ASSERT(mProperties.Length() == 1 &&
103
0
               mProperties[0].mSegments.Length() == 1,
104
0
               "The transition should have one property and one segment");
105
0
    MOZ_ASSERT(mKeyframes.Length() == 2,
106
0
               "Transitions should have exactly two animation keyframes");
107
0
    MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
108
0
               "Transitions should have exactly one property in their first "
109
0
               "frame");
110
0
111
0
    const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
112
0
    const AnimationValue& replacedTo = mReplacedTransition->mToValue;
113
0
    AnimationValue startValue;
114
0
    startValue.mServo =
115
0
      Servo_AnimationValues_Interpolate(replacedFrom.mServo,
116
0
                                        replacedTo.mServo,
117
0
                                        valuePosition).Consume();
118
0
    if (startValue.mServo) {
119
0
      mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
120
0
        Servo_AnimationValue_Uncompute(startValue.mServo).Consume();
121
0
      mProperties[0].mSegments[0].mFromValue = std::move(startValue);
122
0
    }
123
0
  }
124
0
125
0
  mReplacedTransition.reset();
126
0
}
127
128
////////////////////////// CSSTransition ////////////////////////////
129
130
JSObject*
131
CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
132
0
{
133
0
  return dom::CSSTransition_Binding::Wrap(aCx, this, aGivenProto);
134
0
}
135
136
void
137
CSSTransition::GetTransitionProperty(nsString& aRetVal) const
138
0
{
139
0
  MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
140
0
             "Transition Property should be initialized");
141
0
  aRetVal =
142
0
    NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
143
0
}
144
145
AnimationPlayState
146
CSSTransition::PlayStateFromJS() const
147
0
{
148
0
  FlushUnanimatedStyle();
149
0
  return Animation::PlayStateFromJS();
150
0
}
151
152
bool
153
CSSTransition::PendingFromJS() const
154
0
{
155
0
  // Transitions don't become pending again after they start running but, if
156
0
  // while the transition is still pending, style is updated in such a way
157
0
  // that the transition will be canceled, we need to report false here.
158
0
  // Hence we need to flush, but only when we're pending.
159
0
  if (Pending()) {
160
0
    FlushUnanimatedStyle();
161
0
  }
162
0
  return Animation::PendingFromJS();
163
0
}
164
165
void
166
CSSTransition::PlayFromJS(ErrorResult& aRv)
167
0
{
168
0
  FlushUnanimatedStyle();
169
0
  Animation::PlayFromJS(aRv);
170
0
}
171
172
void
173
CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
174
0
{
175
0
  if (mNeedsNewAnimationIndexWhenRun &&
176
0
      PlayState() != AnimationPlayState::Idle) {
177
0
    mAnimationIndex = sNextAnimationIndex++;
178
0
    mNeedsNewAnimationIndexWhenRun = false;
179
0
  }
180
0
181
0
  Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
182
0
}
183
184
void
185
CSSTransition::QueueEvents(const StickyTimeDuration& aActiveTime)
186
0
{
187
0
  if (!mOwningElement.IsSet()) {
188
0
    return;
189
0
  }
190
0
191
0
  nsPresContext* presContext = mOwningElement.GetPresContext();
192
0
  if (!presContext) {
193
0
    return;
194
0
  }
195
0
196
0
  static constexpr StickyTimeDuration zeroDuration = StickyTimeDuration();
197
0
198
0
  TransitionPhase currentPhase;
199
0
  StickyTimeDuration intervalStartTime;
200
0
  StickyTimeDuration intervalEndTime;
201
0
202
0
  if (!mEffect) {
203
0
    currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
204
0
  } else {
205
0
    ComputedTiming computedTiming = mEffect->GetComputedTiming();
206
0
207
0
    currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
208
0
    intervalStartTime = IntervalStartTime(computedTiming.mActiveDuration);
209
0
    intervalEndTime = IntervalEndTime(computedTiming.mActiveDuration);
210
0
  }
211
0
212
0
  if (mPendingState != PendingState::NotPending &&
213
0
      (mPreviousTransitionPhase == TransitionPhase::Idle ||
214
0
       mPreviousTransitionPhase == TransitionPhase::Pending)) {
215
0
    currentPhase = TransitionPhase::Pending;
216
0
  }
217
0
218
0
  if (currentPhase == mPreviousTransitionPhase) {
219
0
    return;
220
0
  }
221
0
222
0
  // TimeStamps to use for ordering the events when they are dispatched. We
223
0
  // use a TimeStamp so we can compare events produced by different elements,
224
0
  // perhaps even with different timelines.
225
0
  // The zero timestamp is for transitionrun events where we ignore the delay
226
0
  // for the purpose of ordering events.
227
0
  TimeStamp zeroTimeStamp  = AnimationTimeToTimeStamp(zeroDuration);
228
0
  TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
229
0
  TimeStamp endTimeStamp   = ElapsedTimeToTimeStamp(intervalEndTime);
230
0
231
0
  AutoTArray<AnimationEventInfo, 3> events;
232
0
233
0
  auto appendTransitionEvent = [&](EventMessage aMessage,
234
0
                                   const StickyTimeDuration& aElapsedTime,
235
0
                                   const TimeStamp& aScheduledEventTimeStamp) {
236
0
    double elapsedTime = aElapsedTime.ToSeconds();
237
0
    if (aMessage == eTransitionCancel) {
238
0
      // 0 is an inappropriate value for this callsite. What we need to do is
239
0
      // use a single random value for all increasing times reportable.
240
0
      // That is to say, whenever elapsedTime goes negative (because an
241
0
      // animation restarts, something rewinds the animation, or otherwise)
242
0
      // a new random value for the mix-in must be generated.
243
0
      elapsedTime = nsRFPService::ReduceTimePrecisionAsSecs(elapsedTime, 0, TimerPrecisionType::RFPOnly);
244
0
    }
245
0
    events.AppendElement(AnimationEventInfo(TransitionProperty(),
246
0
                                            mOwningElement.Target(),
247
0
                                            aMessage,
248
0
                                            elapsedTime,
249
0
                                            aScheduledEventTimeStamp,
250
0
                                            this));
251
0
  };
252
0
253
0
  // Handle cancel events first
254
0
  if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
255
0
       mPreviousTransitionPhase != TransitionPhase::After) &&
256
0
      currentPhase == TransitionPhase::Idle) {
257
0
    appendTransitionEvent(eTransitionCancel,
258
0
                          aActiveTime,
259
0
                          GetTimelineCurrentTimeAsTimeStamp());
260
0
  }
261
0
262
0
  // All other events
263
0
  switch (mPreviousTransitionPhase) {
264
0
    case TransitionPhase::Idle:
265
0
      if (currentPhase == TransitionPhase::Pending ||
266
0
          currentPhase == TransitionPhase::Before) {
267
0
        appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
268
0
      } else if (currentPhase == TransitionPhase::Active) {
269
0
        appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
270
0
        appendTransitionEvent(eTransitionStart,
271
0
                              intervalStartTime,
272
0
                              startTimeStamp);
273
0
      } else if (currentPhase == TransitionPhase::After) {
274
0
        appendTransitionEvent(eTransitionRun, intervalStartTime, zeroTimeStamp);
275
0
        appendTransitionEvent(eTransitionStart,
276
0
                              intervalStartTime,
277
0
                              startTimeStamp);
278
0
        appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
279
0
      }
280
0
      break;
281
0
282
0
    case TransitionPhase::Pending:
283
0
    case TransitionPhase::Before:
284
0
      if (currentPhase == TransitionPhase::Active) {
285
0
        appendTransitionEvent(eTransitionStart,
286
0
                              intervalStartTime,
287
0
                              startTimeStamp);
288
0
      } else if (currentPhase == TransitionPhase::After) {
289
0
        appendTransitionEvent(eTransitionStart,
290
0
                              intervalStartTime,
291
0
                              startTimeStamp);
292
0
        appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
293
0
      }
294
0
      break;
295
0
296
0
    case TransitionPhase::Active:
297
0
      if (currentPhase == TransitionPhase::After) {
298
0
        appendTransitionEvent(eTransitionEnd, intervalEndTime, endTimeStamp);
299
0
      } else if (currentPhase == TransitionPhase::Before) {
300
0
        appendTransitionEvent(eTransitionEnd,
301
0
                              intervalStartTime,
302
0
                              startTimeStamp);
303
0
      }
304
0
      break;
305
0
306
0
    case TransitionPhase::After:
307
0
      if (currentPhase == TransitionPhase::Active) {
308
0
        appendTransitionEvent(eTransitionStart,
309
0
                              intervalEndTime,
310
0
                              startTimeStamp);
311
0
      } else if (currentPhase == TransitionPhase::Before) {
312
0
        appendTransitionEvent(eTransitionStart,
313
0
                              intervalEndTime,
314
0
                              startTimeStamp);
315
0
        appendTransitionEvent(eTransitionEnd,
316
0
                              intervalStartTime,
317
0
                              endTimeStamp);
318
0
      }
319
0
      break;
320
0
  }
321
0
  mPreviousTransitionPhase = currentPhase;
322
0
323
0
  if (!events.IsEmpty()) {
324
0
    presContext->AnimationEventDispatcher()->QueueEvents(std::move(events));
325
0
  }
326
0
}
327
328
void
329
CSSTransition::Tick()
330
0
{
331
0
  Animation::Tick();
332
0
  QueueEvents();
333
0
}
334
335
nsCSSPropertyID
336
CSSTransition::TransitionProperty() const
337
0
{
338
0
  MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
339
0
             "Transition property should be initialized");
340
0
  return mTransitionProperty;
341
0
}
342
343
AnimationValue
344
CSSTransition::ToValue() const
345
0
{
346
0
  MOZ_ASSERT(!mTransitionToValue.IsNull(),
347
0
             "Transition ToValue should be initialized");
348
0
  return mTransitionToValue;
349
0
}
350
351
bool
352
CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
353
0
{
354
0
  MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
355
0
             "Should only be called for CSS transitions that are sorted "
356
0
             "as CSS transitions (i.e. tied to CSS markup)");
357
0
358
0
  // 0. Object-equality case
359
0
  if (&aOther == this) {
360
0
    return false;
361
0
  }
362
0
363
0
  // 1. Sort by document order
364
0
  if (!mOwningElement.Equals(aOther.mOwningElement)) {
365
0
    return mOwningElement.LessThan(
366
0
      const_cast<CSSTransition*>(this)->CachedChildIndexRef(),
367
0
      aOther.mOwningElement,
368
0
      const_cast<CSSTransition*>(&aOther)->CachedChildIndexRef());
369
0
  }
370
0
371
0
  // 2. (Same element and pseudo): Sort by transition generation
372
0
  if (mAnimationIndex != aOther.mAnimationIndex) {
373
0
    return mAnimationIndex < aOther.mAnimationIndex;
374
0
  }
375
0
376
0
  // 3. (Same transition generation): Sort by transition property
377
0
  return nsCSSProps::GetStringValue(TransitionProperty()) <
378
0
         nsCSSProps::GetStringValue(aOther.TransitionProperty());
379
0
}
380
381
/* static */ Nullable<TimeDuration>
382
CSSTransition::GetCurrentTimeAt(const dom::DocumentTimeline& aTimeline,
383
                                const TimeStamp& aBaseTime,
384
                                const TimeDuration& aStartTime,
385
                                double aPlaybackRate)
386
0
{
387
0
  Nullable<TimeDuration> result;
388
0
389
0
  Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
390
0
  if (!timelineTime.IsNull()) {
391
0
    result.SetValue((timelineTime.Value() - aStartTime)
392
0
                      .MultDouble(aPlaybackRate));
393
0
  }
394
0
395
0
  return result;
396
0
}
397
398
void
399
CSSTransition::SetEffectFromStyle(dom::AnimationEffect* aEffect)
400
0
{
401
0
  Animation::SetEffectNoUpdate(aEffect);
402
0
403
0
  // Initialize transition property.
404
0
  ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
405
0
  if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
406
0
    mTransitionProperty = pt->TransitionProperty();
407
0
    mTransitionToValue = pt->ToValue();
408
0
  }
409
0
}
410
411
////////////////////////// nsTransitionManager ////////////////////////////
412
413
414
static inline bool
415
ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
416
                                const ComputedStyle& aComputedStyle,
417
                                AnimationValue& aAnimationValue)
418
0
{
419
0
  if (Servo_Property_IsDiscreteAnimatable(aProperty) &&
420
0
      aProperty != eCSSProperty_visibility) {
421
0
    return false;
422
0
  }
423
0
424
0
  aAnimationValue.mServo =
425
0
    Servo_ComputedValues_ExtractAnimationValue(&aComputedStyle,
426
0
                                               aProperty).Consume();
427
0
  return !!aAnimationValue.mServo;
428
0
}
429
430
431
bool
432
nsTransitionManager::UpdateTransitions(
433
  dom::Element *aElement,
434
  CSSPseudoElementType aPseudoType,
435
  const ComputedStyle& aOldStyle,
436
  const ComputedStyle& aNewStyle)
437
0
{
438
0
  if (!mPresContext->IsDynamic()) {
439
0
    // For print or print preview, ignore transitions.
440
0
    return false;
441
0
  }
442
0
443
0
  CSSTransitionCollection* collection =
444
0
    CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
445
0
  const nsStyleDisplay* disp =
446
0
      aNewStyle.ComputedData()->GetStyleDisplay();
447
0
  return DoUpdateTransitions(*disp,
448
0
                             aElement, aPseudoType,
449
0
                             collection,
450
0
                             aOldStyle, aNewStyle);
451
0
}
452
453
bool
454
nsTransitionManager::DoUpdateTransitions(
455
  const nsStyleDisplay& aDisp,
456
  dom::Element* aElement,
457
  CSSPseudoElementType aPseudoType,
458
  CSSTransitionCollection*& aElementTransitions,
459
  const ComputedStyle& aOldStyle,
460
  const ComputedStyle& aNewStyle)
461
0
{
462
0
  MOZ_ASSERT(!aElementTransitions ||
463
0
             aElementTransitions->mElement == aElement, "Element mismatch");
464
0
465
0
  // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
466
0
  // I'll consider only the transitions from the number of items in
467
0
  // 'transition-property' on down, and later ones will override earlier
468
0
  // ones (tracked using |propertiesChecked|).
469
0
  bool startedAny = false;
470
0
  nsCSSPropertyIDSet propertiesChecked;
471
0
  for (uint32_t i = aDisp.mTransitionPropertyCount; i--; ) {
472
0
    // We're not going to look at any further transitions, so we can just avoid
473
0
    // looking at this if we know it will not start any transitions.
474
0
    if (i == 0 && aDisp.GetTransitionCombinedDuration(i) <= 0.0f) {
475
0
      continue;
476
0
    }
477
0
478
0
    nsCSSPropertyID property = aDisp.GetTransitionProperty(i);
479
0
    if (property == eCSSPropertyExtra_no_properties ||
480
0
        property == eCSSPropertyExtra_variable ||
481
0
        property == eCSSProperty_UNKNOWN) {
482
0
      // Nothing to do.
483
0
      continue;
484
0
    }
485
0
    // We might have something to transition.  See if any of the
486
0
    // properties in question changed and are animatable.
487
0
    // FIXME: Would be good to find a way to share code between this
488
0
    // interpretation of transition-property and the one below.
489
0
    // FIXME(emilio): This should probably just use the "all" shorthand id, and
490
0
    // we should probably remove eCSSPropertyExtra_all_properties.
491
0
    if (property == eCSSPropertyExtra_all_properties) {
492
0
      for (nsCSSPropertyID p = nsCSSPropertyID(0);
493
0
           p < eCSSProperty_COUNT_no_shorthands;
494
0
           p = nsCSSPropertyID(p + 1)) {
495
0
        if (!nsCSSProps::IsEnabled(p, CSSEnabledState::eForAllContent)) {
496
0
          continue;
497
0
        }
498
0
        startedAny |=
499
0
          ConsiderInitiatingTransition(p, aDisp, i, aElement, aPseudoType,
500
0
                                       aElementTransitions,
501
0
                                       aOldStyle, aNewStyle,
502
0
                                       propertiesChecked);
503
0
      }
504
0
    } else if (nsCSSProps::IsShorthand(property)) {
505
0
      CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
506
0
                                           CSSEnabledState::eForAllContent)
507
0
      {
508
0
        startedAny |=
509
0
          ConsiderInitiatingTransition(*subprop, aDisp, i, aElement, aPseudoType,
510
0
                                       aElementTransitions,
511
0
                                       aOldStyle, aNewStyle,
512
0
                                       propertiesChecked);
513
0
      }
514
0
    } else {
515
0
      startedAny |=
516
0
        ConsiderInitiatingTransition(property, aDisp, i, aElement, aPseudoType,
517
0
                                     aElementTransitions,
518
0
                                     aOldStyle, aNewStyle,
519
0
                                     propertiesChecked);
520
0
    }
521
0
  }
522
0
523
0
  // Stop any transitions for properties that are no longer in
524
0
  // 'transition-property', including finished transitions.
525
0
  // Also stop any transitions (and remove any finished transitions)
526
0
  // for properties that just changed (and are still in the set of
527
0
  // properties to transition), but for which we didn't just start the
528
0
  // transition.  This can happen delay and duration are both zero, or
529
0
  // because the new value is not interpolable.
530
0
  // Note that we also do the latter set of work in
531
0
  // nsTransitionManager::PruneCompletedTransitions.
532
0
  if (aElementTransitions) {
533
0
    bool checkProperties =
534
0
      aDisp.GetTransitionProperty(0) != eCSSPropertyExtra_all_properties;
535
0
    nsCSSPropertyIDSet allTransitionProperties;
536
0
    if (checkProperties) {
537
0
      for (uint32_t i = aDisp.mTransitionPropertyCount; i-- != 0; ) {
538
0
        // FIXME: Would be good to find a way to share code between this
539
0
        // interpretation of transition-property and the one above.
540
0
        nsCSSPropertyID property = aDisp.GetTransitionProperty(i);
541
0
        if (property == eCSSPropertyExtra_no_properties ||
542
0
            property == eCSSPropertyExtra_variable ||
543
0
            property == eCSSProperty_UNKNOWN) {
544
0
          // Nothing to do, but need to exclude this from cases below.
545
0
        } else if (property == eCSSPropertyExtra_all_properties) {
546
0
          for (nsCSSPropertyID p = nsCSSPropertyID(0);
547
0
               p < eCSSProperty_COUNT_no_shorthands;
548
0
               p = nsCSSPropertyID(p + 1)) {
549
0
            allTransitionProperties.AddProperty(nsCSSProps::Physicalize(p, aNewStyle));
550
0
          }
551
0
        } else if (nsCSSProps::IsShorthand(property)) {
552
0
          CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
553
0
              subprop, property, CSSEnabledState::eForAllContent) {
554
0
            auto p = nsCSSProps::Physicalize(*subprop, aNewStyle);
555
0
            allTransitionProperties.AddProperty(p);
556
0
          }
557
0
        } else {
558
0
          allTransitionProperties.AddProperty(
559
0
            nsCSSProps::Physicalize(property, aNewStyle));
560
0
        }
561
0
      }
562
0
    }
563
0
564
0
    OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
565
0
    size_t i = animations.Length();
566
0
    MOZ_ASSERT(i != 0, "empty transitions list?");
567
0
    AnimationValue currentValue;
568
0
    do {
569
0
      --i;
570
0
      CSSTransition* anim = animations[i];
571
0
          // properties no longer in 'transition-property'
572
0
      if ((checkProperties &&
573
0
           !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
574
0
          // properties whose computed values changed but for which we
575
0
          // did not start a new transition (because delay and
576
0
          // duration are both zero, or because the new value is not
577
0
          // interpolable); a new transition would have anim->ToValue()
578
0
          // matching currentValue
579
0
          !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
580
0
                                           aNewStyle, currentValue) ||
581
0
          currentValue != anim->ToValue()) {
582
0
        // stop the transition
583
0
        if (anim->HasCurrentEffect()) {
584
0
          EffectSet* effectSet =
585
0
            EffectSet::GetEffectSet(aElement, aPseudoType);
586
0
          if (effectSet) {
587
0
            effectSet->UpdateAnimationGeneration(mPresContext);
588
0
          }
589
0
        }
590
0
        anim->CancelFromStyle();
591
0
        animations.RemoveElementAt(i);
592
0
      }
593
0
    } while (i != 0);
594
0
595
0
    if (animations.IsEmpty()) {
596
0
      aElementTransitions->Destroy();
597
0
      aElementTransitions = nullptr;
598
0
    }
599
0
  }
600
0
601
0
  return startedAny;
602
0
}
603
604
static Keyframe&
605
AppendKeyframe(double aOffset,
606
               nsCSSPropertyID aProperty,
607
               AnimationValue&& aValue,
608
               nsTArray<Keyframe>& aKeyframes)
609
0
{
610
0
  Keyframe& frame = *aKeyframes.AppendElement();
611
0
  frame.mOffset.emplace(aOffset);
612
0
613
0
  if (aValue.mServo) {
614
0
    RefPtr<RawServoDeclarationBlock> decl =
615
0
      Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
616
0
    frame.mPropertyValues.AppendElement(
617
0
      PropertyValuePair(aProperty, std::move(decl)));
618
0
  } else {
619
0
    MOZ_CRASH("old style system disabled");
620
0
  }
621
0
  return frame;
622
0
}
623
624
static nsTArray<Keyframe>
625
GetTransitionKeyframes(nsCSSPropertyID aProperty,
626
                       AnimationValue&& aStartValue,
627
                       AnimationValue&& aEndValue,
628
                       const nsTimingFunction& aTimingFunction)
629
0
{
630
0
  nsTArray<Keyframe> keyframes(2);
631
0
632
0
  Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, std::move(aStartValue),
633
0
                                       keyframes);
634
0
  if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
635
0
    fromFrame.mTimingFunction.emplace();
636
0
    fromFrame.mTimingFunction->Init(aTimingFunction);
637
0
  }
638
0
639
0
  AppendKeyframe(1.0, aProperty, std::move(aEndValue), keyframes);
640
0
641
0
  return keyframes;
642
0
}
643
644
static bool
645
IsTransitionable(nsCSSPropertyID aProperty)
646
0
{
647
0
  return Servo_Property_IsTransitionable(aProperty);
648
0
}
649
650
bool
651
nsTransitionManager::ConsiderInitiatingTransition(
652
  nsCSSPropertyID aProperty,
653
  const nsStyleDisplay& aStyleDisplay,
654
  uint32_t transitionIdx,
655
  dom::Element* aElement,
656
  CSSPseudoElementType aPseudoType,
657
  CSSTransitionCollection*& aElementTransitions,
658
  const ComputedStyle& aOldStyle,
659
  const ComputedStyle& aNewStyle,
660
  nsCSSPropertyIDSet& aPropertiesChecked)
661
0
{
662
0
  // IsShorthand itself will assert if aProperty is not a property.
663
0
  MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty), "property out of range");
664
0
  NS_ASSERTION(!aElementTransitions ||
665
0
               aElementTransitions->mElement == aElement, "Element mismatch");
666
0
667
0
  aProperty = nsCSSProps::Physicalize(aProperty, aNewStyle);
668
0
669
0
  // A later item in transition-property already specified a transition for
670
0
  // this property, so we ignore this one.
671
0
  //
672
0
  // See http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
673
0
  if (aPropertiesChecked.HasProperty(aProperty)) {
674
0
    return false;
675
0
  }
676
0
677
0
  aPropertiesChecked.AddProperty(aProperty);
678
0
679
0
  if (!IsTransitionable(aProperty)) {
680
0
    return false;
681
0
  }
682
0
683
0
  float delay = aStyleDisplay.GetTransitionDelay(transitionIdx);
684
0
685
0
  // The spec says a negative duration is treated as zero.
686
0
  float duration =
687
0
    std::max(aStyleDisplay.GetTransitionDuration(transitionIdx), 0.0f);
688
0
689
0
  // If the combined duration of this transition is 0 or less don't start a
690
0
  // transition.
691
0
  if (delay + duration <= 0.0f) {
692
0
    return false;
693
0
  }
694
0
695
0
  dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
696
0
697
0
  AnimationValue startValue, endValue;
698
0
  bool haveValues =
699
0
    ExtractNonDiscreteComputedValue(aProperty, aOldStyle, startValue) &&
700
0
    ExtractNonDiscreteComputedValue(aProperty, aNewStyle, endValue);
701
0
702
0
  bool haveChange = startValue != endValue;
703
0
704
0
  bool shouldAnimate =
705
0
    haveValues &&
706
0
    haveChange &&
707
0
    startValue.IsInterpolableWith(aProperty, endValue);
708
0
709
0
  bool haveCurrentTransition = false;
710
0
  size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
711
0
  const ElementPropertyTransition *oldPT = nullptr;
712
0
  if (aElementTransitions) {
713
0
    OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
714
0
    for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
715
0
      if (animations[i]->TransitionProperty() == aProperty) {
716
0
        haveCurrentTransition = true;
717
0
        currentIndex = i;
718
0
        oldPT = animations[i]->GetEffect()
719
0
                ? animations[i]->GetEffect()->AsTransition()
720
0
                : nullptr;
721
0
        break;
722
0
      }
723
0
    }
724
0
  }
725
0
726
0
  // If we got a style change that changed the value to the endpoint
727
0
  // of the currently running transition, we don't want to interrupt
728
0
  // its timing function.
729
0
  // This needs to be before the !shouldAnimate && haveCurrentTransition
730
0
  // case below because we might be close enough to the end of the
731
0
  // transition that the current value rounds to the final value.  In
732
0
  // this case, we'll end up with shouldAnimate as false (because
733
0
  // there's no value change), but we need to return early here rather
734
0
  // than cancel the running transition because shouldAnimate is false!
735
0
  //
736
0
  // Likewise, if we got a style change that changed the value to the
737
0
  // endpoint of our finished transition, we also don't want to start
738
0
  // a new transition for the reasons described in
739
0
  // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
740
0
  if (haveCurrentTransition && haveValues &&
741
0
      aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
742
0
    // GetAnimationRule already called RestyleForAnimation.
743
0
    return false;
744
0
  }
745
0
746
0
  if (!shouldAnimate) {
747
0
    if (haveCurrentTransition) {
748
0
      // We're in the middle of a transition, and just got a non-transition
749
0
      // style change to something that we can't animate.  This might happen
750
0
      // because we got a non-transition style change changing to the current
751
0
      // in-progress value (which is particularly easy to cause when we're
752
0
      // currently in the 'transition-delay').  It also might happen because we
753
0
      // just got a style change to a value that can't be interpolated.
754
0
      OwningCSSTransitionPtrArray& animations =
755
0
        aElementTransitions->mAnimations;
756
0
      animations[currentIndex]->CancelFromStyle();
757
0
      oldPT = nullptr; // Clear pointer so it doesn't dangle
758
0
      animations.RemoveElementAt(currentIndex);
759
0
      EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
760
0
      if (effectSet) {
761
0
        effectSet->UpdateAnimationGeneration(mPresContext);
762
0
      }
763
0
764
0
      if (animations.IsEmpty()) {
765
0
        aElementTransitions->Destroy();
766
0
        // |aElementTransitions| is now a dangling pointer!
767
0
        aElementTransitions = nullptr;
768
0
      }
769
0
      // GetAnimationRule already called RestyleForAnimation.
770
0
    }
771
0
    return false;
772
0
  }
773
0
774
0
  const nsTimingFunction &tf =
775
0
    aStyleDisplay.GetTransitionTimingFunction(transitionIdx);
776
0
777
0
  AnimationValue startForReversingTest = startValue;
778
0
  double reversePortion = 1.0;
779
0
780
0
  // If the new transition reverses an existing one, we'll need to
781
0
  // handle the timing differently.
782
0
  // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
783
0
  //        and set the timing function on transitions as an effect-level
784
0
  //        easing (rather than keyframe-level easing). (Bug 1292001)
785
0
  if (haveCurrentTransition &&
786
0
      aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
787
0
      oldPT &&
788
0
      oldPT->mStartForReversingTest == endValue) {
789
0
    // Compute the appropriate negative transition-delay such that right
790
0
    // now we'd end up at the current position.
791
0
    double valuePortion =
792
0
      oldPT->CurrentValuePortion() * oldPT->mReversePortion +
793
0
      (1.0 - oldPT->mReversePortion);
794
0
    // A timing function with negative y1 (or y2!) might make
795
0
    // valuePortion negative.  In this case, we still want to apply our
796
0
    // reversing logic based on relative distances, not make duration
797
0
    // negative.
798
0
    if (valuePortion < 0.0) {
799
0
      valuePortion = -valuePortion;
800
0
    }
801
0
    // A timing function with y2 (or y1!) greater than one might
802
0
    // advance past its terminal value.  It's probably a good idea to
803
0
    // clamp valuePortion to be at most one to preserve the invariant
804
0
    // that a transition will complete within at most its specified
805
0
    // time.
806
0
    if (valuePortion > 1.0) {
807
0
      valuePortion = 1.0;
808
0
    }
809
0
810
0
    // Negative delays are essentially part of the transition
811
0
    // function, so reduce them along with the duration, but don't
812
0
    // reduce positive delays.
813
0
    if (delay < 0.0f) {
814
0
      delay *= valuePortion;
815
0
    }
816
0
817
0
    duration *= valuePortion;
818
0
819
0
    startForReversingTest = oldPT->ToValue();
820
0
    reversePortion = valuePortion;
821
0
  }
822
0
823
0
  TimingParams timing =
824
0
    TimingParamsFromCSSParams(duration, delay,
825
0
                              1.0 /* iteration count */,
826
0
                              dom::PlaybackDirection::Normal,
827
0
                              dom::FillMode::Backwards);
828
0
829
0
  // aElement is non-null here, so we emplace it directly.
830
0
  Maybe<OwningAnimationTarget> target;
831
0
  target.emplace(aElement, aPseudoType);
832
0
  KeyframeEffectParams effectOptions;
833
0
  RefPtr<ElementPropertyTransition> pt =
834
0
    new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
835
0
                                  startForReversingTest, reversePortion,
836
0
                                  effectOptions);
837
0
838
0
  pt->SetKeyframes(GetTransitionKeyframes(aProperty,
839
0
                                          std::move(startValue), std::move(endValue), tf),
840
0
                   &aNewStyle);
841
0
842
0
  RefPtr<CSSTransition> animation =
843
0
    new CSSTransition(mPresContext->Document()->GetScopeObject());
844
0
  animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
845
0
  animation->SetTimelineNoUpdate(timeline);
846
0
  animation->SetCreationSequence(
847
0
    mPresContext->RestyleManager()->GetAnimationGeneration());
848
0
  animation->SetEffectFromStyle(pt);
849
0
  animation->PlayFromStyle();
850
0
851
0
  if (!aElementTransitions) {
852
0
    bool createdCollection = false;
853
0
    aElementTransitions =
854
0
      CSSTransitionCollection::GetOrCreateAnimationCollection(
855
0
        aElement, aPseudoType, &createdCollection);
856
0
    if (!aElementTransitions) {
857
0
      MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
858
0
      NS_WARNING("allocating collection failed");
859
0
      return false;
860
0
    }
861
0
862
0
    if (createdCollection) {
863
0
      AddElementCollection(aElementTransitions);
864
0
    }
865
0
  }
866
0
867
0
  OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
868
#ifdef DEBUG
869
  for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
870
    MOZ_ASSERT(
871
      i == currentIndex || animations[i]->TransitionProperty() != aProperty,
872
      "duplicate transitions for property");
873
  }
874
#endif
875
0
  if (haveCurrentTransition) {
876
0
    // If this new transition is replacing an existing transition that is running
877
0
    // on the compositor, we store select parameters from the replaced transition
878
0
    // so that later, once all scripts have run, we can update the start value
879
0
    // of the transition using TimeStamp::Now(). This allows us to avoid a
880
0
    // large jump when starting a new transition when the main thread lags behind
881
0
    // the compositor.
882
0
    if (oldPT &&
883
0
        oldPT->IsCurrent() &&
884
0
        oldPT->IsRunningOnCompositor() &&
885
0
        !oldPT->GetAnimation()->GetStartTime().IsNull() &&
886
0
        timeline == oldPT->GetAnimation()->GetTimeline()) {
887
0
      const AnimationPropertySegment& segment =
888
0
        oldPT->Properties()[0].mSegments[0];
889
0
      pt->mReplacedTransition.emplace(
890
0
        ElementPropertyTransition::ReplacedTransitionProperties({
891
0
          oldPT->GetAnimation()->GetStartTime().Value(),
892
0
          oldPT->GetAnimation()->PlaybackRate(),
893
0
          oldPT->SpecifiedTiming(),
894
0
          segment.mTimingFunction,
895
0
          segment.mFromValue,
896
0
          segment.mToValue
897
0
        })
898
0
      );
899
0
    }
900
0
    animations[currentIndex]->CancelFromStyle();
901
0
    oldPT = nullptr; // Clear pointer so it doesn't dangle
902
0
    animations[currentIndex] = animation;
903
0
  } else {
904
0
    if (!animations.AppendElement(animation)) {
905
0
      NS_WARNING("out of memory");
906
0
      return false;
907
0
    }
908
0
  }
909
0
910
0
  EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
911
0
  if (effectSet) {
912
0
    effectSet->UpdateAnimationGeneration(mPresContext);
913
0
  }
914
0
915
0
  return true;
916
0
}
917