layout/style/nsTransitionManager.cpp

branch
TOR_BUG_3246
changeset 6
8bccb770b82d
equal deleted inserted replaced
-1:000000000000 0:e60e960ec268
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 "nsIContent.h"
12 #include "nsStyleContext.h"
13 #include "nsCSSProps.h"
14 #include "mozilla/MemoryReporting.h"
15 #include "mozilla/TimeStamp.h"
16 #include "nsRefreshDriver.h"
17 #include "nsRuleProcessorData.h"
18 #include "nsRuleWalker.h"
19 #include "nsCSSPropertySet.h"
20 #include "nsStyleAnimation.h"
21 #include "mozilla/EventDispatcher.h"
22 #include "mozilla/ContentEvents.h"
23 #include "mozilla/dom/Element.h"
24 #include "nsIFrame.h"
25 #include "Layers.h"
26 #include "FrameLayerBuilder.h"
27 #include "nsDisplayList.h"
28 #include "nsStyleChangeList.h"
29 #include "nsStyleSet.h"
30 #include "RestyleManager.h"
31 #include "ActiveLayerTracker.h"
32
33 using mozilla::TimeStamp;
34 using mozilla::TimeDuration;
35
36 using namespace mozilla;
37 using namespace mozilla::layers;
38 using namespace mozilla::css;
39
40 ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement,
41 nsIAtom *aElementProperty,
42 nsTransitionManager *aTransitionManager,
43 TimeStamp aNow)
44 : CommonElementAnimationData(aElement, aElementProperty,
45 aTransitionManager, aNow)
46 {
47 }
48
49 double
50 ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
51 {
52 // Set |timePortion| to the portion of the way we are through the time
53 // input to the transition's timing function (always within the range
54 // 0-1).
55 double duration = mIterationDuration.ToSeconds();
56 NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden");
57 double timePortion;
58 if (IsRemovedSentinel()) {
59 // The transition is being removed, but we still want an update so that any
60 // new transitions start in the right place.
61 timePortion = 1.0;
62 } else if (duration == 0.0) {
63 // When duration is zero, we can still have a transition when delay
64 // is nonzero.
65 if (aRefreshTime >= mStartTime + mDelay) {
66 timePortion = 1.0;
67 } else {
68 timePortion = 0.0;
69 }
70 } else {
71 timePortion = (aRefreshTime - (mStartTime + mDelay)).ToSeconds() / duration;
72 if (timePortion < 0.0)
73 timePortion = 0.0; // use start value during transition-delay
74 if (timePortion > 1.0)
75 timePortion = 1.0; // we might be behind on flushing
76 }
77 MOZ_ASSERT(mProperties.Length() == 1,
78 "Should have one animation property for a transition");
79 MOZ_ASSERT(mProperties[0].mSegments.Length() == 1,
80 "Animation property should have one segment for a transition");
81
82 return mProperties[0].mSegments[0].mTimingFunction.GetValue(timePortion);
83 }
84
85 static void
86 ElementTransitionsPropertyDtor(void *aObject,
87 nsIAtom *aPropertyName,
88 void *aPropertyValue,
89 void *aData)
90 {
91 ElementTransitions *et = static_cast<ElementTransitions*>(aPropertyValue);
92 #ifdef DEBUG
93 NS_ABORT_IF_FALSE(!et->mCalledPropertyDtor, "can't call dtor twice");
94 et->mCalledPropertyDtor = true;
95 #endif
96 delete et;
97 }
98
99 void
100 ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
101 {
102 if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) {
103 mStyleRule = new css::AnimValuesStyleRule();
104 mStyleRuleRefreshTime = aRefreshTime;
105
106 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i)
107 {
108 ElementPropertyTransition &pt = mPropertyTransitions[i];
109 if (pt.IsRemovedSentinel()) {
110 continue;
111 }
112
113 MOZ_ASSERT(pt.mProperties.Length() == 1,
114 "Should have one animation property for a transition");
115 const AnimationProperty &prop = pt.mProperties[0];
116
117 nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty);
118
119 double valuePortion = pt.ValuePortionFor(aRefreshTime);
120
121 MOZ_ASSERT(prop.mSegments.Length() == 1,
122 "Animation property should have one segment for a transition");
123 #ifdef DEBUG
124 bool ok =
125 #endif
126 nsStyleAnimation::Interpolate(prop.mProperty,
127 prop.mSegments[0].mFromValue,
128 prop.mSegments[0].mToValue,
129 valuePortion, *val);
130 NS_ABORT_IF_FALSE(ok, "could not interpolate values");
131 }
132 }
133 }
134
135 bool
136 ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const
137 {
138 for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) {
139 const ElementPropertyTransition& pt = mPropertyTransitions[tranIdx];
140 if (pt.HasAnimationOfProperty(aProperty) && !pt.IsRemovedSentinel()) {
141 return true;
142 }
143 }
144 return false;
145 }
146
147 bool
148 ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
149 {
150 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
151 if (!frame) {
152 return false;
153 }
154
155 if (mElementProperty != nsGkAtoms::transitionsProperty) {
156 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
157 nsCString message;
158 message.AppendLiteral("Gecko bug: Async transition of pseudoelements not supported. See bug 771367");
159 LogAsyncAnimationFailure(message, mElement);
160 }
161 return false;
162 }
163
164 TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
165
166 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
167 const ElementPropertyTransition& pt = mPropertyTransitions[i];
168 MOZ_ASSERT(pt.mProperties.Length() == 1,
169 "Should have one animation property for a transition");
170 if (css::IsGeometricProperty(pt.mProperties[0].mProperty) &&
171 pt.IsRunningAt(now)) {
172 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
173 break;
174 }
175 }
176
177 bool hasOpacity = false;
178 bool hasTransform = false;
179 bool existsProperty = false;
180 for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
181 const ElementPropertyTransition& pt = mPropertyTransitions[i];
182 if (!pt.IsRunningAt(now)) {
183 continue;
184 }
185
186 existsProperty = true;
187
188 MOZ_ASSERT(pt.mProperties.Length() == 1,
189 "Should have one animation property for a transition");
190 const AnimationProperty& prop = pt.mProperties[0];
191
192 if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(
193 mElement, prop.mProperty, aFlags) ||
194 css::CommonElementAnimationData::IsCompositorAnimationDisabledForFrame(frame)) {
195 return false;
196 }
197 if (prop.mProperty == eCSSProperty_opacity) {
198 hasOpacity = true;
199 } else if (prop.mProperty == eCSSProperty_transform) {
200 hasTransform = true;
201 }
202 }
203
204 // No properties to animate
205 if (!existsProperty) {
206 return false;
207 }
208
209 // This transition can be done on the compositor. Mark the frame as active, in
210 // case we are able to throttle this transition.
211 if (hasOpacity) {
212 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
213 }
214 if (hasTransform) {
215 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
216 }
217 return true;
218 }
219
220 /*****************************************************************************
221 * nsTransitionManager *
222 *****************************************************************************/
223
224 void
225 nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
226 nsStyleContext* aParentStyle,
227 nsStyleChangeList& aChangeList)
228 {
229 dom::Element* element;
230 if (aContent->IsElement()) {
231 element = aContent->AsElement();
232 } else {
233 element = nullptr;
234 }
235
236 nsRefPtr<nsStyleContext> newStyle;
237
238 ElementTransitions* et;
239 if (element &&
240 (et = GetElementTransitions(element,
241 nsCSSPseudoElements::ePseudo_NotPseudoElement,
242 false))) {
243 // re-resolve our style
244 newStyle = UpdateThrottledStyle(element, aParentStyle, aChangeList);
245 // remove the current transition from the working set
246 et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
247 } else {
248 newStyle = ReparentContent(aContent, aParentStyle);
249 }
250
251 // walk the children
252 if (newStyle) {
253 for (nsIContent *child = aContent->GetFirstChild(); child;
254 child = child->GetNextSibling()) {
255 UpdateThrottledStylesForSubtree(child, newStyle, aChangeList);
256 }
257 }
258 }
259
260 IMPL_UPDATE_ALL_THROTTLED_STYLES_INTERNAL(nsTransitionManager,
261 GetElementTransitions)
262
263 void
264 nsTransitionManager::UpdateAllThrottledStyles()
265 {
266 if (PR_CLIST_IS_EMPTY(&mElementData)) {
267 // no throttled transitions, leave early
268 mPresContext->TickLastUpdateThrottledTransitionStyle();
269 return;
270 }
271
272 if (mPresContext->ThrottledTransitionStyleIsUpToDate()) {
273 // throttled transitions are up to date, leave early
274 return;
275 }
276
277 mPresContext->TickLastUpdateThrottledTransitionStyle();
278 UpdateAllThrottledStylesInternal();
279 }
280
281 void
282 nsTransitionManager::ElementDataRemoved()
283 {
284 // If we have no transitions or animations left, remove ourselves from
285 // the refresh driver.
286 if (PR_CLIST_IS_EMPTY(&mElementData)) {
287 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
288 }
289 }
290
291 void
292 nsTransitionManager::AddElementData(CommonElementAnimationData* aData)
293 {
294 if (PR_CLIST_IS_EMPTY(&mElementData)) {
295 // We need to observe the refresh driver.
296 nsRefreshDriver *rd = mPresContext->RefreshDriver();
297 rd->AddRefreshObserver(this, Flush_Style);
298 }
299
300 PR_INSERT_BEFORE(aData, &mElementData);
301 }
302
303 already_AddRefed<nsIStyleRule>
304 nsTransitionManager::StyleContextChanged(dom::Element *aElement,
305 nsStyleContext *aOldStyleContext,
306 nsStyleContext *aNewStyleContext)
307 {
308 NS_PRECONDITION(aOldStyleContext->GetPseudo() ==
309 aNewStyleContext->GetPseudo(),
310 "pseudo type mismatch");
311 // If we were called from ReparentStyleContext, this assertion would
312 // actually fire. If we need to be called from there, we can probably
313 // just remove it; the condition probably isn't critical, although
314 // it's worth thinking about some more.
315 NS_PRECONDITION(aOldStyleContext->HasPseudoElementData() ==
316 aNewStyleContext->HasPseudoElementData(),
317 "pseudo type mismatch");
318
319 if (!mPresContext->IsDynamic()) {
320 // For print or print preview, ignore transitions.
321 return nullptr;
322 }
323
324 // NOTE: Things in this function (and ConsiderStartingTransition)
325 // should never call PeekStyleData because we don't preserve gotten
326 // structs across reframes.
327
328 // Return sooner (before the startedAny check below) for the most
329 // common case: no transitions specified or running.
330 const nsStyleDisplay *disp = aNewStyleContext->StyleDisplay();
331 nsCSSPseudoElements::Type pseudoType = aNewStyleContext->GetPseudoType();
332 if (pseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement) {
333 if (pseudoType != nsCSSPseudoElements::ePseudo_before &&
334 pseudoType != nsCSSPseudoElements::ePseudo_after) {
335 return nullptr;
336 }
337
338 NS_ASSERTION((pseudoType == nsCSSPseudoElements::ePseudo_before &&
339 aElement->Tag() == nsGkAtoms::mozgeneratedcontentbefore) ||
340 (pseudoType == nsCSSPseudoElements::ePseudo_after &&
341 aElement->Tag() == nsGkAtoms::mozgeneratedcontentafter),
342 "Unexpected aElement coming through");
343
344 // Else the element we want to use from now on is the element the
345 // :before or :after is attached to.
346 aElement = aElement->GetParent()->AsElement();
347 }
348
349 ElementTransitions *et =
350 GetElementTransitions(aElement, pseudoType, false);
351 if (!et &&
352 disp->mTransitionPropertyCount == 1 &&
353 disp->mTransitions[0].GetDelay() == 0.0f &&
354 disp->mTransitions[0].GetDuration() == 0.0f) {
355 return nullptr;
356 }
357
358
359 if (aNewStyleContext->PresContext()->IsProcessingAnimationStyleChange()) {
360 return nullptr;
361 }
362
363 if (aNewStyleContext->GetParent() &&
364 aNewStyleContext->GetParent()->HasPseudoElementData()) {
365 // Ignore transitions on things that inherit properties from
366 // pseudo-elements.
367 // FIXME (Bug 522599): Add tests for this.
368 return nullptr;
369 }
370
371 NS_WARN_IF_FALSE(!nsLayoutUtils::AreAsyncAnimationsEnabled() ||
372 mPresContext->ThrottledTransitionStyleIsUpToDate(),
373 "throttled animations not up to date");
374
375 // Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
376 // I'll consider only the transitions from the number of items in
377 // 'transition-property' on down, and later ones will override earlier
378 // ones (tracked using |whichStarted|).
379 bool startedAny = false;
380 nsCSSPropertySet whichStarted;
381 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
382 const nsTransition& t = disp->mTransitions[i];
383 // Check delay and duration first, since they default to zero, and
384 // when they're both zero, we can ignore the transition.
385 if (t.GetDelay() != 0.0f || t.GetDuration() != 0.0f) {
386 // We might have something to transition. See if any of the
387 // properties in question changed and are animatable.
388 // FIXME: Would be good to find a way to share code between this
389 // interpretation of transition-property and the one below.
390 nsCSSProperty property = t.GetProperty();
391 if (property == eCSSPropertyExtra_no_properties ||
392 property == eCSSPropertyExtra_variable ||
393 property == eCSSProperty_UNKNOWN) {
394 // Nothing to do, but need to exclude this from cases below.
395 } else if (property == eCSSPropertyExtra_all_properties) {
396 for (nsCSSProperty p = nsCSSProperty(0);
397 p < eCSSProperty_COUNT_no_shorthands;
398 p = nsCSSProperty(p + 1)) {
399 ConsiderStartingTransition(p, t, aElement, et,
400 aOldStyleContext, aNewStyleContext,
401 &startedAny, &whichStarted);
402 }
403 } else if (nsCSSProps::IsShorthand(property)) {
404 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
405 ConsiderStartingTransition(*subprop, t, aElement, et,
406 aOldStyleContext, aNewStyleContext,
407 &startedAny, &whichStarted);
408 }
409 } else {
410 ConsiderStartingTransition(property, t, aElement, et,
411 aOldStyleContext, aNewStyleContext,
412 &startedAny, &whichStarted);
413 }
414 }
415 }
416
417 // Stop any transitions for properties that are no longer in
418 // 'transition-property'.
419 // Also stop any transitions for properties that just changed (and are
420 // still in the set of properties to transition), but we didn't just
421 // start the transition because delay and duration are both zero.
422 if (et) {
423 bool checkProperties =
424 disp->mTransitions[0].GetProperty() != eCSSPropertyExtra_all_properties;
425 nsCSSPropertySet allTransitionProperties;
426 if (checkProperties) {
427 for (uint32_t i = disp->mTransitionPropertyCount; i-- != 0; ) {
428 const nsTransition& t = disp->mTransitions[i];
429 // FIXME: Would be good to find a way to share code between this
430 // interpretation of transition-property and the one above.
431 nsCSSProperty property = t.GetProperty();
432 if (property == eCSSPropertyExtra_no_properties ||
433 property == eCSSPropertyExtra_variable ||
434 property == eCSSProperty_UNKNOWN) {
435 // Nothing to do, but need to exclude this from cases below.
436 } else if (property == eCSSPropertyExtra_all_properties) {
437 for (nsCSSProperty p = nsCSSProperty(0);
438 p < eCSSProperty_COUNT_no_shorthands;
439 p = nsCSSProperty(p + 1)) {
440 allTransitionProperties.AddProperty(p);
441 }
442 } else if (nsCSSProps::IsShorthand(property)) {
443 CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, property) {
444 allTransitionProperties.AddProperty(*subprop);
445 }
446 } else {
447 allTransitionProperties.AddProperty(property);
448 }
449 }
450 }
451
452 nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
453 uint32_t i = pts.Length();
454 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
455 nsStyleAnimation::Value currentValue;
456 do {
457 --i;
458 ElementPropertyTransition &pt = pts[i];
459 MOZ_ASSERT(pt.mProperties.Length() == 1,
460 "Should have one animation property for a transition");
461 MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1,
462 "Animation property should have one segment for a transition");
463 const AnimationProperty& prop = pt.mProperties[0];
464 const AnimationPropertySegment& segment = prop.mSegments[0];
465 // properties no longer in 'transition-property'
466 if ((checkProperties &&
467 !allTransitionProperties.HasProperty(prop.mProperty)) ||
468 // properties whose computed values changed but delay and
469 // duration are both zero
470 !ExtractComputedValueForTransition(prop.mProperty, aNewStyleContext,
471 currentValue) ||
472 currentValue != segment.mToValue) {
473 // stop the transition
474 pts.RemoveElementAt(i);
475 et->UpdateAnimationGeneration(mPresContext);
476 }
477 } while (i != 0);
478
479 if (pts.IsEmpty()) {
480 et->Destroy();
481 et = nullptr;
482 }
483 }
484
485 if (!startedAny) {
486 return nullptr;
487 }
488
489 NS_ABORT_IF_FALSE(et, "must have element transitions if we started "
490 "any transitions");
491
492 // In the CSS working group discussion (2009 Jul 15 telecon,
493 // http://www.w3.org/mid/4A5E1470.4030904@inkedblade.net ) of
494 // http://lists.w3.org/Archives/Public/www-style/2009Jun/0121.html ,
495 // the working group decided that a transition property on an
496 // element should not cause any transitions if the property change
497 // is itself inheriting a value that is transitioning on an
498 // ancestor. So, to get the correct behavior, we continue the
499 // restyle that caused this transition using a "covering" rule that
500 // covers up any changes on which we started transitions, so that
501 // descendants don't start their own transitions. (In the case of
502 // negative transition delay, this covering rule produces different
503 // results than applying the transition rule immediately would).
504 // Our caller is responsible for restyling again using this covering
505 // rule.
506
507 nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
508
509 nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
510 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
511 ElementPropertyTransition &pt = pts[i];
512 MOZ_ASSERT(pt.mProperties.Length() == 1,
513 "Should have one animation property for a transition");
514 MOZ_ASSERT(pt.mProperties[0].mSegments.Length() == 1,
515 "Animation property should have one segment for a transition");
516 AnimationProperty& prop = pt.mProperties[0];
517 AnimationPropertySegment& segment = prop.mSegments[0];
518 if (whichStarted.HasProperty(prop.mProperty)) {
519 coverRule->AddValue(prop.mProperty, segment.mFromValue);
520 }
521 }
522
523 et->mStyleRule = nullptr;
524
525 return coverRule.forget();
526 }
527
528 void
529 nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
530 const nsTransition& aTransition,
531 dom::Element* aElement,
532 ElementTransitions*& aElementTransitions,
533 nsStyleContext* aOldStyleContext,
534 nsStyleContext* aNewStyleContext,
535 bool* aStartedAny,
536 nsCSSPropertySet* aWhichStarted)
537 {
538 // IsShorthand itself will assert if aProperty is not a property.
539 NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
540 "property out of range");
541 NS_ASSERTION(!aElementTransitions ||
542 aElementTransitions->mElement == aElement, "Element mismatch");
543
544 if (aWhichStarted->HasProperty(aProperty)) {
545 // A later item in transition-property already started a
546 // transition for this property, so we ignore this one.
547 // See comment above and
548 // http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html .
549 return;
550 }
551
552 if (nsCSSProps::kAnimTypeTable[aProperty] == eStyleAnimType_None) {
553 return;
554 }
555
556 ElementPropertyTransition pt;
557
558 nsStyleAnimation::Value startValue, endValue, dummyValue;
559 bool haveValues =
560 ExtractComputedValueForTransition(aProperty, aOldStyleContext,
561 startValue) &&
562 ExtractComputedValueForTransition(aProperty, aNewStyleContext,
563 endValue);
564
565 bool haveChange = startValue != endValue;
566
567 bool shouldAnimate =
568 haveValues &&
569 haveChange &&
570 // Check that we can interpolate between these values
571 // (If this is ever a performance problem, we could add a
572 // CanInterpolate method, but it seems fine for now.)
573 nsStyleAnimation::Interpolate(aProperty, startValue, endValue,
574 0.5, dummyValue);
575
576 bool haveCurrentTransition = false;
577 uint32_t currentIndex = nsTArray<ElementPropertyTransition>::NoIndex;
578 const ElementPropertyTransition *oldPT = nullptr;
579 if (aElementTransitions) {
580 nsTArray<ElementPropertyTransition> &pts =
581 aElementTransitions->mPropertyTransitions;
582 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
583 MOZ_ASSERT(pts[i].mProperties.Length() == 1,
584 "Should have one animation property for a transition");
585 if (pts[i].mProperties[0].mProperty == aProperty) {
586 haveCurrentTransition = true;
587 currentIndex = i;
588 oldPT = &aElementTransitions->mPropertyTransitions[currentIndex];
589 break;
590 }
591 }
592 }
593
594 // If we got a style change that changed the value to the endpoint
595 // of the currently running transition, we don't want to interrupt
596 // its timing function.
597 // This needs to be before the !shouldAnimate && haveCurrentTransition
598 // case below because we might be close enough to the end of the
599 // transition that the current value rounds to the final value. In
600 // this case, we'll end up with shouldAnimate as false (because
601 // there's no value change), but we need to return early here rather
602 // than cancel the running transition because shouldAnimate is false!
603 MOZ_ASSERT(!oldPT || oldPT->mProperties[0].mSegments.Length() == 1,
604 "Should have one animation property segment for a transition");
605 if (haveCurrentTransition && haveValues &&
606 oldPT->mProperties[0].mSegments[0].mToValue == endValue) {
607 // WalkTransitionRule already called RestyleForAnimation.
608 return;
609 }
610
611 nsPresContext *presContext = aNewStyleContext->PresContext();
612
613 if (!shouldAnimate) {
614 if (haveCurrentTransition) {
615 // We're in the middle of a transition, and just got a non-transition
616 // style change to something that we can't animate. This might happen
617 // because we got a non-transition style change changing to the current
618 // in-progress value (which is particularly easy to cause when we're
619 // currently in the 'transition-delay'). It also might happen because we
620 // just got a style change to a value that can't be interpolated.
621 nsTArray<ElementPropertyTransition> &pts =
622 aElementTransitions->mPropertyTransitions;
623 pts.RemoveElementAt(currentIndex);
624 aElementTransitions->UpdateAnimationGeneration(mPresContext);
625
626 if (pts.IsEmpty()) {
627 aElementTransitions->Destroy();
628 // |aElementTransitions| is now a dangling pointer!
629 aElementTransitions = nullptr;
630 }
631 // WalkTransitionRule already called RestyleForAnimation.
632 }
633 return;
634 }
635
636 TimeStamp mostRecentRefresh =
637 presContext->RefreshDriver()->MostRecentRefresh();
638
639 const nsTimingFunction &tf = aTransition.GetTimingFunction();
640 float delay = aTransition.GetDelay();
641 float duration = aTransition.GetDuration();
642 if (duration < 0.0) {
643 // The spec says a negative duration is treated as zero.
644 duration = 0.0;
645 }
646 pt.mStartForReversingTest = startValue;
647 pt.mReversePortion = 1.0;
648
649 // If the new transition reverses an existing one, we'll need to
650 // handle the timing differently.
651 if (haveCurrentTransition &&
652 !oldPT->IsRemovedSentinel() &&
653 oldPT->mStartForReversingTest == endValue) {
654 // Compute the appropriate negative transition-delay such that right
655 // now we'd end up at the current position.
656 double valuePortion =
657 oldPT->ValuePortionFor(mostRecentRefresh) * oldPT->mReversePortion +
658 (1.0 - oldPT->mReversePortion);
659 // A timing function with negative y1 (or y2!) might make
660 // valuePortion negative. In this case, we still want to apply our
661 // reversing logic based on relative distances, not make duration
662 // negative.
663 if (valuePortion < 0.0) {
664 valuePortion = -valuePortion;
665 }
666 // A timing function with y2 (or y1!) greater than one might
667 // advance past its terminal value. It's probably a good idea to
668 // clamp valuePortion to be at most one to preserve the invariant
669 // that a transition will complete within at most its specified
670 // time.
671 if (valuePortion > 1.0) {
672 valuePortion = 1.0;
673 }
674
675 // Negative delays are essentially part of the transition
676 // function, so reduce them along with the duration, but don't
677 // reduce positive delays.
678 if (delay < 0.0f) {
679 delay *= valuePortion;
680 }
681
682 duration *= valuePortion;
683
684 pt.mStartForReversingTest = oldPT->mProperties[0].mSegments[0].mToValue;
685 pt.mReversePortion = valuePortion;
686 }
687
688 AnimationProperty& prop = *pt.mProperties.AppendElement();
689 prop.mProperty = aProperty;
690
691 AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
692 segment.mFromValue = startValue;
693 segment.mToValue = endValue;
694 segment.mFromKey = 0;
695 segment.mToKey = 1;
696 segment.mTimingFunction.Init(tf);
697
698 pt.mStartTime = mostRecentRefresh;
699 pt.mDelay = TimeDuration::FromMilliseconds(delay);
700 pt.mIterationDuration = TimeDuration::FromMilliseconds(duration);
701 pt.mIterationCount = 1;
702 pt.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
703 pt.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
704 pt.mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING;
705 pt.mPauseStart = TimeStamp();
706
707 if (!aElementTransitions) {
708 aElementTransitions =
709 GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
710 true);
711 if (!aElementTransitions) {
712 NS_WARNING("allocating ElementTransitions failed");
713 return;
714 }
715 }
716
717 nsTArray<ElementPropertyTransition> &pts =
718 aElementTransitions->mPropertyTransitions;
719 #ifdef DEBUG
720 for (uint32_t i = 0, i_end = pts.Length(); i < i_end; ++i) {
721 NS_ABORT_IF_FALSE(pts[i].mProperties.Length() == 1,
722 "Should have one animation property for a transition");
723 NS_ABORT_IF_FALSE(i == currentIndex ||
724 pts[i].mProperties[0].mProperty != aProperty,
725 "duplicate transitions for property");
726 }
727 #endif
728 if (haveCurrentTransition) {
729 pts[currentIndex] = pt;
730 } else {
731 if (!pts.AppendElement(pt)) {
732 NS_WARNING("out of memory");
733 return;
734 }
735 }
736 aElementTransitions->UpdateAnimationGeneration(mPresContext);
737
738 nsRestyleHint hint =
739 aNewStyleContext->GetPseudoType() ==
740 nsCSSPseudoElements::ePseudo_NotPseudoElement ?
741 eRestyle_Self : eRestyle_Subtree;
742 presContext->PresShell()->RestyleForAnimation(aElement, hint);
743
744 *aStartedAny = true;
745 aWhichStarted->AddProperty(aProperty);
746 }
747
748 ElementTransitions*
749 nsTransitionManager::GetElementTransitions(dom::Element *aElement,
750 nsCSSPseudoElements::Type aPseudoType,
751 bool aCreateIfNeeded)
752 {
753 if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
754 // Early return for the most common case.
755 return nullptr;
756 }
757
758 nsIAtom *propName;
759 if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
760 propName = nsGkAtoms::transitionsProperty;
761 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
762 propName = nsGkAtoms::transitionsOfBeforeProperty;
763 } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
764 propName = nsGkAtoms::transitionsOfAfterProperty;
765 } else {
766 NS_ASSERTION(!aCreateIfNeeded,
767 "should never try to create transitions for pseudo "
768 "other than :before or :after");
769 return nullptr;
770 }
771 ElementTransitions *et = static_cast<ElementTransitions*>(
772 aElement->GetProperty(propName));
773 if (!et && aCreateIfNeeded) {
774 // FIXME: Consider arena-allocating?
775 et = new ElementTransitions(aElement, propName, this,
776 mPresContext->RefreshDriver()->MostRecentRefresh());
777 nsresult rv = aElement->SetProperty(propName, et,
778 ElementTransitionsPropertyDtor, false);
779 if (NS_FAILED(rv)) {
780 NS_WARNING("SetProperty failed");
781 delete et;
782 return nullptr;
783 }
784 if (propName == nsGkAtoms::transitionsProperty) {
785 aElement->SetMayHaveAnimations();
786 }
787
788 AddElementData(et);
789 }
790
791 return et;
792 }
793
794 /*
795 * nsIStyleRuleProcessor implementation
796 */
797
798 void
799 nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData,
800 nsCSSPseudoElements::Type aPseudoType)
801 {
802 ElementTransitions *et =
803 GetElementTransitions(aData->mElement, aPseudoType, false);
804 if (!et) {
805 return;
806 }
807
808 if (!mPresContext->IsDynamic()) {
809 // For print or print preview, ignore animations.
810 return;
811 }
812
813 if (aData->mPresContext->IsProcessingRestyles() &&
814 !aData->mPresContext->IsProcessingAnimationStyleChange()) {
815 // If we're processing a normal style change rather than one from
816 // animation, don't add the transition rule. This allows us to
817 // compute the new style value rather than having the transition
818 // override it, so that we can start transitioning differently.
819
820 // We need to immediately restyle with animation
821 // after doing this.
822 nsRestyleHint hint =
823 aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ?
824 eRestyle_Self : eRestyle_Subtree;
825 mPresContext->PresShell()->RestyleForAnimation(aData->mElement, hint);
826 return;
827 }
828
829 et->EnsureStyleRuleFor(
830 aData->mPresContext->RefreshDriver()->MostRecentRefresh());
831
832 aData->mRuleWalker->Forward(et->mStyleRule);
833 }
834
835 /* virtual */ void
836 nsTransitionManager::RulesMatching(ElementRuleProcessorData* aData)
837 {
838 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
839 "pres context mismatch");
840 WalkTransitionRule(aData,
841 nsCSSPseudoElements::ePseudo_NotPseudoElement);
842 }
843
844 /* virtual */ void
845 nsTransitionManager::RulesMatching(PseudoElementRuleProcessorData* aData)
846 {
847 NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
848 "pres context mismatch");
849
850 // Note: If we're the only thing keeping a pseudo-element frame alive
851 // (per ProbePseudoStyleContext), we still want to keep it alive, so
852 // this is ok.
853 WalkTransitionRule(aData, aData->mPseudoType);
854 }
855
856 /* virtual */ void
857 nsTransitionManager::RulesMatching(AnonBoxRuleProcessorData* aData)
858 {
859 }
860
861 #ifdef MOZ_XUL
862 /* virtual */ void
863 nsTransitionManager::RulesMatching(XULTreeRuleProcessorData* aData)
864 {
865 }
866 #endif
867
868 /* virtual */ size_t
869 nsTransitionManager::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
870 {
871 return CommonAnimationManager::SizeOfExcludingThis(aMallocSizeOf);
872 }
873
874 /* virtual */ size_t
875 nsTransitionManager::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
876 {
877 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
878 }
879
880 struct TransitionEventInfo {
881 nsCOMPtr<nsIContent> mElement;
882 InternalTransitionEvent mEvent;
883
884 TransitionEventInfo(nsIContent *aElement, nsCSSProperty aProperty,
885 TimeDuration aDuration, const nsAString& aPseudoElement)
886 : mElement(aElement)
887 , mEvent(true, NS_TRANSITION_END)
888 {
889 // XXX Looks like nobody initialize WidgetEvent::time
890 mEvent.propertyName =
891 NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
892 mEvent.elapsedTime = aDuration.ToSeconds();
893 mEvent.pseudoElement = aPseudoElement;
894 }
895
896 // InternalTransitionEvent doesn't support copy-construction, so we need
897 // to ourselves in order to work with nsTArray
898 TransitionEventInfo(const TransitionEventInfo &aOther)
899 : mElement(aOther.mElement)
900 , mEvent(true, NS_TRANSITION_END)
901 {
902 mEvent.AssignTransitionEventData(aOther.mEvent, false);
903 }
904 };
905
906 /* virtual */ void
907 nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
908 {
909 NS_ABORT_IF_FALSE(mPresContext,
910 "refresh driver should not notify additional observers "
911 "after pres context has been destroyed");
912 if (!mPresContext->GetPresShell()) {
913 // Someone might be keeping mPresContext alive past the point
914 // where it has been torn down; don't bother doing anything in
915 // this case. But do get rid of all our transitions so we stop
916 // triggering refreshes.
917 RemoveAllElementData();
918 return;
919 }
920
921 FlushTransitions(Can_Throttle);
922 }
923
924 void
925 nsTransitionManager::FlushTransitions(FlushFlags aFlags)
926 {
927 if (PR_CLIST_IS_EMPTY(&mElementData)) {
928 // no transitions, leave early
929 return;
930 }
931
932 nsTArray<TransitionEventInfo> events;
933 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
934 bool didThrottle = false;
935 // Trim transitions that have completed, post restyle events for frames that
936 // are still transitioning, and start transitions with delays.
937 {
938 PRCList *next = PR_LIST_HEAD(&mElementData);
939 while (next != &mElementData) {
940 ElementTransitions *et = static_cast<ElementTransitions*>(next);
941 next = PR_NEXT_LINK(next);
942
943 bool canThrottleTick = aFlags == Can_Throttle &&
944 et->CanPerformOnCompositorThread(
945 CommonElementAnimationData::CanAnimateFlags(0)) &&
946 et->CanThrottleAnimation(now);
947
948 NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() ==
949 mPresContext->Document(),
950 "Element::UnbindFromTree should have "
951 "destroyed the element transitions object");
952
953 uint32_t i = et->mPropertyTransitions.Length();
954 NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
955 bool transitionStartedOrEnded = false;
956 do {
957 --i;
958 ElementPropertyTransition &pt = et->mPropertyTransitions[i];
959 if (pt.IsRemovedSentinel()) {
960 // Actually remove transitions one throttle-able cycle after their
961 // completion. We only clear on a throttle-able cycle because that
962 // means it is a regular restyle tick and thus it is safe to discard
963 // the transition. If the flush is not throttle-able, we might still
964 // have new transitions left to process. See comment below.
965 if (aFlags == Can_Throttle) {
966 et->mPropertyTransitions.RemoveElementAt(i);
967 }
968 } else if (pt.mStartTime + pt.mDelay + pt.mIterationDuration <= now) {
969 MOZ_ASSERT(pt.mProperties.Length() == 1,
970 "Should have one animation property for a transition");
971 nsCSSProperty prop = pt.mProperties[0].mProperty;
972 if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME))
973 {
974 prop = nsCSSProps::OtherNameFor(prop);
975 }
976 nsIAtom* ep = et->mElementProperty;
977 NS_NAMED_LITERAL_STRING(before, "::before");
978 NS_NAMED_LITERAL_STRING(after, "::after");
979 events.AppendElement(
980 TransitionEventInfo(et->mElement, prop, pt.mIterationDuration,
981 ep == nsGkAtoms::transitionsProperty ?
982 EmptyString() :
983 ep == nsGkAtoms::transitionsOfBeforeProperty ?
984 before :
985 after));
986
987 // Leave this transition in the list for one more refresh
988 // cycle, since we haven't yet processed its style change, and
989 // if we also have (already, or will have from processing
990 // transitionend events or other refresh driver notifications)
991 // a non-animation style change that would affect it, we need
992 // to know not to start a new transition for the transition
993 // from the almost-completed value to the final value.
994 pt.SetRemovedSentinel();
995 et->UpdateAnimationGeneration(mPresContext);
996 transitionStartedOrEnded = true;
997 } else if (pt.mStartTime + pt.mDelay <= now && canThrottleTick &&
998 !pt.mIsRunningOnCompositor) {
999 // Start a transition with a delay where we should start the
1000 // transition proper.
1001 et->UpdateAnimationGeneration(mPresContext);
1002 transitionStartedOrEnded = true;
1003 }
1004 } while (i != 0);
1005
1006 // We need to restyle even if the transition rule no longer
1007 // applies (in which case we just made it not apply).
1008 NS_ASSERTION(et->mElementProperty == nsGkAtoms::transitionsProperty ||
1009 et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty ||
1010 et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty,
1011 "Unexpected element property; might restyle too much");
1012 if (!canThrottleTick || transitionStartedOrEnded) {
1013 nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
1014 eRestyle_Self : eRestyle_Subtree;
1015 mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
1016 } else {
1017 didThrottle = true;
1018 }
1019
1020 if (et->mPropertyTransitions.IsEmpty()) {
1021 et->Destroy();
1022 // |et| is now a dangling pointer!
1023 et = nullptr;
1024 }
1025 }
1026 }
1027
1028 if (didThrottle) {
1029 mPresContext->Document()->SetNeedStyleFlush();
1030 }
1031
1032 for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
1033 TransitionEventInfo &info = events[i];
1034 EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);
1035
1036 if (!mPresContext) {
1037 break;
1038 }
1039 }
1040 }

mercurial