From c54f8d475480a9b7f3c0d7a4f444a5e8747516d2 Mon Sep 17 00:00:00 2001 From: Nicholas Cameron Date: Wed, 12 Dec 2012 10:12:43 +1300 Subject: [PATCH] Bug 780692; throttle OMTA (rollup patch). r=dbaron,bz --HG-- extra : rebase_source : 1207275df5c509ac1974e2b9333c738b995f9d5e --- b2g/app/b2g.js | 2 + content/base/public/mozFlushType.h | 13 + gfx/layers/Layers.cpp | 3 +- gfx/layers/Layers.h | 6 + gfx/layers/ipc/CompositorParent.cpp | 2 +- layout/base/nsCSSFrameConstructor.cpp | 23 +- layout/base/nsCSSFrameConstructor.h | 9 + layout/base/nsDisplayList.cpp | 14 + layout/base/nsIPresShell.h | 7 +- layout/base/nsLayoutUtils.cpp | 46 ++-- layout/base/nsPresContext.cpp | 4 + layout/base/nsPresContext.h | 36 ++- layout/base/nsPresShell.cpp | 40 ++- layout/base/nsPresShell.h | 1 + layout/base/nsRefreshDriver.cpp | 5 +- layout/generic/nsGfxScrollFrame.h | 6 + layout/generic/nsIScrollableFrame.h | 4 + layout/style/AnimationCommon.cpp | 106 +++++++- layout/style/AnimationCommon.h | 36 ++- layout/style/nsAnimationManager.cpp | 72 +++-- layout/style/nsAnimationManager.h | 39 ++- layout/style/nsStyleSet.cpp | 17 ++ layout/style/nsStyleSet.h | 15 + layout/style/nsStyleTransformMatrix.cpp | 2 +- layout/style/nsStyleTransformMatrix.h | 8 + layout/style/nsTransitionManager.cpp | 348 +++++++++++++++++++++--- layout/style/nsTransitionManager.h | 71 ++++- 27 files changed, 807 insertions(+), 128 deletions(-) diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index cc917a8c6801..8b0d2a3e1b06 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -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 diff --git a/content/base/public/mozFlushType.h b/content/base/public/mozFlushType.h index 186a8c960073..c68494b0dd60 100644 --- a/content/base/public/mozFlushType.h +++ b/content/base/public/mozFlushType.h @@ -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___ */ diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 338007f0d7e3..d6d1f4576bbc 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -289,7 +289,8 @@ Layer::Layer(LayerManager* aManager, void* aImplData) : mUseClipRect(false), mUseTileSourceRect(false), mIsFixedPosition(false), - mDebugColorIndex(0) + mDebugColorIndex(0), + mAnimationGeneration(0) {} Layer::~Layer() diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index ae4be2e91df4..455560d6b748 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -804,6 +804,9 @@ public: AnimationArray& GetAnimations() { return mAnimations; } InfallibleTArray& 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 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; }; /** diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 6500011ffa8e..2d475c47fafd 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -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 diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index d299d0a2f31a..44a3df4aaf68 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -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 diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index f8c475e1ab5f..171bd397e54c 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -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 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; }; diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 750ee1d76012..cde72d2ad4c3 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -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); } } diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index c87ad1ae2a2f..8925037f7bfe 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -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 diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index fc5086a92393..5d8aac7d635e 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -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 +static bool +HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty) +{ + AnimationsOrTransitions* animations = + static_cast(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(aContent->GetProperty(nsGkAtoms::animationsProperty)); - if (animations) { - bool propertyMatches = animations->HasAnimationOfProperty(aProperty); - if (propertyMatches && animations->CanPerformOnCompositorThread()) { - return true; - } + if (HasAnimationOrTransition + (aContent, nsGkAtoms::animationsProperty, aProperty)) { + return true; } - - ElementTransitions* transitions = - static_cast(aContent->GetProperty(nsGkAtoms::transitionsProperty)); - if (transitions) { - bool propertyMatches = transitions->HasTransitionOfProperty(aProperty); - if (propertyMatches && transitions->CanPerformOnCompositorThread()) { - return true; - } - } - - return false; + return HasAnimationOrTransition + (aContent, nsGkAtoms::transitionsProperty, aProperty); } bool diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index 38f8c5555768..a395cb8d4256 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -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 diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h index 0947090a8f33..62d6b48228b2 100644 --- a/layout/base/nsPresContext.h +++ b/layout/base/nsPresContext.h @@ -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; 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; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 209820bae6a5..b8ca429954ee 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -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(aType) <= ArrayLength(flushTypeNames)); + MOZ_ASSERT(static_cast(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 diff --git a/layout/base/nsPresShell.h b/layout/base/nsPresShell.h index 0aa76c26a1f3..1927b303e7a4 100644 --- a/layout/base/nsPresShell.h +++ b/layout/base/nsPresShell.h @@ -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 diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index cf8ee866a7f7..22da579dccd1 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -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); } } diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 3d739f55e543..492e25a96c04 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -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(); } diff --git a/layout/generic/nsIScrollableFrame.h b/layout/generic/nsIScrollableFrame.h index 0e334d224cd0..912d4cafda7e 100644 --- a/layout/generic/nsIScrollableFrame.h +++ b/layout/generic/nsIScrollableFrame.h @@ -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 diff --git a/layout/style/AnimationCommon.cpp b/layout/style/AnimationCommon.cpp index bb1c56e41331..8751f2a53f80 100644 --- a/layout/style/AnimationCommon.cpp +++ b/layout/style/AnimationCommon.cpp @@ -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(); +} + } } diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h index f75271fef08a..d2d02dc1064c 100644 --- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -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 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; diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp index a9110587ecb0..df14a88d5728 100644 --- a/layout/style/nsAnimationManager.cpp +++ b/layout/style/nsAnimationManager.cpp @@ -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(l); + bool canThrottleTick = aFlags == Can_Throttle && + ea->CanPerformOnCompositorThread( + CommonElementAnimationData::CanAnimateFlags(0)) && + ea->CanThrottleAnimation(now); + nsRefPtr 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 } diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h index 330689d86dd6..8353a240dfd9 100644 --- a/layout/style/nsAnimationManager.h +++ b/layout/style/nsAnimationManager.h @@ -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& aAnimations); bool BuildSegment(InfallibleTArray& aSegments, diff --git a/layout/style/nsStyleSet.cpp b/layout/style/nsStyleSet.cpp index d13890f16518..6ab33e4f3552 100644 --- a/layout/style/nsStyleSet.cpp +++ b/layout/style/nsStyleSet.cpp @@ -972,6 +972,23 @@ nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext, false, nullptr); } +already_AddRefed +nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext, + nsStyleContext* aOldStyle, + const nsTArray& 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 nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext, const nsCOMArray &aRules) diff --git a/layout/style/nsStyleSet.h b/layout/style/nsStyleSet.h index 75d9eb2edba0..5bdb71f3aff9 100644 --- a/layout/style/nsStyleSet.h +++ b/layout/style/nsStyleSet.h @@ -89,6 +89,21 @@ class nsStyleSet ResolveStyleForRules(nsStyleContext* aParentContext, const nsTArray< nsCOMPtr > &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 + ResolveStyleForRules(nsStyleContext* aParentContext, + nsStyleContext* aOldStyle, + const nsTArray& 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). diff --git a/layout/style/nsStyleTransformMatrix.cpp b/layout/style/nsStyleTransformMatrix.cpp index e6a3f657ee95..f2f3c8c9b818 100644 --- a/layout/style/nsStyleTransformMatrix.cpp +++ b/layout/style/nsStyleTransformMatrix.cpp @@ -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, diff --git a/layout/style/nsStyleTransformMatrix.h b/layout/style/nsStyleTransformMatrix.h index c18fccadb183..e8d7cea199e5 100644 --- a/layout/style/nsStyleTransformMatrix.h +++ b/layout/style/nsStyleTransformMatrix.h @@ -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. diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp index 2db9d7a2e502..eddf3529b7c4 100644 --- a/layout/style/nsTransitionManager.cpp +++ b/layout/style/nsTransitionManager.cpp @@ -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 beforeStyle = + aStyleSet->ReparentStyleContext(before->GetStyleContext(), + aNewStyle, aElement); + before->SetStyleContextWithoutNotification(beforeStyle); + } + if (nsIFrame* after = nsLayoutUtils::GetBeforeFrame(aPrimaryFrame)) { + nsRefPtr 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 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 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 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(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 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 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 events; + FlushTransitions(Can_Throttle); +} +void +nsTransitionManager::FlushTransitions(FlushFlags aFlags) +{ + if (PR_CLIST_IS_EMPTY(&mElementData)) { + // no transitions, leave early + return; + } + + nsTArray 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(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); diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h index 161f75c30891..9ec9636157f4 100644 --- a/layout/style/nsTransitionManager.h +++ b/layout/style/nsTransitionManager.h @@ -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 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_) */