Bug 1180125 part 1 - Queue and dispatch CSS animation events as a separate step; r=dbaron

This patch prepares the way for script-generated events by making
event dispatch a separate process that happens after sampling animations.
This will allow us to sample animations from their associated timeline
(removing the need for a further manager to tracker script-generated
animations).

Furthermore, once we sample animations from timelines the order in which they
are sampled is likely to be more or less random so by making event dispatch at
separate step, we have an opportunity to sort the events and dispatch in
a consistent and sensible order. It also ensures that event callbacks will
not be run until all animations (including transitions) have been updated
ensuring they see a consistent view of timing properties.

This patch only affects event handling for CSS animations. Transitions will
be dealt with in a subsequent patch.
This commit is contained in:
Brian Birtles 2015-07-29 10:57:39 +09:00
Родитель 8a25d40d2a
Коммит 880fd89013
8 изменённых файлов: 136 добавлений и 47 удалений

Просмотреть файл

@ -138,7 +138,7 @@ public:
virtual void CancelFromStyle() { DoCancel(); }
void Tick();
virtual void Tick();
/**
* Set the time to use for starting or pausing a pending animation.

Просмотреть файл

@ -1250,6 +1250,10 @@ PresShell::Destroy()
}
}
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(!).

Просмотреть файл

@ -64,6 +64,7 @@
#include "nsThreadUtils.h"
#include "mozilla/unused.h"
#include "mozilla/TimelineConsumers.h"
#include "nsAnimationManager.h"
#ifdef MOZ_NUWA_PROCESS
#include "ipc/Nuwa.h"
@ -1489,6 +1490,40 @@ nsRefreshDriver::DispatchPendingEvents()
}
}
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)
{
@ -1664,6 +1699,7 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime)
if (i == 0) {
// This is the Flush_Style case.
DispatchAnimationEvents();
DispatchPendingEvents();
RunFrameRequestCallbacks(aNowTime);

Просмотреть файл

@ -322,6 +322,7 @@ private:
typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
void DispatchPendingEvents();
void DispatchAnimationEvents();
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);

Просмотреть файл

@ -995,4 +995,24 @@ AnimationCollection::HasCurrentAnimationsForProperties(
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

Просмотреть файл

@ -506,6 +506,14 @@ public:
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;

Просмотреть файл

@ -113,6 +113,13 @@ CSSAnimation::PauseFromStyle()
}
}
void
CSSAnimation::Tick()
{
Animation::Tick();
QueueEvents();
}
bool
CSSAnimation::HasLowerCompositeOrderThan(const Animation& aOther) const
{
@ -158,12 +165,40 @@ CSSAnimation::HasLowerCompositeOrderThan(const Animation& aOther) const
}
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) {
@ -200,10 +235,6 @@ CSSAnimation::QueueEvents(EventArray& aEventsToDispatch)
mPreviousPhaseOrIteration = PREVIOUS_PHASE_AFTER;
}
dom::Element* target;
nsCSSPseudoElements::Type targetPseudoType;
mEffect->GetTarget(target, targetPseudoType);
uint32_t message;
if (!wasActive && isActive) {
@ -218,10 +249,10 @@ CSSAnimation::QueueEvents(EventArray& aEventsToDispatch)
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 {
@ -241,9 +272,9 @@ CSSAnimation::QueueEvents(EventArray& aEventsToDispatch)
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*
@ -270,26 +301,6 @@ CSSAnimation::PseudoTypeAsString(nsCSSPseudoElements::Type aPseudoType)
}
}
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)
{
@ -503,8 +514,7 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
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
@ -516,6 +526,12 @@ nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
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
@ -968,9 +984,9 @@ nsAnimationManager::FlushAnimations(FlushFlags aFlags)
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 {
@ -983,8 +999,6 @@ nsAnimationManager::FlushAnimations(FlushFlags aFlags)
}
MaybeStartOrStopObservingRefreshDriver();
DispatchEvents(); // may destroy us
}
void
@ -992,6 +1006,7 @@ 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);

Просмотреть файл

@ -101,6 +101,8 @@ public:
MOZ_ASSERT(mSequenceNum == kUnsequenced);
}
void Tick() override;
bool IsStylePaused() const { return mIsStylePaused; }
bool HasLowerCompositeOrderThan(const Animation& aOther) const override;
@ -121,8 +123,6 @@ public:
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.
@ -165,6 +165,8 @@ protected:
}
virtual css::CommonAnimationManager* GetAnimationManager() const override;
void QueueEvents();
static nsString PseudoTypeAsString(nsCSSPseudoElements::Type aPseudoType);
nsString mAnimationName;
@ -246,12 +248,6 @@ public:
{
}
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)
@ -279,6 +275,11 @@ public:
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
@ -293,7 +294,11 @@ public:
}
}
void ClearEventQueue() { mPendingEvents.Clear(); }
protected:
virtual ~nsAnimationManager() {}
virtual nsIAtom* GetAnimationsAtom() override {
return nsGkAtoms::animationsProperty;
}