summaryrefslogtreecommitdiffstats
path: root/layout/style
diff options
context:
space:
mode:
Diffstat (limited to 'layout/style')
-rw-r--r--layout/style/AnimationCommon.h20
-rw-r--r--layout/style/nsAnimationManager.cpp179
-rw-r--r--layout/style/nsAnimationManager.h27
-rw-r--r--layout/style/nsTransitionManager.cpp113
-rw-r--r--layout/style/nsTransitionManager.h11
-rw-r--r--layout/style/test/test_animations.html3
-rw-r--r--layout/style/test/test_animations_event_handler_attribute.html40
-rw-r--r--layout/style/test/test_animations_event_order.html117
-rw-r--r--layout/style/test/test_animations_omta.html3
9 files changed, 373 insertions, 140 deletions
diff --git a/layout/style/AnimationCommon.h b/layout/style/AnimationCommon.h
index 37030411c..025c034a4 100644
--- a/layout/style/AnimationCommon.h
+++ b/layout/style/AnimationCommon.h
@@ -251,6 +251,26 @@ ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
aField.Traverse(&aCallback, aName);
}
+// Return the TransitionPhase or AnimationPhase to use when the animation
+// doesn't have a target effect.
+template <typename PhaseType>
+PhaseType GetAnimationPhaseWithoutEffect(const dom::Animation& aAnimation)
+{
+ MOZ_ASSERT(!aAnimation.GetEffect(),
+ "Should only be called when we do not have an effect");
+
+ Nullable<TimeDuration> currentTime = aAnimation.GetCurrentTime();
+ if (currentTime.IsNull()) {
+ return PhaseType::Idle;
+ }
+
+ // If we don't have a target effect, the duration will be zero so the phase is
+ // 'before' if the current time is less than zero.
+ return currentTime.Value() < TimeDuration()
+ ? PhaseType::Before
+ : PhaseType::After;
+};
+
} // namespace mozilla
#endif /* !defined(mozilla_css_AnimationCommon_h) */
diff --git a/layout/style/nsAnimationManager.cpp b/layout/style/nsAnimationManager.cpp
index ed2b5afc7..aa1b6fe78 100644
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -33,11 +33,15 @@ using mozilla::dom::AnimationPlayState;
using mozilla::dom::KeyframeEffectReadOnly;
using mozilla::dom::CSSAnimation;
+typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
+
namespace {
-// Pair of an event message and elapsed time used when determining the set of
-// events to queue.
-typedef Pair<EventMessage, StickyTimeDuration> EventPair;
+struct AnimationEventParams {
+ EventMessage mMessage;
+ StickyTimeDuration mElapsedTime;
+ TimeStamp mTimeStamp;
+};
} // anonymous namespace
@@ -154,12 +158,8 @@ CSSAnimation::HasLowerCompositeOrderThan(const CSSAnimation& aOther) const
}
void
-CSSAnimation::QueueEvents()
+CSSAnimation::QueueEvents(StickyTimeDuration aActiveTime)
{
- if (!mEffect) {
- return;
- }
-
// If the animation is pending, we ignore animation events until we finish
// pending.
if (mPendingState != PendingState::NotPending) {
@@ -194,77 +194,116 @@ CSSAnimation::QueueEvents()
}
nsAnimationManager* manager = presContext->AnimationManager();
- ComputedTiming computedTiming = mEffect->GetComputedTiming();
- if (computedTiming.mPhase == ComputedTiming::AnimationPhase::Null) {
- return; // do nothing
+ const StickyTimeDuration zeroDuration;
+ uint64_t currentIteration = 0;
+ ComputedTiming::AnimationPhase currentPhase;
+ StickyTimeDuration intervalStartTime;
+ StickyTimeDuration intervalEndTime;
+ StickyTimeDuration iterationStartTime;
+
+ if (!mEffect) {
+ currentPhase = GetAnimationPhaseWithoutEffect
+ <ComputedTiming::AnimationPhase>(*this);
+ } else {
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+ currentPhase = computedTiming.mPhase;
+ currentIteration = computedTiming.mCurrentIteration;
+ if (currentPhase == mPreviousPhase &&
+ currentIteration == mPreviousIteration) {
+ return;
+ }
+ intervalStartTime =
+ std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration),
+ zeroDuration);
+ intervalEndTime =
+ std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration),
+ zeroDuration);
+
+ uint64_t iterationBoundary = mPreviousIteration > currentIteration
+ ? currentIteration + 1
+ : currentIteration;
+ iterationStartTime =
+ computedTiming.mDuration.MultDouble(
+ (iterationBoundary - computedTiming.mIterationStart));
}
- // Note that script can change the start time, so we have to handle moving
- // backwards through the animation as well as forwards. An 'animationstart'
- // is dispatched if we enter the active phase (regardless if that is from
- // before or after the animation's active phase). An 'animationend' is
- // dispatched if we leave the active phase (regardless if that is to before
- // or after the animation's active phase).
-
- bool wasActive = mPreviousPhaseOrIteration != PREVIOUS_PHASE_BEFORE &&
- mPreviousPhaseOrIteration != PREVIOUS_PHASE_AFTER;
- bool isActive =
- computedTiming.mPhase == ComputedTiming::AnimationPhase::Active;
- bool isSameIteration =
- computedTiming.mCurrentIteration == mPreviousPhaseOrIteration;
- bool skippedActivePhase =
- (mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
- computedTiming.mPhase == ComputedTiming::AnimationPhase::After) ||
- (mPreviousPhaseOrIteration == PREVIOUS_PHASE_AFTER &&
- computedTiming.mPhase == ComputedTiming::AnimationPhase::Before);
- bool skippedFirstIteration =
- isActive &&
- mPreviousPhaseOrIteration == PREVIOUS_PHASE_BEFORE &&
- computedTiming.mCurrentIteration > 0;
-
- MOZ_ASSERT(!skippedActivePhase || (!isActive && !wasActive),
- "skippedActivePhase only makes sense if we were & are inactive");
-
- 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;
+ TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+ TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
+ TimeStamp iterationTimeStamp = ElapsedTimeToTimeStamp(iterationStartTime);
+
+ AutoTArray<AnimationEventParams, 2> events;
+
+ // Handle cancel event first
+ if ((mPreviousPhase != AnimationPhase::Idle &&
+ mPreviousPhase != AnimationPhase::After) &&
+ currentPhase == AnimationPhase::Idle) {
+ TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
+ events.AppendElement(AnimationEventParams{ eAnimationCancel,
+ aActiveTime,
+ activeTimeStamp });
}
- AutoTArray<EventPair, 2> events;
- StickyTimeDuration initialAdvance = StickyTimeDuration(InitialAdvance());
- StickyTimeDuration iterationStart = computedTiming.mDuration *
- computedTiming.mCurrentIteration;
- const StickyTimeDuration& activeDuration = computedTiming.mActiveDuration;
-
- if (skippedFirstIteration) {
- // Notify animationstart and animationiteration in same tick.
- events.AppendElement(EventPair(eAnimationStart, initialAdvance));
- events.AppendElement(EventPair(eAnimationIteration,
- std::max(iterationStart, initialAdvance)));
- } else if (!wasActive && isActive) {
- events.AppendElement(EventPair(eAnimationStart, initialAdvance));
- } else if (wasActive && !isActive) {
- events.AppendElement(EventPair(eAnimationEnd, activeDuration));
- } else if (wasActive && isActive && !isSameIteration) {
- events.AppendElement(EventPair(eAnimationIteration, iterationStart));
- } else if (skippedActivePhase) {
- events.AppendElement(EventPair(eAnimationStart,
- std::min(initialAdvance, activeDuration)));
- events.AppendElement(EventPair(eAnimationEnd, activeDuration));
- } else {
- return; // No events need to be sent
+ switch (mPreviousPhase) {
+ case AnimationPhase::Idle:
+ case AnimationPhase::Before:
+ if (currentPhase == AnimationPhase::Active) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == AnimationPhase::After) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalStartTime,
+ startTimeStamp });
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+ case AnimationPhase::Active:
+ if (currentPhase == AnimationPhase::Before) {
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == AnimationPhase::Active) {
+ // The currentIteration must have changed or element we would have
+ // returned early above.
+ MOZ_ASSERT(currentIteration != mPreviousIteration);
+ events.AppendElement(AnimationEventParams{ eAnimationIteration,
+ iterationStartTime,
+ iterationTimeStamp });
+ } else if (currentPhase == AnimationPhase::After) {
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
+ case AnimationPhase::After:
+ if (currentPhase == AnimationPhase::Before) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalEndTime,
+ startTimeStamp});
+ events.AppendElement(AnimationEventParams{ eAnimationEnd,
+ intervalStartTime,
+ endTimeStamp });
+ } else if (currentPhase == AnimationPhase::Active) {
+ events.AppendElement(AnimationEventParams{ eAnimationStart,
+ intervalEndTime,
+ endTimeStamp });
+ }
+ break;
}
- for (const EventPair& pair : events){
+ mPreviousPhase = currentPhase;
+ mPreviousIteration = currentIteration;
+
+ for (const AnimationEventParams& event : events){
manager->QueueEvent(
AnimationEventInfo(owningElement, owningPseudoType,
- pair.first(), mAnimationName,
- pair.second(),
- ElapsedTimeToTimeStamp(pair.second()),
+ event.mMessage, mAnimationName,
+ event.mElapsedTime, event.mTimeStamp,
this));
}
}
diff --git a/layout/style/nsAnimationManager.h b/layout/style/nsAnimationManager.h
index abe3aeeb8..d838d090a 100644
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -76,7 +76,8 @@ public:
, mIsStylePaused(false)
, mPauseShouldStick(false)
, mNeedsNewAnimationIndexWhenRun(false)
- , mPreviousPhaseOrIteration(PREVIOUS_PHASE_BEFORE)
+ , mPreviousPhase(ComputedTiming::AnimationPhase::Idle)
+ , mPreviousIteration(0)
{
// We might need to drop this assertion once we add a script-accessible
// constructor but for animations generated from CSS markup the
@@ -109,8 +110,6 @@ public:
void PauseFromStyle();
void CancelFromStyle() override
{
- mOwningElement = OwningElementRef();
-
// When an animation is disassociated with style it enters an odd state
// where its composite order is undefined until it first transitions
// out of the idle state.
@@ -125,10 +124,15 @@ public:
mNeedsNewAnimationIndexWhenRun = true;
Animation::CancelFromStyle();
+
+ // We need to do this *after* calling CancelFromStyle() since
+ // CancelFromStyle might synchronously trigger a cancel event for which
+ // we need an owning element to target the event at.
+ mOwningElement = OwningElementRef();
}
void Tick() override;
- void QueueEvents();
+ void QueueEvents(StickyTimeDuration aActiveTime = StickyTimeDuration());
bool IsStylePaused() const { return mIsStylePaused; }
@@ -157,6 +161,10 @@ public:
// reflect changes to that markup.
bool IsTiedToMarkup() const { return mOwningElement.IsSet(); }
+ void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override {
+ QueueEvents(aActiveTime);
+ }
+
protected:
virtual ~CSSAnimation()
{
@@ -257,13 +265,10 @@ protected:
// its animation index should be updated.
bool mNeedsNewAnimationIndexWhenRun;
- enum {
- PREVIOUS_PHASE_BEFORE = uint64_t(-1),
- PREVIOUS_PHASE_AFTER = uint64_t(-2)
- };
- // One of the PREVIOUS_PHASE_* constants, or an integer for the iteration
- // whose start we last notified on.
- uint64_t mPreviousPhaseOrIteration;
+ // Phase and current iteration from the previous time we queued events.
+ // This is used to determine what new events to dispatch.
+ ComputedTiming::AnimationPhase mPreviousPhase;
+ uint64_t mPreviousIteration;
};
} /* namespace dom */
diff --git a/layout/style/nsTransitionManager.cpp b/layout/style/nsTransitionManager.cpp
index 4a1a5b7ad..118702e8f 100644
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -46,8 +46,6 @@ using mozilla::dom::KeyframeEffectReadOnly;
using namespace mozilla;
using namespace mozilla::css;
-typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
-
namespace {
struct TransitionEventParams {
EventMessage mMessage;
@@ -180,10 +178,9 @@ CSSTransition::UpdateTiming(SeekFlag aSeekFlag, SyncNotifyFlag aSyncNotifyFlag)
}
void
-CSSTransition::QueueEvents()
+CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
{
- if (!mEffect ||
- !mOwningElement.IsSet()) {
+ if (!mOwningElement.IsSet()) {
return;
}
@@ -197,69 +194,123 @@ CSSTransition::QueueEvents()
return;
}
- ComputedTiming computedTiming = mEffect->GetComputedTiming();
- const StickyTimeDuration zeroDuration;
- StickyTimeDuration intervalStartTime =
- std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
- computedTiming.mActiveDuration), zeroDuration);
- StickyTimeDuration intervalEndTime =
- std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
- computedTiming.mActiveDuration), zeroDuration);
+ const StickyTimeDuration zeroDuration = StickyTimeDuration();
+
+ TransitionPhase currentPhase;
+ StickyTimeDuration intervalStartTime;
+ StickyTimeDuration intervalEndTime;
+
+ if (!mEffect) {
+ currentPhase = GetAnimationPhaseWithoutEffect<TransitionPhase>(*this);
+ intervalStartTime = zeroDuration;
+ intervalEndTime = zeroDuration;
+ } else {
+ ComputedTiming computedTiming = mEffect->GetComputedTiming();
+
+ currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
+ intervalStartTime =
+ std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+ intervalEndTime =
+ std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+ computedTiming.mActiveDuration), zeroDuration);
+ }
// TimeStamps to use for ordering the events when they are dispatched. We
// use a TimeStamp so we can compare events produced by different elements,
// perhaps even with different timelines.
// The zero timestamp is for transitionrun events where we ignore the delay
// for the purpose of ordering events.
- TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
- TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
+ TimeStamp zeroTimeStamp = AnimationTimeToTimeStamp(zeroDuration);
+ TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+ TimeStamp endTimeStamp = ElapsedTimeToTimeStamp(intervalEndTime);
- TransitionPhase currentPhase;
if (mPendingState != PendingState::NotPending &&
(mPreviousTransitionPhase == TransitionPhase::Idle ||
mPreviousTransitionPhase == TransitionPhase::Pending))
{
currentPhase = TransitionPhase::Pending;
- } else {
- currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
}
AutoTArray<TransitionEventParams, 3> events;
+
+ // Handle cancel events firts
+ if (mPreviousTransitionPhase != TransitionPhase::Idle &&
+ currentPhase == TransitionPhase::Idle) {
+ TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
+ events.AppendElement(TransitionEventParams{ eTransitionCancel,
+ aActiveTime,
+ activeTimeStamp });
+ }
+
+ // All other events
switch (mPreviousTransitionPhase) {
case TransitionPhase::Idle:
- if (currentPhase == TransitionPhase::After) {
+ if (currentPhase == TransitionPhase::Pending ||
+ currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ } else if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionRun,
+ intervalStartTime,
+ zeroTimeStamp });
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalEndTime,
- endTimeStamp });
+ intervalEndTime,
+ endTimeStamp });
}
break;
case TransitionPhase::Pending:
case TransitionPhase::Before:
- if (currentPhase == TransitionPhase::After) {
+ if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::After) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalStartTime,
+ startTimeStamp });
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalEndTime,
- endTimeStamp });
+ intervalEndTime,
+ endTimeStamp });
}
break;
case TransitionPhase::Active:
if (currentPhase == TransitionPhase::After) {
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalEndTime,
- endTimeStamp });
+ intervalEndTime,
+ endTimeStamp });
} else if (currentPhase == TransitionPhase::Before) {
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalStartTime,
- startTimeStamp });
+ intervalStartTime,
+ startTimeStamp });
}
break;
case TransitionPhase::After:
- if (currentPhase == TransitionPhase::Before) {
+ if (currentPhase == TransitionPhase::Active) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalEndTime,
+ startTimeStamp });
+ } else if (currentPhase == TransitionPhase::Before) {
+ events.AppendElement(TransitionEventParams{ eTransitionStart,
+ intervalEndTime,
+ startTimeStamp });
events.AppendElement(TransitionEventParams{ eTransitionEnd,
- intervalStartTime,
- endTimeStamp });
+ intervalStartTime,
+ endTimeStamp });
}
break;
}
diff --git a/layout/style/nsTransitionManager.h b/layout/style/nsTransitionManager.h
index 56ec61572..1c48cc8cd 100644
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -214,6 +214,10 @@ public:
const TimeDuration& aStartTime,
double aPlaybackRate);
+ void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override {
+ QueueEvents(aActiveTime);
+ }
+
protected:
virtual ~CSSTransition()
{
@@ -225,7 +229,10 @@ protected:
void UpdateTiming(SeekFlag aSeekFlag,
SyncNotifyFlag aSyncNotifyFlag) override;
- void QueueEvents();
+ void QueueEvents(StickyTimeDuration activeTime = StickyTimeDuration());
+
+
+ enum class TransitionPhase;
// The (pseudo-)element whose computed transition-property refers to this
// transition (if any).
@@ -250,7 +257,7 @@ protected:
// to be queued on this tick.
// See: https://drafts.csswg.org/css-transitions-2/#transition-phase
enum class TransitionPhase {
- Idle = static_cast<int>(ComputedTiming::AnimationPhase::Null),
+ Idle = static_cast<int>(ComputedTiming::AnimationPhase::Idle),
Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
After = static_cast<int>(ComputedTiming::AnimationPhase::After),
diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html
index eaccba122..4019af77f 100644
--- a/layout/style/test/test_animations.html
+++ b/layout/style/test/test_animations.html
@@ -1195,9 +1195,6 @@ is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01,
"large negative delay test at 0ms");
check_events([{ type: 'animationstart', target: div,
animationName: 'anim2', elapsedTime: 3.6,
- pseudoElement: "" },
- { type: 'animationiteration', target: div,
- animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" }],
"right after start in large negative delay test");
advance_clock(380);
diff --git a/layout/style/test/test_animations_event_handler_attribute.html b/layout/style/test/test_animations_event_handler_attribute.html
index e5def2b34..036a77779 100644
--- a/layout/style/test/test_animations_event_handler_attribute.html
+++ b/layout/style/test/test_animations_event_handler_attribute.html
@@ -88,21 +88,55 @@ checkReceivedEvents("animationend", targets);
targets.forEach(div => { div.remove(); });
-// 2. Test CSS Transition event handlers.
+// 2a. Test CSS Transition event handlers (without transitioncancel)
-var targets = createAndRegisterTargets([ 'ontransitionend' ]);
+var targets = createAndRegisterTargets([ 'ontransitionrun',
+ 'ontransitionstart',
+ 'ontransitionend',
+ 'ontransitioncancel' ]);
targets.forEach(div => {
- div.style.transition = 'margin-left 100ms';
+ div.style.transition = 'margin-left 100ms 200ms';
getComputedStyle(div).marginLeft; // flush
div.style.marginLeft = "200px";
getComputedStyle(div).marginLeft; // flush
});
+advance_clock(0);
+checkReceivedEvents("transitionrun", targets);
+
+advance_clock(200);
+checkReceivedEvents("transitionstart", targets);
+
advance_clock(100);
checkReceivedEvents("transitionend", targets);
targets.forEach(div => { div.remove(); });
+// 2b. Test CSS Transition cancel event handler.
+
+var targets = createAndRegisterTargets([ 'ontransitioncancel' ]);
+targets.forEach(div => {
+ div.style.transition = 'margin-left 100ms 200ms';
+ getComputedStyle(div).marginLeft; // flush
+ div.style.marginLeft = "200px";
+ getComputedStyle(div).marginLeft; // flush
+});
+
+advance_clock(200);
+
+targets.forEach(div => {
+ div.style.display = "none"
+});
+getComputedStyle(targets[0]).display; // flush
+
+advance_clock(0);
+checkReceivedEvents("transitioncancel", targets);
+
+advance_clock(100);
+targets.forEach( div => { is(div.receivedEventType, undefined); });
+
+targets.forEach(div => { div.remove(); });
+
// 3. Test prefixed CSS Animation event handlers.
var targets = createAndRegisterTargets([ 'onwebkitanimationstart',
diff --git a/layout/style/test/test_animations_event_order.html b/layout/style/test/test_animations_event_order.html
index 5af7639cc..7204934d2 100644
--- a/layout/style/test/test_animations_event_order.html
+++ b/layout/style/test/test_animations_event_order.html
@@ -46,7 +46,10 @@ var gDisplay = document.getElementById('display');
[ 'animationstart',
'animationiteration',
'animationend',
- 'transitionend' ]
+ 'transitionrun',
+ 'transitionstart',
+ 'transitionend',
+ 'transitioncancel' ]
.forEach(event =>
gDisplay.addEventListener(event,
event => gEventsReceived.push(event),
@@ -322,9 +325,13 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[1], 'transitionend' ],
- 'Simultaneous transitionend on siblings');
+ 'Simultaneous transitionrun/start/end on siblings');
divs.forEach(div => div.remove());
divs = [];
@@ -360,10 +367,16 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[2], 'transitionrun' ],
+ [ divs[2], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[2], 'transitionend' ],
[ divs[1], 'transitionend' ],
- 'Simultaneous transitionend on children');
+ 'Simultaneous transitionrun/start/end on children');
divs.forEach(div => div.remove());
divs = [];
@@ -408,11 +421,19 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[0], '::before', 'transitionrun' ],
+ [ divs[0], '::before', 'transitionstart' ],
+ [ divs[0], '::after', 'transitionrun' ],
+ [ divs[0], '::after', 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[0], '::before', 'transitionend' ],
[ divs[0], '::after', 'transitionend' ],
[ divs[1], 'transitionend' ],
- 'Simultaneous transitionend on pseudo-elements');
+ 'Simultaneous transitionrun/start/end on pseudo-elements');
divs.forEach(div => div.remove());
divs = [];
@@ -441,9 +462,13 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[1], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
[ divs[0], 'transitionend' ],
- 'Sorting of transitionend events by time');
+ 'Sorting of transitionrun/start/end events by time');
divs.forEach(div => div.remove());
divs = [];
@@ -468,9 +493,13 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10 * 1000);
-checkEventOrder([ divs[1], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
[ divs[0], 'transitionend' ],
- 'Sorting of transitionend events by time' +
+ 'Sorting of transitionrun/start/end events by time' +
'(including delay)');
divs.forEach(div => div.remove());
@@ -492,9 +521,14 @@ getComputedStyle(div).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ 'margin-left', 'transitionend' ],
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'margin-left', 'transitionend' ],
[ 'opacity', 'transitionend' ],
- 'Sorting of transitionend events by transition-property')
+ 'Sorting of transitionrun/start/end events by ' +
+ 'transition-property')
div.remove();
div = undefined;
@@ -519,7 +553,11 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ divs[0], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionend' ],
[ divs[1], 'transitionend' ],
'Transition events are sorted by document position first, ' +
'before transition-property');
@@ -543,7 +581,11 @@ getComputedStyle(div).marginLeft;
advance_clock(0);
advance_clock(10000);
-checkEventOrder([ 'opacity', 'transitionend' ],
+checkEventOrder([ 'margin-left', 'transitionrun' ],
+ [ 'margin-left', 'transitionstart' ],
+ [ 'opacity', 'transitionrun' ],
+ [ 'opacity', 'transitionstart' ],
+ [ 'opacity', 'transitionend' ],
[ 'margin-left', 'transitionend' ],
'Transition events are sorted by time first, before ' +
'transition-property');
@@ -571,9 +613,50 @@ getComputedStyle(divs[0]).marginLeft;
advance_clock(0);
advance_clock(15 * 1000);
-checkEventOrder([ divs[1], 'transitionend' ],
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitionend' ],
[ divs[0], 'transitionend' ],
- 'Simultaneous transitionend on siblings');
+ 'Simultaneous transitionrun/start/end on siblings');
+
+divs.forEach(div => div.remove());
+divs = [];
+
+// 4j. Test sorting transitions with cancel
+// The order of transitioncancel is based on StyleManager.
+// So this test looks like wrong result at a glance. However
+// the gecko will cancel div1's transition before div2 in this case.
+
+divs = [ document.createElement('div'),
+ document.createElement('div') ];
+divs.forEach((div, i) => {
+ gDisplay.appendChild(div);
+ div.style.marginLeft = '0px';
+ div.setAttribute('id', 'div' + i);
+});
+
+divs[0].style.transition = 'margin-left 10s 5s';
+divs[1].style.transition = 'margin-left 10s';
+
+getComputedStyle(divs[0]).marginLeft;
+divs.forEach(div => div.style.marginLeft = '100px');
+getComputedStyle(divs[0]).marginLeft;
+
+advance_clock(0);
+advance_clock(5 * 1000);
+divs.forEach(div => div.style.display = 'none' );
+getComputedStyle(divs[0]).display;
+advance_clock(10 * 1000);
+
+checkEventOrder([ divs[0], 'transitionrun' ],
+ [ divs[1], 'transitionrun' ],
+ [ divs[1], 'transitionstart' ],
+ [ divs[0], 'transitionstart' ],
+ [ divs[1], 'transitioncancel' ],
+ [ divs[0], 'transitioncancel' ],
+ 'Simultaneous transitionrun/start/cancel on siblings');
divs.forEach(div => div.remove());
divs = [];
diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html
index 4b276c896..0b2a61ecc 100644
--- a/layout/style/test/test_animations_omta.html
+++ b/layout/style/test/test_animations_omta.html
@@ -1408,9 +1408,6 @@ addAsyncAnimTest(function *() {
"large negative delay test at 0ms");
check_events([{ type: 'animationstart', target: gDiv,
animationName: 'anim2', elapsedTime: 3.6,
- pseudoElement: "" },
- { type: 'animationiteration', target: gDiv,
- animationName: 'anim2', elapsedTime: 3.6,
pseudoElement: "" }],
"right after start in large negative delay test");
advance_clock(380);