gecko/layout/style/nsTransitionManager.h
L. David Baron 5edf98e9eb Bug 978712 - Prevent non-running transitions and animations (animations or transitions during their delay period, and animations after they finish) from repeatedly poking layer activity because we think we can run them on the compositor. r=heycam
This changes the behavior of the CanPerformOnCompositorThread methods of
both ElementAnimations and ElementTransitions to check that the
respective animations or transitions are actually running.  This is ok
because:
 - The main caller is nsLayoutUtils::HasAnimationsForCompositor, and all
   of its callers pretty clearly want the more restricted behavior (they're
   concerned with layer activity)
 - The only other callers of these functions are
   nsAnimationManager::FlushAnimations and
   nsTransitionManager::FlushTransitions (determining when to do
   throttling), nsAnimationManager::GetAnimationsForCompositor (whose
   only caller,
   nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer, also checks
   IsRunningAt).  I think these also all want or are fine with having
   the IsRunningAt check.

As to the actual changes:
 - In the animation manager, I think it's a mistake that
   ElementAnimation::IsRunningAt didn't already check
   mIterationDuration, since we throw out animations with a bad
   iteration-duration in ElementAnimations::EnsureStyleRuleFor.  So this
   makes that change as well.
 - In the transition manager, IsRunningAt already checks
   !IsRemovedSentinel().

I've confirmed in gdb on a device that this fixes the repeated
nsIFrame::SchedulePaint calls that were the symptom of this bug.

I believe this patch also makes it so that a short animation of a
property that can't be animated on the compositor doesn't prevent the
entire duration of the animation of a property that can from being
throttled (having the main thread style updates suppressed).
2014-03-06 22:08:57 -08:00

244 lines
10 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/. */
/* Code to start and animate CSS transitions. */
#ifndef nsTransitionManager_h_
#define nsTransitionManager_h_
#include "mozilla/Attributes.h"
#include "mozilla/MemoryReporting.h"
#include "AnimationCommon.h"
#include "nsCSSPseudoElements.h"
class nsStyleContext;
class nsPresContext;
class nsCSSPropertySet;
struct nsTransition;
struct ElementDependentRuleProcessorData;
/*****************************************************************************
* Per-Element data *
*****************************************************************************/
struct ElementPropertyTransition
{
ElementPropertyTransition()
: mIsRunningOnCompositor(false)
{}
nsCSSProperty mProperty;
nsStyleAnimation::Value mStartValue, mEndValue;
mozilla::TimeStamp mStartTime; // actual start plus transition delay
// data from the relevant nsTransition
mozilla::TimeDuration mDuration;
mozilla::css::ComputedTimingFunction mTimingFunction;
// This is the start value to be used for a check for whether a
// transition is being reversed. Normally the same as mStartValue,
// except when this transition started as the reversal of another
// in-progress transition. Needed so we can handle two reverses in a
// row.
nsStyleAnimation::Value mStartForReversingTest;
// Likewise, the portion (in value space) of the "full" reversed
// transition that we're actually covering. For example, if a :hover
// effect has a transition that moves the element 10px to the right
// (by changing 'left' from 0px to 10px), and the mouse moves in to
// the element (starting the transition) but then moves out after the
// transition has advanced 4px, the second transition (from 10px/4px
// to 0px) will have mReversePortion of 0.4. (If the mouse then moves
// in again when the transition is back to 2px, the mReversePortion
// for the third transition (from 0px/2px to 10px) will be 0.8.
double mReversePortion;
// true when the transition is running on the compositor. In particular,
// mIsRunningOnCompositor will be false if the transition has a delay and we
// are not yet at mStartTime, so there is no animation on the layer.
bool mIsRunningOnCompositor;
// Compute the portion of the *value* space that we should be through
// at the given time. (The input to the transition timing function
// has time units, the output has value units.)
double ValuePortionFor(mozilla::TimeStamp aRefreshTime) const;
bool IsRemovedSentinel() const
{
return mStartTime.IsNull();
}
void SetRemovedSentinel()
{
// assign the null time stamp
mStartTime = mozilla::TimeStamp();
}
bool IsRunningAt(mozilla::TimeStamp aTime) const;
};
struct ElementTransitions MOZ_FINAL
: public mozilla::css::CommonElementAnimationData
{
ElementTransitions(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
nsTransitionManager *aTransitionManager,
mozilla::TimeStamp aNow);
void EnsureStyleRuleFor(mozilla::TimeStamp aRefreshTime);
virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE;
// If aFlags contains CanAnimate_AllowPartial, returns whether the
// state of this element's transitions at the current refresh driver
// time contains transition data that can be done on the compositor
// thread. (This is useful for determining whether a layer should be
// active, or whether to send data to the layer.)
// If aFlags does not contain CanAnimate_AllowPartial, returns whether
// the state of this element's transitions at the current refresh driver
// time can be fully represented by data sent to the compositor.
// (This is useful for determining whether throttle the transition
// (suppress main-thread style updates).)
// Note that when CanPerformOnCompositorThread returns true, it also,
// as a side-effect, notifies the ActiveLayerTracker. FIXME: This
// should probably move to the relevant callers.
virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE;
// Either zero or one for each CSS property:
nsTArray<ElementPropertyTransition> mPropertyTransitions;
};
class nsTransitionManager MOZ_FINAL
: public mozilla::css::CommonAnimationManager
{
public:
nsTransitionManager(nsPresContext *aPresContext)
: mozilla::css::CommonAnimationManager(aPresContext)
{
}
static ElementTransitions* GetTransitions(nsIContent* aContent) {
return static_cast<ElementTransitions*>
(aContent->GetProperty(nsGkAtoms::transitionsProperty));
}
// Returns true if aContent or any of its ancestors has a transition.
static bool ContentOrAncestorHasTransition(nsIContent* aContent) {
do {
if (GetTransitions(aContent)) {
return true;
}
} while ((aContent = aContent->GetParent()));
return false;
}
typedef mozilla::css::CommonElementAnimationData CommonElementAnimationData;
static ElementTransitions*
GetTransitionsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty)
{
if (!aContent->MayHaveAnimations()) {
return nullptr;
}
ElementTransitions* transitions = GetTransitions(aContent);
if (!transitions ||
!transitions->HasAnimationOfProperty(aProperty) ||
!transitions->CanPerformOnCompositorThread(
CommonElementAnimationData::CanAnimate_AllowPartial)) {
return nullptr;
}
return transitions;
}
/**
* StyleContextChanged
*
* To be called from nsFrameManager::ReResolveStyleContext when the
* style of an element has changed, to initiate transitions from
* that style change. For style contexts with :before and :after
* pseudos, aElement is expected to be the generated before/after
* element.
*
* It may return a "cover rule" (see CoverTransitionStartStyleRule) to
* cover up some of the changes for the duration of the restyling of
* descendants. If it does, this function will take care of causing
* the necessary restyle afterwards, but the caller must restyle the
* element *again* with the original sequence of rules plus the
* returned cover rule as the most specific rule.
*/
already_AddRefed<nsIStyleRule>
StyleContextChanged(mozilla::dom::Element *aElement,
nsStyleContext *aOldStyleContext,
nsStyleContext *aNewStyleContext);
// nsIStyleRuleProcessor (parts)
virtual void RulesMatching(ElementRuleProcessorData* aData) MOZ_OVERRIDE;
virtual void RulesMatching(PseudoElementRuleProcessorData* aData) MOZ_OVERRIDE;
virtual void RulesMatching(AnonBoxRuleProcessorData* aData) MOZ_OVERRIDE;
#ifdef MOZ_XUL
virtual void RulesMatching(XULTreeRuleProcessorData* aData) MOZ_OVERRIDE;
#endif
virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
MOZ_MUST_OVERRIDE MOZ_OVERRIDE;
virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
MOZ_MUST_OVERRIDE MOZ_OVERRIDE;
// nsARefreshObserver
virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE;
void FlushTransitions(FlushFlags aFlags);
// Performs a 'mini-flush' to make styles from throttled transitions
// up-to-date prior to processing an unrelated style change, so that
// any transitions triggered by that style change produce correct
// results.
//
// In more detail: when we're able to run animations on the
// compositor, we sometimes "throttle" these animations by skipping
// updating style data on the main thread. However, whenever we
// process a normal (non-animation) style change, any changes in
// computed style on elements that have transition-* properties set
// may need to trigger new transitions; this process requires knowing
// both the old and new values of the property. To do this correctly,
// we need to have an up-to-date *old* value of the property on the
// primary frame. So the purpose of the mini-flush is to update the
// style for all throttled transitions and animations to the current
// animation state without making any other updates, so that when we
// process the queued style updates we'll have correct old data to
// compare against. When we do this, we don't bother touching frames
// other than primary frames.
void UpdateAllThrottledStyles();
ElementTransitions* GetElementTransitions(mozilla::dom::Element *aElement,
nsCSSPseudoElements::Type aPseudoType,
bool aCreateIfNeeded);
protected:
virtual void ElementDataRemoved() MOZ_OVERRIDE;
virtual void AddElementData(mozilla::css::CommonElementAnimationData* aData) MOZ_OVERRIDE;
private:
void ConsiderStartingTransition(nsCSSProperty aProperty,
const nsTransition& aTransition,
mozilla::dom::Element *aElement,
ElementTransitions *&aElementTransitions,
nsStyleContext *aOldStyleContext,
nsStyleContext *aNewStyleContext,
bool *aStartedAny,
nsCSSPropertySet *aWhichStarted);
void WalkTransitionRule(ElementDependentRuleProcessorData* aData,
nsCSSPseudoElements::Type aPseudoType);
// Update the animated styles of an element and its descendants.
// If the element has a transition, it is flushed back to its primary frame.
// If the element does not have a transition, then its style is reparented.
void UpdateThrottledStylesForSubtree(nsIContent* aContent,
nsStyleContext* aParentStyle,
nsStyleChangeList &aChangeList);
void UpdateAllThrottledStylesInternal();
};
#endif /* !defined(nsTransitionManager_h_) */