Bug 1197620 - Part 1 - Stop all animations in destroyed frames. r=bbirtles

This commit is contained in:
Hiroyuki Ikezoe 2015-09-14 23:42:00 +02:00
parent e7ba3880f4
commit 3525eca033
6 changed files with 144 additions and 0 deletions

View File

@ -90,6 +90,7 @@ RestyleManager::RestyleManager(nsPresContext* aPresContext)
MostRecentRefresh())
, mAnimationGeneration(0)
, mReframingStyleContexts(nullptr)
, mAnimationsWithDestroyedFrame(nullptr)
, mPendingRestyles(ELEMENT_HAS_PENDING_RESTYLE |
ELEMENT_IS_POTENTIAL_RESTYLE_ROOT |
ELEMENT_IS_CONDITIONAL_RESTYLE_ANCESTOR)
@ -1081,6 +1082,44 @@ RestyleManager::ReframingStyleContexts::~ReframingStyleContexts()
mRestyleManager->mPresContext->FrameConstructor()->CreateNeededFrames();
}
RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
RestyleManager* aRestyleManager)
: mRestyleManager(aRestyleManager)
, mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame)
{
MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
"shouldn't construct recursively");
mRestyleManager->mAnimationsWithDestroyedFrame = this;
}
void
RestyleManager::AnimationsWithDestroyedFrame::StopAnimationsForElementsWithoutFrames()
{
StopAnimationsWithoutFrame(mContents,
nsCSSPseudoElements::ePseudo_NotPseudoElement);
StopAnimationsWithoutFrame(mBeforeContents,
nsCSSPseudoElements::ePseudo_before);
StopAnimationsWithoutFrame(mAfterContents,
nsCSSPseudoElements::ePseudo_after);
}
void
RestyleManager::AnimationsWithDestroyedFrame::StopAnimationsWithoutFrame(
nsTArray<nsRefPtr<nsIContent>>& aArray,
nsCSSPseudoElements::Type aPseudoType)
{
nsAnimationManager* animationManager =
mRestyleManager->PresContext()->AnimationManager();
for (nsIContent* content : aArray) {
if (content->GetPrimaryFrame()) {
continue;
}
dom::Element* element = content->AsElement();
animationManager->StopAnimationsForElement(element, aPseudoType);
}
}
static inline dom::Element*
ElementForStyleContext(nsIContent* aParentContent,
nsIFrame* aFrame,
@ -1780,6 +1819,10 @@ RestyleManager::EndProcessingRestyles()
{
FlushOverflowChangedTracker();
MOZ_ASSERT(mAnimationsWithDestroyedFrame);
mAnimationsWithDestroyedFrame->
StopAnimationsForElementsWithoutFrames();
// Set mInStyleRefresh to false now, since the EndUpdate call might
// add more restyles.
mInStyleRefresh = false;

View File

@ -49,6 +49,8 @@ private:
{
MOZ_ASSERT(!mReframingStyleContexts,
"temporary member should be nulled out before destruction");
MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
"leaving dangling pointers from AnimationsWithDestroyedFrame");
}
public:
@ -247,6 +249,65 @@ public:
nsStyleContext* aOldStyleContext,
nsRefPtr<nsStyleContext>* aNewStyleContext /* inout */);
// AnimationsWithDestroyedFrame is used to stop animations on elements that
// have no frame at the end of the restyling process.
// It only lives during the restyling process.
class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
public:
// Construct a AnimationsWithDestroyedFrame object. The caller must
// ensure that aRestyleManager lives at least as long as the
// object. (This is generally easy since the caller is typically a
// method of RestyleManager.)
explicit AnimationsWithDestroyedFrame(RestyleManager* aRestyleManager);
~AnimationsWithDestroyedFrame()
{
}
// This method takes the content node for the generated content for
// animation on ::before and ::after, rather than the content node for
// the real element.
void Put(nsIContent* aContent, nsStyleContext* aStyleContext) {
MOZ_ASSERT(aContent);
nsCSSPseudoElements::Type pseudoType = aStyleContext->GetPseudoType();
if (pseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
mContents.AppendElement(aContent);
} else if (pseudoType == nsCSSPseudoElements::ePseudo_before) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore);
mBeforeContents.AppendElement(aContent->GetParent());
} else if (pseudoType == nsCSSPseudoElements::ePseudo_after) {
MOZ_ASSERT(aContent->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter);
mAfterContents.AppendElement(aContent->GetParent());
}
}
void StopAnimationsForElementsWithoutFrames();
private:
void StopAnimationsWithoutFrame(nsTArray<nsRefPtr<nsIContent>>& aArray,
nsCSSPseudoElements::Type aPseudoType);
RestyleManager* mRestyleManager;
AutoRestore<AnimationsWithDestroyedFrame*> mRestorePointer;
// Below three arrays might include elements that have already had their
// animations stopped.
//
// mBeforeContents and mAfterContents hold the real element rather than
// the content node for the generated content (which might change during
// a reframe)
nsTArray<nsRefPtr<nsIContent>> mContents;
nsTArray<nsRefPtr<nsIContent>> mBeforeContents;
nsTArray<nsRefPtr<nsIContent>> mAfterContents;
};
/**
* Return the current AnimationsWithDestroyedFrame struct, or null if we're
* not currently in a restyling operation.
*/
AnimationsWithDestroyedFrame* GetAnimationsWithDestroyedFrame() {
return mAnimationsWithDestroyedFrame;
}
private:
void RestyleForEmptyChange(Element* aContainer);
@ -491,6 +552,7 @@ private:
uint64_t mAnimationGeneration;
ReframingStyleContexts* mReframingStyleContexts;
AnimationsWithDestroyedFrame* mAnimationsWithDestroyedFrame;
RestyleTracker mPendingRestyles;

View File

@ -225,6 +225,12 @@ RestyleTracker::DoProcessRestyles()
docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
}
// Create a AnimationsWithDestroyedFrame during restyling process to
// stop animations on elements that have no frame at the end of the
// restyling process.
RestyleManager::AnimationsWithDestroyedFrame
animationsWithDestroyedFrame(mRestyleManager);
// Create a ReframingStyleContexts struct on the stack and put it in our
// mReframingStyleContexts for almost all of the remaining scope of
// this function.

View File

@ -687,6 +687,17 @@ nsFrame::DestroyFrom(nsIFrame* aDestructRoot)
}
}
if (nsLayoutUtils::HasCurrentAnimations(static_cast<nsIFrame*>(this))) {
// If no new frame for this element is created by the end of the
// restyling process, stop animations for this frame
RestyleManager::AnimationsWithDestroyedFrame* adf =
presContext->RestyleManager()->GetAnimationsWithDestroyedFrame();
// AnimationsWithDestroyedFrame only lives during the restyling process.
if (adf) {
adf->Put(mContent, mStyleContext);
}
}
shell->NotifyDestroyingFrame(this);
if (mState & NS_FRAME_EXTERNAL_REFERENCE) {

View File

@ -551,6 +551,22 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
}
void
nsAnimationManager::StopAnimationsForElement(
mozilla::dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType)
{
MOZ_ASSERT(aElement);
AnimationCollection* collection =
GetAnimations(aElement, aPseudoType, false);
if (!collection) {
return;
}
nsAutoAnimationMutationBatch mb(aElement->OwnerDoc());
collection->Destroy();
}
struct KeyframeData {
float mKey;
uint32_t mIndex; // store original order since sort algorithm is not stable

View File

@ -290,6 +290,12 @@ public:
void DispatchEvents() { mEventDispatcher.DispatchEvents(mPresContext); }
void ClearEventQueue() { mEventDispatcher.ClearEventQueue(); }
// Stop animations on the element. This method takes the real element
// rather than the element for the generated content for animations on
// ::before and ::after.
void StopAnimationsForElement(mozilla::dom::Element* aElement,
nsCSSPseudoElements::Type aPseudoType);
protected:
virtual ~nsAnimationManager() {}