Bug 780692; throttle OMTA (rollup patch). r=dbaron,bz

--HG--
extra : rebase_source : 1207275df5c509ac1974e2b9333c738b995f9d5e
This commit is contained in:
Nicholas Cameron 2012-12-12 10:12:43 +13:00
parent 3ee3eaf9fe
commit c54f8d4754
27 changed files with 807 additions and 128 deletions

View File

@ -241,6 +241,7 @@ pref("dom.ipc.tabs.disabled", true);
pref("layers.offmainthreadcomposition.enabled", false);
pref("layers.offmainthreadcomposition.animate-opacity", false);
pref("layers.offmainthreadcomposition.animate-transform", false);
pref("layers.offmainthreadcomposition.throttle-animations", false);
pref("layers.async-video.enabled", false);
#else
pref("dom.ipc.tabs.disabled", false);
@ -248,6 +249,7 @@ pref("layers.offmainthreadcomposition.enabled", true);
pref("layers.acceleration.disabled", false);
pref("layers.offmainthreadcomposition.animate-opacity", true);
pref("layers.offmainthreadcomposition.animate-transform", true);
pref("layers.offmainthreadcomposition.throttle-animations", true);
pref("layers.async-video.enabled", true);
pref("layers.async-pan-zoom.enabled", true);
#endif

View File

@ -27,4 +27,17 @@ enum mozFlushType {
Flush_Display = 6 /* As above, plus flush painting */
};
namespace mozilla {
struct ChangesToFlush {
ChangesToFlush(mozFlushType aFlushType, bool aFlushAnimations)
: mFlushType(aFlushType)
, mFlushAnimations(aFlushAnimations)
{}
mozFlushType mFlushType;
bool mFlushAnimations;
};
}
#endif /* mozFlushType_h___ */

View File

@ -289,7 +289,8 @@ Layer::Layer(LayerManager* aManager, void* aImplData) :
mUseClipRect(false),
mUseTileSourceRect(false),
mIsFixedPosition(false),
mDebugColorIndex(0)
mDebugColorIndex(0),
mAnimationGeneration(0)
{}
Layer::~Layer()

View File

@ -804,6 +804,9 @@ public:
AnimationArray& GetAnimations() { return mAnimations; }
InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
/**
* DRAWING PHASE ONLY
*
@ -1108,6 +1111,9 @@ protected:
bool mIsFixedPosition;
gfxPoint mAnchor;
DebugOnly<uint32_t> mDebugColorIndex;
// If this layer is used for OMTA, then this counter is used to ensure we
// stay in sync with the animation manager
uint64_t mAnimationGeneration;
};
/**

View File

@ -818,7 +818,7 @@ CompositorParent::TransformShadowTree(TimeStamp aCurrentFrame)
// NB: we must sample animations *before* sampling pan/zoom
// transforms.
wantNextFrame |= SampleAnimations(root, mLastCompose);
wantNextFrame |= SampleAnimations(root, aCurrentFrame);
const FrameMetrics& metrics = container->GetFrameMetrics();
// We must apply the resolution scale before a pan/zoom transform, so we call

View File

@ -89,6 +89,8 @@
#include "nsCSSRenderingBorders.h"
#include "nsRenderingContext.h"
#include "nsStyleStructInlines.h"
#include "nsAnimationManager.h"
#include "nsTransitionManager.h"
#ifdef MOZ_XUL
#include "nsIRootBox.h"
@ -1397,6 +1399,7 @@ nsCSSFrameConstructor::nsCSSFrameConstructor(nsIDocument *aDocument,
, mInStyleRefresh(false)
, mHoverGeneration(0)
, mRebuildAllExtraHint(nsChangeHint(0))
, mAnimationGeneration(0)
, mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE |
ELEMENT_IS_POTENTIAL_RESTYLE_ROOT, this)
, mPendingAnimationRestyles(ELEMENT_HAS_PENDING_ANIMATION_RESTYLE |
@ -8238,9 +8241,12 @@ nsCSSFrameConstructor::ProcessRestyledFrames(nsStyleChangeList& aChangeList,
#ifdef DEBUG
// reget frame from content since it may have been regenerated...
if (changeData->mContent) {
nsIFrame* frame = changeData->mContent->GetPrimaryFrame();
if (frame) {
DebugVerifyStyleTree(frame);
if (!nsAnimationManager::ContentOrAncestorHasAnimation(changeData->mContent) &&
!nsTransitionManager::ContentOrAncestorHasTransition(changeData->mContent)) {
nsIFrame* frame = changeData->mContent->GetPrimaryFrame();
if (frame) {
DebugVerifyStyleTree(frame);
}
}
} else {
NS_WARNING("Unable to test style tree integrity -- no content node");
@ -12075,6 +12081,17 @@ nsCSSFrameConstructor::ProcessPendingRestyles()
"Nesting calls to ProcessPendingRestyles?");
presContext->SetProcessingRestyles(true);
// Before we process any restyles, we need to ensure that style
// resulting from any throttled animations (animations that we're
// running entirely on the compositor thread) is up-to-date, so that
// if any style changes we cause trigger transitions, we have the
// correct old style for starting the transition.
if (css::CommonAnimationManager::ThrottlingEnabled() &&
mPendingRestyles.Count() > 0) {
++mAnimationGeneration;
presContext->TransitionManager()->UpdateAllThrottledStyles();
}
mPendingRestyles.ProcessRestyles();
#ifdef DEBUG

View File

@ -232,6 +232,10 @@ public:
// as a result of a change to the :hover content state.
uint32_t GetHoverGeneration() const { return mHoverGeneration; }
// Get a counter that increments on every style change, that we use to
// track whether off-main-thread animations are up-to-date.
uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
// Note: It's the caller's responsibility to make sure to wrap a
// ProcessRestyledFrames call in a view update batch and a script blocker.
// This function does not call ProcessAttachedQueue() on the binding manager.
@ -302,6 +306,7 @@ public:
{
PostRestyleEventCommon(aElement, aRestyleHint, aMinChangeHint, true);
}
private:
/**
* Notify the frame constructor that an element needs to have its
@ -1886,6 +1891,10 @@ private:
nsCOMPtr<nsILayoutHistoryState> mTempFrameTreeState;
// The total number of animation flushes by this frame constructor.
// Used to keep the layer and animation manager in sync.
uint64_t mAnimationGeneration;
RestyleTracker mPendingRestyles;
RestyleTracker mPendingAnimationRestyles;
};

View File

@ -247,6 +247,18 @@ static void AddTransformFunctions(nsCSSValueList* aList,
aFunctions.AppendElement(TransformMatrix(matrix));
break;
}
case eCSSKeyword_interpolatematrix:
{
gfx3DMatrix matrix;
nsStyleTransformMatrix::ProcessInterpolateMatrix(matrix, array,
aContext,
aPresContext,
canStoreInRuleTree,
aBounds,
aAppUnitsPerPixel);
aFunctions.AppendElement(TransformMatrix(matrix));
break;
}
case eCSSKeyword_perspective:
{
aFunctions.AppendElement(Perspective(array->Item(1).GetFloatValue()));
@ -407,6 +419,7 @@ AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayListBuilder* aBuilder
AddAnimationsForProperty(frame, aProperty, &anim,
aLayer, data);
}
aLayer->SetAnimationGeneration(et->mAnimationGeneration);
}
if (ea) {
@ -419,6 +432,7 @@ AddAnimationsAndTransitionsToLayer(Layer* aLayer, nsDisplayListBuilder* aBuilder
AddAnimationsForProperty(frame, aProperty, anim,
aLayer, data);
}
aLayer->SetAnimationGeneration(ea->mAnimationGeneration);
}
}

View File

@ -118,10 +118,10 @@ typedef struct CapturingContentInfo {
nsIContent* mContent;
} CapturingContentInfo;
// 0d3bfc0e-661c-4e70-933e-98efc912a75b
// a43e26cd-9573-44c7-8fe5-859549eff814
#define NS_IPRESSHELL_IID \
{ 0x0d3bfc0e, 0x661c, 0x4e70, \
{ 0x93, 0x3e, 0x98, 0xef, 0xc9, 0x12, 0xa7, 0x5b } }
{ 0x13b031cb, 0x738a, 0x4e97, \
{ 0xb0, 0xca, 0x8b, 0x4b, 0x6c, 0xbb, 0xea, 0xa9 } }
// debug VerifyReflow flags
#define VERIFY_REFLOW_ON 0x01
@ -524,6 +524,7 @@ public:
* @param aType the type of notifications to flush
*/
virtual NS_HIDDEN_(void) FlushPendingNotifications(mozFlushType aType) = 0;
virtual NS_HIDDEN_(void) FlushPendingNotifications(mozilla::ChangesToFlush aType) = 0;
/**
* Callbacks will be called even if reflow itself fails for

View File

@ -91,8 +91,9 @@
#include "nsTransitionManager.h"
using namespace mozilla;
using namespace mozilla::layers;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::layers;
using namespace mozilla::layout;
#define FLEXBOX_ENABLED_PREF_NAME "layout.css.flexbox.enabled"
@ -177,31 +178,38 @@ FlexboxEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
}
#endif // MOZ_FLEXBOX
template <class AnimationsOrTransitions>
static bool
HasAnimationOrTransition(nsIContent* aContent,
nsIAtom* aAnimationProperty,
nsCSSProperty aProperty)
{
AnimationsOrTransitions* animations =
static_cast<AnimationsOrTransitions*>(aContent->GetProperty(aAnimationProperty));
if (animations) {
bool propertyMatches = animations->HasAnimationOfProperty(aProperty);
if (propertyMatches &&
animations->CanPerformOnCompositorThread(
CommonElementAnimationData::CanAnimate_AllowPartial)) {
return true;
}
}
return false;
}
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;
}
if (HasAnimationOrTransition<ElementAnimations>
(aContent, nsGkAtoms::animationsProperty, aProperty)) {
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;
return HasAnimationOrTransition<ElementTransitions>
(aContent, nsGkAtoms::transitionsProperty, aProperty);
}
bool

View File

@ -957,6 +957,10 @@ nsPresContext::Init(nsDeviceContext* aDeviceContext)
}
}
// Initialise refresh tick counters for OMTA
mLastStyleUpdateForAllAnimations =
mLastUpdateThrottledStyle = mRefreshDriver->MostRecentRefresh();
mLangService = do_GetService(NS_LANGUAGEATOMSERVICE_CONTRACTID);
// Register callbacks so we're notified when the preferences change

View File

@ -33,6 +33,7 @@
#include "mozilla/TimeStamp.h"
#include "prclist.h"
#include "Layers.h"
#include "nsRefreshDriver.h"
#ifdef IBMBIDI
class nsBidiPresUtils;
@ -66,7 +67,6 @@ struct nsFontFaceRuleContainer;
class nsObjectFrame;
class nsTransitionManager;
class nsAnimationManager;
class nsRefreshDriver;
class imgIContainer;
class nsIDOMMediaQueryList;
@ -654,6 +654,22 @@ public:
{
mDrawColorBackground = aCanDraw;
}
/**
* Getter and setter for OMTA time counters
*/
bool ThrottledStyleIsUpToDate() const {
return mLastUpdateThrottledStyle == mRefreshDriver->MostRecentRefresh();
}
void TickLastUpdateThrottledStyle() {
mLastUpdateThrottledStyle = mRefreshDriver->MostRecentRefresh();
}
bool StyleUpdateForAllAnimationsIsUpToDate() const {
return mLastStyleUpdateForAllAnimations == mRefreshDriver->MostRecentRefresh();
}
void TickLastStyleUpdateForAllAnimations() {
mLastStyleUpdateForAllAnimations = mRefreshDriver->MostRecentRefresh();
}
#ifdef IBMBIDI
/**
@ -971,6 +987,17 @@ public:
mUsesViewportUnits = aValue;
}
// true if there are OMTA transition updates for the current document which
// have been throttled, and therefore some style information may not be up
// to date
bool ExistThrottledUpdates() const {
return mExistThrottledUpdates;
}
void SetExistThrottledUpdates(bool aExistThrottledUpdates) {
mExistThrottledUpdates = aExistThrottledUpdates;
}
protected:
friend class nsRunnableMethod<nsPresContext>;
NS_HIDDEN_(void) ThemeChangedInternal();
@ -1182,6 +1209,8 @@ protected:
ScrollbarStyles mViewportStyleOverflow;
uint8_t mFocusRingWidth;
bool mExistThrottledUpdates;
uint16_t mImageAnimationMode;
uint16_t mImageAnimationModePref;
@ -1193,6 +1222,11 @@ protected:
mozilla::TimeStamp mReflowStartTime;
// last time animations/transition styles were flushed to their primary frames
mozilla::TimeStamp mLastUpdateThrottledStyle;
// last time we did a full style flush
mozilla::TimeStamp mLastStyleUpdateForAllAnimations;
unsigned mHasPendingInterrupt : 1;
unsigned mInterruptsEnabled : 1;
unsigned mUseDocumentFonts : 1;

View File

@ -179,6 +179,7 @@
#include "mozilla/css/ImageLoader.h"
#include "Layers.h"
#include "nsTransitionManager.h"
#include "LayerTreeInvalidation.h"
#include "nsAsyncDOMEvent.h"
@ -186,6 +187,7 @@
(nsIPresShell::SCROLL_OVERFLOW_HIDDEN | nsIPresShell::SCROLL_NO_PARENT_FRAMES)
using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::layers;
@ -3749,12 +3751,21 @@ PresShell::IsSafeToFlush() const
void
PresShell::FlushPendingNotifications(mozFlushType aType)
{
// by default, flush animations if aType >= Flush_Style
mozilla::ChangesToFlush flush(aType, aType >= Flush_Style);
FlushPendingNotifications(flush);
}
void
PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aFlush)
{
/**
* VERY IMPORTANT: If you add some sort of new flushing to this
* method, make sure to add the relevant SetNeedLayoutFlush or
* SetNeedStyleFlush calls on the document.
*/
mozFlushType flushType = aFlush.mFlushType;
#ifdef MOZ_ENABLE_PROFILER_SPS
static const char flushTypeNames[][20] = {
@ -3765,11 +3776,12 @@ PresShell::FlushPendingNotifications(mozFlushType aType)
"Layout",
"Display"
};
// Make sure that we don't miss things added to mozFlushType!
MOZ_ASSERT(static_cast<uint32_t>(aType) <= ArrayLength(flushTypeNames));
MOZ_ASSERT(static_cast<uint32_t>(flushType) <= ArrayLength(flushTypeNames));
SAMPLE_LABEL_PRINTF("layout", "Flush", "(Flush_%s)",
flushTypeNames[aType - 1]);
flushTypeNames[flushType - 1]);
#endif
#ifdef ACCESSIBILITY
@ -3782,7 +3794,7 @@ PresShell::FlushPendingNotifications(mozFlushType aType)
#endif
#endif
NS_ASSERTION(aType >= Flush_Frames, "Why did we get called?");
NS_ASSERTION(flushType >= Flush_Frames, "Why did we get called?");
bool isSafeToFlush = IsSafeToFlush();
@ -3815,7 +3827,7 @@ PresShell::FlushPendingNotifications(mozFlushType aType)
// filter to work). We only need external resources to be flushed when the
// main document is flushing >= Flush_Frames, so we flush external
// resources here instead of nsDocument::FlushPendingNotifications.
mDocument->FlushExternalResources(aType);
mDocument->FlushExternalResources(flushType);
// Force flushing of any pending content notifications that might have
// queued up while our event was pending. That will ensure that we don't
@ -3839,6 +3851,16 @@ PresShell::FlushPendingNotifications(mozFlushType aType)
mDocument->GetAnimationController()->FlushResampleRequests();
}
if (aFlush.mFlushAnimations &&
(!CommonAnimationManager::ThrottlingEnabled() ||
!mPresContext->StyleUpdateForAllAnimationsIsUpToDate())) {
mPresContext->AnimationManager()->
FlushAnimations(CommonAnimationManager::Cannot_Throttle);
mPresContext->TransitionManager()->
FlushTransitions(CommonAnimationManager::Cannot_Throttle);
mPresContext->TickLastStyleUpdateForAllAnimations();
}
// The FlushResampleRequests() above flushed style changes.
if (!mIsDestroying) {
nsAutoScriptBlocker scriptBlocker;
@ -3878,11 +3900,11 @@ PresShell::FlushPendingNotifications(mozFlushType aType)
// worry about them. They can't be triggered during reflow, so we should
// be good.
if (aType >= (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout) &&
if (flushType >= (mSuppressInterruptibleReflows ? Flush_Layout : Flush_InterruptibleLayout) &&
!mIsDestroying) {
mFrameConstructor->RecalcQuotesAndCounters();
mViewManager->FlushDelayedResize(true);
if (ProcessReflowCommands(aType < Flush_Layout) && mContentToScrollTo) {
if (ProcessReflowCommands(flushType < Flush_Layout) && mContentToScrollTo) {
// We didn't get interrupted. Go ahead and scroll to our content
DoScrollContentIntoView();
if (mContentToScrollTo) {
@ -3891,13 +3913,13 @@ PresShell::FlushPendingNotifications(mozFlushType aType)
}
}
} else if (!mIsDestroying && mSuppressInterruptibleReflows &&
aType == Flush_InterruptibleLayout) {
flushType == Flush_InterruptibleLayout) {
// We suppressed this flush, but the document thinks it doesn't
// need to flush anymore. Let it know what's really going on.
mDocument->SetNeedLayoutFlush();
}
if (aType >= Flush_Layout) {
if (flushType >= Flush_Layout) {
if (!mIsDestroying) {
mViewManager->UpdateWidgetGeometry();
}
@ -7114,7 +7136,7 @@ PresShell::WillPaint(bool aWillSendDidPaint)
// reflow being interspersed. Note that we _do_ allow this to be
// interruptible; if we can't do all the reflows it's better to flicker a bit
// than to freeze up.
FlushPendingNotifications(Flush_InterruptibleLayout);
FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout, false));
}
void

View File

@ -102,6 +102,7 @@ public:
virtual NS_HIDDEN_(void) CancelAllPendingReflows();
virtual NS_HIDDEN_(bool) IsSafeToFlush() const;
virtual NS_HIDDEN_(void) FlushPendingNotifications(mozFlushType aType);
virtual NS_HIDDEN_(void) FlushPendingNotifications(mozilla::ChangesToFlush aType);
/**
* Recreates the frames for a node

View File

@ -383,7 +383,7 @@ nsRefreshDriver::Notify(nsITimer *aTimer)
NS_ADDREF(shell);
mStyleFlushObservers.RemoveElement(shell);
shell->FrameConstructor()->mObservingRefreshDriver = false;
shell->FlushPendingNotifications(Flush_Style);
shell->FlushPendingNotifications(ChangesToFlush(Flush_Style, false));
NS_RELEASE(shell);
}
}
@ -403,7 +403,8 @@ nsRefreshDriver::Notify(nsITimer *aTimer)
mLayoutFlushObservers.RemoveElement(shell);
shell->mReflowScheduled = false;
shell->mSuppressInterruptibleReflows = false;
shell->FlushPendingNotifications(Flush_InterruptibleLayout);
shell->FlushPendingNotifications(ChangesToFlush(Flush_InterruptibleLayout,
false));
NS_RELEASE(shell);
}
}

View File

@ -470,6 +470,9 @@ public:
virtual nsPoint GetScrollPosition() const MOZ_OVERRIDE {
return mInner.GetScrollPosition();
}
virtual nsPoint GetLogicalScrollPosition() const MOZ_OVERRIDE {
return mInner.GetLogicalScrollPosition();
}
virtual nsRect GetScrollRange() const MOZ_OVERRIDE {
return mInner.GetScrollRange();
}
@ -720,6 +723,9 @@ public:
virtual nsPoint GetScrollPosition() const MOZ_OVERRIDE {
return mInner.GetScrollPosition();
}
virtual nsPoint GetLogicalScrollPosition() const MOZ_OVERRIDE {
return mInner.GetLogicalScrollPosition();
}
virtual nsRect GetScrollRange() const MOZ_OVERRIDE {
return mInner.GetScrollRange();
}

View File

@ -93,6 +93,10 @@ public:
* This will always be a multiple of device pixels.
*/
virtual nsPoint GetScrollPosition() const = 0;
/**
* As GetScrollPosition(), but uses the top-right as origin for RTL frames.
*/
virtual nsPoint GetLogicalScrollPosition() const = 0;
/**
* Get the area that must contain the scroll position. Typically
* (but not always, e.g. for RTL content) x and y will be 0, and

View File

@ -6,11 +6,19 @@
#include "gfxPlatform.h"
#include "AnimationCommon.h"
#include "nsRuleData.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSValue.h"
#include "nsStyleContext.h"
#include "nsIFrame.h"
#include "nsAnimationManager.h"
#include "nsLayoutUtils.h"
#include "mozilla/LookAndFeel.h"
#include "Layers.h"
#include "FrameLayerBuilder.h"
#include "nsDisplayList.h"
#include "mozilla/Preferences.h"
using namespace mozilla::layers;
namespace mozilla {
namespace css {
@ -151,6 +159,22 @@ CommonAnimationManager::ExtractComputedValueForTransition(
return result;
}
/* static */ bool
CommonAnimationManager::ThrottlingEnabled()
{
static bool sThrottlePref = false;
static bool sThrottlePrefCached = false;
if (!sThrottlePrefCached) {
Preferences::AddBoolVarCache(&sThrottlePref,
"layers.offmainthreadcomposition.throttle-animations", false);
sThrottlePrefCached = true;
}
return sThrottlePref;
}
NS_IMPL_ISUPPORTS1(AnimValuesStyleRule, nsIStyleRule)
/* virtual */ void
@ -243,7 +267,7 @@ ComputedTimingFunction::GetValue(double aPortion) const
bool
CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *aElement,
nsCSSProperty aProperty,
bool aHasGeometricProperties)
CanAnimateFlags aFlags)
{
bool shouldLog = nsLayoutUtils::IsAnimationLoggingEnabled();
if (shouldLog && !gfxPlatform::OffMainThreadCompositingEnabled()) {
@ -291,7 +315,7 @@ CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *a
}
return false;
}
if (aHasGeometricProperties) {
if (aFlags & CanAnimate_HasGeometricProperty) {
if (shouldLog) {
nsCString message;
message.AppendLiteral("Performance warning: Async animation of 'transform' not possible due to presence of geometric properties");
@ -307,7 +331,7 @@ CommonElementAnimationData::CanAnimatePropertyOnCompositor(const dom::Element *a
}
return enabled;
}
return true;
return aFlags & CanAnimate_AllowPartial;
}
/* static */ void
@ -329,5 +353,81 @@ CommonElementAnimationData::LogAsyncAnimationFailure(nsCString& aMessage,
printf_stderr(aMessage.get());
}
bool
CommonElementAnimationData::CanThrottleTransformChanges(TimeStamp aTime)
{
if (!CommonAnimationManager::ThrottlingEnabled()) {
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 ((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(mElement->GetPrimaryFrame());
if (!scrollable) {
return true;
}
nsPresContext::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
CommonElementAnimationData::CanThrottleAnimation(TimeStamp aTime)
{
nsIFrame* frame = mElement->GetPrimaryFrame();
if (!frame) {
return false;
}
bool hasTransform = HasAnimationOfProperty(eCSSProperty_transform);
bool hasOpacity = HasAnimationOfProperty(eCSSProperty_opacity);
if (hasOpacity) {
Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
frame, nsDisplayItem::TYPE_OPACITY);
if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) {
return false;
}
}
if (!hasTransform) {
return true;
}
Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
frame, nsDisplayItem::TYPE_TRANSFORM);
if (!layer || mAnimationGeneration > layer->GetAnimationGeneration()) {
return false;
}
return CanThrottleTransformChanges(aTime);
}
void
CommonElementAnimationData::UpdateAnimationGeneration(nsPresContext* aPresContext)
{
mAnimationGeneration =
aPresContext->PresShell()->FrameConstructor()->GetAnimationGeneration();
}
}
}

View File

@ -52,10 +52,16 @@ public:
*/
void Disconnect();
enum FlushFlags {
Can_Throttle,
Cannot_Throttle
};
static bool ExtractComputedValueForTransition(
nsCSSProperty aProperty,
nsStyleContext* aStyleContext,
nsStyleAnimation::Value& aComputedValue);
static bool ThrottlingEnabled();
protected:
friend struct CommonElementAnimationData; // for ElementDataRemoved
@ -151,10 +157,28 @@ struct CommonElementAnimationData : public PRCList
mElement->DeleteProperty(mElementProperty);
}
bool CanThrottleTransformChanges(mozilla::TimeStamp aTime);
bool CanThrottleAnimation(mozilla::TimeStamp aTime);
enum CanAnimateFlags {
// Testing for width, height, top, right, bottom, or left.
CanAnimate_HasGeometricProperty = 1,
// Allow the case where OMTA is allowed in general, but not for the
// specified property.
CanAnimate_AllowPartial = 2
};
static bool
CanAnimatePropertyOnCompositor(const dom::Element *aElement,
nsCSSProperty aProperty,
bool aHasGeometricProperties);
CanAnimateFlags aFlags);
// True if this animation can be performed on the compositor thread.
// Do not pass CanAnimate_AllowPartial to make sure that all properties of this
// animation are supported by the compositor.
virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const = 0;
virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const = 0;
static void LogAsyncAnimationFailure(nsCString& aMessage,
const nsIContent* aContent = nullptr);
@ -175,6 +199,16 @@ struct CommonElementAnimationData : public PRCList
// NOTE: If we don't need to apply any styles, mStyleRule will be
// null, but mStyleRuleRefreshTime will still be valid.
nsRefPtr<mozilla::css::AnimValuesStyleRule> mStyleRule;
// nsCSSFrameConstructor keeps track of the number of animation 'mini-flushes'
// (see nsTransitionManager::UpdateAllThrottledStyles()). mFlushCount is
// the last flush where a transition/animation changed. We keep a similar
// count on the corresponding layer so we can check that the layer is up to
// date with the animation manager.
uint64_t mAnimationGeneration;
// Update mFlushCount to nsCSSFrameConstructor's count
void UpdateAnimationGeneration(nsPresContext* aPresContext);
// The refresh time associated with mStyleRule.
TimeStamp mStyleRuleRefreshTime;

View File

@ -11,7 +11,6 @@
#include "nsStyleAnimation.h"
#include "nsSMILKeySpline.h"
#include "nsEventDispatcher.h"
#include "nsDisplayList.h"
#include "nsCSSFrameConstructor.h"
using namespace mozilla;
@ -60,9 +59,6 @@ ElementAnimations::GetPositionInIteration(TimeStamp aStartTime, TimeStamp aCurre
aAnimation->mLastNotification !=
ElementAnimation::LAST_NOTIFICATION_END) {
aAnimation->mLastNotification = ElementAnimation::LAST_NOTIFICATION_END;
// XXXdz: if this animation was done on the compositor, we should
// invalidate the frame and update style once we start throttling style
// updates.
AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END,
currentTimeDuration);
aEventsToDispatch->AppendElement(ei);
@ -140,7 +136,7 @@ ElementAnimations::GetPositionInIteration(TimeStamp aStartTime, TimeStamp aCurre
uint32_t message =
aAnimation->mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE
? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
// XXXdz: If this is a start, invalidate the frame here once we throttle animations.
aAnimation->mLastNotification = whichIteration;
AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message,
currentTimeDuration);
@ -152,10 +148,13 @@ ElementAnimations::GetPositionInIteration(TimeStamp aStartTime, TimeStamp aCurre
void
ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
EventArray& aEventsToDispatch)
EventArray& aEventsToDispatch,
bool aIsThrottled)
{
if (!mNeedsRefreshes) {
// All of our animations are paused or completed.
if (!mNeedsRefreshes ||
aIsThrottled) {
// All of our animations are paused or completed or this animation is being
// handled on the compositor thread, so we shouldn't interpolate here.
mStyleRuleRefreshTime = aRefreshTime;
return;
}
@ -269,7 +268,7 @@ ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
bool
ElementAnimation::IsRunningAt(TimeStamp aTime) const
{
return !IsPaused() && aTime > mStartTime &&
return !IsPaused() && aTime >= mStartTime &&
(aTime - mStartTime) / mIterationDuration < mIterationCount;
}
@ -300,7 +299,7 @@ ElementAnimations::HasAnimationOfProperty(nsCSSProperty aProperty) const
}
bool
ElementAnimations::CanPerformOnCompositorThread() const
ElementAnimations::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
{
nsIFrame* frame = mElement->GetPrimaryFrame();
if (!frame) {
@ -310,7 +309,9 @@ ElementAnimations::CanPerformOnCompositorThread() const
if (mElementProperty != nsGkAtoms::animationsProperty) {
if (nsLayoutUtils::IsAnimationLoggingEnabled()) {
nsCString message;
message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367");
message.AppendLiteral("Gecko bug: Async animation of pseudoelements not supported. See bug 771367 (");
message.Append(nsAtomCString(mElementProperty));
message.AppendLiteral(")");
LogAsyncAnimationFailure(message, mElement);
}
return false;
@ -318,14 +319,13 @@ ElementAnimations::CanPerformOnCompositorThread() const
TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
bool hasGeometricProperty = false;
for (uint32_t animIdx = mAnimations.Length(); animIdx-- != 0; ) {
const ElementAnimation& anim = mAnimations[animIdx];
for (uint32_t propIdx = 0, propEnd = anim.mProperties.Length();
propIdx != propEnd; ++propIdx) {
if (IsGeometricProperty(anim.mProperties[propIdx].mProperty) &&
anim.IsRunningAt(now)) {
hasGeometricProperty = true;
aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
break;
}
}
@ -345,7 +345,7 @@ ElementAnimations::CanPerformOnCompositorThread() const
const AnimationProperty& prop = anim.mProperties[propIdx];
if (!CanAnimatePropertyOnCompositor(mElement,
prop.mProperty,
hasGeometricProperty)) {
aFlags)) {
return false;
}
if (prop.mProperty == eCSSProperty_opacity) {
@ -411,6 +411,15 @@ nsAnimationManager::GetElementAnimations(dom::Element *aElement,
return ea;
}
void
nsAnimationManager::EnsureStyleRuleFor(ElementAnimations* aET)
{
aET->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
mPendingEvents,
false);
}
/* virtual */ void
nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
{
@ -505,10 +514,9 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
if (ea) {
// XXXdz: Invalidate the frame since the animation changed.
// The cached style rule is invalid.
ea->mStyleRule = nullptr;
ea->mStyleRuleRefreshTime = TimeStamp();
ea->UpdateAnimationGeneration(mPresContext);
// Copy over the start times and (if still paused) pause starts
// for each animation (matching on name only) that was also in the
@ -567,7 +575,7 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
ea->mAnimations.SwapElements(newAnimations);
ea->mNeedsRefreshes = true;
ea->EnsureStyleRuleFor(refreshTime, mPendingEvents);
ea->EnsureStyleRuleFor(refreshTime, mPendingEvents, false);
// We don't actually dispatch the mPendingEvents now. We'll either
// dispatch them the next time we get a refresh driver notification
// or the next time somebody calls
@ -893,10 +901,6 @@ nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
return nullptr;
}
NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime ==
mPresContext->RefreshDriver()->MostRecentRefresh(),
"should already have refreshed style rule");
if (mPresContext->IsProcessingRestyles() &&
!mPresContext->IsProcessingAnimationStyleChange()) {
// During the non-animation part of processing restyles, we don't
@ -909,6 +913,10 @@ nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
return nullptr;
}
NS_WARN_IF_FALSE(ea->mStyleRuleRefreshTime ==
mPresContext->RefreshDriver()->MostRecentRefresh(),
"should already have refreshed style rule");
return ea->mStyleRule;
}
@ -927,20 +935,38 @@ nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
return;
}
FlushAnimations(Can_Throttle);
}
void
nsAnimationManager::FlushAnimations(FlushFlags aFlags)
{
// FIXME: check that there's at least one style rule that's not
// in its "done" state, and if there isn't, remove ourselves from
// the refresh driver (but leave the animations!).
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
bool didThrottle = false;
for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
l = PR_NEXT_LINK(l)) {
ElementAnimations *ea = static_cast<ElementAnimations*>(l);
bool canThrottleTick = aFlags == Can_Throttle &&
ea->CanPerformOnCompositorThread(
CommonElementAnimationData::CanAnimateFlags(0)) &&
ea->CanThrottleAnimation(now);
nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh(),
mPendingEvents);
ea->EnsureStyleRuleFor(now, mPendingEvents, canThrottleTick);
if (oldStyleRule != ea->mStyleRule) {
ea->PostRestyleForAnimation(mPresContext);
} else {
didThrottle = true;
}
}
if (didThrottle) {
mPresContext->Document()->SetNeedStyleFlush();
}
DispatchEvents(); // may destroy us
}

View File

@ -90,7 +90,7 @@ struct ElementAnimation
return mPlayState == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
}
bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
bool IsRunningAt(mozilla::TimeStamp aTime) const;
mozilla::TimeStamp mStartTime; // with delay taken into account
@ -111,7 +111,8 @@ struct ElementAnimation
/**
* Data about all of the animations running on an element.
*/
struct ElementAnimations : public mozilla::css::CommonElementAnimationData
struct ElementAnimations MOZ_FINAL
: public mozilla::css::CommonElementAnimationData
{
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
@ -136,13 +137,14 @@ struct ElementAnimations : public mozilla::css::CommonElementAnimationData
TimeDuration aDuration,
double aIterationCount,
uint32_t aDirection,
bool IsForElement = true,
bool aIsForElement = true,
ElementAnimation* aAnimation = nullptr,
ElementAnimations* aEa = nullptr,
EventArray* aEventsToDispatch = nullptr);
void EnsureStyleRuleFor(TimeStamp aRefreshTime,
EventArray &aEventsToDispatch);
EventArray &aEventsToDispatch,
bool aIsThrottled);
bool IsForElement() const { // rather than for a pseudo-element
return mElementProperty == nsGkAtoms::animationsProperty;
@ -154,8 +156,8 @@ struct ElementAnimations : public mozilla::css::CommonElementAnimationData
}
// True if this animation can be performed on the compositor thread.
bool CanPerformOnCompositorThread() const;
bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) 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
@ -185,10 +187,26 @@ public:
if (!animations)
return nullptr;
bool propertyMatches = animations->HasAnimationOfProperty(aProperty);
return (propertyMatches && animations->CanPerformOnCompositorThread()) ?
animations : nullptr;
return (propertyMatches &&
animations->CanPerformOnCompositorThread(
mozilla::css::CommonElementAnimationData::CanAnimate_AllowPartial))
? animations
: nullptr;
}
// Returns true if aContent or any of its ancestors has an animation.
static bool ContentOrAncestorHasAnimation(nsIContent* aContent) {
do {
if (aContent->GetProperty(nsGkAtoms::animationsProperty)) {
return true;
}
} while ((aContent = aContent->GetParent()));
return false;
}
void EnsureStyleRuleFor(ElementAnimations* aET);
// nsIStyleRuleProcessor (parts)
virtual void RulesMatching(ElementRuleProcessorData* aData) MOZ_OVERRIDE;
virtual void RulesMatching(PseudoElementRuleProcessorData* aData) MOZ_OVERRIDE;
@ -204,6 +222,8 @@ public:
// nsARefreshObserver
virtual void WillRefresh(mozilla::TimeStamp aTime) MOZ_OVERRIDE;
void FlushAnimations(FlushFlags aFlags);
/**
* Return the style rule that RulesMatching should add for
* aStyleContext. This might be different from what RulesMatching
@ -236,10 +256,11 @@ public:
}
}
private:
ElementAnimations* GetElementAnimations(mozilla::dom::Element *aElement,
nsCSSPseudoElements::Type aPseudoType,
bool aCreateIfNeeded);
private:
void BuildAnimations(nsStyleContext* aStyleContext,
InfallibleTArray<ElementAnimation>& aAnimations);
bool BuildSegment(InfallibleTArray<AnimationPropertySegment>& aSegments,

View File

@ -972,6 +972,23 @@ nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext,
false, nullptr);
}
already_AddRefed<nsStyleContext>
nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext,
nsStyleContext* aOldStyle,
const nsTArray<RuleAndLevel>& aRules)
{
nsRuleWalker ruleWalker(mRuleTree);
for (int32_t i = aRules.Length() - 1; i >= 0; --i) {
ruleWalker.SetLevel(aRules[i].mLevel, false, false);
ruleWalker.ForwardOnPossiblyCSSRule(aRules[i].mRule);
}
return GetContext(aParentContext, ruleWalker.CurrentNode(), nullptr,
aOldStyle->IsLinkContext(), aOldStyle->RelevantLinkVisited(),
nullptr, nsCSSPseudoElements::ePseudo_NotPseudoElement,
false, nullptr);
}
already_AddRefed<nsStyleContext>
nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext,
const nsCOMArray<nsIStyleRule> &aRules)

View File

@ -89,6 +89,21 @@ class nsStyleSet
ResolveStyleForRules(nsStyleContext* aParentContext,
const nsTArray< nsCOMPtr<nsIStyleRule> > &aRules);
// used in ResolveStyleForRules below
struct RuleAndLevel
{
nsIStyleRule* mRule;
uint8_t mLevel;
};
// Get a new style context for aElement for the rules in aRules
// aRules is an array of rules and their levels in reverse order,
// that is from the leaf-most to the root-most rule in the rule tree.
already_AddRefed<nsStyleContext>
ResolveStyleForRules(nsStyleContext* aParentContext,
nsStyleContext* aOldStyle,
const nsTArray<RuleAndLevel>& aRules);
// Get a style context that represents aBaseContext, but as though
// it additionally matched the rules in the aRules array (in that
// order, as more specific than any other rules).

View File

@ -160,7 +160,7 @@ ProcessMatrix3D(gfx3DMatrix& aMatrix,
}
/* Helper function to process two matrices that we need to interpolate between */
static void
void
ProcessInterpolateMatrix(gfx3DMatrix& aMatrix,
const nsCSSValue::Array* aData,
nsStyleContext* aContext,

View File

@ -37,6 +37,14 @@ namespace nsStyleTransformMatrix {
nscoord aSize,
float aAppUnitsPerMatrixUnit);
void
ProcessInterpolateMatrix(gfx3DMatrix& aMatrix,
const nsCSSValue::Array* aData,
nsStyleContext* aContext,
nsPresContext* aPresContext,
bool& aCanStoreInRuleTree,
nsRect& aBounds, float aAppUnitsPerMatrixUnit);
/**
* Given an nsCSSValueList containing -moz-transform functions,
* returns a matrix containing the value of those functions.

View File

@ -7,6 +7,7 @@
/* Code to start and animate CSS transitions. */
#include "nsTransitionManager.h"
#include "nsAnimationManager.h"
#include "nsIContent.h"
#include "nsStyleContext.h"
#include "nsCSSProps.h"
@ -24,17 +25,23 @@
#include "mozilla/dom/Element.h"
#include "nsIFrame.h"
#include "nsCSSFrameConstructor.h"
#include "Layers.h"
#include "FrameLayerBuilder.h"
#include "nsDisplayList.h"
using mozilla::TimeStamp;
using mozilla::TimeDuration;
namespace dom = mozilla::dom;
namespace css = mozilla::css;
using namespace mozilla;
using namespace mozilla::layers;
using namespace mozilla::css;
ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
nsTransitionManager *aTransitionManager)
: CommonElementAnimationData(aElement, aElementProperty,
aTransitionManager)
ElementTransitions::ElementTransitions(mozilla::dom::Element *aElement,
nsIAtom *aElementProperty,
nsTransitionManager *aTransitionManager,
TimeStamp aNow)
: CommonElementAnimationData(aElement, aElementProperty, aTransitionManager)
, mFlushGeneration(aNow)
{
}
@ -47,7 +54,11 @@ ElementPropertyTransition::ValuePortionFor(TimeStamp aRefreshTime) const
double duration = mDuration.ToSeconds();
NS_ABORT_IF_FALSE(duration >= 0.0, "negative duration forbidden");
double timePortion;
if (duration == 0.0) {
if (IsRemovedSentinel()) {
// The transition is being removed, but we still want an update so that any
// new transitions start in the right place.
timePortion = 1.0;
} else if (duration == 0.0) {
// When duration is zero, we can still have a transition when delay
// is nonzero. mStartTime already incorporates delay.
if (aRefreshTime >= mStartTime) {
@ -110,11 +121,13 @@ ElementTransitions::EnsureStyleRuleFor(TimeStamp aRefreshTime)
bool
ElementPropertyTransition::IsRunningAt(TimeStamp aTime) const {
return !IsRemovedSentinel() && mStartTime < aTime && aTime < mStartTime + mDuration;
return !IsRemovedSentinel() &&
mStartTime <= aTime &&
aTime < mStartTime + mDuration;
}
bool
ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const
ElementTransitions::HasAnimationOfProperty(nsCSSProperty aProperty) const
{
for (uint32_t tranIdx = mPropertyTransitions.Length(); tranIdx-- != 0; ) {
if (aProperty == mPropertyTransitions[tranIdx].mProperty) {
@ -125,7 +138,7 @@ ElementTransitions::HasTransitionOfProperty(nsCSSProperty aProperty) const
}
bool
ElementTransitions::CanPerformOnCompositorThread() const
ElementTransitions::CanPerformOnCompositorThread(CanAnimateFlags aFlags) const
{
nsIFrame* frame = mElement->GetPrimaryFrame();
if (!frame) {
@ -143,25 +156,28 @@ ElementTransitions::CanPerformOnCompositorThread() const
TimeStamp now = frame->PresContext()->RefreshDriver()->MostRecentRefresh();
bool hasGeometricProperty = false;
for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
const ElementPropertyTransition& pt = mPropertyTransitions[i];
if (css::IsGeometricProperty(pt.mProperty) && pt.IsRunningAt(now)) {
hasGeometricProperty = true;
aFlags = CanAnimateFlags(aFlags | CanAnimate_HasGeometricProperty);
break;
}
}
bool hasOpacity = false;
bool hasTransform = false;
bool existsProperty = false;
for (uint32_t i = 0, i_end = mPropertyTransitions.Length(); i < i_end; ++i) {
const ElementPropertyTransition& pt = mPropertyTransitions[i];
if (pt.IsRemovedSentinel()) {
continue;
}
existsProperty = true;
if (!css::CommonElementAnimationData::CanAnimatePropertyOnCompositor(mElement,
pt.mProperty,
hasGeometricProperty)) {
aFlags)) {
return false;
}
if (pt.mProperty == eCSSProperty_opacity) {
@ -170,6 +186,12 @@ ElementTransitions::CanPerformOnCompositorThread() const
hasTransform = true;
}
}
// No properties to animate
if (!existsProperty) {
return false;
}
// This transition can be done on the compositor. Mark the frame as active, in
// case we are able to throttle this transition.
if (hasOpacity) {
@ -185,6 +207,211 @@ ElementTransitions::CanPerformOnCompositorThread() const
* nsTransitionManager *
*****************************************************************************/
// reparent :before and :after pseudo elements of aElement
static void ReparentBeforeAndAfter(dom::Element* aElement,
nsIFrame* aPrimaryFrame,
nsStyleContext* aNewStyle,
nsStyleSet* aStyleSet)
{
if (nsIFrame* before = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) {
nsRefPtr<nsStyleContext> beforeStyle =
aStyleSet->ReparentStyleContext(before->GetStyleContext(),
aNewStyle, aElement);
before->SetStyleContextWithoutNotification(beforeStyle);
}
if (nsIFrame* after = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) {
nsRefPtr<nsStyleContext> afterStyle =
aStyleSet->ReparentStyleContext(after->GetStyleContext(),
aNewStyle, aElement);
after->SetStyleContextWithoutNotification(afterStyle);
}
}
// Ensure that the next repaint rebuilds the layer tree for aFrame. That
// means that changes to animations on aFrame's layer are propagated to
// the compositor, which is needed for correct behaviour of new
// transitions.
static void
ForceLayerRerendering(nsIFrame* aFrame, CommonElementAnimationData* aData)
{
if (aData->HasAnimationOfProperty(eCSSProperty_opacity)) {
if (Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
aFrame, nsDisplayItem::TYPE_OPACITY)) {
layer->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey());
}
}
if (aData->HasAnimationOfProperty(eCSSProperty_transform)) {
if (Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
aFrame, nsDisplayItem::TYPE_TRANSFORM)) {
layer->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey());
}
}
}
nsStyleContext*
nsTransitionManager::UpdateThrottledStyle(dom::Element* aElement,
nsStyleContext* aParentStyle)
{
NS_ASSERTION(GetElementTransitions(aElement,
nsCSSPseudoElements::ePseudo_NotPseudoElement,
false), "element not transitioning");
nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
if (!primaryFrame) {
return nullptr;
}
nsStyleContext* oldStyle = primaryFrame->GetStyleContext();
nsRuleNode* ruleNode = oldStyle->GetRuleNode();
nsTArray<nsStyleSet::RuleAndLevel> rules;
do {
if (ruleNode->IsRoot()) {
break;
}
nsStyleSet::RuleAndLevel* curRule = rules.AppendElement();
curRule->mLevel = ruleNode->GetLevel();
if (curRule->mLevel == nsStyleSet::eAnimationSheet) {
ElementAnimations* ea =
mPresContext->AnimationManager()->GetElementAnimations(aElement,
oldStyle->GetPseudoType(),
false);
NS_ASSERTION(ea, "Rule has level eAnimationSheet without animation on manager");
mPresContext->AnimationManager()->EnsureStyleRuleFor(ea);
curRule->mRule = ea->mStyleRule;
ForceLayerRerendering(primaryFrame, ea);
} else if (curRule->mLevel == nsStyleSet::eTransitionSheet) {
ElementTransitions *et =
GetElementTransitions(aElement, oldStyle->GetPseudoType(), false);
NS_ASSERTION(et, "Rule has level eTransitionSheet without transition on manager");
et->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh());
curRule->mRule = et->mStyleRule;
ForceLayerRerendering(primaryFrame, et);
} else {
curRule->mRule = ruleNode->GetRule();
}
} while (ruleNode = ruleNode->GetParent());
nsRefPtr<nsStyleContext> newStyle = mPresContext->PresShell()->StyleSet()->
ResolveStyleForRules(aParentStyle, oldStyle, rules);
primaryFrame->SetStyleContextWithoutNotification(newStyle);
ReparentBeforeAndAfter(aElement, primaryFrame, newStyle, mPresContext->PresShell()->StyleSet());
return newStyle;
}
void
nsTransitionManager::UpdateThrottledStylesForSubtree(nsIContent* aContent,
nsStyleContext* aParentStyle)
{
dom::Element* element;
if (aContent->IsElement()) {
element = aContent->AsElement();
} else {
element = nullptr;
}
nsRefPtr<nsStyleContext> newStyle;
ElementTransitions* et;
if (element &&
(et = GetElementTransitions(element,
nsCSSPseudoElements::ePseudo_NotPseudoElement,
false))) {
// re-resolve our style
newStyle = UpdateThrottledStyle(element, aParentStyle);
// remove the current transition from the working set
et->mFlushGeneration = mPresContext->RefreshDriver()->MostRecentRefresh();
;
} else {
// reparent the element's style
nsStyleSet* styleSet = mPresContext->PresShell()->StyleSet();
nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
if (!primaryFrame) {
return;
}
newStyle = styleSet->ReparentStyleContext(primaryFrame->GetStyleContext(),
aParentStyle, element);
primaryFrame->SetStyleContextWithoutNotification(newStyle);
ReparentBeforeAndAfter(element, primaryFrame, newStyle, styleSet);
}
// walk the children
if (newStyle) {
nsIContent* child = aContent->GetFirstChild();
for (nsIContent *child = aContent->GetFirstChild(); child;
child = child->GetNextSibling()) {
UpdateThrottledStylesForSubtree(child, newStyle);
}
}
}
void
nsTransitionManager::UpdateAllThrottledStyles()
{
if (PR_CLIST_IS_EMPTY(&mElementData)) {
// no throttled transitions, leave early
mPresContext->TickLastUpdateThrottledStyle();
return;
}
if (mPresContext->ThrottledStyleIsUpToDate()) {
// throttled transitions are up to date, leave early
return;
}
mPresContext->TickLastUpdateThrottledStyle();
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
// update each transitioning element by finding its root-most ancestor with a
// transition, and flushing the style on that ancestor and all its descendants
PRCList *next = PR_LIST_HEAD(&mElementData);
while (next != &mElementData) {
ElementTransitions* et = static_cast<ElementTransitions*>(next);
next = PR_NEXT_LINK(next);
if (et->mFlushGeneration == now) {
// this element has been ticked already
continue;
}
// element is initialised to the starting element (i.e., one we know has
// a transition) and ends up with the root-most transitioning ancestor,
// that is, the element where we begin updates.
dom::Element* element = et->mElement;
// make a list of ancestors
nsTArray<dom::Element*> ancestors;
do {
ancestors.AppendElement(element);
} while (element = element->GetElementParent());
// walk down the ancestors until we find one with a throttled transition
for (int32_t i = ancestors.Length() - 1; i >= 0; --i) {
if (GetElementTransitions(ancestors[i],
nsCSSPseudoElements::ePseudo_NotPseudoElement,
false)) {
element = ancestors[i];
break;
}
}
nsIFrame* primaryFrame;
if (element &&
(primaryFrame = element->GetPrimaryFrame())) {
UpdateThrottledStylesForSubtree(element,
primaryFrame->GetStyleContext()->GetParent());
}
}
}
already_AddRefed<nsIStyleRule>
nsTransitionManager::StyleContextChanged(dom::Element *aElement,
nsStyleContext *aOldStyleContext,
@ -248,6 +475,10 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
return nullptr;
}
NS_WARN_IF_FALSE(!CommonAnimationManager::ThrottlingEnabled() ||
mPresContext->ThrottledStyleIsUpToDate(),
"throttled animations not up to date");
// Per http://lists.w3.org/Archives/Public/www-style/2009Aug/0109.html
// I'll consider only the transitions from the number of items in
// 'transition-property' on down, and later ones will override earlier
@ -340,6 +571,7 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
currentValue != pt.mEndValue) {
// stop the transition
pts.RemoveElementAt(i);
et->UpdateAnimationGeneration(mPresContext);
}
} while (i != 0);
@ -386,17 +618,19 @@ nsTransitionManager::StyleContextChanged(dom::Element *aElement,
void
nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
const nsTransition& aTransition,
dom::Element *aElement,
ElementTransitions *&aElementTransitions,
nsStyleContext *aOldStyleContext,
nsStyleContext *aNewStyleContext,
bool *aStartedAny,
nsCSSPropertySet *aWhichStarted)
const nsTransition& aTransition,
dom::Element* aElement,
ElementTransitions*& aElementTransitions,
nsStyleContext* aOldStyleContext,
nsStyleContext* aNewStyleContext,
bool* aStartedAny,
nsCSSPropertySet* aWhichStarted)
{
// IsShorthand itself will assert if aProperty is not a property.
NS_ABORT_IF_FALSE(!nsCSSProps::IsShorthand(aProperty),
"property out of range");
NS_ASSERTION(!aElementTransitions ||
aElementTransitions->mElement == aElement, "Element mismatch");
if (aWhichStarted->HasProperty(aProperty)) {
// A later item in transition-property already started a
@ -414,22 +648,15 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
nsStyleAnimation::Value dummyValue;
bool haveValues =
ExtractComputedValueForTransition(aProperty, aOldStyleContext,
pt.mStartValue) &&
pt.mStartValue) &&
ExtractComputedValueForTransition(aProperty, aNewStyleContext,
pt.mEndValue);
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 &&
(haveChange || haveOMTA) &&
haveChange &&
// 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.)
@ -465,6 +692,8 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
// is almost done (and whose current value rounds to its end
// value) just because we got an unrelated style change.
pts.RemoveElementAt(currentIndex);
aElementTransitions->UpdateAnimationGeneration(mPresContext);
if (pts.IsEmpty()) {
aElementTransitions->Destroy();
// |aElementTransitions| is now a dangling pointer!
@ -569,13 +798,13 @@ nsTransitionManager::ConsiderStartingTransition(nsCSSProperty aProperty,
return;
}
}
aElementTransitions->UpdateAnimationGeneration(mPresContext);
nsRestyleHint hint =
aNewStyleContext->GetPseudoType() ==
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);
@ -608,7 +837,8 @@ nsTransitionManager::GetElementTransitions(dom::Element *aElement,
aElement->GetProperty(propName));
if (!et && aCreateIfNeeded) {
// FIXME: Consider arena-allocating?
et = new ElementTransitions(aElement, propName, this);
et = new ElementTransitions(aElement, propName, this,
mPresContext->RefreshDriver()->MostRecentRefresh());
nsresult rv = aElement->SetProperty(propName, et,
ElementTransitionsPropertyDtor, false);
if (NS_FAILED(rv)) {
@ -747,8 +977,20 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
return;
}
nsTArray<TransitionEventInfo> events;
FlushTransitions(Can_Throttle);
}
void
nsTransitionManager::FlushTransitions(FlushFlags aFlags)
{
if (PR_CLIST_IS_EMPTY(&mElementData)) {
// no transitions, leave early
return;
}
nsTArray<TransitionEventInfo> events;
TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
bool didThrottle = false;
// Trim transitions that have completed, and post restyle events for
// frames that are still transitioning.
{
@ -757,6 +999,11 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
ElementTransitions *et = static_cast<ElementTransitions*>(next);
next = PR_NEXT_LINK(next);
bool canThrottleTick = aFlags == Can_Throttle &&
et->CanPerformOnCompositorThread(
CommonElementAnimationData::CanAnimateFlags(0)) &&
et->CanThrottleAnimation(now);
NS_ABORT_IF_FALSE(et->mElement->GetCurrentDoc() ==
mPresContext->Document(),
"Element::UnbindFromTree should have "
@ -764,14 +1011,20 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
uint32_t i = et->mPropertyTransitions.Length();
NS_ABORT_IF_FALSE(i != 0, "empty transitions list?");
bool transitionEnded = false;
do {
--i;
ElementPropertyTransition &pt = et->mPropertyTransitions[i];
if (pt.IsRemovedSentinel()) {
// Actually remove transitions one cycle after their
// completion. See comment below.
et->mPropertyTransitions.RemoveElementAt(i);
} else if (pt.mStartTime + pt.mDuration <= aTime) {
// Actually remove transitions one throttle-able cycle after their
// completion. We only clear on a throttle-able cycle because that
// means it is a regular restyle tick and thus it is safe to discard
// the transition. If the flush is not throttle-able, we might still
// have new transitions left to process. See comment below.
if (aFlags == Can_Throttle) {
et->mPropertyTransitions.RemoveElementAt(i);
}
} else if (pt.mStartTime + pt.mDuration <= now) {
// Fire transitionend events only for transitions on elements
// and not those on pseudo-elements, since we can't target an
// event at pseudo-elements.
@ -793,6 +1046,8 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
// to know not to start a new transition for the transition
// from the almost-completed value to the final value.
pt.SetRemovedSentinel();
et->UpdateAnimationGeneration(mPresContext);
transitionEnded = true;
}
} while (i != 0);
@ -802,12 +1057,13 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
et->mElementProperty == nsGkAtoms::transitionsOfBeforeProperty ||
et->mElementProperty == nsGkAtoms::transitionsOfAfterProperty,
"Unexpected element property; might restyle too much");
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 (!canThrottleTick || transitionEnded) {
nsRestyleHint hint = et->mElementProperty == nsGkAtoms::transitionsProperty ?
eRestyle_Self : eRestyle_Subtree;
mPresContext->PresShell()->RestyleForAnimation(et->mElement, hint);
} else {
didThrottle = true;
}
if (et->mPropertyTransitions.IsEmpty()) {
et->Destroy();
@ -820,6 +1076,10 @@ nsTransitionManager::WillRefresh(mozilla::TimeStamp aTime)
// We might have removed transitions above.
ElementDataRemoved();
if (didThrottle) {
mPresContext->Document()->SetNeedStyleFlush();
}
for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) {
TransitionEventInfo &info = events[i];
nsEventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent);

View File

@ -70,19 +70,25 @@ struct ElementPropertyTransition
bool IsRunningAt(mozilla::TimeStamp aTime) const;
};
struct ElementTransitions : public mozilla::css::CommonElementAnimationData
struct ElementTransitions MOZ_FINAL
: public mozilla::css::CommonElementAnimationData
{
ElementTransitions(mozilla::dom::Element *aElement, nsIAtom *aElementProperty,
nsTransitionManager *aTransitionManager);
nsTransitionManager *aTransitionManager,
mozilla::TimeStamp aNow);
void EnsureStyleRuleFor(mozilla::TimeStamp aRefreshTime);
virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE;
virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE;
bool HasTransitionOfProperty(nsCSSProperty aProperty) const;
// True if this animation can be performed on the compositor thread.
bool CanPerformOnCompositorThread() const;
// Either zero or one for each CSS property:
nsTArray<ElementPropertyTransition> mPropertyTransitions;
// Generation counter for flushes of throttled transitions.
// Used to prevent updating the styles twice for a given element during
// UpdateAllThrottledStyles.
mozilla::TimeStamp mFlushGeneration;
};
@ -100,16 +106,31 @@ public:
(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())
if (!aContent->MayHaveAnimations()) {
return nullptr;
}
ElementTransitions* transitions = GetTransitions(aContent);
if (!transitions ||
!transitions->HasTransitionOfProperty(aProperty) ||
!transitions->CanPerformOnCompositorThread()) {
!transitions->HasAnimationOfProperty(aProperty) ||
!transitions->CanPerformOnCompositorThread(
CommonElementAnimationData::CanAnimate_AllowPartial)) {
return nullptr;
}
return transitions;
@ -151,6 +172,29 @@ public:
// 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();
private:
void ConsiderStartingTransition(nsCSSProperty aProperty,
const nsTransition& aTransition,
@ -165,6 +209,17 @@ private:
bool aCreateIfNeeded);
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);
// Update the style on aElement from the transition stored in this manager and
// the new parent style - aParentStyle. aElement must be transitioning or
// animated. Returns the updated style.
nsStyleContext* UpdateThrottledStyle(mozilla::dom::Element* aElement,
nsStyleContext* aParentStyle);
};
#endif /* !defined(nsTransitionManager_h_) */