author | Brian Birtles <birtles@gmail.com> |
Wed, 29 Jul 2015 10:57:39 +0900 | |
changeset 256724 | f30ecbdba384066bf0e5b8ac7ded27c88160304c |
parent 256723 | 94a07b627f21a086015e45941b33ea4f8280dead |
child 256725 | 7c94690a852b2cb1632ef18f1b3d8c5d898296a3 |
push id | 29187 |
push user | cbook@mozilla.com |
push date | Fri, 07 Aug 2015 11:13:32 +0000 |
treeherder | mozilla-central@3e51753a099f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | dbaron |
bugs | 1180125 |
milestone | 42.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/animation/Animation.h +++ b/dom/animation/Animation.h @@ -136,17 +136,17 @@ public: * CSSAnimation::PauseFromJS so we leave it for now. */ void PauseFromJS(ErrorResult& aRv) { Pause(aRv); } // Wrapper functions for Animation DOM methods when called from style. virtual void CancelFromStyle() { DoCancel(); } - void Tick(); + virtual void Tick(); /** * Set the time to use for starting or pausing a pending animation. * * Typically, when an animation is played, it does not start immediately but * is added to a table of pending animations on the document of its effect. * In the meantime it sets its hold time to the time from which playback * should begin.
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -1245,16 +1245,20 @@ PresShell::Destroy() NS_ASSERTION(mDocument->GetShell() == this, "Wrong shell?"); mDocument->DeleteShell(); if (mDocument->HasAnimationController()) { mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd); } } + if (mPresContext) { + mPresContext->AnimationManager()->ClearEventQueue(); + } + // Revoke any pending events. We need to do this and cancel pending reflows // before we destroy the frame manager, since apparently frame destruction // sometimes spins the event queue when plug-ins are involved(!). rd->RemoveLayoutFlushObserver(this); if (mHiddenInvalidationObserverRefreshDriver) { mHiddenInvalidationObserverRefreshDriver->RemovePresShellToInvalidateIfHidden(this); }
--- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -59,16 +59,17 @@ #include "mozilla/ipc/PBackgroundChild.h" #include "nsIIPCBackgroundChildCreateCallback.h" #include "mozilla/layout/VsyncChild.h" #include "VsyncSource.h" #include "mozilla/VsyncDispatcher.h" #include "nsThreadUtils.h" #include "mozilla/unused.h" #include "mozilla/TimelineConsumers.h" +#include "nsAnimationManager.h" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif using namespace mozilla; using namespace mozilla::widget; using namespace mozilla::ipc; @@ -1484,16 +1485,50 @@ nsRefreshDriver::DispatchPendingEvents() // Swap out the current pending events nsTArray<PendingEvent> pendingEvents(Move(mPendingEvents)); for (PendingEvent& event : pendingEvents) { bool dummy; event.mTarget->DispatchEvent(event.mEvent, &dummy); } } +static bool +DispatchAnimationEventsOnSubDocuments(nsIDocument* aDocument, + void* aRefreshDriver) +{ + nsIPresShell* shell = aDocument->GetShell(); + if (!shell) { + return true; + } + + nsPresContext* context = shell->GetPresContext(); + if (!context || context->RefreshDriver() != aRefreshDriver) { + return true; + } + + nsCOMPtr<nsIDocument> kungFuDeathGrip(aDocument); + + context->AnimationManager()->DispatchEvents(); + aDocument->EnumerateSubDocuments(DispatchAnimationEventsOnSubDocuments, + nullptr); + + return true; +} + +void +nsRefreshDriver::DispatchAnimationEvents() +{ + if (!mPresContext) { + return; + } + + nsIDocument* doc = mPresContext->Document(); + DispatchAnimationEventsOnSubDocuments(doc, this); +} + void nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime) { // Grab all of our frame request callbacks up front. nsTArray<DocumentFrameCallbacks> frameRequestCallbacks(mFrameRequestCallbackDocs.Length() + mThrottledFrameRequestCallbackDocs.Length()); @@ -1659,16 +1694,17 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, StopTimer(); return; } } if (i == 0) { // This is the Flush_Style case. + DispatchAnimationEvents(); DispatchPendingEvents(); RunFrameRequestCallbacks(aNowTime); if (mPresContext && mPresContext->GetPresShell()) { bool tracingStyleFlush = false; nsAutoTArray<nsIPresShell*, 16> observers; observers.AppendElements(mStyleFlushObservers); for (uint32_t j = observers.Length();
--- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -317,16 +317,17 @@ private: } mozilla::Maybe<mozilla::TimeStamp> mStartTime; RequestTable mEntries; }; typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable; void DispatchPendingEvents(); + void DispatchAnimationEvents(); void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime); void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime); enum EnsureTimerStartedFlags { eNone = 0, eAdjustingTimer = 1 << 0, eAllowTimeToGoBackwards = 1 << 1
--- a/layout/style/AnimationCommon.cpp +++ b/layout/style/AnimationCommon.cpp @@ -992,9 +992,29 @@ AnimationCollection::HasCurrentAnimation effect->HasAnimationOfProperties(aProperties, aPropertyCount)) { return true; } } return false; } +nsPresContext* +OwningElementRef::GetRenderedPresContext() const +{ + if (!mElement) { + return nullptr; + } + + nsIDocument* doc = mElement->GetComposedDoc(); + if (!doc) { + return nullptr; + } + + nsIPresShell* shell = doc->GetShell(); + if (!shell) { + return nullptr; + } + + return shell->GetPresContext(); +} + } // namespace mozilla
--- a/layout/style/AnimationCommon.h +++ b/layout/style/AnimationCommon.h @@ -502,16 +502,24 @@ public: return mPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement || (mPseudoType == nsCSSPseudoElements::ePseudo_before && aOther.mPseudoType == nsCSSPseudoElements::ePseudo_after); } bool IsSet() const { return !!mElement; } + void GetElement(dom::Element*& aElement, + nsCSSPseudoElements::Type& aPseudoType) const { + aElement = mElement; + aPseudoType = mPseudoType; + } + + nsPresContext* GetRenderedPresContext() const; + private: dom::Element* MOZ_NON_OWNING_REF mElement; nsCSSPseudoElements::Type mPseudoType; }; } // namespace mozilla #endif /* !defined(mozilla_css_AnimationCommon_h) */
--- a/layout/style/nsAnimationManager.cpp +++ b/layout/style/nsAnimationManager.cpp @@ -108,16 +108,23 @@ CSSAnimation::PauseFromStyle() // and then updated animation-play-state. It's an unusual case and there's // no obvious way to pass on the exception information so we just silently // fail for now. if (rv.Failed()) { NS_WARNING("Unexpected exception pausing animation - silently failing"); } } +void +CSSAnimation::Tick() +{ + Animation::Tick(); + QueueEvents(); +} + bool CSSAnimation::HasLowerCompositeOrderThan(const Animation& aOther) const { // 0. Object-equality case if (&aOther == this) { return false; } @@ -153,22 +160,50 @@ CSSAnimation::HasLowerCompositeOrderThan return mOwningElement.LessThan(otherAnimation->OwningElement()); } // 4. (Same element and pseudo): Sort by position in animation-name return mSequenceNum < otherAnimation->mSequenceNum; } void -CSSAnimation::QueueEvents(EventArray& aEventsToDispatch) +CSSAnimation::QueueEvents() { if (!mEffect) { return; } + // CSS animations dispatch events at their owning element. This allows + // script to repurpose a CSS animation to target a different element, + // to use a group effect (which has no obvious "target element"), or + // to remove the animation effect altogether whilst still getting + // animation events. + // + // It does mean, however, that for a CSS animation that has no owning + // element (e.g. it was created using the CSSAnimation constructor or + // disassociated from CSS) no events are fired. If it becomes desirable + // for these animations to still fire events we should spec the concept + // of the "original owning element" or "event target" and allow script + // to set it when creating a CSSAnimation object. + if (!mOwningElement.IsSet()) { + return; + } + + dom::Element* owningElement; + nsCSSPseudoElements::Type owningPseudoType; + mOwningElement.GetElement(owningElement, owningPseudoType); + MOZ_ASSERT(owningElement, "Owning element should be set"); + + // Get the nsAnimationManager so we can queue events on it + nsPresContext* presContext = mOwningElement.GetRenderedPresContext(); + if (!presContext) { + return; + } + nsAnimationManager* manager = presContext->AnimationManager(); + ComputedTiming computedTiming = mEffect->GetComputedTiming(); if (computedTiming.mPhase == ComputedTiming::AnimationPhase_Null) { return; // do nothing } // Note that script can change the start time, so we have to handle moving // backwards through the animation as well as forwards. An 'animationstart' @@ -195,38 +230,34 @@ CSSAnimation::QueueEvents(EventArray& aE if (computedTiming.mPhase == ComputedTiming::AnimationPhase_Before) { mPreviousPhaseOrIteration = PREVIOUS_PHASE_BEFORE; } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) { mPreviousPhaseOrIteration = computedTiming.mCurrentIteration; } else if (computedTiming.mPhase == ComputedTiming::AnimationPhase_After) { mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER; } - dom::Element* target; - nsCSSPseudoElements::Type targetPseudoType; - mEffect->GetTarget(target, targetPseudoType); - uint32_t message; if (!wasActive && isActive) { message = NS_ANIMATION_START; } else if (wasActive && !isActive) { message = NS_ANIMATION_END; } else if (wasActive && isActive && !isSameIteration) { message = NS_ANIMATION_ITERATION; } else if (skippedActivePhase) { // First notifying for start of 0th iteration by appending an // 'animationstart': StickyTimeDuration elapsedTime = std::min(StickyTimeDuration(mEffect->InitialAdvance()), computedTiming.mActiveDuration); - AnimationEventInfo ei(target, mAnimationName, NS_ANIMATION_START, + AnimationEventInfo ei(owningElement, mAnimationName, NS_ANIMATION_START, elapsedTime, - PseudoTypeAsString(targetPseudoType)); - aEventsToDispatch.AppendElement(ei); + PseudoTypeAsString(owningPseudoType)); + manager->QueueEvent(ei); // Then have the shared code below append an 'animationend': message = NS_ANIMATION_END; } else { return; // No events need to be sent } StickyTimeDuration elapsedTime; @@ -236,19 +267,19 @@ CSSAnimation::QueueEvents(EventArray& aE computedTiming.mCurrentIteration; elapsedTime = StickyTimeDuration(std::max(iterationStart, mEffect->InitialAdvance())); } else { MOZ_ASSERT(message == NS_ANIMATION_END); elapsedTime = computedTiming.mActiveDuration; } - AnimationEventInfo ei(target, mAnimationName, message, elapsedTime, - PseudoTypeAsString(targetPseudoType)); - aEventsToDispatch.AppendElement(ei); + AnimationEventInfo ei(owningElement, mAnimationName, message, elapsedTime, + PseudoTypeAsString(owningPseudoType)); + manager->QueueEvent(ei); } CommonAnimationManager* CSSAnimation::GetAnimationManager() const { nsPresContext* context = GetPresContext(); if (!context) { return nullptr; @@ -266,36 +297,16 @@ CSSAnimation::PseudoTypeAsString(nsCSSPs case nsCSSPseudoElements::ePseudo_after: return NS_LITERAL_STRING("::after"); default: return EmptyString(); } } void -nsAnimationManager::UpdateStyleAndEvents(AnimationCollection* aCollection, - TimeStamp aRefreshTime, - EnsureStyleRuleFlags aFlags) -{ - aCollection->EnsureStyleRuleFor(aRefreshTime, aFlags); - QueueEvents(aCollection, mPendingEvents); -} - -void -nsAnimationManager::QueueEvents(AnimationCollection* aCollection, - EventArray& aEventsToDispatch) -{ - for (size_t animIdx = aCollection->mAnimations.Length(); animIdx-- != 0; ) { - CSSAnimation* anim = aCollection->mAnimations[animIdx]->AsCSSAnimation(); - MOZ_ASSERT(anim, "Expected a collection of CSS Animations"); - anim->QueueEvents(aEventsToDispatch); - } -} - -void nsAnimationManager::MaybeUpdateCascadeResults(AnimationCollection* aCollection) { for (size_t animIdx = aCollection->mAnimations.Length(); animIdx-- != 0; ) { CSSAnimation* anim = aCollection->mAnimations[animIdx]->AsCSSAnimation(); if (anim->IsInEffect() != anim->mInEffectForCascadeResults) { // Update our own cascade results. mozilla::dom::Element* element = aCollection->GetElementToRestyle(); bool updatedCascadeResults = false; @@ -498,29 +509,34 @@ nsAnimationManager::CheckAnimationRule(n // Cancel removed animations for (size_t newAnimIdx = newAnimations.Length(); newAnimIdx-- != 0; ) { newAnimations[newAnimIdx]->CancelFromStyle(); } UpdateCascadeResults(aStyleContext, collection); TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh(); - UpdateStyleAndEvents(collection, refreshTime, - EnsureStyleRule_IsNotThrottled); + collection->EnsureStyleRuleFor(refreshTime, EnsureStyleRule_IsNotThrottled); // 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 // nsPresShell::FlushPendingNotifications. if (!mPendingEvents.IsEmpty()) { mPresContext->Document()->SetNeedStyleFlush(); } return GetAnimationRule(aElement, aStyleContext->GetPseudoType()); } +void +nsAnimationManager::QueueEvent(AnimationEventInfo& aEventInfo) +{ + mPendingEvents.AppendElement(aEventInfo); +} + struct KeyframeData { float mKey; uint32_t mIndex; // store original order since sort algorithm is not stable nsCSSKeyframeRule *mRule; }; struct KeyframeDataComparator { bool Equals(const KeyframeData& A, const KeyframeData& B) const { @@ -963,40 +979,39 @@ nsAnimationManager::FlushAnimations(Flus collection->Tick(); bool canThrottleTick = aFlags == Can_Throttle && collection->CanPerformOnCompositorThread( AnimationCollection::CanAnimateFlags(0)) && collection->CanThrottleAnimation(now); nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = collection->mStyleRule; - UpdateStyleAndEvents(collection, now, canThrottleTick - ? EnsureStyleRule_IsThrottled - : EnsureStyleRule_IsNotThrottled); + collection->EnsureStyleRuleFor(now, canThrottleTick + ? EnsureStyleRule_IsThrottled + : EnsureStyleRule_IsNotThrottled); if (oldStyleRule != collection->mStyleRule) { collection->PostRestyleForAnimation(mPresContext); } else { didThrottle = true; } } if (didThrottle) { mPresContext->Document()->SetNeedStyleFlush(); } MaybeStartOrStopObservingRefreshDriver(); - - DispatchEvents(); // may destroy us } void nsAnimationManager::DoDispatchEvents() { EventArray events; mPendingEvents.SwapElements(events); + // FIXME: Sort events here in timeline order, then document order for (uint32_t i = 0, i_end = events.Length(); i < i_end; ++i) { AnimationEventInfo &info = events[i]; EventDispatcher::Dispatch(info.mElement, mPresContext, &info.mEvent); if (!mPresContext) { break; } }
--- a/layout/style/nsAnimationManager.h +++ b/layout/style/nsAnimationManager.h @@ -96,16 +96,18 @@ public: void PauseFromStyle(); void CancelFromStyle() override { mOwningElement = OwningElementRef(); Animation::CancelFromStyle(); MOZ_ASSERT(mSequenceNum == kUnsequenced); } + void Tick() override; + bool IsStylePaused() const { return mIsStylePaused; } bool HasLowerCompositeOrderThan(const Animation& aOther) const override; bool IsUsingCustomCompositeOrder() const override { return mOwningElement.IsSet(); } @@ -116,18 +118,16 @@ public: } void CopyAnimationIndex(const CSSAnimation& aOther) { MOZ_ASSERT(IsUsingCustomCompositeOrder() && aOther.IsUsingCustomCompositeOrder()); mSequenceNum = aOther.mSequenceNum; } - void QueueEvents(EventArray& aEventsToDispatch); - // Returns the element or pseudo-element whose animation-name property // this CSSAnimation corresponds to (if any). This is used for determining // the relative composite order of animations generated from CSS markup. // // Typically this will be the same as the target element of the keyframe // effect associated with this animation. However, it can differ in the // following circumstances: // @@ -160,16 +160,18 @@ public: protected: virtual ~CSSAnimation() { MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared " "before a CSS animation is destroyed"); } virtual css::CommonAnimationManager* GetAnimationManager() const override; + void QueueEvents(); + static nsString PseudoTypeAsString(nsCSSPseudoElements::Type aPseudoType); nsString mAnimationName; // The (pseudo-)element whose computed animation-name refers to this // animation (if any). OwningElementRef mOwningElement; @@ -241,22 +243,16 @@ class nsAnimationManager final : public mozilla::css::CommonAnimationManager { public: explicit nsAnimationManager(nsPresContext *aPresContext) : mozilla::css::CommonAnimationManager(aPresContext) { } - void UpdateStyleAndEvents(mozilla::AnimationCollection* aEA, - mozilla::TimeStamp aRefreshTime, - mozilla::EnsureStyleRuleFlags aFlags); - void QueueEvents(mozilla::AnimationCollection* aEA, - mozilla::EventArray &aEventsToDispatch); - void MaybeUpdateCascadeResults(mozilla::AnimationCollection* aCollection); // nsIStyleRuleProcessor (parts) virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_MUST_OVERRIDE override; virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_MUST_OVERRIDE override; @@ -275,30 +271,39 @@ public: * * aStyleContext may be a style context for aElement or for its * :before or :after pseudo-element. */ nsIStyleRule* CheckAnimationRule(nsStyleContext* aStyleContext, mozilla::dom::Element* aElement); /** + * Add a pending event. + */ + void QueueEvent(mozilla::AnimationEventInfo& aEventInfo); + + /** * Dispatch any pending events. We accumulate animationend and * animationiteration events only during refresh driver notifications * (and dispatch them at the end of such notifications), but we * accumulate animationstart events at other points when style * contexts are created. */ void DispatchEvents() { // Fast-path the common case: no events if (!mPendingEvents.IsEmpty()) { DoDispatchEvents(); } } + void ClearEventQueue() { mPendingEvents.Clear(); } + protected: + virtual ~nsAnimationManager() {} + virtual nsIAtom* GetAnimationsAtom() override { return nsGkAtoms::animationsProperty; } virtual nsIAtom* GetAnimationsBeforeAtom() override { return nsGkAtoms::animationsOfBeforeProperty; } virtual nsIAtom* GetAnimationsAfterAtom() override { return nsGkAtoms::animationsOfAfterProperty;