Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
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 "nsContentUtils.h"
15 : #include "nsStyleContext.h"
16 : #include "mozilla/MemoryReporting.h"
17 : #include "mozilla/TimeStamp.h"
18 : #include "nsRefreshDriver.h"
19 : #include "nsRuleProcessorData.h"
20 : #include "nsRuleWalker.h"
21 : #include "nsCSSPropertyIDSet.h"
22 : #include "mozilla/EffectCompositor.h"
23 : #include "mozilla/EffectSet.h"
24 : #include "mozilla/EventDispatcher.h"
25 : #include "mozilla/ServoBindings.h"
26 : #include "mozilla/StyleAnimationValue.h"
27 : #include "mozilla/dom/DocumentTimeline.h"
28 : #include "mozilla/dom/Element.h"
29 : #include "nsIFrame.h"
30 : #include "Layers.h"
31 : #include "FrameLayerBuilder.h"
32 : #include "nsCSSProps.h"
33 : #include "nsCSSPseudoElements.h"
34 : #include "nsDisplayList.h"
35 : #include "nsStyleChangeList.h"
36 : #include "nsStyleSet.h"
37 : #include "mozilla/RestyleManager.h"
38 : #include "mozilla/RestyleManagerInlines.h"
39 : #include "nsDOMMutationObserver.h"
40 :
41 : using mozilla::TimeStamp;
42 : using mozilla::TimeDuration;
43 : using mozilla::dom::Animation;
44 : using mozilla::dom::AnimationPlayState;
45 : using mozilla::dom::CSSTransition;
46 : using mozilla::dom::KeyframeEffectReadOnly;
47 :
48 : using namespace mozilla;
49 : using namespace mozilla::css;
50 :
51 : namespace {
52 : struct TransitionEventParams {
53 : EventMessage mMessage;
54 : StickyTimeDuration mElapsedTime;
55 : TimeStamp mTimeStamp;
56 : };
57 : } // anonymous namespace
58 :
59 : double
60 0 : ElementPropertyTransition::CurrentValuePortion() const
61 : {
62 0 : MOZ_ASSERT(!GetLocalTime().IsNull(),
63 : "Getting the value portion of an animation that's not being "
64 : "sampled");
65 :
66 : // Transitions use a fill mode of 'backwards' so GetComputedTiming will
67 : // never return a null time progress due to being *before* the animation
68 : // interval. However, it might be possible that we're behind on flushing
69 : // causing us to get called *after* the animation interval. So, just in
70 : // case, we override the fill mode to 'both' to ensure the progress
71 : // is never null.
72 0 : TimingParams timingToUse = SpecifiedTiming();
73 0 : timingToUse.SetFill(dom::FillMode::Both);
74 0 : ComputedTiming computedTiming = GetComputedTiming(&timingToUse);
75 :
76 0 : MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
77 : "Got a null progress for a fill mode of 'both'");
78 0 : MOZ_ASSERT(mKeyframes.Length() == 2,
79 : "Should have two animation keyframes for a transition");
80 0 : return ComputedTimingFunction::GetPortion(mKeyframes[0].mTimingFunction,
81 0 : computedTiming.mProgress.Value(),
82 0 : computedTiming.mBeforeFlag);
83 : }
84 :
85 : void
86 0 : ElementPropertyTransition::UpdateStartValueFromReplacedTransition()
87 : {
88 0 : if (!mReplacedTransition) {
89 0 : return;
90 : }
91 0 : MOZ_ASSERT(nsCSSProps::PropHasFlags(TransitionProperty(),
92 : CSS_PROPERTY_CAN_ANIMATE_ON_COMPOSITOR),
93 : "The transition property should be able to be run on the "
94 : "compositor");
95 0 : MOZ_ASSERT(mTarget && mTarget->mElement->OwnerDoc(),
96 : "We should have a valid document at this moment");
97 :
98 0 : dom::DocumentTimeline* timeline = mTarget->mElement->OwnerDoc()->Timeline();
99 : ComputedTiming computedTiming = GetComputedTimingAt(
100 0 : dom::CSSTransition::GetCurrentTimeAt(*timeline,
101 0 : TimeStamp::Now(),
102 0 : mReplacedTransition->mStartTime,
103 0 : mReplacedTransition->mPlaybackRate),
104 0 : mReplacedTransition->mTiming,
105 0 : mReplacedTransition->mPlaybackRate);
106 :
107 0 : if (!computedTiming.mProgress.IsNull()) {
108 : double valuePosition =
109 0 : ComputedTimingFunction::GetPortion(mReplacedTransition->mTimingFunction,
110 0 : computedTiming.mProgress.Value(),
111 0 : computedTiming.mBeforeFlag);
112 :
113 0 : MOZ_ASSERT(mProperties.Length() == 1 &&
114 : mProperties[0].mSegments.Length() == 1,
115 : "The transition should have one property and one segment");
116 0 : MOZ_ASSERT(mKeyframes.Length() == 2,
117 : "Transitions should have exactly two animation keyframes");
118 0 : MOZ_ASSERT(mKeyframes[0].mPropertyValues.Length() == 1,
119 : "Transitions should have exactly one property in their first "
120 : "frame");
121 :
122 0 : const AnimationValue& replacedFrom = mReplacedTransition->mFromValue;
123 0 : const AnimationValue& replacedTo = mReplacedTransition->mToValue;
124 0 : AnimationValue startValue;
125 0 : if (mDocument->IsStyledByServo()) {
126 : startValue.mServo =
127 0 : Servo_AnimationValues_Interpolate(replacedFrom.mServo,
128 : replacedTo.mServo,
129 0 : valuePosition).Consume();
130 0 : if (startValue.mServo) {
131 0 : mKeyframes[0].mPropertyValues[0].mServoDeclarationBlock =
132 0 : Servo_AnimationValue_Uncompute(startValue.mServo).Consume();
133 0 : mProperties[0].mSegments[0].mFromValue = Move(startValue);
134 : }
135 0 : } else if (StyleAnimationValue::Interpolate(mProperties[0].mProperty,
136 : replacedFrom.mGecko,
137 : replacedTo.mGecko,
138 : valuePosition,
139 : startValue.mGecko)) {
140 0 : nsCSSValue cssValue;
141 : DebugOnly<bool> uncomputeResult =
142 0 : StyleAnimationValue::UncomputeValue(mProperties[0].mProperty,
143 : startValue.mGecko,
144 0 : cssValue);
145 0 : MOZ_ASSERT(uncomputeResult, "UncomputeValue should not fail");
146 0 : mKeyframes[0].mPropertyValues[0].mValue = cssValue;
147 :
148 0 : mProperties[0].mSegments[0].mFromValue = Move(startValue);
149 : }
150 : }
151 :
152 0 : mReplacedTransition.reset();
153 : }
154 :
155 : ////////////////////////// CSSTransition ////////////////////////////
156 :
157 : JSObject*
158 0 : CSSTransition::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
159 : {
160 0 : return dom::CSSTransitionBinding::Wrap(aCx, this, aGivenProto);
161 : }
162 :
163 : void
164 0 : CSSTransition::GetTransitionProperty(nsString& aRetVal) const
165 : {
166 0 : MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
167 : "Transition Property should be initialized");
168 : aRetVal =
169 0 : NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(mTransitionProperty));
170 0 : }
171 :
172 : AnimationPlayState
173 0 : CSSTransition::PlayStateFromJS() const
174 : {
175 0 : FlushStyle();
176 0 : return Animation::PlayStateFromJS();
177 : }
178 :
179 : void
180 0 : CSSTransition::PlayFromJS(ErrorResult& aRv)
181 : {
182 0 : FlushStyle();
183 0 : Animation::PlayFromJS(aRv);
184 0 : }
185 :
186 : void
187 32 : CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
188 : {
189 32 : if (mNeedsNewAnimationIndexWhenRun &&
190 0 : PlayState() != AnimationPlayState::Idle) {
191 0 : mAnimationIndex = sNextAnimationIndex++;
192 0 : mNeedsNewAnimationIndexWhenRun = false;
193 : }
194 :
195 32 : Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
196 32 : }
197 :
198 : void
199 24 : CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
200 : {
201 24 : if (!mOwningElement.IsSet()) {
202 0 : return;
203 : }
204 :
205 : dom::Element* owningElement;
206 : CSSPseudoElementType owningPseudoType;
207 24 : mOwningElement.GetElement(owningElement, owningPseudoType);
208 24 : MOZ_ASSERT(owningElement, "Owning element should be set");
209 :
210 : nsPresContext* presContext =
211 24 : nsContentUtils::GetContextForContent(owningElement);
212 24 : if (!presContext) {
213 0 : return;
214 : }
215 :
216 24 : const StickyTimeDuration zeroDuration = StickyTimeDuration();
217 :
218 : TransitionPhase currentPhase;
219 24 : StickyTimeDuration intervalStartTime;
220 24 : StickyTimeDuration intervalEndTime;
221 :
222 24 : if (!mEffect) {
223 0 : currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
224 : } else {
225 48 : ComputedTiming computedTiming = mEffect->GetComputedTiming();
226 :
227 24 : currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
228 24 : intervalStartTime =
229 48 : std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().Delay()),
230 24 : computedTiming.mActiveDuration), zeroDuration);
231 24 : intervalEndTime =
232 48 : std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().Delay()),
233 24 : computedTiming.mActiveDuration), zeroDuration);
234 : }
235 :
236 : // TimeStamps to use for ordering the events when they are dispatched. We
237 : // use a TimeStamp so we can compare events produced by different elements,
238 : // perhaps even with different timelines.
239 : // The zero timestamp is for transitionrun events where we ignore the delay
240 : // for the purpose of ordering events.
241 24 : TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
242 24 : TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
243 24 : TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
244 :
245 42 : if (mPendingState != PendingState::NotPending &&
246 34 : (mPreviousTransitionPhase == TransitionPhase::Idle ||
247 16 : mPreviousTransitionPhase == TransitionPhase::Pending))
248 : {
249 18 : currentPhase = TransitionPhase::Pending;
250 : }
251 :
252 48 : AutoTArray<TransitionEventParams, 3> events;
253 :
254 : // Handle cancel events first
255 46 : if ((mPreviousTransitionPhase != TransitionPhase::Idle &&
256 44 : mPreviousTransitionPhase != TransitionPhase::After) &&
257 : currentPhase == TransitionPhase::Idle) {
258 0 : TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
259 0 : events.AppendElement(TransitionEventParams{ eTransitionCancel,
260 : aActiveTime,
261 0 : activeTimeStamp });
262 : }
263 :
264 : // All other events
265 24 : switch (mPreviousTransitionPhase) {
266 : case TransitionPhase::Idle:
267 2 : if (currentPhase == TransitionPhase::Pending ||
268 : currentPhase == TransitionPhase::Before) {
269 4 : events.AppendElement(TransitionEventParams{ eTransitionRun,
270 : intervalStartTime,
271 4 : zeroTimeStamp });
272 0 : } else if (currentPhase == TransitionPhase::Active) {
273 0 : events.AppendElement(TransitionEventParams{ eTransitionRun,
274 : intervalStartTime,
275 0 : zeroTimeStamp });
276 0 : events.AppendElement(TransitionEventParams{ eTransitionStart,
277 : intervalStartTime,
278 0 : startTimeStamp });
279 0 : } else if (currentPhase == TransitionPhase::After) {
280 0 : events.AppendElement(TransitionEventParams{ eTransitionRun,
281 : intervalStartTime,
282 0 : zeroTimeStamp });
283 0 : events.AppendElement(TransitionEventParams{ eTransitionStart,
284 : intervalStartTime,
285 0 : startTimeStamp });
286 0 : events.AppendElement(TransitionEventParams{ eTransitionEnd,
287 : intervalEndTime,
288 0 : endTimeStamp });
289 : }
290 2 : break;
291 :
292 : case TransitionPhase::Pending:
293 : case TransitionPhase::Before:
294 18 : if (currentPhase == TransitionPhase::Active) {
295 4 : events.AppendElement(TransitionEventParams{ eTransitionStart,
296 : intervalStartTime,
297 2 : startTimeStamp });
298 16 : } else if (currentPhase == TransitionPhase::After) {
299 0 : events.AppendElement(TransitionEventParams{ eTransitionStart,
300 : intervalStartTime,
301 0 : startTimeStamp });
302 0 : events.AppendElement(TransitionEventParams{ eTransitionEnd,
303 : intervalEndTime,
304 0 : endTimeStamp });
305 : }
306 18 : break;
307 :
308 : case TransitionPhase::Active:
309 4 : if (currentPhase == TransitionPhase::After) {
310 4 : events.AppendElement(TransitionEventParams{ eTransitionEnd,
311 : intervalEndTime,
312 2 : endTimeStamp });
313 2 : } else if (currentPhase == TransitionPhase::Before) {
314 0 : events.AppendElement(TransitionEventParams{ eTransitionEnd,
315 : intervalStartTime,
316 0 : startTimeStamp });
317 : }
318 4 : break;
319 :
320 : case TransitionPhase::After:
321 0 : if (currentPhase == TransitionPhase::Active) {
322 0 : events.AppendElement(TransitionEventParams{ eTransitionStart,
323 : intervalEndTime,
324 0 : startTimeStamp });
325 0 : } else if (currentPhase == TransitionPhase::Before) {
326 0 : events.AppendElement(TransitionEventParams{ eTransitionStart,
327 : intervalEndTime,
328 0 : startTimeStamp });
329 0 : events.AppendElement(TransitionEventParams{ eTransitionEnd,
330 : intervalStartTime,
331 0 : endTimeStamp });
332 : }
333 0 : break;
334 : }
335 24 : mPreviousTransitionPhase = currentPhase;
336 :
337 24 : nsTransitionManager* manager = presContext->TransitionManager();
338 30 : for (const TransitionEventParams& evt : events) {
339 18 : manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
340 6 : evt.mMessage,
341 : TransitionProperty(),
342 : evt.mElapsedTime,
343 : evt.mTimeStamp,
344 6 : this));
345 : }
346 : }
347 :
348 : void
349 24 : CSSTransition::Tick()
350 : {
351 24 : Animation::Tick();
352 24 : QueueEvents();
353 24 : }
354 :
355 : nsCSSPropertyID
356 34 : CSSTransition::TransitionProperty() const
357 : {
358 34 : MOZ_ASSERT(eCSSProperty_UNKNOWN != mTransitionProperty,
359 : "Transition property should be initialized");
360 34 : return mTransitionProperty;
361 : }
362 :
363 : AnimationValue
364 18 : CSSTransition::ToValue() const
365 : {
366 18 : MOZ_ASSERT(!mTransitionToValue.IsNull(),
367 : "Transition ToValue should be initialized");
368 18 : return mTransitionToValue;
369 : }
370 :
371 : bool
372 3 : CSSTransition::HasLowerCompositeOrderThan(const CSSTransition& aOther) const
373 : {
374 3 : MOZ_ASSERT(IsTiedToMarkup() && aOther.IsTiedToMarkup(),
375 : "Should only be called for CSS transitions that are sorted "
376 : "as CSS transitions (i.e. tied to CSS markup)");
377 :
378 : // 0. Object-equality case
379 3 : if (&aOther == this) {
380 0 : return false;
381 : }
382 :
383 : // 1. Sort by document order
384 3 : if (!mOwningElement.Equals(aOther.mOwningElement)) {
385 3 : return mOwningElement.LessThan(aOther.mOwningElement);
386 : }
387 :
388 : // 2. (Same element and pseudo): Sort by transition generation
389 0 : if (mAnimationIndex != aOther.mAnimationIndex) {
390 0 : return mAnimationIndex < aOther.mAnimationIndex;
391 : }
392 :
393 : // 3. (Same transition generation): Sort by transition property
394 0 : return nsCSSProps::GetStringValue(TransitionProperty()) <
395 0 : nsCSSProps::GetStringValue(aOther.TransitionProperty());
396 : }
397 :
398 : /* static */ Nullable<TimeDuration>
399 0 : CSSTransition::GetCurrentTimeAt(const dom::DocumentTimeline& aTimeline,
400 : const TimeStamp& aBaseTime,
401 : const TimeDuration& aStartTime,
402 : double aPlaybackRate)
403 : {
404 0 : Nullable<TimeDuration> result;
405 :
406 0 : Nullable<TimeDuration> timelineTime = aTimeline.ToTimelineTime(aBaseTime);
407 0 : if (!timelineTime.IsNull()) {
408 0 : result.SetValue((timelineTime.Value() - aStartTime)
409 0 : .MultDouble(aPlaybackRate));
410 : }
411 :
412 0 : return result;
413 : }
414 :
415 : void
416 2 : CSSTransition::SetEffectFromStyle(dom::AnimationEffectReadOnly* aEffect)
417 : {
418 2 : Animation::SetEffectNoUpdate(aEffect);
419 :
420 : // Initialize transition property.
421 2 : ElementPropertyTransition* pt = aEffect ? aEffect->AsTransition() : nullptr;
422 2 : if (eCSSProperty_UNKNOWN == mTransitionProperty && pt) {
423 2 : mTransitionProperty = pt->TransitionProperty();
424 2 : mTransitionToValue = pt->ToValue();
425 : }
426 2 : }
427 :
428 : ////////////////////////// nsTransitionManager ////////////////////////////
429 :
430 0 : NS_IMPL_CYCLE_COLLECTION(nsTransitionManager, mEventDispatcher)
431 :
432 0 : NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTransitionManager, AddRef)
433 0 : NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTransitionManager, Release)
434 :
435 : static inline bool
436 510 : ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
437 : nsStyleContext* aStyleContext,
438 : AnimationValue& aAnimationValue)
439 : {
440 528 : return (nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_Discrete ||
441 1020 : aProperty == eCSSProperty_visibility) &&
442 510 : StyleAnimationValue::ExtractComputedValue(aProperty, aStyleContext,
443 510 : aAnimationValue.mGecko);
444 : }
445 :
446 : static inline bool
447 0 : ExtractNonDiscreteComputedValue(nsCSSPropertyID aProperty,
448 : const ServoComputedValues* aComputedStyle,
449 : AnimationValue& aAnimationValue)
450 : {
451 0 : if (Servo_Property_IsDiscreteAnimatable(aProperty) &&
452 : aProperty != eCSSProperty_visibility) {
453 0 : return false;
454 : }
455 :
456 : aAnimationValue.mServo =
457 0 : Servo_ComputedValues_ExtractAnimationValue(aComputedStyle,
458 0 : aProperty).Consume();
459 0 : return !!aAnimationValue.mServo;
460 : }
461 :
462 : void
463 792 : nsTransitionManager::StyleContextChanged(dom::Element *aElement,
464 : nsStyleContext *aOldStyleContext,
465 : RefPtr<nsStyleContext>* aNewStyleContext /* inout */)
466 : {
467 792 : nsStyleContext* newStyleContext = *aNewStyleContext;
468 :
469 792 : NS_PRECONDITION(aOldStyleContext->GetPseudo() == newStyleContext->GetPseudo(),
470 : "pseudo type mismatch");
471 :
472 792 : if (mInAnimationOnlyStyleUpdate) {
473 : // If we're doing an animation-only style update, return, since the
474 : // purpose of an animation-only style update is to update only the
475 : // animation styles so that we don't consider style changes
476 : // resulting from changes in the animation time for starting a
477 : // transition.
478 724 : return;
479 : }
480 :
481 788 : if (!mPresContext->IsDynamic()) {
482 : // For print or print preview, ignore transitions.
483 0 : return;
484 : }
485 :
486 1576 : if (aOldStyleContext->HasPseudoElementData() !=
487 788 : newStyleContext->HasPseudoElementData()) {
488 : // If the old style context and new style context differ in terms of
489 : // whether they're inside ::first-letter, ::first-line, or similar,
490 : // bail. We can't hit this codepath for normal style changes
491 : // involving moving frames around the boundaries of these
492 : // pseudo-elements since we don't call StyleContextChanged from
493 : // ReparentStyleContext. However, we can hit this codepath during
494 : // the handling of transitions that start across reframes.
495 : //
496 : // While there isn't an easy *perfect* way to handle this case, err
497 : // on the side of missing some transitions that we ought to have
498 : // rather than having bogus transitions that we shouldn't.
499 : //
500 : // We could consider changing this handling, although it's worth
501 : // thinking about whether the code below could do anything weird in
502 : // this case.
503 0 : return;
504 : }
505 :
506 : // NOTE: Things in this function (and ConsiderInitiatingTransition)
507 : // should never call PeekStyleData because we don't preserve gotten
508 : // structs across reframes.
509 :
510 : // Return sooner (before the startedAny check below) for the most
511 : // common case: no transitions specified or running.
512 788 : const nsStyleDisplay *disp = newStyleContext->StyleDisplay();
513 788 : CSSPseudoElementType pseudoType = newStyleContext->GetPseudoType();
514 788 : if (pseudoType != CSSPseudoElementType::NotPseudo) {
515 102 : if (pseudoType != CSSPseudoElementType::before &&
516 : pseudoType != CSSPseudoElementType::after) {
517 80 : return;
518 : }
519 :
520 22 : NS_ASSERTION((pseudoType == CSSPseudoElementType::before &&
521 : aElement->IsGeneratedContentContainerForBefore()) ||
522 : (pseudoType == CSSPseudoElementType::after &&
523 : aElement->IsGeneratedContentContainerForAfter()),
524 : "Unexpected aElement coming through");
525 :
526 : // Else the element we want to use from now on is the element the
527 : // :before or :after is attached to.
528 22 : aElement = aElement->GetParent()->AsElement();
529 : }
530 :
531 : CSSTransitionCollection* collection =
532 708 : CSSTransitionCollection::GetAnimationCollection(aElement, pseudoType);
533 2110 : if (!collection &&
534 1362 : disp->mTransitionPropertyCount == 1 &&
535 654 : disp->mTransitions[0].GetCombinedDuration() <= 0.0f) {
536 630 : return;
537 : }
538 :
539 78 : MOZ_ASSERT(mPresContext->RestyleManager()->IsGecko(),
540 : "ServoRestyleManager should not use nsTransitionManager "
541 : "for transitions");
542 92 : if (collection &&
543 14 : collection->mCheckGeneration ==
544 92 : mPresContext->RestyleManager()->GetAnimationGeneration()) {
545 : // When we start a new transition, we immediately post a restyle.
546 : // If the animation generation on the collection is current, that
547 : // means *this* is that restyle, since we bump the animation
548 : // generation on the restyle manager whenever there's a real style
549 : // change (i.e., one where mInAnimationOnlyStyleUpdate isn't true,
550 : // which causes us to return above). Thus we shouldn't do anything.
551 6 : return;
552 : }
553 144 : if (newStyleContext->GetParent() &&
554 72 : newStyleContext->GetParent()->HasPseudoElementData()) {
555 : // Ignore transitions on things that inherit properties from
556 : // pseudo-elements.
557 : // FIXME (Bug 522599): Add tests for this.
558 0 : return;
559 : }
560 :
561 72 : NS_WARNING_ASSERTION(
562 : !mPresContext->EffectCompositor()->HasThrottledStyleUpdates(),
563 : "throttled animations not up to date");
564 :
565 : // Compute what the css-transitions spec calls the "after-change
566 : // style", which is the new style without any data from transitions,
567 : // but still inheriting from data that contains transitions that are
568 : // not stopping or starting right now.
569 144 : RefPtr<nsStyleContext> afterChangeStyle;
570 72 : if (collection) {
571 8 : MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
572 : "ServoStyleSets should not use nsTransitionManager "
573 : "for transitions");
574 8 : nsStyleSet* styleSet = mPresContext->StyleSet()->AsGecko();
575 : afterChangeStyle =
576 16 : styleSet->ResolveStyleByRemovingAnimation(aElement, newStyleContext,
577 8 : eRestyle_CSSTransitions);
578 : } else {
579 64 : afterChangeStyle = newStyleContext;
580 : }
581 :
582 144 : nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
583 :
584 144 : DebugOnly<bool> startedAny = false;
585 : // We don't have to update transitions if display:none, although we will
586 : // cancel them after restyling.
587 72 : if (!afterChangeStyle->IsInDisplayNoneSubtree()) {
588 144 : startedAny = DoUpdateTransitions(disp,
589 : aElement,
590 : afterChangeStyle->GetPseudoType(),
591 : collection,
592 : aOldStyleContext,
593 72 : afterChangeStyle.get());
594 : }
595 :
596 72 : MOZ_ASSERT(!startedAny || collection,
597 : "must have element transitions if we started any transitions");
598 :
599 : EffectCompositor::CascadeLevel cascadeLevel =
600 72 : EffectCompositor::CascadeLevel::Transitions;
601 :
602 72 : if (collection) {
603 10 : collection->UpdateCheckGeneration(mPresContext);
604 10 : mPresContext->EffectCompositor()->MaybeUpdateAnimationRule(aElement,
605 : pseudoType,
606 : cascadeLevel,
607 10 : newStyleContext);
608 : }
609 :
610 : // We want to replace the new style context with the after-change style.
611 72 : *aNewStyleContext = afterChangeStyle;
612 72 : if (collection) {
613 : // Since we have transition styles, we have to undo this replacement.
614 : // The check of collection->mCheckGeneration against the restyle
615 : // manager's GetAnimationGeneration() will ensure that we don't go
616 : // through the rest of this function again when we do.
617 10 : mPresContext->EffectCompositor()->PostRestyleForAnimation(aElement,
618 : pseudoType,
619 10 : cascadeLevel);
620 : }
621 : }
622 :
623 : bool
624 0 : nsTransitionManager::UpdateTransitions(
625 : dom::Element *aElement,
626 : CSSPseudoElementType aPseudoType,
627 : const ServoComputedValues* aOldStyle,
628 : const ServoComputedValues* aNewStyle)
629 : {
630 0 : if (!mPresContext->IsDynamic()) {
631 : // For print or print preview, ignore transitions.
632 0 : return false;
633 : }
634 :
635 : CSSTransitionCollection* collection =
636 0 : CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
637 0 : const nsStyleDisplay *disp = Servo_GetStyleDisplay(aNewStyle);
638 : return DoUpdateTransitions(disp,
639 : aElement, aPseudoType,
640 : collection,
641 0 : aOldStyle, aNewStyle);
642 : }
643 :
644 : template<typename StyleType>
645 : bool
646 72 : nsTransitionManager::DoUpdateTransitions(
647 : const nsStyleDisplay* aDisp,
648 : dom::Element* aElement,
649 : CSSPseudoElementType aPseudoType,
650 : CSSTransitionCollection*& aElementTransitions,
651 : StyleType aOldStyle,
652 : StyleType aNewStyle)
653 : {
654 72 : MOZ_ASSERT(aDisp, "Null nsStyleDisplay");
655 72 : MOZ_ASSERT(!aElementTransitions ||
656 : aElementTransitions->mElement == aElement, "Element mismatch");
657 :
658 : // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
659 : // I'll consider only the transitions from the number of items in
660 : // 'transition-property' on down, and later ones will override earlier
661 : // ones (tracked using |whichStarted|).
662 72 : bool startedAny = false;
663 72 : nsCSSPropertyIDSet whichStarted;
664 220 : for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
665 148 : const StyleTransition& t = aDisp->mTransitions[i];
666 : // Check the combined duration (combination of delay and duration)
667 : // first, since it defaults to zero, which means we can ignore the
668 : // transition.
669 148 : if (t.GetCombinedDuration() > 0.0f) {
670 : // We might have something to transition. See if any of the
671 : // properties in question changed and are animatable.
672 : // FIXME: Would be good to find a way to share code between this
673 : // interpretation of transition-property and the one below.
674 148 : nsCSSPropertyID property = t.GetProperty();
675 148 : if (property == eCSSPropertyExtra_no_properties ||
676 148 : property == eCSSPropertyExtra_variable ||
677 : property == eCSSProperty_UNKNOWN) {
678 : // Nothing to do, but need to exclude this from cases below.
679 148 : } else if (property == eCSSPropertyExtra_all_properties) {
680 0 : for (nsCSSPropertyID p = nsCSSPropertyID(0);
681 0 : p < eCSSProperty_COUNT_no_shorthands;
682 : p = nsCSSPropertyID(p + 1)) {
683 0 : ConsiderInitiatingTransition(p, t, aElement, aPseudoType,
684 : aElementTransitions,
685 : aOldStyle, aNewStyle,
686 : &startedAny, &whichStarted);
687 : }
688 148 : } else if (nsCSSProps::IsShorthand(property)) {
689 170 : CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property,
690 : CSSEnabledState::eForAllContent)
691 : {
692 136 : ConsiderInitiatingTransition(*subprop, t, aElement, aPseudoType,
693 : aElementTransitions,
694 : aOldStyle, aNewStyle,
695 : &startedAny, &whichStarted);
696 : }
697 : } else {
698 114 : ConsiderInitiatingTransition(property, t, aElement, aPseudoType,
699 : aElementTransitions,
700 : aOldStyle, aNewStyle,
701 : &startedAny, &whichStarted);
702 : }
703 : }
704 : }
705 :
706 : // Stop any transitions for properties that are no longer in
707 : // 'transition-property', including finished transitions.
708 : // Also stop any transitions (and remove any finished transitions)
709 : // for properties that just changed (and are still in the set of
710 : // properties to transition), but for which we didn't just start the
711 : // transition. This can happen delay and duration are both zero, or
712 : // because the new value is not interpolable.
713 : // Note that we also do the latter set of work in
714 : // nsTransitionManager::PruneCompletedTransitions.
715 72 : if (aElementTransitions) {
716 : bool checkProperties =
717 10 : aDisp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
718 10 : nsCSSPropertyIDSet allTransitionProperties;
719 10 : if (checkProperties) {
720 20 : for (uint32_t i = aDisp->mTransitionPropertyCount; i-- != 0; ) {
721 10 : const StyleTransition& t = aDisp->mTransitions[i];
722 : // FIXME: Would be good to find a way to share code between this
723 : // interpretation of transition-property and the one above.
724 10 : nsCSSPropertyID property = t.GetProperty();
725 10 : if (property == eCSSPropertyExtra_no_properties ||
726 10 : property == eCSSPropertyExtra_variable ||
727 : property == eCSSProperty_UNKNOWN) {
728 : // Nothing to do, but need to exclude this from cases below.
729 10 : } else if (property == eCSSPropertyExtra_all_properties) {
730 0 : for (nsCSSPropertyID p = nsCSSPropertyID(0);
731 0 : p < eCSSProperty_COUNT_no_shorthands;
732 : p = nsCSSPropertyID(p + 1)) {
733 0 : allTransitionProperties.AddProperty(p);
734 : }
735 10 : } else if (nsCSSProps::IsShorthand(property)) {
736 0 : CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
737 : subprop, property, CSSEnabledState::eForAllContent) {
738 0 : allTransitionProperties.AddProperty(*subprop);
739 : }
740 : } else {
741 10 : allTransitionProperties.AddProperty(property);
742 : }
743 : }
744 : }
745 :
746 10 : OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
747 10 : size_t i = animations.Length();
748 10 : MOZ_ASSERT(i != 0, "empty transitions list?");
749 20 : AnimationValue currentValue;
750 0 : do {
751 10 : --i;
752 10 : CSSTransition* anim = animations[i];
753 : // properties no longer in 'transition-property'
754 30 : if ((checkProperties &&
755 20 : !allTransitionProperties.HasProperty(anim->TransitionProperty())) ||
756 : // properties whose computed values changed but for which we
757 : // did not start a new transition (because delay and
758 : // duration are both zero, or because the new value is not
759 : // interpolable); a new transition would have anim->ToValue()
760 : // matching currentValue
761 10 : !ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
762 30 : aNewStyle, currentValue) ||
763 40 : currentValue != anim->ToValue()) {
764 : // stop the transition
765 0 : if (anim->HasCurrentEffect()) {
766 : EffectSet* effectSet =
767 0 : EffectSet::GetEffectSet(aElement, aPseudoType);
768 0 : if (effectSet) {
769 0 : effectSet->UpdateAnimationGeneration(mPresContext);
770 : }
771 : }
772 0 : anim->CancelFromStyle();
773 0 : animations.RemoveElementAt(i);
774 : }
775 10 : } while (i != 0);
776 :
777 10 : if (animations.IsEmpty()) {
778 0 : aElementTransitions->Destroy();
779 0 : aElementTransitions = nullptr;
780 : }
781 : }
782 :
783 72 : return startedAny;
784 : }
785 :
786 : static Keyframe&
787 4 : AppendKeyframe(double aOffset,
788 : nsCSSPropertyID aProperty,
789 : AnimationValue&& aValue,
790 : nsTArray<Keyframe>& aKeyframes)
791 : {
792 4 : Keyframe& frame = *aKeyframes.AppendElement();
793 4 : frame.mOffset.emplace(aOffset);
794 :
795 4 : if (aValue.mServo) {
796 : RefPtr<RawServoDeclarationBlock> decl =
797 0 : Servo_AnimationValue_Uncompute(aValue.mServo).Consume();
798 0 : frame.mPropertyValues.AppendElement(
799 0 : Move(PropertyValuePair(aProperty, Move(decl))));
800 : } else {
801 8 : nsCSSValue propertyValue;
802 : DebugOnly<bool> uncomputeResult =
803 8 : StyleAnimationValue::UncomputeValue(aProperty, Move(aValue.mGecko),
804 8 : propertyValue);
805 4 : MOZ_ASSERT(uncomputeResult,
806 : "Unable to get specified value from computed value");
807 4 : frame.mPropertyValues.AppendElement(
808 8 : Move(PropertyValuePair(aProperty, Move(propertyValue))));
809 : }
810 4 : return frame;
811 : }
812 :
813 : static nsTArray<Keyframe>
814 2 : GetTransitionKeyframes(nsCSSPropertyID aProperty,
815 : AnimationValue&& aStartValue,
816 : AnimationValue&& aEndValue,
817 : const nsTimingFunction& aTimingFunction)
818 : {
819 2 : nsTArray<Keyframe> keyframes(2);
820 :
821 2 : Keyframe& fromFrame = AppendKeyframe(0.0, aProperty, Move(aStartValue),
822 2 : keyframes);
823 2 : if (aTimingFunction.mType != nsTimingFunction::Type::Linear) {
824 2 : fromFrame.mTimingFunction.emplace();
825 2 : fromFrame.mTimingFunction->Init(aTimingFunction);
826 : }
827 :
828 2 : AppendKeyframe(1.0, aProperty, Move(aEndValue), keyframes);
829 :
830 2 : return keyframes;
831 : }
832 :
833 : static bool
834 250 : IsTransitionable(nsCSSPropertyID aProperty, bool aIsServo)
835 : {
836 250 : if (aIsServo) {
837 0 : return Servo_Property_IsTransitionable(aProperty);
838 : }
839 :
840 : // FIXME: This should also exclude discretely-animated properties.
841 250 : return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None;
842 : }
843 :
844 : template<typename StyleType>
845 : void
846 250 : nsTransitionManager::ConsiderInitiatingTransition(
847 : nsCSSPropertyID aProperty,
848 : const StyleTransition& aTransition,
849 : dom::Element* aElement,
850 : CSSPseudoElementType aPseudoType,
851 : CSSTransitionCollection*& aElementTransitions,
852 : StyleType aOldStyle,
853 : StyleType aNewStyle,
854 : bool* aStartedAny,
855 : nsCSSPropertyIDSet* aWhichStarted)
856 : {
857 : // IsShorthand itself will assert if aProperty is not a property.
858 250 : MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
859 : "property out of range");
860 250 : NS_ASSERTION(!aElementTransitions ||
861 : aElementTransitions->mElement == aElement, "Element mismatch");
862 :
863 : // Ignore disabled properties. We can arrive here if the transition-property
864 : // is 'all' and the disabled property has a default value which derives value
865 : // from another property, e.g. color.
866 250 : if (!nsCSSProps::IsEnabled(aProperty, CSSEnabledState::eForAllContent)) {
867 248 : return;
868 : }
869 :
870 250 : if (aWhichStarted->HasProperty(aProperty)) {
871 : // A later item in transition-property already started a
872 : // transition for this property, so we ignore this one.
873 : // See comment above and
874 : // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
875 0 : return;
876 : }
877 :
878 250 : if (!IsTransitionable(aProperty, aElement->IsStyledByServo())) {
879 0 : return;
880 : }
881 :
882 250 : dom::DocumentTimeline* timeline = aElement->OwnerDoc()->Timeline();
883 :
884 252 : AnimationValue startValue, endValue;
885 : bool haveValues =
886 500 : ExtractNonDiscreteComputedValue(aProperty, aOldStyle, startValue) &&
887 500 : ExtractNonDiscreteComputedValue(aProperty, aNewStyle, endValue);
888 :
889 250 : bool haveChange = startValue != endValue;
890 :
891 : bool shouldAnimate =
892 250 : haveValues &&
893 256 : haveChange &&
894 256 : startValue.IsInterpolableWith(aProperty, endValue);
895 :
896 250 : bool haveCurrentTransition = false;
897 250 : size_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
898 250 : const ElementPropertyTransition *oldPT = nullptr;
899 250 : if (aElementTransitions) {
900 8 : OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
901 8 : for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
902 8 : if (animations[i]->TransitionProperty() == aProperty) {
903 8 : haveCurrentTransition = true;
904 8 : currentIndex = i;
905 16 : oldPT = animations[i]->GetEffect()
906 8 : ? animations[i]->GetEffect()->AsTransition()
907 : : nullptr;
908 8 : break;
909 : }
910 : }
911 : }
912 :
913 : // If we got a style change that changed the value to the endpoint
914 : // of the currently running transition, we don't want to interrupt
915 : // its timing function.
916 : // This needs to be before the !shouldAnimate && haveCurrentTransition
917 : // case below because we might be close enough to the end of the
918 : // transition that the current value rounds to the final value. In
919 : // this case, we'll end up with shouldAnimate as false (because
920 : // there's no value change), but we need to return early here rather
921 : // than cancel the running transition because shouldAnimate is false!
922 : //
923 : // Likewise, if we got a style change that changed the value to the
924 : // endpoint of our finished transition, we also don't want to start
925 : // a new transition for the reasons described in
926 : // https://lists.w3.org/Archives/Public/www-style/2015Jan/0444.html .
927 758 : if (haveCurrentTransition && haveValues &&
928 274 : aElementTransitions->mAnimations[currentIndex]->ToValue() == endValue) {
929 : // GetAnimationRule already called RestyleForAnimation.
930 8 : return;
931 : }
932 :
933 242 : if (!shouldAnimate) {
934 240 : if (haveCurrentTransition) {
935 : // We're in the middle of a transition, and just got a non-transition
936 : // style change to something that we can't animate. This might happen
937 : // because we got a non-transition style change changing to the current
938 : // in-progress value (which is particularly easy to cause when we're
939 : // currently in the 'transition-delay'). It also might happen because we
940 : // just got a style change to a value that can't be interpolated.
941 : OwningCSSTransitionPtrArray& animations =
942 0 : aElementTransitions->mAnimations;
943 0 : animations[currentIndex]->CancelFromStyle();
944 0 : oldPT = nullptr; // Clear pointer so it doesn't dangle
945 0 : animations.RemoveElementAt(currentIndex);
946 0 : EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
947 0 : if (effectSet) {
948 0 : effectSet->UpdateAnimationGeneration(mPresContext);
949 : }
950 :
951 0 : if (animations.IsEmpty()) {
952 0 : aElementTransitions->Destroy();
953 : // |aElementTransitions| is now a dangling pointer!
954 0 : aElementTransitions = nullptr;
955 : }
956 : // GetAnimationRule already called RestyleForAnimation.
957 : }
958 240 : return;
959 : }
960 :
961 2 : const nsTimingFunction &tf = aTransition.GetTimingFunction();
962 2 : float delay = aTransition.GetDelay();
963 2 : float duration = aTransition.GetDuration();
964 2 : if (duration < 0.0) {
965 : // The spec says a negative duration is treated as zero.
966 0 : duration = 0.0;
967 : }
968 :
969 4 : AnimationValue startForReversingTest = startValue;
970 2 : double reversePortion = 1.0;
971 :
972 : // If the new transition reverses an existing one, we'll need to
973 : // handle the timing differently.
974 : // FIXME: Move mStartForReversingTest, mReversePortion to CSSTransition,
975 : // and set the timing function on transitions as an effect-level
976 : // easing (rather than keyframe-level easing). (Bug 1292001)
977 2 : if (haveCurrentTransition &&
978 0 : aElementTransitions->mAnimations[currentIndex]->HasCurrentEffect() &&
979 2 : oldPT &&
980 0 : oldPT->mStartForReversingTest == endValue) {
981 : // Compute the appropriate negative transition-delay such that right
982 : // now we'd end up at the current position.
983 : double valuePortion =
984 0 : oldPT->CurrentValuePortion() * oldPT->mReversePortion +
985 0 : (1.0 - oldPT->mReversePortion);
986 : // A timing function with negative y1 (or y2!) might make
987 : // valuePortion negative. In this case, we still want to apply our
988 : // reversing logic based on relative distances, not make duration
989 : // negative.
990 0 : if (valuePortion < 0.0) {
991 0 : valuePortion = -valuePortion;
992 : }
993 : // A timing function with y2 (or y1!) greater than one might
994 : // advance past its terminal value. It's probably a good idea to
995 : // clamp valuePortion to be at most one to preserve the invariant
996 : // that a transition will complete within at most its specified
997 : // time.
998 0 : if (valuePortion > 1.0) {
999 0 : valuePortion = 1.0;
1000 : }
1001 :
1002 : // Negative delays are essentially part of the transition
1003 : // function, so reduce them along with the duration, but don't
1004 : // reduce positive delays.
1005 0 : if (delay < 0.0f) {
1006 0 : delay *= valuePortion;
1007 : }
1008 :
1009 0 : duration *= valuePortion;
1010 :
1011 0 : startForReversingTest = oldPT->ToValue();
1012 0 : reversePortion = valuePortion;
1013 : }
1014 :
1015 : TimingParams timing =
1016 : TimingParamsFromCSSParams(duration, delay,
1017 : 1.0 /* iteration count */,
1018 : dom::PlaybackDirection::Normal,
1019 4 : dom::FillMode::Backwards);
1020 :
1021 : // aElement is non-null here, so we emplace it directly.
1022 4 : Maybe<OwningAnimationTarget> target;
1023 2 : target.emplace(aElement, aPseudoType);
1024 2 : KeyframeEffectParams effectOptions;
1025 : RefPtr<ElementPropertyTransition> pt =
1026 4 : new ElementPropertyTransition(aElement->OwnerDoc(), target, timing,
1027 : startForReversingTest, reversePortion,
1028 6 : effectOptions);
1029 :
1030 2 : pt->SetKeyframes(GetTransitionKeyframes(aProperty,
1031 2 : Move(startValue), Move(endValue), tf),
1032 : aNewStyle);
1033 :
1034 : RefPtr<CSSTransition> animation =
1035 6 : new CSSTransition(mPresContext->Document()->GetScopeObject());
1036 2 : animation->SetOwningElement(OwningElementRef(*aElement, aPseudoType));
1037 2 : animation->SetTimelineNoUpdate(timeline);
1038 2 : animation->SetCreationSequence(
1039 2 : mPresContext->RestyleManager()->GetAnimationGeneration());
1040 2 : animation->SetEffectFromStyle(pt);
1041 2 : animation->PlayFromStyle();
1042 :
1043 2 : if (!aElementTransitions) {
1044 2 : bool createdCollection = false;
1045 2 : aElementTransitions =
1046 2 : CSSTransitionCollection::GetOrCreateAnimationCollection(
1047 : aElement, aPseudoType, &createdCollection);
1048 2 : if (!aElementTransitions) {
1049 0 : MOZ_ASSERT(!createdCollection, "outparam should agree with return value");
1050 0 : NS_WARNING("allocating collection failed");
1051 0 : return;
1052 : }
1053 :
1054 2 : if (createdCollection) {
1055 2 : AddElementCollection(aElementTransitions);
1056 : }
1057 : }
1058 :
1059 2 : OwningCSSTransitionPtrArray& animations = aElementTransitions->mAnimations;
1060 : #ifdef DEBUG
1061 2 : for (size_t i = 0, i_end = animations.Length(); i < i_end; ++i) {
1062 0 : MOZ_ASSERT(
1063 : i == currentIndex || animations[i]->TransitionProperty() != aProperty,
1064 : "duplicate transitions for property");
1065 : }
1066 : #endif
1067 2 : if (haveCurrentTransition) {
1068 : // If this new transition is replacing an existing transition that is running
1069 : // on the compositor, we store select parameters from the replaced transition
1070 : // so that later, once all scripts have run, we can update the start value
1071 : // of the transition using TimeStamp::Now(). This allows us to avoid a
1072 : // large jump when starting a new transition when the main thread lags behind
1073 : // the compositor.
1074 0 : if (oldPT &&
1075 0 : oldPT->IsCurrent() &&
1076 0 : oldPT->IsRunningOnCompositor() &&
1077 0 : !oldPT->GetAnimation()->GetStartTime().IsNull() &&
1078 0 : timeline == oldPT->GetAnimation()->GetTimeline()) {
1079 : const AnimationPropertySegment& segment =
1080 0 : oldPT->Properties()[0].mSegments[0];
1081 0 : pt->mReplacedTransition.emplace(
1082 : ElementPropertyTransition::ReplacedTransitionProperties({
1083 0 : oldPT->GetAnimation()->GetStartTime().Value(),
1084 0 : oldPT->GetAnimation()->PlaybackRate(),
1085 : oldPT->SpecifiedTiming(),
1086 : segment.mTimingFunction,
1087 : segment.mFromValue,
1088 : segment.mToValue
1089 : })
1090 : );
1091 : }
1092 0 : animations[currentIndex]->CancelFromStyle();
1093 0 : oldPT = nullptr; // Clear pointer so it doesn't dangle
1094 0 : animations[currentIndex] = animation;
1095 : } else {
1096 2 : if (!animations.AppendElement(animation)) {
1097 0 : NS_WARNING("out of memory");
1098 0 : return;
1099 : }
1100 : }
1101 :
1102 2 : EffectSet* effectSet = EffectSet::GetEffectSet(aElement, aPseudoType);
1103 2 : if (effectSet) {
1104 2 : effectSet->UpdateAnimationGeneration(mPresContext);
1105 : }
1106 :
1107 2 : *aStartedAny = true;
1108 2 : aWhichStarted->AddProperty(aProperty);
1109 : }
1110 :
1111 : void
1112 87 : nsTransitionManager::PruneCompletedTransitions(mozilla::dom::Element* aElement,
1113 : CSSPseudoElementType aPseudoType,
1114 : nsStyleContext* aNewStyleContext)
1115 : {
1116 87 : MOZ_ASSERT(!aElement->IsGeneratedContentContainerForBefore() &&
1117 : !aElement->IsGeneratedContentContainerForAfter());
1118 :
1119 : CSSTransitionCollection* collection =
1120 87 : CSSTransitionCollection::GetAnimationCollection(aElement, aPseudoType);
1121 87 : if (!collection) {
1122 87 : return;
1123 : }
1124 :
1125 : // Remove any finished transitions whose style doesn't match the new
1126 : // style.
1127 : // This is similar to some of the work that happens near the end of
1128 : // nsTransitionManager::StyleContextChanged.
1129 : // FIXME (bug 1158431): Really, we should also cancel running
1130 : // transitions whose destination doesn't match as well.
1131 0 : OwningCSSTransitionPtrArray& animations = collection->mAnimations;
1132 0 : size_t i = animations.Length();
1133 0 : MOZ_ASSERT(i != 0, "empty transitions list?");
1134 0 : do {
1135 0 : --i;
1136 0 : CSSTransition* anim = animations[i];
1137 :
1138 0 : if (anim->HasCurrentEffect()) {
1139 0 : continue;
1140 : }
1141 :
1142 : // Since effect is a finished transition, we know it didn't
1143 : // influence style.
1144 0 : AnimationValue currentValue;
1145 0 : if (!ExtractNonDiscreteComputedValue(anim->TransitionProperty(),
1146 0 : aNewStyleContext, currentValue) ||
1147 0 : currentValue != anim->ToValue()) {
1148 0 : anim->CancelFromStyle();
1149 0 : animations.RemoveElementAt(i);
1150 : }
1151 0 : } while (i != 0);
1152 :
1153 0 : if (collection->mAnimations.IsEmpty()) {
1154 0 : collection->Destroy();
1155 : // |collection| is now a dangling pointer!
1156 0 : collection = nullptr;
1157 : }
1158 9 : }
|