Bug 768440 Part 2: Animate CSS Transitions on the compositor r=roc,dbaron

This commit is contained in:
David Zbarsky 2012-07-31 10:28:22 -07:00
parent 3328afd1c5
commit 8c734bee81
8 changed files with 261 additions and 124 deletions

View File

@ -11,6 +11,7 @@
#include "nsChangeHint.h"
#include "nsINode.h"
#include "nsIDocument.h" // for IsInHTMLDocument
#include "nsCSSProperty.h"
// Forward declarations
class nsIAtom;

View File

@ -39,6 +39,7 @@
#include "nsSVGClipPathFrame.h"
#include "sampler.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
#include "nsIViewManager.h"
#include "mozilla/StandardInteger.h"
@ -267,116 +268,147 @@ ToTimingFunction(css::ComputedTimingFunction& aCTF)
}
static void
AddTransformAnimations(ElementAnimations* ea, Layer* aLayer,
const nsPoint& aOrigin)
AddAnimationsForProperty(nsIFrame* aFrame, nsCSSProperty aProperty,
ElementAnimation* ea, Layer* aLayer,
AnimationData& aData)
{
if (!ea)
return;
NS_ASSERTION(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
nsIFrame* frame = ea->mElement->GetPrimaryFrame();
nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(frame);
nsStyleContext* styleContext = aFrame->GetStyleContext();
nsPresContext* presContext = aFrame->PresContext();
nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(aFrame);
float scale = nsDeviceContext::AppUnitsPerCSSPixel();
gfxPoint3D offsetToTransformOrigin =
nsDisplayTransform::GetDeltaToMozTransformOrigin(frame, scale, &bounds);
gfxPoint3D offsetToPerspectiveOrigin =
nsDisplayTransform::GetDeltaToMozPerspectiveOrigin(frame, scale);
nscoord perspective = 0.0;
nsStyleContext* parentStyleContext = frame->GetStyleContext()->GetParent();
if (parentStyleContext) {
const nsStyleDisplay* disp = parentStyleContext->GetStyleDisplay();
if (disp && disp->mChildPerspective.GetUnit() == eStyleUnit_Coord) {
perspective = disp->mChildPerspective.GetCoordValue();
}
}
for (PRUint32 animIdx = 0; animIdx < ea->mAnimations.Length(); animIdx++) {
ElementAnimation* anim = &ea->mAnimations[animIdx];
if (!anim->CanPerformOnCompositor(ea->mElement, TimeStamp::Now())) {
float iterations = ea->mIterationCount != NS_IEEEPositiveInfinity()
? ea->mIterationCount : -1;
for (PRUint32 propIdx = 0; propIdx < ea->mProperties.Length(); propIdx++) {
AnimationProperty* property = &ea->mProperties[propIdx];
InfallibleTArray<AnimationSegment> segments;
if (aProperty != property->mProperty) {
continue;
}
float iterations = anim->mIterationCount != NS_IEEEPositiveInfinity()
? anim->mIterationCount : -1;
for (PRUint32 propIdx = 0; propIdx < anim->mProperties.Length(); propIdx++) {
AnimationProperty* property = &anim->mProperties[propIdx];
InfallibleTArray<AnimationSegment> segments;
if (property->mProperty != eCSSProperty_transform) {
continue;
}
for (PRUint32 segIdx = 0; segIdx < property->mSegments.Length(); segIdx++) {
AnimationPropertySegment* segment = &property->mSegments[segIdx];
for (PRUint32 segIdx = 0; segIdx < property->mSegments.Length(); segIdx++) {
AnimationPropertySegment* segment = &property->mSegments[segIdx];
if (aProperty == eCSSProperty_transform) {
nsCSSValueList* list = segment->mFromValue.GetCSSValueListValue();
InfallibleTArray<TransformFunction> fromFunctions;
AddTransformFunctions(list, frame->GetStyleContext(),
frame->PresContext(), bounds,
AddTransformFunctions(list, styleContext,
presContext, bounds,
scale, fromFunctions);
list = segment->mToValue.GetCSSValueListValue();
InfallibleTArray<TransformFunction> toFunctions;
AddTransformFunctions(list, frame->GetStyleContext(),
frame->PresContext(), bounds,
AddTransformFunctions(list, styleContext,
presContext, bounds,
scale, toFunctions);
segments.AppendElement(AnimationSegment(fromFunctions, toFunctions,
segment->mFromKey, segment->mToKey,
ToTimingFunction(segment->mTimingFunction)));
}
if (segments.Length() == 0) {
continue;
}
aLayer->AddAnimation(Animation(anim->mStartTime,
anim->mIterationDuration,
segments,
iterations,
anim->mDirection,
TransformData(aOrigin, offsetToTransformOrigin,
offsetToPerspectiveOrigin,
bounds, perspective)));
}
}
}
static void
AddOpacityAnimations(ElementAnimations* ea, Layer* aLayer)
{
if (!ea)
return;
NS_ASSERTION(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
for (PRUint32 animIdx = 0; animIdx < ea->mAnimations.Length(); animIdx++) {
ElementAnimation* anim = &ea->mAnimations[animIdx];
float iterations = anim->mIterationCount != NS_IEEEPositiveInfinity()
? anim->mIterationCount : -1;
if (!anim->CanPerformOnCompositor(ea->mElement, TimeStamp::Now())) {
continue;
}
for (PRUint32 propIdx = 0; propIdx < anim->mProperties.Length(); propIdx++) {
AnimationProperty* property = &anim->mProperties[propIdx];
InfallibleTArray<AnimationSegment> segments;
if (property->mProperty != eCSSProperty_opacity) {
continue;
}
for (PRUint32 segIdx = 0; segIdx < property->mSegments.Length(); segIdx++) {
AnimationPropertySegment* segment = &property->mSegments[segIdx];
} else if (aProperty == eCSSProperty_opacity) {
segments.AppendElement(AnimationSegment(Opacity(segment->mFromValue.GetFloatValue()),
Opacity(segment->mToValue.GetFloatValue()),
segment->mFromKey,
segment->mToKey,
ToTimingFunction(segment->mTimingFunction)));
}
}
aLayer->AddAnimation(Animation(anim->mStartTime,
anim->mIterationDuration,
segments,
iterations,
anim->mDirection,
null_t()));
}
aLayer->AddAnimation(Animation(ea->mStartTime,
ea->mIterationDuration,
segments,
iterations,
ea->mDirection,
aData));
}
}
static void
AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayItem* aItem,
nsCSSProperty aProperty)
{
aLayer->ClearAnimations();
nsIFrame* frame = aItem->GetUnderlyingFrame();
nsIContent* aContent = frame->GetContent();
ElementTransitions* et =
nsTransitionManager::GetTransitionsForCompositor(aContent, aProperty);
ElementAnimations* ea =
nsAnimationManager::GetAnimationsForCompositor(aContent, aProperty);
if (!ea && !et) {
return;
}
mozilla::TimeStamp currentTime =
frame->PresContext()->RefreshDriver()->MostRecentRefresh();
AnimationData data;
if (aProperty == eCSSProperty_transform) {
nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(frame);
float scale = nsDeviceContext::AppUnitsPerCSSPixel();
gfxPoint3D offsetToTransformOrigin =
nsDisplayTransform::GetDeltaToMozTransformOrigin(frame, scale, &bounds);
gfxPoint3D offsetToPerspectiveOrigin =
nsDisplayTransform::GetDeltaToMozPerspectiveOrigin(frame, scale);
nscoord perspective = 0.0;
nsStyleContext* parentStyleContext = frame->GetStyleContext()->GetParent();
if (parentStyleContext) {
const nsStyleDisplay* disp = parentStyleContext->GetStyleDisplay();
if (disp && disp->mChildPerspective.GetUnit() == eStyleUnit_Coord) {
perspective = disp->mChildPerspective.GetCoordValue();
}
}
nsPoint origin = aItem->ToReferenceFrame();
data = TransformData(origin, offsetToTransformOrigin,
offsetToPerspectiveOrigin, bounds, perspective);
} else if (aProperty == eCSSProperty_opacity) {
data = null_t();
}
if (et) {
for (PRUint32 tranIdx = 0; tranIdx < et->mPropertyTransitions.Length(); tranIdx++) {
ElementPropertyTransition* pt = &et->mPropertyTransitions[tranIdx];
if (!pt->CanPerformOnCompositor(et->mElement, currentTime)) {
continue;
}
ElementAnimation anim;
anim.mIterationCount = 1;
anim.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
anim.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_NONE;
anim.mStartTime = pt->mStartTime;
anim.mIterationDuration = pt->mDuration;
AnimationProperty& prop = *anim.mProperties.AppendElement();
prop.mProperty = pt->mProperty;
AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
segment.mFromKey = 0;
segment.mToKey = 1;
segment.mFromValue = pt->mStartValue;
segment.mToValue = pt->mEndValue;
segment.mTimingFunction = pt->mTimingFunction;
AddAnimationsForProperty(frame, aProperty, &anim,
aLayer, data);
}
}
if (ea) {
for (PRUint32 animIdx = 0; animIdx < ea->mAnimations.Length(); animIdx++) {
ElementAnimation* anim = &ea->mAnimations[animIdx];
if (!anim->CanPerformOnCompositor(ea->mElement, currentTime)) {
continue;
}
AddAnimationsForProperty(frame, aProperty, anim,
aLayer, data);
}
}
}
nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
Mode aMode, bool aBuildCaret)
@ -2321,12 +2353,7 @@ nsDisplayOpacity::BuildLayer(nsDisplayListBuilder* aBuilder,
return nullptr;
container->SetOpacity(mFrame->GetStyleDisplay()->mOpacity);
container->ClearAnimations();
ElementAnimations* ea =
nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_opacity);
AddOpacityAnimations(ea, container);
AddAnimationsAndTransitionsToLayer(container, this, eCSSProperty_opacity);
return container.forget();
}
@ -2354,8 +2381,8 @@ nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
!IsItemTooSmallForActiveLayer(this))
return LAYER_ACTIVE;
if (mFrame->GetContent()) {
if (nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_opacity)) {
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_opacity)) {
return LAYER_ACTIVE;
}
}
@ -3338,12 +3365,7 @@ already_AddRefed<Layer> nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBu
container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_PRESERVE_3D);
}
container->ClearAnimations();
ElementAnimations* ea =
nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_transform);
AddTransformAnimations(ea, container, ToReferenceFrame());
AddAnimationsAndTransitionsToLayer(container, this, eCSSProperty_transform);
return container.forget();
}
@ -3359,8 +3381,8 @@ nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
if (!GetTransform(mFrame->PresContext()->AppUnitsPerDevPixel()).Is2D() || mFrame->Preserves3D())
return LAYER_ACTIVE;
if (mFrame->GetContent()) {
if (nsAnimationManager::GetAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_transform)) {
if (nsLayoutUtils::HasAnimationsForCompositor(mFrame->GetContent(),
eCSSProperty_transform)) {
return LAYER_ACTIVE;
}
}

View File

@ -108,10 +108,10 @@ public:
* display lists that we make.
*/
enum Mode {
PAINTING,
EVENT_DELIVERY,
PLUGIN_GEOMETRY,
OTHER
PAINTING,
EVENT_DELIVERY,
PLUGIN_GEOMETRY,
OTHER
};
nsDisplayListBuilder(nsIFrame* aReferenceFrame, Mode aMode, bool aBuildCaret);
~nsDisplayListBuilder();

View File

@ -83,6 +83,8 @@
#endif
#include "sampler.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
using namespace mozilla;
using namespace mozilla::layers;
@ -113,6 +115,32 @@ static ContentMap& GetContentMap() {
return *sContentMap;
}
bool
nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty)
{
if (!aContent->MayHaveAnimations())
return false;
ElementAnimations* animations =
static_cast<ElementAnimations*>(aContent->GetProperty(nsGkAtoms::animationsProperty));
if (animations) {
bool propertyMatches = animations->HasAnimationOfProperty(aProperty);
if (propertyMatches && animations->CanPerformOnCompositorThread()) {
return true;
}
}
ElementTransitions* transitions =
static_cast<ElementTransitions*>(aContent->GetProperty(nsGkAtoms::transitionsProperty));
if (transitions) {
bool propertyMatches = transitions->HasTransitionOfProperty(aProperty);
if (propertyMatches && transitions->CanPerformOnCompositorThread()) {
return true;
}
}
return false;
}
bool
nsLayoutUtils::Are3DTransformsEnabled()

View File

@ -1497,6 +1497,13 @@ public:
nsMallocSizeOfFun aMallocSizeOf,
bool clear);
/**
* Returns true if the content node has animations or transitions that can be
* performed on the compositor.
*/
static bool HasAnimationsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty);
/**
* Checks if CSS 3D transforms are currently enabled.
*/

View File

@ -93,6 +93,7 @@
#include "nsAbsoluteContainingBlock.h"
#include "nsFontInflationData.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/LookAndFeel.h"
@ -940,14 +941,16 @@ nsIFrame::IsTransformed() const
(GetStyleDisplay()->HasTransform() ||
IsSVGTransformed() ||
(mContent &&
nsAnimationManager::GetAnimationsForCompositor(mContent, eCSSProperty_transform))));
nsLayoutUtils::HasAnimationsForCompositor(mContent,
eCSSProperty_transform))));
}
bool
nsIFrame::HasOpacity() const
{
return GetStyleDisplay()->mOpacity < 1.0f || (mContent &&
nsAnimationManager::GetAnimationsForCompositor(mContent, eCSSProperty_opacity));
nsLayoutUtils::HasAnimationsForCompositor(mContent,
eCSSProperty_opacity));
}
bool
@ -1774,9 +1777,12 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
nsRect clipPropClip;
const nsStyleDisplay* disp = GetStyleDisplay();
// We can stop right away if this is a zero-opacity stacking context and
// we're painting.
if (disp->mOpacity == 0.0 && aBuilder->IsForPainting())
// we're painting, and we're not animating opacity.
if (disp->mOpacity == 0.0 && aBuilder->IsForPainting() &&
!nsLayoutUtils::HasAnimationsForCompositor(mContent,
eCSSProperty_opacity)) {
return NS_OK;
}
bool applyClipPropClipping =
ApplyClipPropClipping(aBuilder, disp, this, &clipPropClip);
@ -1791,7 +1797,7 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
// Trying to back-transform arbitrary rects gives us really weird results. I believe
// this is from points that lie beyond the vanishing point. As a workaround we transform t
// he overflow rect into screen space and compare in that coordinate system.
// Transform the overflow rect into screen space
nsRect overflow = GetVisualOverflowRectRelativeToSelf();
nsPoint offset = aBuilder->ToReferenceFrame(this);
@ -1921,7 +1927,6 @@ nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
// resultList was emptied
resultList.AppendToTop(item);
}
/* If there are any SVG effects, wrap the list up in an SVG effects item
* (which also handles CSS group opacity). Note that we create an SVG effects
* item even if resultList is empty, since a filter can produce graphical

View File

@ -22,6 +22,8 @@
#include "nsEventDispatcher.h"
#include "nsGUIEvent.h"
#include "mozilla/dom/Element.h"
#include "nsIFrame.h"
#include "nsCSSFrameConstructor.h"
using mozilla::TimeStamp;
using mozilla::TimeDuration;
@ -36,7 +38,6 @@ ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, nsIAtom
{
}
double
ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
{
@ -107,6 +108,41 @@ ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
}
}
bool
ElementPropertyTransition::CanPerformOnCompositor(mozilla::dom::Element* aElement,
TimeStamp aTime) const {
return css::CommonElementAnimationData::
CanAnimatePropertyOnCompositor(aElement, mProperty) && !IsRemovedSentinel() &&
mStartTime < aTime && aTime < mStartTime + mDuration;
}
bool
ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const
{
for (PRUint32 tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) {
if (aProperty == mPropertyTransitions[tranIdx].mProperty) {
return true;
}
}
return false;
}
bool
ElementTransitions::CanPerformOnCompositorThread() const
{
for (PRUint32 i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
const ElementPropertyTransition &pt = mPropertyTransitions[i];
if (pt.IsRemovedSentinel()) {
continue;
}
if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(mElement,
pt.mProperty)) {
return false;
}
}
return true;
}
/*****************************************************************************
* nsTransitionManager *
*****************************************************************************/
@ -298,10 +334,6 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
// rule.
nsRefPtr<css::AnimValuesStyleRule> coverRule = new css::AnimValuesStyleRule;
if (!coverRule) {
NS_WARNING("out of memory");
return nullptr;
}
nsTArray<ElementPropertyTransition> &pts = et->mPropertyTransitions;
for (PRUint32 i = 0, i_end = pts.Length(); i < i_end; ++i) {
@ -347,9 +379,19 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
pt.mStartValue) &&
ExtractComputedValueForTransition(aProperty, aNewStyleContext,
pt.mEndValue);
bool haveChange = pt.mStartValue != pt.mEndValue;
bool haveOMTA = false;
if (!aNewStyleContext->GetPseudoType()) {
ElementTransitions* et = nsTransitionManager::GetTransitions(aElement);
if (et) {
haveOMTA = et->CanPerformOnCompositorThread();
}
}
bool shouldAnimate =
haveValues &&
pt.mStartValue != pt.mEndValue &&
(haveChange || haveOMTA) &&
// Check that we can interpolate between these values
// (If this is ever a performance problem, we could add a
// CanInterpolate method, but it seems fine for now.)
@ -450,6 +492,7 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
// reduce positive delays.
if (delay < 0.0f)
delay *= valuePortion;
duration *= valuePortion;
pt.mStartForReversingTest = oldPT.mEndValue;
@ -461,7 +504,6 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
pt.mStartTime = mostRecentRefresh + TimeDuration::FromMilliseconds(delay);
pt.mDuration = TimeDuration::FromMilliseconds(duration);
pt.mTimingFunction.Init(tf);
if (!aElementTransitions) {
aElementTransitions =
GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
@ -495,6 +537,7 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
nsCSSPseudoElements::ePseudo_NotPseudoElement ?
eRestyle_Self : eRestyle_Subtree;
presContext->PresShell()->RestyleForAnimation(aElement, hint);
// XXXdz: invalidate the frame here, once animations are throttled.
*aStartedAny = true;
aWhichStarted->AddProperty(aProperty);
@ -535,6 +578,9 @@ nsTransitionManager::GetElementTransitions(dom::Element *aElement,
delete et;
return nullptr;
}
if (propName == nsGkAtoms::transitionsProperty) {
aElement->SetMayHaveAnimations();
}
AddElementData(et);
}
@ -688,8 +734,6 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
// completion. See comment below.
et->mPropertyTransitions.RemoveElementAt(i);
} else if (pt.mStartTime + pt.mDuration <= aTime) {
// This transition has completed.
// Fire transitionend events only for transitions on elements
// and not those on pseudo-elements, since we can't target an
// event at pseudo-elements.
@ -723,6 +767,9 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
eRestyle_Self : eRestyle_Subtree;
mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
// XXXdz: if we have started a transition since the last tick and are
// performing the transition off the main thread, we need to invalidate
// the frame once we start throttling animation ticks.
if (et->mPropertyTransitions.IsEmpty()) {
et->Destroy();

View File

@ -22,6 +22,8 @@ struct nsTransition;
struct ElementPropertyTransition
{
ElementPropertyTransition() {}
nsCSSProperty mProperty;
nsStyleAnimation::Value mStartValue, mEndValue;
mozilla::TimeStamp mStartTime; // actual start plus transition delay
@ -29,7 +31,7 @@ struct ElementPropertyTransition
// 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
@ -46,24 +48,27 @@ struct ElementPropertyTransition
// 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;
// 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 CanPerformOnCompositor(mozilla::dom::Element* aElement,
mozilla::TimeStamp aTime) const;
};
struct ElementTransitions : public mozilla::css::CommonElementAnimationData
{
ElementTransitions(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
@ -71,11 +76,13 @@ struct ElementTransitions : public mozilla::css::CommonElementAnimationData
void EnsureStyleRuleFor(mozilla::TimeStamp aRefreshTime);
bool HasTransitionOfProperty(nsCSSProperty aProperty) const;
// True if this animation can be performed on the compositor thread.
// virtual CanPerformOnCompositorThread() const;
bool CanPerformOnCompositorThread() const;
// Either zero or one for each CSS property:
nsTArray<ElementPropertyTransition> mPropertyTransitions;
// This style rule overrides style data with the currently
// transitioning value for an element that is executing a transition.
// It only matches when styling with animation. When we style without
@ -97,8 +104,28 @@ public:
{
}
static ElementTransitions* GetTransitions(nsIContent* aContent) {
return static_cast<ElementTransitions*>
(aContent->GetProperty(nsGkAtoms::transitionsProperty));
}
static ElementTransitions*
GetTransitionsForCompositor(nsIContent* aContent,
nsCSSProperty aProperty)
{
if (!aContent->MayHaveAnimations())
return nullptr;
ElementTransitions* transitions = GetTransitions(aContent);
if (!transitions ||
!transitions->HasTransitionOfProperty(aProperty) ||
!transitions->CanPerformOnCompositorThread()) {
return nullptr;
}
return transitions;
}
/**
* StyleContextChanged
* StyleContextChanged
*
* To be called from nsFrameManager::ReResolveStyleContext when the
* style of an element has changed, to initiate transitions from