Bug 1025709 part 4 - Move EnsureStyleRuleFor from ElementTransitions and ElementAnimations to CommonElementAnimationData; r=heycam

Both ElementAnimations and ElementTransitions have an EnsureStyleRuleFor method.
The ElementAnimations version is a more general of the ElementTransitions one
with the exception that the ElementTransitions version checks for finished
transitions. This patch moves the code from ElementAnimations to
CommonElementAnimationData with one minor change: adding the checks for finished
transitions. The ElementTransitions version is removed.

Since the ElementAnimations version contains a second parameter, aIsThrottled,
callers of ElementTransitions must include this extra parameter. In
a subsequent patch we add an enum for this parameter to make call sites easier
to read.

The ElementAnimations version also sets the mNeedsRefreshes member so at the
same time we move mNeedsRefreshes to CommonElementAnimationData. Furthermore,
since the ElementAnimations version which we have adopted returns early if
mNeedsRefreshes is false, this patch ensures that when we call
EnsureStyleRuleFor from ElementTransitions::WalkTransitionRule, we set
mNeedsRefreshes to true first.

Another difference to account for is that the ElementTransitions version of
EnsureStyleRuleFor *always* sets mStyleRule (even if it doesn't add anything to
it) where as the ElementAnimations version only creates the rule when necessary
so we need to add a check to ElementTransitions::WalkTransitionRule that
mStyleRule is actually set before using it.
This commit is contained in:
Brian Birtles 2014-06-20 12:39:24 +09:00
parent 77f5401b11
commit 5e704e1b3d
6 changed files with 185 additions and 208 deletions

View File

@ -9,6 +9,7 @@
#include "gfxPlatform.h" #include "gfxPlatform.h"
#include "nsRuleData.h" #include "nsRuleData.h"
#include "nsCSSPropertySet.h"
#include "nsCSSValue.h" #include "nsCSSValue.h"
#include "nsStyleContext.h" #include "nsStyleContext.h"
#include "nsIFrame.h" #include "nsIFrame.h"
@ -241,7 +242,8 @@ CommonAnimationManager::UpdateThrottledStyle(dom::Element* aElement,
NS_ASSERTION(et, NS_ASSERTION(et,
"Rule has level eTransitionSheet without transition on manager"); "Rule has level eTransitionSheet without transition on manager");
et->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh()); et->EnsureStyleRuleFor(
mPresContext->RefreshDriver()->MostRecentRefresh(), false);
curRule.mRule = et->mStyleRule; curRule.mRule = et->mStyleRule;
} else { } else {
curRule.mRule = ruleNode->GetRule(); curRule.mRule = ruleNode->GetRule();
@ -612,6 +614,169 @@ CommonElementAnimationData::LogAsyncAnimationFailure(nsCString& aMessage,
printf_stderr(aMessage.get()); printf_stderr(aMessage.get());
} }
void
CommonElementAnimationData::EnsureStyleRuleFor(TimeStamp aRefreshTime,
bool aIsThrottled)
{
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 behaviour (the styles of the animation disappear, or the fill
// mode behaviour). This loop checks for any finishing animations and forces
// the style recalculation if we find any.
if (aIsThrottled) {
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
ElementAnimation* anim = mAnimations[animIdx];
// Skip finished transitions or animations whose @keyframes rule
// is empty.
if (anim->IsFinishedTransition() || anim->mProperties.IsEmpty()) {
continue;
}
// The GetLocalTimeAt() call here handles pausing. But:
// FIXME: avoid recalculating every time when paused.
TimeDuration localTime = anim->GetLocalTimeAt(aRefreshTime);
ComputedTiming computedTiming =
ElementAnimation::GetComputedTimingAt(localTime, anim->mTiming);
// XXX We shouldn't really be using mLastNotification as a general
// indicator that the animation has finished, it should be reserved for
// events. If we use it differently in the future this use might need
// changing.
if (!anim->mIsRunningOnCompositor ||
(computedTiming.mPhase == ComputedTiming::AnimationPhase_After &&
anim->mLastNotification != ElementAnimation::LAST_NOTIFICATION_END))
{
aIsThrottled = false;
break;
}
}
}
if (aIsThrottled) {
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;
// FIXME(spec): assume that properties in higher animations override
// those in lower ones.
// Therefore, we iterate from last animation to first.
nsCSSPropertySet properties;
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
ElementAnimation* anim = mAnimations[animIdx];
if (anim->IsFinishedTransition()) {
continue;
}
// The GetLocalTimeAt() call here handles pausing. But:
// FIXME: avoid recalculating every time when paused.
TimeDuration localTime = anim->GetLocalTimeAt(aRefreshTime);
ComputedTiming computedTiming =
ElementAnimation::GetComputedTimingAt(localTime, anim->mTiming);
if ((computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) &&
!anim->IsPaused()) {
mNeedsRefreshes = true;
}
// If the time fraction is null, we don't have fill data for the current
// time so we shouldn't animate.
if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
continue;
}
NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction &&
computedTiming.mTimeFraction <= 1.0,
"timing fraction should be in [0-1]");
for (uint32_t propIdx = 0, propEnd = anim->mProperties.Length();
propIdx != propEnd; ++propIdx)
{
const AnimationProperty &prop = anim->mProperties[propIdx];
NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
"incorrect first from key");
NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
== 1.0,
"incorrect last to key");
if (properties.HasProperty(prop.mProperty)) {
// A later animation already set this property.
continue;
}
properties.AddProperty(prop.mProperty);
NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
"property should not be in animations if it "
"has no segments");
// FIXME: Maybe cache the current segment?
const AnimationPropertySegment *segment = prop.mSegments.Elements(),
*segmentEnd = segment + prop.mSegments.Length();
while (segment->mToKey < computedTiming.mTimeFraction) {
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
"incorrect keys");
++segment;
if (segment == segmentEnd) {
NS_ABORT_IF_FALSE(false, "incorrect time fraction");
break; // in order to continue in outer loop (just below)
}
NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
"incorrect keys");
}
if (segment == segmentEnd) {
continue;
}
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
"incorrect keys");
NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
size_t(segment - prop.mSegments.Elements()) <
prop.mSegments.Length(),
"out of array bounds");
if (!mStyleRule) {
// Allocate the style rule now that we know we have animation data.
mStyleRule = new css::AnimValuesStyleRule();
}
double positionInSegment =
(computedTiming.mTimeFraction - segment->mFromKey) /
(segment->mToKey - segment->mFromKey);
double valuePosition =
segment->mTimingFunction.GetValue(positionInSegment);
nsStyleAnimation::Value *val =
mStyleRule->AddEmptyValue(prop.mProperty);
#ifdef DEBUG
bool result =
#endif
nsStyleAnimation::Interpolate(prop.mProperty,
segment->mFromValue, segment->mToValue,
valuePosition, *val);
NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
}
}
}
}
bool bool
CommonElementAnimationData::CanThrottleTransformChanges(TimeStamp aTime) CommonElementAnimationData::CanThrottleTransformChanges(TimeStamp aTime)
{ {

View File

@ -398,6 +398,7 @@ struct CommonElementAnimationData : public PRCList
, mManager(aManager) , mManager(aManager)
, mAnimationGeneration(0) , mAnimationGeneration(0)
, mFlushGeneration(aNow) , mFlushGeneration(aNow)
, mNeedsRefreshes(true)
#ifdef DEBUG #ifdef DEBUG
, mCalledPropertyDtor(false) , mCalledPropertyDtor(false)
#endif #endif
@ -420,6 +421,12 @@ struct CommonElementAnimationData : public PRCList
mElement->DeleteProperty(mElementProperty); mElement->DeleteProperty(mElementProperty);
} }
// This updates mNeedsRefreshes so the caller may need to check
// for changes to values (for example, nsAnimationManager provides
// CheckNeedsRefresh to register or unregister from observing the refresh
// driver when this value changes).
void EnsureStyleRuleFor(TimeStamp aRefreshTime, bool aIsThrottled);
bool CanThrottleTransformChanges(mozilla::TimeStamp aTime); bool CanThrottleTransformChanges(mozilla::TimeStamp aTime);
bool CanThrottleAnimation(mozilla::TimeStamp aTime); bool CanThrottleAnimation(mozilla::TimeStamp aTime);
@ -489,6 +496,11 @@ struct CommonElementAnimationData : public PRCList
// UpdateAllThrottledStyles. // UpdateAllThrottledStyles.
TimeStamp mFlushGeneration; TimeStamp mFlushGeneration;
// False when we know that our current style rule is valid
// indefinitely into the future (because all of our animations are
// either completed or paused). May be invalidated by a style change.
bool mNeedsRefreshes;
#ifdef DEBUG #ifdef DEBUG
bool mCalledPropertyDtor; bool mCalledPropertyDtor;
#endif #endif

View File

@ -30,8 +30,7 @@ ElementAnimations::ElementAnimations(mozilla::dom::Element *aElement,
nsAnimationManager *aAnimationManager, nsAnimationManager *aAnimationManager,
TimeStamp aNow) TimeStamp aNow)
: CommonElementAnimationData(aElement, aElementProperty, : CommonElementAnimationData(aElement, aElementProperty,
aAnimationManager, aNow), aAnimationManager, aNow)
mNeedsRefreshes(true)
{ {
} }
@ -49,163 +48,6 @@ ElementAnimationsPropertyDtor(void *aObject,
delete ea; delete ea;
} }
void
ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
bool aIsThrottled)
{
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 behaviour (the styles of the animation disappear, or the fill
// mode behaviour). This loop checks for any finishing animations and forces
// the style recalculation if we find any.
if (aIsThrottled) {
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
ElementAnimation* anim = mAnimations[animIdx];
if (anim->mProperties.IsEmpty()) {
// Empty @keyframes rule.
continue;
}
// The GetLocalTimeAt() call here handles pausing. But:
// FIXME: avoid recalculating every time when paused.
TimeDuration localTime = anim->GetLocalTimeAt(aRefreshTime);
ComputedTiming computedTiming =
ElementAnimation::GetComputedTimingAt(localTime, anim->mTiming);
// XXX We shouldn't really be using mLastNotification as a general
// indicator that the animation has finished, it should be reserved for
// events. If we use it differently in the future this use might need
// changing.
if (!anim->mIsRunningOnCompositor ||
(computedTiming.mPhase == ComputedTiming::AnimationPhase_After &&
anim->mLastNotification != ElementAnimation::LAST_NOTIFICATION_END))
{
aIsThrottled = false;
break;
}
}
}
if (aIsThrottled) {
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;
// FIXME(spec): assume that properties in higher animations override
// those in lower ones.
// Therefore, we iterate from last animation to first.
nsCSSPropertySet properties;
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
ElementAnimation* anim = mAnimations[animIdx];
// The GetLocalTimeAt() call here handles pausing. But:
// FIXME: avoid recalculating every time when paused.
TimeDuration localTime = anim->GetLocalTimeAt(aRefreshTime);
ComputedTiming computedTiming =
ElementAnimation::GetComputedTimingAt(localTime, anim->mTiming);
if ((computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) &&
!anim->IsPaused()) {
mNeedsRefreshes = true;
}
// If the time fraction is null, we don't have fill data for the current
// time so we shouldn't animate.
if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
continue;
}
NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction &&
computedTiming.mTimeFraction <= 1.0,
"timing fraction should be in [0-1]");
for (uint32_t propIdx = 0, propEnd = anim->mProperties.Length();
propIdx != propEnd; ++propIdx)
{
const AnimationProperty &prop = anim->mProperties[propIdx];
NS_ABORT_IF_FALSE(prop.mSegments[0].mFromKey == 0.0,
"incorrect first from key");
NS_ABORT_IF_FALSE(prop.mSegments[prop.mSegments.Length() - 1].mToKey
== 1.0,
"incorrect last to key");
if (properties.HasProperty(prop.mProperty)) {
// A later animation already set this property.
continue;
}
properties.AddProperty(prop.mProperty);
NS_ABORT_IF_FALSE(prop.mSegments.Length() > 0,
"property should not be in animations if it "
"has no segments");
// FIXME: Maybe cache the current segment?
const AnimationPropertySegment *segment = prop.mSegments.Elements(),
*segmentEnd = segment + prop.mSegments.Length();
while (segment->mToKey < computedTiming.mTimeFraction) {
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
"incorrect keys");
++segment;
if (segment == segmentEnd) {
NS_ABORT_IF_FALSE(false, "incorrect time fraction");
break; // in order to continue in outer loop (just below)
}
NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
"incorrect keys");
}
if (segment == segmentEnd) {
continue;
}
NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
"incorrect keys");
NS_ABORT_IF_FALSE(segment >= prop.mSegments.Elements() &&
size_t(segment - prop.mSegments.Elements()) <
prop.mSegments.Length(),
"out of array bounds");
if (!mStyleRule) {
// Allocate the style rule now that we know we have animation data.
mStyleRule = new css::AnimValuesStyleRule();
}
double positionInSegment =
(computedTiming.mTimeFraction - segment->mFromKey) /
(segment->mToKey - segment->mFromKey);
double valuePosition =
segment->mTimingFunction.GetValue(positionInSegment);
nsStyleAnimation::Value *val =
mStyleRule->AddEmptyValue(prop.mProperty);
#ifdef DEBUG
bool result =
#endif
nsStyleAnimation::Interpolate(prop.mProperty,
segment->mFromValue, segment->mToValue,
valuePosition, *val);
NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
}
}
}
}
void void
ElementAnimations::GetEventsAt(TimeStamp aRefreshTime, ElementAnimations::GetEventsAt(TimeStamp aRefreshTime,
EventArray& aEventsToDispatch) EventArray& aEventsToDispatch)

View File

@ -60,9 +60,6 @@ struct ElementAnimations MOZ_FINAL
ElementAnimations(mozilla::dom::Element *aElement, nsIAtom *aElementProperty, ElementAnimations(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
nsAnimationManager *aAnimationManager, TimeStamp aNow); nsAnimationManager *aAnimationManager, TimeStamp aNow);
// After calling this, be sure to call CheckNeedsRefresh on the animation
// manager afterwards.
void EnsureStyleRuleFor(TimeStamp aRefreshTime, bool aIsThrottled);
void GetEventsAt(TimeStamp aRefreshTime, EventArray &aEventsToDispatch); void GetEventsAt(TimeStamp aRefreshTime, EventArray &aEventsToDispatch);
bool IsForElement() const { // rather than for a pseudo-element bool IsForElement() const { // rather than for a pseudo-element
@ -99,11 +96,6 @@ struct ElementAnimations MOZ_FINAL
virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE; virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE;
virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE; virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE;
// False when we know that our current style rule is valid
// indefinitely into the future (because all of our animations are
// either completed or paused). May be invalidated by a style change.
bool mNeedsRefreshes;
}; };
class nsAnimationManager MOZ_FINAL class nsAnimationManager MOZ_FINAL

View File

@ -97,42 +97,6 @@ ElementTransitionsPropertyDtor(void *aObject,
delete et; delete et;
} }
void
ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
{
if (!mStyleRule || mStyleRuleRefreshTime != aRefreshTime) {
mStyleRule = new css::AnimValuesStyleRule();
mStyleRuleRefreshTime = aRefreshTime;
for (uint32_t i = 0, i_end = mAnimations.Length(); i < i_end; ++i)
{
ElementPropertyTransition* pt = mAnimations[i]->AsTransition();
if (pt->IsFinishedTransition()) {
continue;
}
MOZ_ASSERT(pt->mProperties.Length() == 1,
"Should have one animation property for a transition");
const AnimationProperty &prop = pt->mProperties[0];
nsStyleAnimation::Value *val = mStyleRule->AddEmptyValue(prop.mProperty);
double valuePortion = pt->ValuePortionFor(aRefreshTime);
MOZ_ASSERT(prop.mSegments.Length() == 1,
"Animation property should have one segment for a transition");
#ifdef DEBUG
bool ok =
#endif
nsStyleAnimation::Interpolate(prop.mProperty,
prop.mSegments[0].mFromValue,
prop.mSegments[0].mToValue,
valuePortion, *val);
NS_ABORT_IF_FALSE(ok, "could not interpolate values");
}
}
}
bool bool
ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const
{ {
@ -826,10 +790,14 @@ nsTransitionManager::WalkTransitionRule(ElementDependentRuleProcessorData* aData
return; return;
} }
et->mNeedsRefreshes = true;
et->EnsureStyleRuleFor( et->EnsureStyleRuleFor(
aData->mPresContext->RefreshDriver()->MostRecentRefresh()); aData->mPresContext->RefreshDriver()->MostRecentRefresh(),
false);
aData->mRuleWalker->Forward(et->mStyleRule); if (et->mStyleRule) {
aData->mRuleWalker->Forward(et->mStyleRule);
}
} }
/* virtual */ void /* virtual */ void

View File

@ -58,8 +58,6 @@ struct ElementTransitions MOZ_FINAL
nsTransitionManager *aTransitionManager, nsTransitionManager *aTransitionManager,
mozilla::TimeStamp aNow); mozilla::TimeStamp aNow);
void EnsureStyleRuleFor(mozilla::TimeStamp aRefreshTime);
virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE; virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE;
// If aFlags contains CanAnimate_AllowPartial, returns whether the // If aFlags contains CanAnimate_AllowPartial, returns whether the