gecko-dev/layout/style/AnimationCommon.cpp
Brian Birtles 8530476b49 Bug 1200568 - Don't create animations for elements that are not part of the document tree; r=dbaron
As well as ensuring that we don't create animations for elements that are not
part of the document tree, this test also adjusts the assertion that checks
this in the following ways:

* Calls GetComposedDoc() instead of GetCrossShadowCurrentDoc() since the
  latter is deprecated.
* Moves it from RequestRestyle to FlushAnimations since, depending on how we
  refactor this code in the future, it's possible we might end up calling
  RequestRestyle even for animations on elements that have been removed from the
  document but we shouldn't call FlushAnimations in this case.
2015-09-09 10:10:41 +09:00

1001 lines
30 KiB
C++

/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AnimationCommon.h"
#include "nsTransitionManager.h"
#include "nsAnimationManager.h"
#include "ActiveLayerTracker.h"
#include "gfxPlatform.h"
#include "nsRuleData.h"
#include "nsCSSPropertySet.h"
#include "nsCSSValue.h"
#include "nsCycleCollectionParticipant.h"
#include "nsDOMMutationObserver.h"
#include "nsStyleContext.h"
#include "nsIFrame.h"
#include "nsLayoutUtils.h"
#include "mozilla/LookAndFeel.h"
#include "LayerAnimationInfo.h" // For LayerAnimationInfo::sRecords
#include "Layers.h"
#include "FrameLayerBuilder.h"
#include "nsDisplayList.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/dom/KeyframeEffect.h"
#include "RestyleManager.h"
#include "nsRuleProcessorData.h"
#include "nsStyleSet.h"
#include "nsStyleChangeList.h"
using mozilla::layers::Layer;
using mozilla::dom::Animation;
using mozilla::dom::KeyframeEffectReadOnly;
namespace mozilla {
/* static */ bool
IsGeometricProperty(nsCSSProperty aProperty)
{
switch (aProperty) {
case eCSSProperty_bottom:
case eCSSProperty_height:
case eCSSProperty_left:
case eCSSProperty_right:
case eCSSProperty_top:
case eCSSProperty_width:
return true;
default:
return false;
}
}
CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext)
: mPresContext(aPresContext)
, mIsObservingRefreshDriver(false)
{
}
CommonAnimationManager::~CommonAnimationManager()
{
MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
}
void
CommonAnimationManager::Disconnect()
{
// Content nodes might outlive the transition or animation manager.
RemoveAllElementCollections();
mPresContext = nullptr;
}
void
CommonAnimationManager::AddElementCollection(AnimationCollection* aCollection)
{
if (!mIsObservingRefreshDriver) {
NS_ASSERTION(aCollection->mNeedsRefreshes,
"Added data which doesn't need refreshing?");
// We need to observe the refresh driver.
mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
mIsObservingRefreshDriver = true;
}
mElementCollections.insertBack(aCollection);
}
void
CommonAnimationManager::RemoveAllElementCollections()
{
while (AnimationCollection* head = mElementCollections.getFirst()) {
head->Destroy(); // Note: this removes 'head' from mElementCollections.
}
}
void
CommonAnimationManager::MaybeStartObservingRefreshDriver()
{
if (mIsObservingRefreshDriver || !NeedsRefresh()) {
return;
}
mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
mIsObservingRefreshDriver = true;
}
void
CommonAnimationManager::MaybeStartOrStopObservingRefreshDriver()
{
bool needsRefresh = NeedsRefresh();
if (needsRefresh && !mIsObservingRefreshDriver) {
mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
} else if (!needsRefresh && mIsObservingRefreshDriver) {
mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
}
mIsObservingRefreshDriver = needsRefresh;
}
bool
CommonAnimationManager::NeedsRefresh() const
{
for (const AnimationCollection* collection = mElementCollections.getFirst();
collection; collection = collection->getNext()) {
if (collection->mNeedsRefreshes) {
return true;
}
}
return false;
}
AnimationCollection*
CommonAnimationManager::GetAnimationCollection(const nsIFrame* aFrame)
{
nsIContent* content = aFrame->GetContent();
if (!content) {
return nullptr;
}
nsIAtom* animProp;
if (aFrame->IsGeneratedContentFrame()) {
nsIFrame* parent = aFrame->GetParent();
if (parent->IsGeneratedContentFrame()) {
return nullptr;
}
nsIAtom* name = content->NodeInfo()->NameAtom();
if (name == nsGkAtoms::mozgeneratedcontentbefore) {
animProp = GetAnimationsBeforeAtom();
} else if (name == nsGkAtoms::mozgeneratedcontentafter) {
animProp = GetAnimationsAfterAtom();
} else {
return nullptr;
}
content = content->GetParent();
if (!content) {
return nullptr;
}
} else {
if (!content->MayHaveAnimations()) {
return nullptr;
}
animProp = GetAnimationsAtom();
}
return static_cast<AnimationCollection*>(content->GetProperty(animProp));
}
AnimationCollection*
CommonAnimationManager::GetAnimationsForCompositor(const nsIFrame* aFrame,
nsCSSProperty aProperty)
{
AnimationCollection* collection = GetAnimationCollection(aFrame);
if (!collection ||
!collection->HasCurrentAnimationOfProperty(aProperty) ||
!collection->CanPerformOnCompositorThread(
AnimationCollection::CanAnimate_AllowPartial)) {
return nullptr;
}
// This animation can be done on the compositor.
return collection;
}
nsRestyleHint
CommonAnimationManager::HasStateDependentStyle(StateRuleProcessorData* aData)
{
return nsRestyleHint(0);
}
nsRestyleHint
CommonAnimationManager::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
{
return nsRestyleHint(0);
}
bool
CommonAnimationManager::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
{
return false;
}
nsRestyleHint
CommonAnimationManager::HasAttributeDependentStyle(
AttributeRuleProcessorData* aData,
RestyleHintData& aRestyleHintDataResult)
{
return nsRestyleHint(0);
}
/* virtual */ bool
CommonAnimationManager::MediumFeaturesChanged(nsPresContext* aPresContext)
{
return false;
}
/* virtual */ void
CommonAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
{
MOZ_ASSERT(aData->mPresContext == mPresContext,
"pres context mismatch");
nsIStyleRule *rule =
GetAnimationRule(aData->mElement,
nsCSSPseudoElements::ePseudo_NotPseudoElement);
if (rule) {
aData->mRuleWalker->Forward(rule);
aData->mRuleWalker->CurrentNode()->SetIsAnimationRule();
}
}
/* virtual */ void
CommonAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
{
MOZ_ASSERT(aData->mPresContext == mPresContext,
"pres context mismatch");
if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
return;
}
// FIXME: Do we really want to be the only thing keeping a
// pseudo-element alive? I *think* the non-animation restyle should
// handle that, but should add a test.
nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
if (rule) {
aData->mRuleWalker->Forward(rule);
aData->mRuleWalker->CurrentNode()->SetIsAnimationRule();
}
}
/* virtual */ void
CommonAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
{
}
#ifdef MOZ_XUL
/* virtual */ void
CommonAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
{
}
#endif
/* virtual */ size_t
CommonAnimationManager::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
// Measurement of the following members may be added later if DMD finds it is
// worthwhile:
// - mElementCollections
//
// The following members are not measured
// - mPresContext, because it's non-owning
return 0;
}
/* virtual */ size_t
CommonAnimationManager::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
void
CommonAnimationManager::AddStyleUpdatesTo(RestyleTracker& aTracker)
{
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
for (AnimationCollection* collection = mElementCollections.getFirst();
collection; collection = collection->getNext()) {
collection->EnsureStyleRuleFor(now);
dom::Element* elementToRestyle = collection->GetElementToRestyle();
if (elementToRestyle) {
nsRestyleHint rshint = collection->IsForTransitions()
? eRestyle_CSSTransitions : eRestyle_CSSAnimations;
aTracker.AddPendingRestyle(elementToRestyle, rshint, nsChangeHint(0));
}
}
}
/* static */ bool
CommonAnimationManager::ExtractComputedValueForTransition(
nsCSSProperty aProperty,
nsStyleContext* aStyleContext,
StyleAnimationValue& aComputedValue)
{
bool result = StyleAnimationValue::ExtractComputedValue(aProperty,
aStyleContext,
aComputedValue);
if (aProperty == eCSSProperty_visibility) {
MOZ_ASSERT(aComputedValue.GetUnit() ==
StyleAnimationValue::eUnit_Enumerated,
"unexpected unit");
aComputedValue.SetIntValue(aComputedValue.GetIntValue(),
StyleAnimationValue::eUnit_Visibility);
}
return result;
}
AnimationCollection*
CommonAnimationManager::GetAnimations(dom::Element *aElement,
nsCSSPseudoElements::Type aPseudoType,
bool aCreateIfNeeded)
{
if (!aCreateIfNeeded && mElementCollections.isEmpty()) {
// Early return for the most common case.
return nullptr;
}
nsIAtom *propName;
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
propName = GetAnimationsAtom();
} else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
propName = GetAnimationsBeforeAtom();
} else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
propName = GetAnimationsAfterAtom();
} else {
NS_ASSERTION(!aCreateIfNeeded,
"should never try to create transitions for pseudo "
"other than :before or :after");
return nullptr;
}
AnimationCollection* collection =
static_cast<AnimationCollection*>(aElement->GetProperty(propName));
if (!collection && aCreateIfNeeded) {
// FIXME: Consider arena-allocating?
collection = new AnimationCollection(aElement, propName, this);
nsresult rv =
aElement->SetProperty(propName, collection,
&AnimationCollection::PropertyDtor, false);
if (NS_FAILED(rv)) {
NS_WARNING("SetProperty failed");
// The collection must be destroyed via PropertyDtor, otherwise
// mCalledPropertyDtor assertion is triggered in destructor.
AnimationCollection::PropertyDtor(aElement, propName, collection, nullptr);
return nullptr;
}
if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
aElement->SetMayHaveAnimations();
}
AddElementCollection(collection);
}
return collection;
}
void
CommonAnimationManager::FlushAnimations()
{
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
for (AnimationCollection* collection = mElementCollections.getFirst();
collection; collection = collection->getNext()) {
if (collection->mStyleRuleRefreshTime == now) {
continue;
}
MOZ_ASSERT(collection->mElement->GetComposedDoc() ==
mPresContext->Document(),
"Should not have a transition/animations collection for an "
"element that is not part of the document tree");
collection->RequestRestyle(AnimationCollection::RestyleType::Standard);
}
}
nsIStyleRule*
CommonAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType)
{
MOZ_ASSERT(
aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
aPseudoType == nsCSSPseudoElements::ePseudo_before ||
aPseudoType == nsCSSPseudoElements::ePseudo_after,
"forbidden pseudo type");
if (!mPresContext->IsDynamic()) {
// For print or print preview, ignore animations.
return nullptr;
}
AnimationCollection* collection =
GetAnimations(aElement, aPseudoType, false);
if (!collection) {
return nullptr;
}
RestyleManager* restyleManager = mPresContext->RestyleManager();
if (restyleManager->SkipAnimationRules()) {
return nullptr;
}
collection->EnsureStyleRuleFor(
mPresContext->RefreshDriver()->MostRecentRefresh());
return collection->mStyleRule;
}
/* virtual */ void
CommonAnimationManager::WillRefresh(TimeStamp aTime)
{
MOZ_ASSERT(mPresContext,
"refresh driver should not notify additional observers "
"after pres context has been destroyed");
if (!mPresContext->GetPresShell()) {
// Someone might be keeping mPresContext alive past the point
// where it has been torn down; don't bother doing anything in
// this case. But do get rid of all our animations so we stop
// triggering refreshes.
RemoveAllElementCollections();
return;
}
nsAutoAnimationMutationBatch mb(mPresContext->Document());
for (AnimationCollection* collection = mElementCollections.getFirst();
collection; collection = collection->getNext()) {
collection->Tick();
}
MaybeStartOrStopObservingRefreshDriver();
}
NS_IMPL_ISUPPORTS(AnimValuesStyleRule, nsIStyleRule)
/* virtual */ void
AnimValuesStyleRule::MapRuleInfoInto(nsRuleData* aRuleData)
{
nsStyleContext *contextParent = aRuleData->mStyleContext->GetParent();
if (contextParent && contextParent->HasPseudoElementData()) {
// Don't apply transitions or animations to things inside of
// pseudo-elements.
// FIXME (Bug 522599): Add tests for this.
// Prevent structs from being cached on the rule node since we're inside
// a pseudo-element, as we could determine cacheability differently
// when walking the rule tree for a style context that is not inside
// a pseudo-element. Note that nsRuleNode::GetStyle##name_ and GetStyleData
// will never look at cached structs when we're animating things inside
// a pseduo-element, so that we don't incorrectly return a struct that
// is only appropriate for non-pseudo-elements.
aRuleData->mConditions.SetUncacheable();
return;
}
for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
PropertyValuePair &cv = mPropertyValuePairs[i];
if (aRuleData->mSIDs & nsCachedStyleData::GetBitForSID(
nsCSSProps::kSIDTable[cv.mProperty]))
{
nsCSSValue *prop = aRuleData->ValueFor(cv.mProperty);
if (prop->GetUnit() == eCSSUnit_Null) {
#ifdef DEBUG
bool ok =
#endif
StyleAnimationValue::UncomputeValue(cv.mProperty, cv.mValue, *prop);
MOZ_ASSERT(ok, "could not store computed value");
}
}
}
}
#ifdef DEBUG
/* virtual */ void
AnimValuesStyleRule::List(FILE* out, int32_t aIndent) const
{
nsAutoCString str;
for (int32_t index = aIndent; --index >= 0; ) {
str.AppendLiteral(" ");
}
str.AppendLiteral("[anim values] { ");
for (uint32_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
const PropertyValuePair &pair = mPropertyValuePairs[i];
str.Append(nsCSSProps::GetStringValue(pair.mProperty));
str.AppendLiteral(": ");
nsAutoString value;
StyleAnimationValue::UncomputeValue(pair.mProperty, pair.mValue, value);
AppendUTF16toUTF8(value, str);
str.AppendLiteral("; ");
}
str.AppendLiteral("}\n");
fprintf_stderr(out, "%s", str.get());
}
#endif
bool
AnimationCollection::CanAnimatePropertyOnCompositor(
const dom::Element *aElement,
nsCSSProperty aProperty,
CanAnimateFlags aFlags)
{
bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
if (!gfxPlatform::OffMainThreadCompositingEnabled()) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Performance warning: Compositor disabled");
LogAsyncAnimationFailure(message);
}
return false;
}
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(aElement);
if (IsGeometricProperty(aProperty)) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Performance warning: Async animation of geometric property '");
message.Append(nsCSSProps::GetStringValue(aProperty));
message.AppendLiteral("' is disabled");
LogAsyncAnimationFailure(message, aElement);
}
return false;
}
if (aProperty == eCSSProperty_transform) {
if (frame->Preserves3D() ||
frame->Preserves3DChildren()) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Gecko bug: Async animation of 'preserve-3d' transforms is not supported. See bug 779598");
LogAsyncAnimationFailure(message, aElement);
}
return false;
}
// Note that testing BackfaceIsHidden() is not a sufficient test for
// what we need for animating backface-visibility correctly if we
// remove the above test for Preserves3DChildren(); that would require
// looking at backface-visibility on descendants as well.
if (frame->StyleDisplay()->BackfaceIsHidden()) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Gecko bug: Async animation of 'backface-visibility: hidden' transforms is not supported. See bug 1186204.");
LogAsyncAnimationFailure(message, aElement);
}
return false;
}
if (frame->IsSVGTransformed()) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Gecko bug: Async 'transform' animations of frames with SVG transforms is not supported. See bug 779599");
LogAsyncAnimationFailure(message, aElement);
}
return false;
}
if (aFlags & CanAnimate_HasGeometricProperty) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties");
LogAsyncAnimationFailure(message, aElement);
}
return false;
}
}
bool enabled = nsLayoutUtils::AreAsyncAnimationsEnabled();
if (!enabled && shouldLog) {
nsCString message;
message.AppendLiteral("Performance warning: Async animations are disabled");
LogAsyncAnimationFailure(message);
}
bool propertyAllowed = (aProperty == eCSSProperty_transform) ||
(aProperty == eCSSProperty_opacity) ||
(aFlags & CanAnimate_AllowPartial);
return enabled && propertyAllowed;
}
/* static */ bool
AnimationCollection::IsCompositorAnimationDisabledForFrame(
nsIFrame* aFrame)
{
void* prop = aFrame->Properties().Get(nsIFrame::RefusedAsyncAnimation());
return bool(reinterpret_cast<intptr_t>(prop));
}
bool
AnimationCollection::CanPerformOnCompositorThread(
CanAnimateFlags aFlags) const
{
dom::Element* element = GetElementToRestyle();
if (!element) {
return false;
}
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element);
if (!frame) {
return false;
}
for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
const Animation* anim = mAnimations[animIdx];
if (!anim->IsPlaying()) {
continue;
}
const KeyframeEffectReadOnly* effect = anim->GetEffect();
MOZ_ASSERT(effect, "A playing animation should have an effect");
for (size_t propIdx = 0, propEnd = effect->Properties().Length();
propIdx != propEnd; ++propIdx) {
if (IsGeometricProperty(effect->Properties()[propIdx].mProperty)) {
aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
break;
}
}
}
bool existsProperty = false;
for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
const Animation* anim = mAnimations[animIdx];
if (!anim->IsPlaying()) {
continue;
}
const KeyframeEffectReadOnly* effect = anim->GetEffect();
MOZ_ASSERT(effect, "A playing animation should have an effect");
existsProperty = existsProperty || effect->Properties().Length() > 0;
for (size_t propIdx = 0, propEnd = effect->Properties().Length();
propIdx != propEnd; ++propIdx) {
const AnimationProperty& prop = effect->Properties()[propIdx];
if (!CanAnimatePropertyOnCompositor(element,
prop.mProperty,
aFlags) ||
IsCompositorAnimationDisabledForFrame(frame)) {
return false;
}
}
}
// No properties to animate
if (!existsProperty) {
return false;
}
return true;
}
bool
AnimationCollection::HasCurrentAnimationOfProperty(nsCSSProperty
aProperty) const
{
for (Animation* animation : mAnimations) {
if (animation->HasCurrentEffect() &&
animation->GetEffect()->HasAnimationOfProperty(aProperty)) {
return true;
}
}
return false;
}
/*static*/ nsString
AnimationCollection::PseudoTypeAsString(nsCSSPseudoElements::Type aPseudoType)
{
switch (aPseudoType) {
case nsCSSPseudoElements::ePseudo_before:
return NS_LITERAL_STRING("::before");
case nsCSSPseudoElements::ePseudo_after:
return NS_LITERAL_STRING("::after");
default:
MOZ_ASSERT(aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement,
"Unexpected pseudo type");
return EmptyString();
}
}
mozilla::dom::Element*
AnimationCollection::GetElementToRestyle() const
{
if (IsForElement()) {
return mElement;
}
nsIFrame* primaryFrame = mElement->GetPrimaryFrame();
if (!primaryFrame) {
return nullptr;
}
nsIFrame* pseudoFrame;
if (IsForBeforePseudo()) {
pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame);
} else if (IsForAfterPseudo()) {
pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame);
} else {
MOZ_ASSERT(false, "unknown mElementProperty");
return nullptr;
}
if (!pseudoFrame) {
return nullptr;
}
return pseudoFrame->GetContent()->AsElement();
}
/* static */ void
AnimationCollection::LogAsyncAnimationFailure(nsCString& aMessage,
const nsIContent* aContent)
{
if (aContent) {
aMessage.AppendLiteral(" [");
aMessage.Append(nsAtomCString(aContent->NodeInfo()->NameAtom()));
nsIAtom* id = aContent->GetID();
if (id) {
aMessage.AppendLiteral(" with id '");
aMessage.Append(nsAtomCString(aContent->GetID()));
aMessage.Append('\'');
}
aMessage.Append(']');
}
aMessage.Append('\n');
printf_stderr("%s", aMessage.get());
}
/*static*/ void
AnimationCollection::PropertyDtor(void *aObject, nsIAtom *aPropertyName,
void *aPropertyValue, void *aData)
{
AnimationCollection* collection =
static_cast<AnimationCollection*>(aPropertyValue);
#ifdef DEBUG
MOZ_ASSERT(!collection->mCalledPropertyDtor, "can't call dtor twice");
collection->mCalledPropertyDtor = true;
#endif
{
nsAutoAnimationMutationBatch mb(collection->mElement->OwnerDoc());
for (size_t animIdx = collection->mAnimations.Length(); animIdx-- != 0; ) {
collection->mAnimations[animIdx]->CancelFromStyle();
}
}
delete collection;
}
void
AnimationCollection::Tick()
{
for (size_t animIdx = 0, animEnd = mAnimations.Length();
animIdx != animEnd; animIdx++) {
mAnimations[animIdx]->Tick();
}
}
void
AnimationCollection::EnsureStyleRuleFor(TimeStamp aRefreshTime)
{
mHasPendingAnimationRestyle = false;
if (!mNeedsRefreshes) {
mStyleRuleRefreshTime = aRefreshTime;
return;
}
if (!mStyleRuleRefreshTime.IsNull() &&
mStyleRuleRefreshTime == aRefreshTime) {
// mStyleRule may be null and valid, if we have no style to apply.
return;
}
if (mManager->IsAnimationManager()) {
// Update cascade results before updating the style rule, since the
// cascade results can influence the style rule.
static_cast<nsAnimationManager*>(mManager)->MaybeUpdateCascadeResults(this);
}
mStyleRuleRefreshTime = aRefreshTime;
mStyleRule = nullptr;
// We'll set mNeedsRefreshes to true below in all cases where we need them.
mNeedsRefreshes = false;
// If multiple animations specify behavior for the same property the
// animation which occurs last in the value of animation-name wins.
// As a result, we iterate from last animation to first and, if a
// property has already been set, we don't leave it.
nsCSSPropertySet properties;
for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
mAnimations[animIdx]->ComposeStyle(mStyleRule, properties, mNeedsRefreshes);
}
mManager->MaybeStartObservingRefreshDriver();
}
bool
AnimationCollection::CanThrottleTransformChanges(TimeStamp aTime)
{
if (!nsLayoutUtils::AreAsyncAnimationsEnabled()) {
return false;
}
// If we know that the animation cannot cause overflow,
// we can just disable flushes for this animation.
// If we don't show scrollbars, we don't care about overflow.
if (LookAndFeel::GetInt(LookAndFeel::eIntID_ShowHideScrollbars) == 0) {
return true;
}
// If this animation can cause overflow, we can throttle some of the ticks.
if (!mStyleRuleRefreshTime.IsNull() &&
(aTime - mStyleRuleRefreshTime) < TimeDuration::FromMilliseconds(200)) {
return true;
}
dom::Element* element = GetElementToRestyle();
if (!element) {
return false;
}
// If the nearest scrollable ancestor has overflow:hidden,
// we don't care about overflow.
nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame(
nsLayoutUtils::GetStyleFrame(element));
if (!scrollable) {
return true;
}
ScrollbarStyles ss = scrollable->GetScrollbarStyles();
if (ss.mVertical == NS_STYLE_OVERFLOW_HIDDEN &&
ss.mHorizontal == NS_STYLE_OVERFLOW_HIDDEN &&
scrollable->GetLogicalScrollPosition() == nsPoint(0, 0)) {
return true;
}
return false;
}
bool
AnimationCollection::CanThrottleAnimation(TimeStamp aTime)
{
dom::Element* element = GetElementToRestyle();
if (!element) {
return false;
}
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(element);
if (!frame) {
return false;
}
for (const LayerAnimationInfo::Record& record :
LayerAnimationInfo::sRecords) {
// We only need to worry about *current* animations here.
// - If we have a newly-finished animation, Animation::CanThrottle will
// detect that and force an unthrottled sample.
// - If we have a newly-idle animation, then whatever caused the animation
// to be idle will update the animation generation so we'll return false
// from the layer generation check below for any other running compositor
// animations (and if no other compositor animations exist we won't get
// this far).
if (!HasCurrentAnimationOfProperty(record.mProperty)) {
continue;
}
Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
frame, record.mLayerType);
if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) {
return false;
}
if (record.mProperty == eCSSProperty_transform &&
!CanThrottleTransformChanges(aTime)) {
return false;
}
}
return true;
}
void
AnimationCollection::RequestRestyle(RestyleType aRestyleType)
{
MOZ_ASSERT(IsForElement() || IsForBeforePseudo() || IsForAfterPseudo(),
"Unexpected mElementProperty; might restyle too much");
nsPresContext* presContext = mManager->PresContext();
if (!presContext) {
// Pres context will be null after the manager is disconnected.
return;
}
// Steps for Restyle::Layer:
if (aRestyleType == RestyleType::Layer) {
mStyleRuleRefreshTime = TimeStamp();
// FIXME: We should be able to remove these two lines once we move
// ticking to animation timelines as part of bug 1151731.
mNeedsRefreshes = true;
mManager->MaybeStartObservingRefreshDriver();
// Prompt layers to re-sync their animations.
presContext->ClearLastStyleUpdateForAllAnimations();
presContext->RestyleManager()->IncrementAnimationGeneration();
UpdateAnimationGeneration(presContext);
}
// Steps for RestyleType::Standard and above:
if (mHasPendingAnimationRestyle) {
return;
}
// Upgrade throttled restyles if other factors prevent
// throttling (e.g. async animations are not enabled).
if (aRestyleType == RestyleType::Throttled) {
TimeStamp now = presContext->RefreshDriver()->MostRecentRefresh();
if (!CanPerformOnCompositorThread(CanAnimateFlags(0)) ||
!CanThrottleAnimation(now)) {
aRestyleType = RestyleType::Standard;
}
}
if (aRestyleType >= RestyleType::Standard) {
mHasPendingAnimationRestyle = true;
PostRestyleForAnimation(presContext);
return;
}
// Steps for RestyleType::Throttled:
MOZ_ASSERT(aRestyleType == RestyleType::Throttled,
"Should have already handled all non-throttled restyles");
presContext->Document()->SetNeedStyleFlush();
}
void
AnimationCollection::UpdateAnimationGeneration(nsPresContext* aPresContext)
{
mAnimationGeneration =
aPresContext->RestyleManager()->GetAnimationGeneration();
}
void
AnimationCollection::UpdateCheckGeneration(
nsPresContext* aPresContext)
{
mCheckGeneration =
aPresContext->RestyleManager()->GetAnimationGeneration();
}
bool
AnimationCollection::HasCurrentAnimations() const
{
for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
if (mAnimations[animIdx]->HasCurrentEffect()) {
return true;
}
}
return false;
}
bool
AnimationCollection::HasCurrentAnimationsForProperties(
const nsCSSProperty* aProperties,
size_t aPropertyCount) const
{
for (size_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
const Animation& anim = *mAnimations[animIdx];
const KeyframeEffectReadOnly* effect = anim.GetEffect();
if (effect &&
effect->IsCurrent(anim) &&
effect->HasAnimationOfProperties(aProperties, aPropertyCount)) {
return true;
}
}
return false;
}
nsPresContext*
OwningElementRef::GetRenderedPresContext() const
{
if (!mElement) {
return nullptr;
}
nsIDocument* doc = mElement->GetComposedDoc();
if (!doc) {
return nullptr;
}
nsIPresShell* shell = doc->GetShell();
if (!shell) {
return nullptr;
}
return shell->GetPresContext();
}
} // namespace mozilla