Bug 1073336 part 5 - Add AnimationPlayerCollection::PlayerUpdated; r=dbaron
[gecko.git] / layout / style / AnimationCommon.cpp
blob6f75d8ca438acc2df7de929940304d8bb6df9b68
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"
17 #include "nsIFrame.h"
18 #include "nsLayoutUtils.h"
19 #include "mozilla/LookAndFeel.h"
20 #include "Layers.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;
33 namespace mozilla {
35 /* static */ bool
36 IsGeometricProperty(nsCSSProperty aProperty)
38 switch (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:
45 return true;
46 default:
47 return false;
51 namespace css {
53 CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext)
54 : mPresContext(aPresContext)
55 , mIsObservingRefreshDriver(false)
57 PR_INIT_CLIST(&mElementCollections);
60 CommonAnimationManager::~CommonAnimationManager()
62 NS_ABORT_IF_FALSE(!mPresContext, "Disconnect should have been called");
65 void
66 CommonAnimationManager::Disconnect()
68 // Content nodes might outlive the transition or animation manager.
69 RemoveAllElementCollections();
71 mPresContext = nullptr;
74 void
75 CommonAnimationManager::AddElementCollection(AnimationPlayerCollection*
76 aCollection)
78 if (!mIsObservingRefreshDriver) {
79 NS_ASSERTION(aCollection->mNeedsRefreshes,
80 "Added data which doesn't need refreshing?");
81 // We need to observe the refresh driver.
82 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
83 mIsObservingRefreshDriver = true;
86 PR_INSERT_BEFORE(aCollection, &mElementCollections);
89 void
90 CommonAnimationManager::RemoveAllElementCollections()
92 while (!PR_CLIST_IS_EMPTY(&mElementCollections)) {
93 AnimationPlayerCollection* head =
94 static_cast<AnimationPlayerCollection*>(
95 PR_LIST_HEAD(&mElementCollections));
96 head->Destroy();
100 void
101 CommonAnimationManager::CheckNeedsRefresh()
103 for (PRCList *l = PR_LIST_HEAD(&mElementCollections);
104 l != &mElementCollections;
105 l = PR_NEXT_LINK(l)) {
106 if (static_cast<AnimationPlayerCollection*>(l)->mNeedsRefreshes) {
107 if (!mIsObservingRefreshDriver) {
108 mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
109 mIsObservingRefreshDriver = true;
111 return;
114 if (mIsObservingRefreshDriver) {
115 mIsObservingRefreshDriver = false;
116 mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
120 AnimationPlayerCollection*
121 CommonAnimationManager::GetAnimationsForCompositor(nsIContent* aContent,
122 nsIAtom* aElementProperty,
123 nsCSSProperty aProperty)
125 if (!aContent->MayHaveAnimations())
126 return nullptr;
127 AnimationPlayerCollection* collection =
128 static_cast<AnimationPlayerCollection*>(
129 aContent->GetProperty(aElementProperty));
130 if (!collection ||
131 !collection->HasAnimationOfProperty(aProperty) ||
132 !collection->CanPerformOnCompositorThread(
133 AnimationPlayerCollection::CanAnimate_AllowPartial)) {
134 return nullptr;
137 // This animation can be done on the compositor.
138 // Mark the frame as active, in case we are able to throttle this animation.
139 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(collection->mElement);
140 if (frame) {
141 if (aProperty == eCSSProperty_opacity) {
142 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_opacity);
143 } else if (aProperty == eCSSProperty_transform) {
144 ActiveLayerTracker::NotifyAnimated(frame, eCSSProperty_transform);
148 return collection;
152 * nsISupports implementation
155 NS_IMPL_ISUPPORTS(CommonAnimationManager, nsIStyleRuleProcessor)
157 nsRestyleHint
158 CommonAnimationManager::HasStateDependentStyle(StateRuleProcessorData* aData)
160 return nsRestyleHint(0);
163 nsRestyleHint
164 CommonAnimationManager::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
166 return nsRestyleHint(0);
169 bool
170 CommonAnimationManager::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
172 return false;
175 nsRestyleHint
176 CommonAnimationManager::HasAttributeDependentStyle(AttributeRuleProcessorData* aData)
178 return nsRestyleHint(0);
181 /* virtual */ bool
182 CommonAnimationManager::MediumFeaturesChanged(nsPresContext* aPresContext)
184 return false;
187 /* virtual */ size_t
188 CommonAnimationManager::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
190 // Measurement of the following members may be added later if DMD finds it is
191 // worthwhile:
192 // - mElementCollections
194 // The following members are not measured
195 // - mPresContext, because it's non-owning
197 return 0;
200 /* virtual */ size_t
201 CommonAnimationManager::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
203 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
206 void
207 CommonAnimationManager::AddStyleUpdatesTo(RestyleTracker& aTracker)
209 TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
211 PRCList* next = PR_LIST_HEAD(&mElementCollections);
212 while (next != &mElementCollections) {
213 AnimationPlayerCollection* collection =
214 static_cast<AnimationPlayerCollection*>(next);
215 next = PR_NEXT_LINK(next);
217 collection->EnsureStyleRuleFor(now, EnsureStyleRule_IsNotThrottled);
219 dom::Element* elementToRestyle = collection->GetElementToRestyle();
220 if (elementToRestyle) {
221 nsRestyleHint rshint = collection->IsForTransitions()
222 ? eRestyle_CSSTransitions : eRestyle_CSSAnimations;
223 aTracker.AddPendingRestyle(elementToRestyle, rshint, nsChangeHint(0));
228 void
229 CommonAnimationManager::NotifyCollectionUpdated(AnimationPlayerCollection&
230 aCollection)
232 CheckNeedsRefresh();
233 mPresContext->ClearLastStyleUpdateForAllAnimations();
234 aCollection.PostRestyleForAnimation(mPresContext);
237 /* static */ bool
238 CommonAnimationManager::ExtractComputedValueForTransition(
239 nsCSSProperty aProperty,
240 nsStyleContext* aStyleContext,
241 StyleAnimationValue& aComputedValue)
243 bool result = StyleAnimationValue::ExtractComputedValue(aProperty,
244 aStyleContext,
245 aComputedValue);
246 if (aProperty == eCSSProperty_visibility) {
247 NS_ABORT_IF_FALSE(aComputedValue.GetUnit() ==
248 StyleAnimationValue::eUnit_Enumerated,
249 "unexpected unit");
250 aComputedValue.SetIntValue(aComputedValue.GetIntValue(),
251 StyleAnimationValue::eUnit_Visibility);
253 return result;
256 NS_IMPL_ISUPPORTS(AnimValuesStyleRule, nsIStyleRule)
258 /* virtual */ void
259 AnimValuesStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
261 nsStyleContext *contextParent = aRuleData->mStyleContext->GetParent();
262 if (contextParent && contextParent->HasPseudoElementData()) {
263 // Don't apply transitions or animations to things inside of
264 // pseudo-elements.
265 // FIXME (Bug 522599): Add tests for this.
266 return;
269 for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
270 PropertyValuePair &cv = mPropertyValuePairs[i];
271 if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
272 nsCSSProps::kSIDTable[cv.mProperty]))
274 nsCSSValue *prop = aRuleData->ValueFor(cv.mProperty);
275 if (prop->GetUnit() == eCSSUnit_Null) {
276 #ifdef DEBUG
277 bool ok =
278 #endif
279 StyleAnimationValue::UncomputeValue(cv.mProperty, cv.mValue, *prop);
280 NS_ABORT_IF_FALSE(ok, "could not store computed value");
286 #ifdef DEBUG
287 /* virtual */ void
288 AnimValuesStyleRule::List(FILE* out, int32_t aIndent) const
290 for (int32_t index = aIndent; --index >= 0; ) fputs(" ", out);
291 fputs("[anim values] { ", out);
292 for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
293 const PropertyValuePair &pair = mPropertyValuePairs[i];
294 nsAutoString value;
295 StyleAnimationValue::UncomputeValue(pair.mProperty, pair.mValue, value);
296 fprintf(out, "%s: %s; ", nsCSSProps::GetStringValue(pair.mProperty).get(),
297 NS_ConvertUTF16toUTF8(value).get());
299 fputs("}\n", out);
301 #endif
303 } /* end sub-namespace css */
305 bool
306 AnimationPlayerCollection::CanAnimatePropertyOnCompositor(
307 const dom::Element *aElement,
308 nsCSSProperty aProperty,
309 CanAnimateFlags aFlags)
311 bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
312 if (!gfxPlatform::OffMainThreadCompositingEnabled()) {
313 if (shouldLog) {
314 nsCString message;
315 message.AppendLiteral("Performance warning: Compositor disabled");
316 LogAsyncAnimationFailure(message);
318 return false;
321 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement);
322 if (IsGeometricProperty(aProperty)) {
323 if (shouldLog) {
324 nsCString message;
325 message.AppendLiteral("Performance warning: Async animation of geometric property '");
326 message.Append(nsCSSProps::GetStringValue(aProperty));
327 message.AppendLiteral("' is disabled");
328 LogAsyncAnimationFailure(message, aElement);
330 return false;
332 if (aProperty == eCSSProperty_transform) {
333 if (frame->Preserves3D() &&
334 frame->Preserves3DChildren()) {
335 if (shouldLog) {
336 nsCString message;
337 message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' transforms is not supported. See bug 779598");
338 LogAsyncAnimationFailure(message, aElement);
340 return false;
342 if (frame->IsSVGTransformed()) {
343 if (shouldLog) {
344 nsCString message;
345 message.AppendLiteral("Gecko bug: Async 'transform' animations of frames with SVG transforms is not supported. See bug 779599");
346 LogAsyncAnimationFailure(message, aElement);
348 return false;
350 if (aFlags & CanAnimate_HasGeometricProperty) {
351 if (shouldLog) {
352 nsCString message;
353 message.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties");
354 LogAsyncAnimationFailure(message, aElement);
356 return false;
359 bool enabled = nsLayoutUtils::AreAsyncAnimationsEnabled();
360 if (!enabled && shouldLog) {
361 nsCString message;
362 message.AppendLiteral("Performance warning: Async animations are disabled");
363 LogAsyncAnimationFailure(message);
365 bool propertyAllowed = (aProperty == eCSSProperty_transform) ||
366 (aProperty == eCSSProperty_opacity) ||
367 (aFlags & CanAnimate_AllowPartial);
368 return enabled && propertyAllowed;
371 /* static */ bool
372 AnimationPlayerCollection::IsCompositorAnimationDisabledForFrame(
373 nsIFrame* aFrame)
375 void* prop = aFrame->Properties().Get(nsIFrame::RefusedAsyncAnimation());
376 return bool(reinterpret_cast<intptr_t>(prop));
379 bool
380 AnimationPlayerCollection::CanPerformOnCompositorThread(
381 CanAnimateFlags aFlags) const
383 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
384 if (!frame) {
385 return false;
388 if (mElementProperty != nsGkAtoms::transitionsProperty &&
389 mElementProperty != nsGkAtoms::animationsProperty) {
390 if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
391 nsCString message;
392 message.AppendLiteral("Gecko bug: Async animation of pseudoelements"
393 " not supported. See bug 771367 (");
394 message.Append(nsAtomCString(mElementProperty));
395 message.Append(")");
396 LogAsyncAnimationFailure(message, mElement);
398 return false;
401 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
402 const AnimationPlayer* player = mPlayers[playerIdx];
403 if (!player->IsRunning() || !player->GetSource()) {
404 continue;
406 const Animation* anim = player->GetSource();
407 for (size_t propIdx = 0, propEnd = anim->Properties().Length();
408 propIdx != propEnd; ++propIdx) {
409 if (IsGeometricProperty(anim->Properties()[propIdx].mProperty)) {
410 aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
411 break;
416 bool existsProperty = false;
417 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
418 const AnimationPlayer* player = mPlayers[playerIdx];
419 if (!player->IsRunning() || !player->GetSource()) {
420 continue;
423 const Animation* anim = player->GetSource();
424 existsProperty = existsProperty || anim->Properties().Length() > 0;
426 for (size_t propIdx = 0, propEnd = anim->Properties().Length();
427 propIdx != propEnd; ++propIdx) {
428 const AnimationProperty& prop = anim->Properties()[propIdx];
429 if (!CanAnimatePropertyOnCompositor(mElement,
430 prop.mProperty,
431 aFlags) ||
432 IsCompositorAnimationDisabledForFrame(frame)) {
433 return false;
438 // No properties to animate
439 if (!existsProperty) {
440 return false;
443 return true;
446 bool
447 AnimationPlayerCollection::HasAnimationOfProperty(
448 nsCSSProperty aProperty) const
450 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
451 const Animation* anim = mPlayers[playerIdx]->GetSource();
452 if (anim && anim->HasAnimationOfProperty(aProperty) &&
453 !anim->IsFinishedTransition()) {
454 return true;
457 return false;
460 mozilla::dom::Element*
461 AnimationPlayerCollection::GetElementToRestyle() const
463 if (IsForElement()) {
464 return mElement;
467 nsIFrame* primaryFrame = mElement->GetPrimaryFrame();
468 if (!primaryFrame) {
469 return nullptr;
471 nsIFrame* pseudoFrame;
472 if (IsForBeforePseudo()) {
473 pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame);
474 } else if (IsForAfterPseudo()) {
475 pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame);
476 } else {
477 MOZ_ASSERT(false, "unknown mElementProperty");
478 return nullptr;
480 if (!pseudoFrame) {
481 return nullptr;
483 return pseudoFrame->GetContent()->AsElement();
486 void
487 AnimationPlayerCollection::NotifyPlayerUpdated()
489 // On the next flush, force us to update the style rule
490 mNeedsRefreshes = true;
491 mStyleRuleRefreshTime = TimeStamp();
493 mManager->NotifyCollectionUpdated(*this);
496 /* static */ void
497 AnimationPlayerCollection::LogAsyncAnimationFailure(nsCString& aMessage,
498 const nsIContent* aContent)
500 if (aContent) {
501 aMessage.AppendLiteral(" [");
502 aMessage.Append(nsAtomCString(aContent->Tag()));
504 nsIAtom* id = aContent->GetID();
505 if (id) {
506 aMessage.AppendLiteral(" with id '");
507 aMessage.Append(nsAtomCString(aContent->GetID()));
508 aMessage.Append('\'');
510 aMessage.Append(']');
512 aMessage.Append('\n');
513 printf_stderr("%s", aMessage.get());
516 /*static*/ void
517 AnimationPlayerCollection::PropertyDtor(void *aObject, nsIAtom *aPropertyName,
518 void *aPropertyValue, void *aData)
520 AnimationPlayerCollection* collection =
521 static_cast<AnimationPlayerCollection*>(aPropertyValue);
522 #ifdef DEBUG
523 NS_ABORT_IF_FALSE(!collection->mCalledPropertyDtor, "can't call dtor twice");
524 collection->mCalledPropertyDtor = true;
525 #endif
526 delete collection;
529 void
530 AnimationPlayerCollection::Tick()
532 for (size_t playerIdx = 0, playerEnd = mPlayers.Length();
533 playerIdx != playerEnd; playerIdx++) {
534 mPlayers[playerIdx]->Tick();
538 void
539 AnimationPlayerCollection::EnsureStyleRuleFor(TimeStamp aRefreshTime,
540 EnsureStyleRuleFlags aFlags)
542 if (!mNeedsRefreshes) {
543 mStyleRuleRefreshTime = aRefreshTime;
544 return;
547 // If we're performing animations on the compositor thread, then we can skip
548 // most of the work in this method. But even if we are throttled, then we
549 // have to do the work if an animation is ending in order to get correct end
550 // of animation behaviour (the styles of the animation disappear, or the fill
551 // mode behaviour). CanThrottle returns false for any finishing animations
552 // so we can force style recalculation in that case.
553 if (aFlags == EnsureStyleRule_IsThrottled) {
554 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
555 if (!mPlayers[playerIdx]->CanThrottle()) {
556 aFlags = EnsureStyleRule_IsNotThrottled;
557 break;
562 if (aFlags == EnsureStyleRule_IsThrottled) {
563 return;
566 // mStyleRule may be null and valid, if we have no style to apply.
567 if (mStyleRuleRefreshTime.IsNull() ||
568 mStyleRuleRefreshTime != aRefreshTime) {
569 mStyleRuleRefreshTime = aRefreshTime;
570 mStyleRule = nullptr;
571 // We'll set mNeedsRefreshes to true below in all cases where we need them.
572 mNeedsRefreshes = false;
574 // If multiple animations specify behavior for the same property the
575 // animation which occurs last in the value of animation-name wins.
576 // As a result, we iterate from last animation to first and, if a
577 // property has already been set, we don't leave it.
578 nsCSSPropertySet properties;
580 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
581 mPlayers[playerIdx]->ComposeStyle(mStyleRule, properties,
582 mNeedsRefreshes);
586 mManager->CheckNeedsRefresh();
589 bool
590 AnimationPlayerCollection::CanThrottleTransformChanges(TimeStamp aTime)
592 if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
593 return false;
596 // If we know that the animation cannot cause overflow,
597 // we can just disable flushes for this animation.
599 // If we don't show scrollbars, we don't care about overflow.
600 if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) {
601 return true;
604 // If this animation can cause overflow, we can throttle some of the ticks.
605 if ((aTime - mStyleRuleRefreshTime) < TimeDuration::FromMilliseconds(200)) {
606 return true;
609 // If the nearest scrollable ancestor has overflow:hidden,
610 // we don't care about overflow.
611 nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame(
612 nsLayoutUtils::GetStyleFrame(mElement));
613 if (!scrollable) {
614 return true;
617 ScrollbarStyles ss = scrollable->GetScrollbarStyles();
618 if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
619 ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
620 scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
621 return true;
624 return false;
627 bool
628 AnimationPlayerCollection::CanThrottleAnimation(TimeStamp aTime)
630 nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
631 if (!frame) {
632 return false;
635 bool hasTransform = HasAnimationOfProperty(eCSSProperty_transform);
636 bool hasOpacity = HasAnimationOfProperty(eCSSProperty_opacity);
637 if (hasOpacity) {
638 Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
639 frame, nsDisplayItem::TYPE_OPACITY);
640 if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) {
641 return false;
645 if (!hasTransform) {
646 return true;
649 Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
650 frame, nsDisplayItem::TYPE_TRANSFORM);
651 if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) {
652 return false;
655 return CanThrottleTransformChanges(aTime);
658 void
659 AnimationPlayerCollection::UpdateAnimationGeneration(
660 nsPresContext* aPresContext)
662 mAnimationGeneration =
663 aPresContext->RestyleManager()->GetAnimationGeneration();
666 bool
667 AnimationPlayerCollection::HasCurrentAnimations() const
669 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
670 if (mPlayers[playerIdx]->HasCurrentSource()) {
671 return true;
675 return false;
678 bool
679 AnimationPlayerCollection::HasCurrentAnimationsForProperty(nsCSSProperty
680 aProperty) const
682 for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
683 const Animation* anim = mPlayers[playerIdx]->GetSource();
684 if (anim && anim->IsCurrent() && anim->HasAnimationOfProperty(aProperty)) {
685 return true;
689 return false;