mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-19 09:30:44 +00:00
Bug 780692; throttle OMTA (rollup patch). r=dbaron,bz
--HG-- extra : rebase_source : 1207275df5c509ac1974e2b9333c738b995f9d5e
This commit is contained in:
parent
3ee3eaf9fe
commit
c54f8d4754
@ -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
|
||||
|
@ -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___ */
|
||||
|
@ -289,7 +289,8 @@ Layer::Layer(LayerManager* aManager, void* aImplData) :
|
||||
mUseClipRect(false),
|
||||
mUseTileSourceRect(false),
|
||||
mIsFixedPosition(false),
|
||||
mDebugColorIndex(0)
|
||||
mDebugColorIndex(0),
|
||||
mAnimationGeneration(0)
|
||||
{}
|
||||
|
||||
Layer::~Layer()
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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).
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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_) */
|
||||
|
Loading…
x
Reference in New Issue
Block a user