mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
d5bb664bcf
Note that this means that when we start transitions, we post restyles that are processed during the current restyling operation, rather than in a later phase. This depends on patch 11, which makes the transition manager skip style changes that it posts while starting transitions, to ensure that this doesn't lead to an infinite loop. This also depends on patch 16, which only consumes restyle data for the primary frame, to ensure that the animation restyles posted are processed properly. It also depends on patch 14, which makes us retain data on finished transitions, to avoid triggering extra transitions on descendants when both an ancestor and a descendant transition an inherited property, and the descendant does so faster. This fixes a known failure in layout/style/test/test_animations.html and test_animations_omta.html (as visible in the patch). I believe this is because this patch changes us to compute keyframe values for animations on top of a style context *with* animation data rather than one without, which means what we're computing them on top of changes each time. (The purpose of patch 3 was to avoid this in the case where avoiding it matters, i.e., implicit 0% and 100% keyframes.)
859 lines
26 KiB
C++
859 lines
26 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 "nsStyleContext.h"
|
|
#include "nsIFrame.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "mozilla/LookAndFeel.h"
|
|
#include "Layers.h"
|
|
#include "FrameLayerBuilder.h"
|
|
#include "nsDisplayList.h"
|
|
#include "mozilla/MemoryReporting.h"
|
|
#include "RestyleManager.h"
|
|
#include "nsRuleProcessorData.h"
|
|
#include "nsStyleSet.h"
|
|
#include "nsStyleChangeList.h"
|
|
|
|
|
|
using mozilla::layers::Layer;
|
|
using mozilla::dom::AnimationPlayer;
|
|
using mozilla::dom::Animation;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
namespace css {
|
|
|
|
CommonAnimationManager::CommonAnimationManager(nsPresContext *aPresContext)
|
|
: mPresContext(aPresContext)
|
|
, mIsObservingRefreshDriver(false)
|
|
{
|
|
PR_INIT_CLIST(&mElementCollections);
|
|
}
|
|
|
|
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(AnimationPlayerCollection*
|
|
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;
|
|
}
|
|
|
|
PR_INSERT_BEFORE(aCollection, &mElementCollections);
|
|
}
|
|
|
|
void
|
|
CommonAnimationManager::RemoveAllElementCollections()
|
|
{
|
|
while (!PR_CLIST_IS_EMPTY(&mElementCollections)) {
|
|
AnimationPlayerCollection* head =
|
|
static_cast<AnimationPlayerCollection*>(
|
|
PR_LIST_HEAD(&mElementCollections));
|
|
head->Destroy();
|
|
}
|
|
}
|
|
|
|
void
|
|
CommonAnimationManager::CheckNeedsRefresh()
|
|
{
|
|
for (PRCList *l = PR_LIST_HEAD(&mElementCollections);
|
|
l != &mElementCollections;
|
|
l = PR_NEXT_LINK(l)) {
|
|
if (static_cast<AnimationPlayerCollection*>(l)->mNeedsRefreshes) {
|
|
if (!mIsObservingRefreshDriver) {
|
|
mPresContext->RefreshDriver()->AddRefreshObserver(this, Flush_Style);
|
|
mIsObservingRefreshDriver = true;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (mIsObservingRefreshDriver) {
|
|
mIsObservingRefreshDriver = false;
|
|
mPresContext->RefreshDriver()->RemoveRefreshObserver(this, Flush_Style);
|
|
}
|
|
}
|
|
|
|
AnimationPlayerCollection*
|
|
CommonAnimationManager::GetAnimationsForCompositor(nsIContent* aContent,
|
|
nsIAtom* aElementProperty,
|
|
nsCSSProperty aProperty)
|
|
{
|
|
if (!aContent->MayHaveAnimations())
|
|
return nullptr;
|
|
AnimationPlayerCollection* collection =
|
|
static_cast<AnimationPlayerCollection*>(
|
|
aContent->GetProperty(aElementProperty));
|
|
if (!collection ||
|
|
!collection->HasAnimationOfProperty(aProperty) ||
|
|
!collection->CanPerformOnCompositorThread(
|
|
AnimationPlayerCollection::CanAnimate_AllowPartial)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// This animation can be done on the compositor.
|
|
// Mark the frame as active, in case we are able to throttle this animation.
|
|
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(collection->mElement);
|
|
if (frame) {
|
|
const auto& info = sLayerAnimationInfo;
|
|
for (size_t i = 0; i < ArrayLength(info); i++) {
|
|
if (aProperty == info[i].mProperty) {
|
|
ActiveLayerTracker::NotifyAnimated(frame, aProperty);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return collection;
|
|
}
|
|
|
|
/*
|
|
* nsISupports implementation
|
|
*/
|
|
|
|
NS_IMPL_ISUPPORTS(CommonAnimationManager, nsIStyleRuleProcessor)
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
}
|
|
|
|
/* 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();
|
|
|
|
PRCList* next = PR_LIST_HEAD(&mElementCollections);
|
|
while (next != &mElementCollections) {
|
|
AnimationPlayerCollection* collection =
|
|
static_cast<AnimationPlayerCollection*>(next);
|
|
next = PR_NEXT_LINK(next);
|
|
|
|
collection->EnsureStyleRuleFor(now, EnsureStyleRule_IsNotThrottled);
|
|
|
|
dom::Element* elementToRestyle = collection->GetElementToRestyle();
|
|
if (elementToRestyle) {
|
|
nsRestyleHint rshint = collection->IsForTransitions()
|
|
? eRestyle_CSSTransitions : eRestyle_CSSAnimations;
|
|
aTracker.AddPendingRestyle(elementToRestyle, rshint, nsChangeHint(0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CommonAnimationManager::NotifyCollectionUpdated(AnimationPlayerCollection&
|
|
aCollection)
|
|
{
|
|
CheckNeedsRefresh();
|
|
mPresContext->ClearLastStyleUpdateForAllAnimations();
|
|
mPresContext->RestyleManager()->IncrementAnimationGeneration();
|
|
aCollection.UpdateAnimationGeneration(mPresContext);
|
|
aCollection.PostRestyleForAnimation(mPresContext);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
AnimationPlayerCollection*
|
|
CommonAnimationManager::GetAnimationPlayers(dom::Element *aElement,
|
|
nsCSSPseudoElements::Type aPseudoType,
|
|
bool aCreateIfNeeded)
|
|
{
|
|
if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementCollections)) {
|
|
// 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;
|
|
}
|
|
AnimationPlayerCollection* collection =
|
|
static_cast<AnimationPlayerCollection*>(aElement->GetProperty(propName));
|
|
if (!collection && aCreateIfNeeded) {
|
|
// FIXME: Consider arena-allocating?
|
|
collection =
|
|
new AnimationPlayerCollection(aElement, propName, this);
|
|
nsresult rv =
|
|
aElement->SetProperty(propName, collection,
|
|
&AnimationPlayerCollection::PropertyDtor, false);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("SetProperty failed");
|
|
delete collection;
|
|
return nullptr;
|
|
}
|
|
if (propName == nsGkAtoms::animationsProperty ||
|
|
propName == nsGkAtoms::transitionsProperty) {
|
|
aElement->SetMayHaveAnimations();
|
|
}
|
|
|
|
AddElementCollection(collection);
|
|
}
|
|
|
|
return collection;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
AnimationPlayerCollection* collection =
|
|
GetAnimationPlayers(aElement, aPseudoType, false);
|
|
if (!collection) {
|
|
return nullptr;
|
|
}
|
|
|
|
RestyleManager* restyleManager = mPresContext->RestyleManager();
|
|
if (restyleManager->SkipAnimationRules()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Animations should already be refreshed, but transitions may not be.
|
|
// Note that this is temporary, we would like both animations and transitions
|
|
// to both be refreshed by this point.
|
|
if (IsAnimationManager()) {
|
|
NS_WARN_IF_FALSE(!collection->mNeedsRefreshes ||
|
|
collection->mStyleRuleRefreshTime ==
|
|
mPresContext->RefreshDriver()->MostRecentRefresh(),
|
|
"should already have refreshed style rule");
|
|
} else {
|
|
// FIXME: Remove this assignment. See bug 1061364.
|
|
collection->mNeedsRefreshes = true;
|
|
collection->EnsureStyleRuleFor(
|
|
mPresContext->RefreshDriver()->MostRecentRefresh(),
|
|
EnsureStyleRule_IsNotThrottled);
|
|
}
|
|
|
|
return collection->mStyleRule;
|
|
}
|
|
|
|
/* static */ const CommonAnimationManager::LayerAnimationRecord
|
|
CommonAnimationManager::sLayerAnimationInfo[] =
|
|
{ { eCSSProperty_transform,
|
|
nsDisplayItem::TYPE_TRANSFORM,
|
|
nsChangeHint_UpdateTransformLayer },
|
|
{ eCSSProperty_opacity,
|
|
nsDisplayItem::TYPE_OPACITY,
|
|
nsChangeHint_UpdateOpacityLayer } };
|
|
|
|
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.
|
|
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
|
|
|
|
} /* end sub-namespace css */
|
|
|
|
bool
|
|
AnimationPlayerCollection::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;
|
|
}
|
|
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
|
|
AnimationPlayerCollection::IsCompositorAnimationDisabledForFrame(
|
|
nsIFrame* aFrame)
|
|
{
|
|
void* prop = aFrame->Properties().Get(nsIFrame::RefusedAsyncAnimation());
|
|
return bool(reinterpret_cast<intptr_t>(prop));
|
|
}
|
|
|
|
bool
|
|
AnimationPlayerCollection::CanPerformOnCompositorThread(
|
|
CanAnimateFlags aFlags) const
|
|
{
|
|
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
|
|
if (!frame) {
|
|
return false;
|
|
}
|
|
|
|
if (mElementProperty != nsGkAtoms::transitionsProperty &&
|
|
mElementProperty != nsGkAtoms::animationsProperty) {
|
|
if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
|
|
nsCString message;
|
|
message.AppendLiteral("Gecko bug: Async animation of pseudoelements"
|
|
" not supported. See bug 771367 (");
|
|
message.Append(nsAtomCString(mElementProperty));
|
|
message.Append(")");
|
|
LogAsyncAnimationFailure(message, mElement);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
const AnimationPlayer* player = mPlayers[playerIdx];
|
|
if (!player->IsRunning() || !player->GetSource()) {
|
|
continue;
|
|
}
|
|
const Animation* anim = player->GetSource();
|
|
for (size_t propIdx = 0, propEnd = anim->Properties().Length();
|
|
propIdx != propEnd; ++propIdx) {
|
|
if (IsGeometricProperty(anim->Properties()[propIdx].mProperty)) {
|
|
aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool existsProperty = false;
|
|
for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
const AnimationPlayer* player = mPlayers[playerIdx];
|
|
if (!player->IsRunning() || !player->GetSource()) {
|
|
continue;
|
|
}
|
|
|
|
const Animation* anim = player->GetSource();
|
|
existsProperty = existsProperty || anim->Properties().Length() > 0;
|
|
|
|
for (size_t propIdx = 0, propEnd = anim->Properties().Length();
|
|
propIdx != propEnd; ++propIdx) {
|
|
const AnimationProperty& prop = anim->Properties()[propIdx];
|
|
if (!CanAnimatePropertyOnCompositor(mElement,
|
|
prop.mProperty,
|
|
aFlags) ||
|
|
IsCompositorAnimationDisabledForFrame(frame)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No properties to animate
|
|
if (!existsProperty) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
AnimationPlayerCollection::HasAnimationOfProperty(
|
|
nsCSSProperty aProperty) const
|
|
{
|
|
for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
const Animation* anim = mPlayers[playerIdx]->GetSource();
|
|
if (anim && anim->HasAnimationOfProperty(aProperty) &&
|
|
!anim->IsFinishedTransition()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
mozilla::dom::Element*
|
|
AnimationPlayerCollection::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();
|
|
}
|
|
|
|
void
|
|
AnimationPlayerCollection::NotifyPlayerUpdated()
|
|
{
|
|
// On the next flush, force us to update the style rule
|
|
mNeedsRefreshes = true;
|
|
mStyleRuleRefreshTime = TimeStamp();
|
|
|
|
mManager->NotifyCollectionUpdated(*this);
|
|
}
|
|
|
|
/* static */ void
|
|
AnimationPlayerCollection::LogAsyncAnimationFailure(nsCString& aMessage,
|
|
const nsIContent* aContent)
|
|
{
|
|
if (aContent) {
|
|
aMessage.AppendLiteral(" [");
|
|
aMessage.Append(nsAtomCString(aContent->Tag()));
|
|
|
|
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
|
|
AnimationPlayerCollection::PropertyDtor(void *aObject, nsIAtom *aPropertyName,
|
|
void *aPropertyValue, void *aData)
|
|
{
|
|
AnimationPlayerCollection* collection =
|
|
static_cast<AnimationPlayerCollection*>(aPropertyValue);
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(!collection->mCalledPropertyDtor, "can't call dtor twice");
|
|
collection->mCalledPropertyDtor = true;
|
|
#endif
|
|
delete collection;
|
|
}
|
|
|
|
void
|
|
AnimationPlayerCollection::Tick()
|
|
{
|
|
for (size_t playerIdx = 0, playerEnd = mPlayers.Length();
|
|
playerIdx != playerEnd; playerIdx++) {
|
|
mPlayers[playerIdx]->Tick();
|
|
}
|
|
}
|
|
|
|
void
|
|
AnimationPlayerCollection::EnsureStyleRuleFor(TimeStamp aRefreshTime,
|
|
EnsureStyleRuleFlags aFlags)
|
|
{
|
|
if (!mNeedsRefreshes) {
|
|
mStyleRuleRefreshTime = aRefreshTime;
|
|
return;
|
|
}
|
|
|
|
// If we're performing animations on the compositor thread, then we can skip
|
|
// most of the work in this method. But even if we are throttled, then we
|
|
// have to do the work if an animation is ending in order to get correct end
|
|
// of animation behavior (the styles of the animation disappear, or the fill
|
|
// mode behavior). CanThrottle returns false for any finishing animations
|
|
// so we can force style recalculation in that case.
|
|
if (aFlags == EnsureStyleRule_IsThrottled) {
|
|
for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
if (!mPlayers[playerIdx]->CanThrottle()) {
|
|
aFlags = EnsureStyleRule_IsNotThrottled;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aFlags == EnsureStyleRule_IsThrottled) {
|
|
return;
|
|
}
|
|
|
|
// mStyleRule may be null and valid, if we have no style to apply.
|
|
if (mStyleRuleRefreshTime.IsNull() ||
|
|
mStyleRuleRefreshTime != aRefreshTime) {
|
|
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 playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
mPlayers[playerIdx]->ComposeStyle(mStyleRule, properties,
|
|
mNeedsRefreshes);
|
|
}
|
|
}
|
|
|
|
mManager->CheckNeedsRefresh();
|
|
}
|
|
|
|
bool
|
|
AnimationPlayerCollection::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;
|
|
}
|
|
|
|
// If the nearest scrollable ancestor has overflow:hidden,
|
|
// we don't care about overflow.
|
|
nsIScrollableFrame* scrollable = nsLayoutUtils::GetNearestScrollableFrame(
|
|
nsLayoutUtils::GetStyleFrame(mElement));
|
|
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
|
|
AnimationPlayerCollection::CanThrottleAnimation(TimeStamp aTime)
|
|
{
|
|
nsIFrame* frame = nsLayoutUtils::GetStyleFrame(mElement);
|
|
if (!frame) {
|
|
return false;
|
|
}
|
|
|
|
|
|
const auto& info = css::CommonAnimationManager::sLayerAnimationInfo;
|
|
for (size_t i = 0; i < ArrayLength(info); i++) {
|
|
auto record = info[i];
|
|
if (!HasAnimationOfProperty(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
|
|
AnimationPlayerCollection::UpdateAnimationGeneration(
|
|
nsPresContext* aPresContext)
|
|
{
|
|
mAnimationGeneration =
|
|
aPresContext->RestyleManager()->GetAnimationGeneration();
|
|
}
|
|
|
|
void
|
|
AnimationPlayerCollection::UpdateCheckGeneration(
|
|
nsPresContext* aPresContext)
|
|
{
|
|
mCheckGeneration =
|
|
aPresContext->RestyleManager()->GetAnimationGeneration();
|
|
}
|
|
|
|
bool
|
|
AnimationPlayerCollection::HasCurrentAnimations() const
|
|
{
|
|
for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
if (mPlayers[playerIdx]->HasCurrentSource()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
AnimationPlayerCollection::HasCurrentAnimationsForProperty(nsCSSProperty
|
|
aProperty) const
|
|
{
|
|
for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
|
|
const Animation* anim = mPlayers[playerIdx]->GetSource();
|
|
if (anim && anim->IsCurrent() && anim->HasAnimationOfProperty(aProperty)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
}
|