1 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "AnimationCommon.h"
7 #include "nsTransitionManager.h"
8 #include "nsAnimationManager.h"
10 #include "ActiveLayerTracker.h"
11 #include "gfxPlatform.h"
12 #include "nsRuleData.h"
13 #include "nsCSSPropertySet.h"
14 #include "nsCSSValue.h"
15 #include "nsCycleCollectionParticipant.h"
16 #include "nsStyleContext.h"
18 #include "nsLayoutUtils.h"
19 #include "mozilla/LookAndFeel.h"
21 #include "FrameLayerBuilder.h"
22 #include "nsDisplayList.h"
23 #include "mozilla/MemoryReporting.h"
24 #include "RestyleManager.h"
25 #include "nsStyleSet.h"
26 #include "nsStyleChangeList.h"
29 using mozilla::layers::Layer
;
30 using mozilla::dom::AnimationPlayer
;
31 using mozilla::dom::Animation
;
36 IsGeometricProperty(nsCSSProperty aProperty
)
39 case eCSSProperty_bottom
:
40 case eCSSProperty_height
:
41 case eCSSProperty_left
:
42 case eCSSProperty_right
:
43 case eCSSProperty_top
:
44 case eCSSProperty_width
:
53 CommonAnimationManager::CommonAnimationManager(nsPresContext
*aPresContext
)
54 : mPresContext(aPresContext
)
56 PR_INIT_CLIST(&mElementCollections
);
59 CommonAnimationManager::~CommonAnimationManager()
61 NS_ABORT_IF_FALSE(!mPresContext
, "Disconnect should have been called");
65 CommonAnimationManager::Disconnect()
67 // Content nodes might outlive the transition or animation manager.
68 RemoveAllElementCollections();
70 mPresContext
= nullptr;
74 CommonAnimationManager::RemoveAllElementCollections()
76 while (!PR_CLIST_IS_EMPTY(&mElementCollections
)) {
77 AnimationPlayerCollection
* head
=
78 static_cast<AnimationPlayerCollection
*>(
79 PR_LIST_HEAD(&mElementCollections
));
84 AnimationPlayerCollection
*
85 CommonAnimationManager::GetAnimationsForCompositor(nsIContent
* aContent
,
86 nsIAtom
* aElementProperty
,
87 nsCSSProperty aProperty
)
89 if (!aContent
->MayHaveAnimations())
91 AnimationPlayerCollection
* collection
=
92 static_cast<AnimationPlayerCollection
*>(
93 aContent
->GetProperty(aElementProperty
));
95 !collection
->HasAnimationOfProperty(aProperty
) ||
96 !collection
->CanPerformOnCompositorThread(
97 AnimationPlayerCollection::CanAnimate_AllowPartial
)) {
101 // This animation can be done on the compositor.
102 // Mark the frame as active, in case we are able to throttle this animation.
103 nsIFrame
* frame
= nsLayoutUtils::GetStyleFrame(collection
->mElement
);
105 if (aProperty
== eCSSProperty_opacity
) {
106 ActiveLayerTracker::NotifyAnimated(frame
, eCSSProperty_opacity
);
107 } else if (aProperty
== eCSSProperty_transform
) {
108 ActiveLayerTracker::NotifyAnimated(frame
, eCSSProperty_transform
);
116 * nsISupports implementation
119 NS_IMPL_ISUPPORTS(CommonAnimationManager
, nsIStyleRuleProcessor
)
122 CommonAnimationManager::HasStateDependentStyle(StateRuleProcessorData
* aData
)
124 return nsRestyleHint(0);
128 CommonAnimationManager::HasStateDependentStyle(PseudoElementStateRuleProcessorData
* aData
)
130 return nsRestyleHint(0);
134 CommonAnimationManager::HasDocumentStateDependentStyle(StateRuleProcessorData
* aData
)
140 CommonAnimationManager::HasAttributeDependentStyle(AttributeRuleProcessorData
* aData
)
142 return nsRestyleHint(0);
146 CommonAnimationManager::MediumFeaturesChanged(nsPresContext
* aPresContext
)
152 CommonAnimationManager::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const
154 // Measurement of the following members may be added later if DMD finds it is
156 // - mElementCollections
158 // The following members are not measured
159 // - mPresContext, because it's non-owning
165 CommonAnimationManager::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const
167 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
171 CommonAnimationManager::AddStyleUpdatesTo(RestyleTracker
& aTracker
)
173 PRCList
* next
= PR_LIST_HEAD(&mElementCollections
);
174 while (next
!= &mElementCollections
) {
175 AnimationPlayerCollection
* collection
=
176 static_cast<AnimationPlayerCollection
*>(next
);
177 next
= PR_NEXT_LINK(next
);
179 dom::Element
* elementToRestyle
= collection
->GetElementToRestyle();
180 if (elementToRestyle
) {
181 nsRestyleHint rshint
= collection
->IsForTransitions()
182 ? eRestyle_CSSTransitions
: eRestyle_CSSAnimations
;
183 aTracker
.AddPendingRestyle(elementToRestyle
, rshint
, nsChangeHint(0));
189 CommonAnimationManager::ExtractComputedValueForTransition(
190 nsCSSProperty aProperty
,
191 nsStyleContext
* aStyleContext
,
192 StyleAnimationValue
& aComputedValue
)
194 bool result
= StyleAnimationValue::ExtractComputedValue(aProperty
,
197 if (aProperty
== eCSSProperty_visibility
) {
198 NS_ABORT_IF_FALSE(aComputedValue
.GetUnit() ==
199 StyleAnimationValue::eUnit_Enumerated
,
201 aComputedValue
.SetIntValue(aComputedValue
.GetIntValue(),
202 StyleAnimationValue::eUnit_Visibility
);
207 NS_IMPL_ISUPPORTS(AnimValuesStyleRule
, nsIStyleRule
)
210 AnimValuesStyleRule::MapRuleInfoInto(nsRuleData
* aRuleData
)
212 nsStyleContext
*contextParent
= aRuleData
->mStyleContext
->GetParent();
213 if (contextParent
&& contextParent
->HasPseudoElementData()) {
214 // Don't apply transitions or animations to things inside of
216 // FIXME (Bug 522599): Add tests for this.
220 for (uint32_t i
= 0, i_end
= mPropertyValuePairs
.Length(); i
< i_end
; ++i
) {
221 PropertyValuePair
&cv
= mPropertyValuePairs
[i
];
222 if (aRuleData
->mSIDs
& nsCachedStyleData::GetBitForSID(
223 nsCSSProps::kSIDTable
[cv
.mProperty
]))
225 nsCSSValue
*prop
= aRuleData
->ValueFor(cv
.mProperty
);
226 if (prop
->GetUnit() == eCSSUnit_Null
) {
230 StyleAnimationValue::UncomputeValue(cv
.mProperty
, cv
.mValue
, *prop
);
231 NS_ABORT_IF_FALSE(ok
, "could not store computed value");
239 AnimValuesStyleRule::List(FILE* out
, int32_t aIndent
) const
241 for (int32_t index
= aIndent
; --index
>= 0; ) fputs(" ", out
);
242 fputs("[anim values] { ", out
);
243 for (uint32_t i
= 0, i_end
= mPropertyValuePairs
.Length(); i
< i_end
; ++i
) {
244 const PropertyValuePair
&pair
= mPropertyValuePairs
[i
];
246 StyleAnimationValue::UncomputeValue(pair
.mProperty
, pair
.mValue
, value
);
247 fprintf(out
, "%s: %s; ", nsCSSProps::GetStringValue(pair
.mProperty
).get(),
248 NS_ConvertUTF16toUTF8(value
).get());
254 } /* end sub-namespace css */
257 AnimationPlayerCollection::CanAnimatePropertyOnCompositor(
258 const dom::Element
*aElement
,
259 nsCSSProperty aProperty
,
260 CanAnimateFlags aFlags
)
262 bool shouldLog
= nsLayoutUtils::IsAnimationLoggingEnabled();
263 if (!gfxPlatform::OffMainThreadCompositingEnabled()) {
266 message
.AppendLiteral("Performance warning: Compositor disabled");
267 LogAsyncAnimationFailure(message
);
272 nsIFrame
* frame
= nsLayoutUtils::GetStyleFrame(aElement
);
273 if (IsGeometricProperty(aProperty
)) {
276 message
.AppendLiteral("Performance warning: Async animation of geometric property '");
277 message
.Append(nsCSSProps::GetStringValue(aProperty
));
278 message
.AppendLiteral("' is disabled");
279 LogAsyncAnimationFailure(message
, aElement
);
283 if (aProperty
== eCSSProperty_transform
) {
284 if (frame
->Preserves3D() &&
285 frame
->Preserves3DChildren()) {
288 message
.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' transforms is not supported. See bug 779598");
289 LogAsyncAnimationFailure(message
, aElement
);
293 if (frame
->IsSVGTransformed()) {
296 message
.AppendLiteral("Gecko bug: Async 'transform' animations of frames with SVG transforms is not supported. See bug 779599");
297 LogAsyncAnimationFailure(message
, aElement
);
301 if (aFlags
& CanAnimate_HasGeometricProperty
) {
304 message
.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties");
305 LogAsyncAnimationFailure(message
, aElement
);
310 bool enabled
= nsLayoutUtils::AreAsyncAnimationsEnabled();
311 if (!enabled
&& shouldLog
) {
313 message
.AppendLiteral("Performance warning: Async animations are disabled");
314 LogAsyncAnimationFailure(message
);
316 bool propertyAllowed
= (aProperty
== eCSSProperty_transform
) ||
317 (aProperty
== eCSSProperty_opacity
) ||
318 (aFlags
& CanAnimate_AllowPartial
);
319 return enabled
&& propertyAllowed
;
323 AnimationPlayerCollection::IsCompositorAnimationDisabledForFrame(
326 void* prop
= aFrame
->Properties().Get(nsIFrame::RefusedAsyncAnimation());
327 return bool(reinterpret_cast<intptr_t>(prop
));
331 AnimationPlayerCollection::CanPerformOnCompositorThread(
332 CanAnimateFlags aFlags
) const
334 nsIFrame
* frame
= nsLayoutUtils::GetStyleFrame(mElement
);
339 if (mElementProperty
!= nsGkAtoms::transitionsProperty
&&
340 mElementProperty
!= nsGkAtoms::animationsProperty
) {
341 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
343 message
.AppendLiteral("Gecko bug: Async animation of pseudoelements"
344 " not supported. See bug 771367 (");
345 message
.Append(nsAtomCString(mElementProperty
));
347 LogAsyncAnimationFailure(message
, mElement
);
352 for (size_t playerIdx
= mPlayers
.Length(); playerIdx
-- != 0; ) {
353 const AnimationPlayer
* player
= mPlayers
[playerIdx
];
354 if (!player
->IsRunning() || !player
->GetSource()) {
357 const Animation
* anim
= player
->GetSource();
358 for (size_t propIdx
= 0, propEnd
= anim
->Properties().Length();
359 propIdx
!= propEnd
; ++propIdx
) {
360 if (IsGeometricProperty(anim
->Properties()[propIdx
].mProperty
)) {
361 aFlags
= CanAnimateFlags(aFlags
| CanAnimate_HasGeometricProperty
);
367 bool existsProperty
= false;
368 for (size_t playerIdx
= mPlayers
.Length(); playerIdx
-- != 0; ) {
369 const AnimationPlayer
* player
= mPlayers
[playerIdx
];
370 if (!player
->IsRunning() || !player
->GetSource()) {
374 const Animation
* anim
= player
->GetSource();
375 existsProperty
= existsProperty
|| anim
->Properties().Length() > 0;
377 for (size_t propIdx
= 0, propEnd
= anim
->Properties().Length();
378 propIdx
!= propEnd
; ++propIdx
) {
379 const AnimationProperty
& prop
= anim
->Properties()[propIdx
];
380 if (!CanAnimatePropertyOnCompositor(mElement
,
383 IsCompositorAnimationDisabledForFrame(frame
)) {
389 // No properties to animate
390 if (!existsProperty
) {
398 AnimationPlayerCollection::HasAnimationOfProperty(
399 nsCSSProperty aProperty
) const
401 for (size_t playerIdx
= mPlayers
.Length(); playerIdx
-- != 0; ) {
402 const Animation
* anim
= mPlayers
[playerIdx
]->GetSource();
403 if (anim
&& anim
->HasAnimationOfProperty(aProperty
) &&
404 !anim
->IsFinishedTransition()) {
411 mozilla::dom::Element
*
412 AnimationPlayerCollection::GetElementToRestyle() const
414 if (IsForElement()) {
418 nsIFrame
* primaryFrame
= mElement
->GetPrimaryFrame();
422 nsIFrame
* pseudoFrame
;
423 if (IsForBeforePseudo()) {
424 pseudoFrame
= nsLayoutUtils::GetBeforeFrame(primaryFrame
);
425 } else if (IsForAfterPseudo()) {
426 pseudoFrame
= nsLayoutUtils::GetAfterFrame(primaryFrame
);
428 MOZ_ASSERT(false, "unknown mElementProperty");
434 return pseudoFrame
->GetContent()->AsElement();
438 AnimationPlayerCollection::LogAsyncAnimationFailure(nsCString
& aMessage
,
439 const nsIContent
* aContent
)
442 aMessage
.AppendLiteral(" [");
443 aMessage
.Append(nsAtomCString(aContent
->Tag()));
445 nsIAtom
* id
= aContent
->GetID();
447 aMessage
.AppendLiteral(" with id '");
448 aMessage
.Append(nsAtomCString(aContent
->GetID()));
449 aMessage
.Append('\'');
451 aMessage
.Append(']');
453 aMessage
.Append('\n');
454 printf_stderr("%s", aMessage
.get());
458 AnimationPlayerCollection::PropertyDtor(void *aObject
, nsIAtom
*aPropertyName
,
459 void *aPropertyValue
, void *aData
)
461 AnimationPlayerCollection
* collection
=
462 static_cast<AnimationPlayerCollection
*>(aPropertyValue
);
464 NS_ABORT_IF_FALSE(!collection
->mCalledPropertyDtor
, "can't call dtor twice");
465 collection
->mCalledPropertyDtor
= true;
471 AnimationPlayerCollection::Tick()
473 for (size_t playerIdx
= 0, playerEnd
= mPlayers
.Length();
474 playerIdx
!= playerEnd
; playerIdx
++) {
475 mPlayers
[playerIdx
]->Tick();
480 AnimationPlayerCollection::EnsureStyleRuleFor(TimeStamp aRefreshTime
,
481 EnsureStyleRuleFlags aFlags
)
483 if (!mNeedsRefreshes
) {
484 mStyleRuleRefreshTime
= aRefreshTime
;
488 // If we're performing animations on the compositor thread, then we can skip
489 // most of the work in this method. But even if we are throttled, then we
490 // have to do the work if an animation is ending in order to get correct end
491 // of animation behaviour (the styles of the animation disappear, or the fill
492 // mode behaviour). This loop checks for any finishing animations and forces
493 // the style recalculation if we find any.
494 if (aFlags
== EnsureStyleRule_IsThrottled
) {
495 for (size_t playerIdx
= mPlayers
.Length(); playerIdx
-- != 0; ) {
496 AnimationPlayer
* player
= mPlayers
[playerIdx
];
498 // Skip player with no source content, finished transitions, or animations
499 // whose @keyframes rule is empty.
500 if (!player
->GetSource() ||
501 player
->GetSource()->IsFinishedTransition() ||
502 player
->GetSource()->Properties().IsEmpty()) {
506 // The GetComputedTiming() call here handles pausing. But:
507 // FIXME: avoid recalculating every time when paused.
508 ComputedTiming computedTiming
= player
->GetSource()->GetComputedTiming();
510 // XXX We shouldn't really be using LastNotification() as a general
511 // indicator that the animation has finished, it should be reserved for
512 // events. If we use it differently in the future this use might need
514 if (!player
->mIsRunningOnCompositor
||
515 (computedTiming
.mPhase
== ComputedTiming::AnimationPhase_After
&&
516 player
->GetSource()->LastNotification()
517 != Animation::LAST_NOTIFICATION_END
))
519 aFlags
= EnsureStyleRule_IsNotThrottled
;
525 if (aFlags
== EnsureStyleRule_IsThrottled
) {
529 // mStyleRule may be null and valid, if we have no style to apply.
530 if (mStyleRuleRefreshTime
.IsNull() ||
531 mStyleRuleRefreshTime
!= aRefreshTime
) {
532 mStyleRuleRefreshTime
= aRefreshTime
;
533 mStyleRule
= nullptr;
534 // We'll set mNeedsRefreshes to true below in all cases where we need them.
535 mNeedsRefreshes
= false;
537 // FIXME(spec): assume that properties in higher animations override
538 // those in lower ones.
539 // Therefore, we iterate from last animation to first.
540 nsCSSPropertySet properties
;
542 for (size_t playerIdx
= mPlayers
.Length(); playerIdx
-- != 0; ) {
543 AnimationPlayer
* player
= mPlayers
[playerIdx
];
545 if (!player
->GetSource() || player
->GetSource()->IsFinishedTransition()) {
549 // The GetComputedTiming() call here handles pausing. But:
550 // FIXME: avoid recalculating every time when paused.
551 ComputedTiming computedTiming
= player
->GetSource()->GetComputedTiming();
553 if ((computedTiming
.mPhase
== ComputedTiming::AnimationPhase_Before
||
554 computedTiming
.mPhase
== ComputedTiming::AnimationPhase_Active
) &&
555 !player
->IsPaused()) {
556 mNeedsRefreshes
= true;
559 // If the time fraction is null, we don't have fill data for the current
560 // time so we shouldn't animate.
561 // Likewise, if the player has no source content.
562 if (computedTiming
.mTimeFraction
== ComputedTiming::kNullTimeFraction
) {
566 NS_ABORT_IF_FALSE(0.0 <= computedTiming
.mTimeFraction
&&
567 computedTiming
.mTimeFraction
<= 1.0,
568 "timing fraction should be in [0-1]");
570 const Animation
* anim
= player
->GetSource();
571 for (size_t propIdx
= 0, propEnd
= anim
->Properties().Length();
572 propIdx
!= propEnd
; ++propIdx
)
574 const AnimationProperty
& prop
= anim
->Properties()[propIdx
];
576 NS_ABORT_IF_FALSE(prop
.mSegments
[0].mFromKey
== 0.0,
577 "incorrect first from key");
578 NS_ABORT_IF_FALSE(prop
.mSegments
[prop
.mSegments
.Length() - 1].mToKey
580 "incorrect last to key");
582 if (properties
.HasProperty(prop
.mProperty
)) {
583 // A later animation already set this property.
586 properties
.AddProperty(prop
.mProperty
);
588 NS_ABORT_IF_FALSE(prop
.mSegments
.Length() > 0,
589 "property should not be in animations if it "
592 // FIXME: Maybe cache the current segment?
593 const AnimationPropertySegment
*segment
= prop
.mSegments
.Elements(),
594 *segmentEnd
= segment
+ prop
.mSegments
.Length();
595 while (segment
->mToKey
< computedTiming
.mTimeFraction
) {
596 NS_ABORT_IF_FALSE(segment
->mFromKey
< segment
->mToKey
,
599 if (segment
== segmentEnd
) {
600 NS_ABORT_IF_FALSE(false, "incorrect time fraction");
601 break; // in order to continue in outer loop (just below)
603 NS_ABORT_IF_FALSE(segment
->mFromKey
== (segment
-1)->mToKey
,
606 if (segment
== segmentEnd
) {
609 NS_ABORT_IF_FALSE(segment
->mFromKey
< segment
->mToKey
,
611 NS_ABORT_IF_FALSE(segment
>= prop
.mSegments
.Elements() &&
612 size_t(segment
- prop
.mSegments
.Elements()) <
613 prop
.mSegments
.Length(),
614 "out of array bounds");
617 // Allocate the style rule now that we know we have animation data.
618 mStyleRule
= new css::AnimValuesStyleRule();
621 double positionInSegment
=
622 (computedTiming
.mTimeFraction
- segment
->mFromKey
) /
623 (segment
->mToKey
- segment
->mFromKey
);
624 double valuePosition
=
625 segment
->mTimingFunction
.GetValue(positionInSegment
);
627 StyleAnimationValue
*val
=
628 mStyleRule
->AddEmptyValue(prop
.mProperty
);
633 StyleAnimationValue::Interpolate(prop
.mProperty
,
636 valuePosition
, *val
);
637 NS_ABORT_IF_FALSE(result
, "interpolate must succeed now");
645 AnimationPlayerCollection::CanThrottleTransformChanges(TimeStamp aTime
)
647 if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
651 // If we know that the animation cannot cause overflow,
652 // we can just disable flushes for this animation.
654 // If we don't show scrollbars, we don't care about overflow.
655 if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars
) == 0) {
659 // If this animation can cause overflow, we can throttle some of the ticks.
660 if ((aTime
- mStyleRuleRefreshTime
) < TimeDuration::FromMilliseconds(200)) {
664 // If the nearest scrollable ancestor has overflow:hidden,
665 // we don't care about overflow.
666 nsIScrollableFrame
* scrollable
= nsLayoutUtils::GetNearestScrollableFrame(
667 nsLayoutUtils::GetStyleFrame(mElement
));
672 ScrollbarStyles ss
= scrollable
->GetScrollbarStyles();
673 if (ss
.mVertical
== NS_STYLE_OVERFLOW_HIDDEN
&&
674 ss
.mHorizontal
== NS_STYLE_OVERFLOW_HIDDEN
&&
675 scrollable
->GetLogicalScrollPosition() == nsPoint(0, 0)) {
683 AnimationPlayerCollection::CanThrottleAnimation(TimeStamp aTime
)
685 nsIFrame
* frame
= nsLayoutUtils::GetStyleFrame(mElement
);
690 bool hasTransform
= HasAnimationOfProperty(eCSSProperty_transform
);
691 bool hasOpacity
= HasAnimationOfProperty(eCSSProperty_opacity
);
693 Layer
* layer
= FrameLayerBuilder::GetDedicatedLayer(
694 frame
, nsDisplayItem::TYPE_OPACITY
);
695 if (!layer
|| mAnimationGeneration
> layer
->GetAnimationGeneration()) {
704 Layer
* layer
= FrameLayerBuilder::GetDedicatedLayer(
705 frame
, nsDisplayItem::TYPE_TRANSFORM
);
706 if (!layer
|| mAnimationGeneration
> layer
->GetAnimationGeneration()) {
710 return CanThrottleTransformChanges(aTime
);
714 AnimationPlayerCollection::UpdateAnimationGeneration(
715 nsPresContext
* aPresContext
)
717 mAnimationGeneration
=
718 aPresContext
->RestyleManager()->GetAnimationGeneration();
722 AnimationPlayerCollection::HasCurrentAnimations()
724 for (size_t playerIdx
= mPlayers
.Length(); playerIdx
-- != 0; ) {
725 if (mPlayers
[playerIdx
]->IsCurrent()) {