Commit bbc1223b authored by timloh@chromium.org's avatar timloh@chromium.org

Web Animations: Sort Animations in the AnimationStack

This patch sorts animations in the stack so that they will be applied in the
correct order. Note that due to the way compositor integration currently is
implemented, we don't know the exact time when new animations will be started,
but expect it will be very close to the current timeline time.

http://dev.w3.org/fxtf/web-animations/#the-animation-stack

BUG=

Review URL: https://codereview.chromium.org/210703002

git-svn-id: svn://svn.chromium.org/blink/trunk@170257 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent bc3f0c91
......@@ -56,13 +56,13 @@ AnimationPlayer::AnimationPlayer(DocumentTimeline& timeline, TimedItem* content)
, m_startTime(nullValue())
, m_holdTime(nullValue())
, m_storedTimeLag(0)
, m_sortInfo(nextSequenceNumber(), timeline.effectiveTime())
, m_content(content)
, m_timeline(&timeline)
, m_paused(false)
, m_held(false)
, m_isPausedForTesting(false)
, m_outdated(false)
, m_sequenceNumber(nextSequenceNumber())
{
if (m_content) {
if (m_content->player())
......@@ -93,10 +93,7 @@ double AnimationPlayer::currentTimeWithoutLag() const
{
if (isNull(m_startTime) || !m_timeline)
return 0;
double timelineTime = m_timeline->currentTime();
if (isNull(timelineTime))
timelineTime = 0;
return (timelineTime - m_startTime) * m_playbackRate;
return (m_timeline->effectiveTime() - m_startTime) * m_playbackRate;
}
double AnimationPlayer::currentTimeWithLag() const
......@@ -157,6 +154,7 @@ void AnimationPlayer::setStartTime(double newStartTime)
return;
updateCurrentTimingState(); // Update the value of held
m_startTime = newStartTime;
m_sortInfo.m_startTime = newStartTime;
if (m_held)
return;
updateCurrentTimingState();
......@@ -314,15 +312,14 @@ void AnimationPlayer::cancel()
m_content = nullptr;
}
bool AnimationPlayer::hasLowerPriority(AnimationPlayer* player1, AnimationPlayer* player2)
bool AnimationPlayer::SortInfo::operator<(const SortInfo& other) const
{
if (player1->m_startTime < player2->m_startTime)
ASSERT(!std::isnan(m_startTime) && !std::isnan(other.m_startTime));
if (m_startTime < other.m_startTime)
return true;
if (player1->m_startTime > player2->m_startTime)
if (m_startTime > other.m_startTime)
return false;
if (isNull(player1->m_startTime) != isNull(player2->m_startTime))
return isNull(player1->m_startTime);
return player1->m_sequenceNumber < player2->m_sequenceNumber;
return m_sequenceNumber < other.m_sequenceNumber;
}
void AnimationPlayer::pauseForTesting(double pauseTime)
......
......@@ -97,7 +97,25 @@ public:
void cancelAnimationOnCompositor();
bool hasActiveAnimationsOnCompositor();
static bool hasLowerPriority(AnimationPlayer*, AnimationPlayer*);
class SortInfo {
public:
friend class AnimationPlayer;
bool operator<(const SortInfo& other) const;
private:
SortInfo(unsigned sequenceNumber, double startTime)
: m_sequenceNumber(sequenceNumber)
, m_startTime(startTime)
{ }
unsigned m_sequenceNumber;
double m_startTime;
};
const SortInfo& sortInfo() const { return m_sortInfo; }
static bool hasLowerPriority(AnimationPlayer* player1, AnimationPlayer* player2)
{
return player1->sortInfo() < player2->sortInfo();
}
private:
AnimationPlayer(DocumentTimeline&, TimedItem*);
......@@ -113,6 +131,8 @@ private:
double m_holdTime;
double m_storedTimeLag;
SortInfo m_sortInfo;
RefPtr<TimedItem> m_content;
// FIXME: We should keep the timeline alive and have this as non-null
// but this is tricky to do without Oilpan
......@@ -125,8 +145,6 @@ private:
// This indicates timing information relevant to the player has changed by
// means other than the ordinary progression of time
bool m_outdated;
unsigned m_sequenceNumber;
};
} // namespace
......
......@@ -694,7 +694,8 @@ TEST_F(AnimationAnimationPlayerTest, AttachedAnimationPlayers)
TEST_F(AnimationAnimationPlayerTest, HasLowerPriority)
{
// Note that start time defaults to null
// Sort time defaults to timeline current time
updateTimeline(15);
RefPtr<AnimationPlayer> player1 = timeline->createAnimationPlayer(0);
RefPtr<AnimationPlayer> player2 = timeline->createAnimationPlayer(0);
player2->setStartTime(10);
......@@ -706,11 +707,11 @@ TEST_F(AnimationAnimationPlayerTest, HasLowerPriority)
RefPtr<AnimationPlayer> player6 = timeline->createAnimationPlayer(0);
player6->setStartTime(-10);
Vector<RefPtr<AnimationPlayer> > players;
players.append(player1);
players.append(player3);
players.append(player6);
players.append(player2);
players.append(player5);
players.append(player1);
players.append(player3);
players.append(player4);
for (size_t i = 0; i < players.size(); i++) {
for (size_t j = 0; j < players.size(); j++)
......
......@@ -30,8 +30,8 @@
#include "config.h"
#include "core/animation/AnimationStack.h"
#include "core/animation/Interpolation.h"
#include "core/animation/Interpolation.h"
#include "core/animation/css/CSSAnimations.h"
namespace WebCore {
......@@ -47,6 +47,22 @@ void copyToActiveInterpolationMap(const WillBeHeapVector<RefPtrWillBeMember<WebC
}
}
bool compareAnimations(Animation* animation1, Animation* animation2)
{
ASSERT(animation1->player() && animation2->player());
return AnimationPlayer::hasLowerPriority(animation1->player(), animation2->player());
}
void copyNewAnimationsToActiveInterpolationMap(const Vector<InertAnimation*>& newAnimations, WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> >& result)
{
for (size_t i = 0; i < newAnimations.size(); ++i) {
OwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation> > > sample = newAnimations[i]->sample();
if (sample) {
copyToActiveInterpolationMap(*sample, result);
}
}
}
} // namespace
bool AnimationStack::affects(CSSPropertyID property) const
......@@ -67,30 +83,31 @@ bool AnimationStack::hasActiveAnimationsOnCompositor(CSSPropertyID property) con
return false;
}
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > AnimationStack::activeInterpolations(const AnimationStack* animationStack, const Vector<InertAnimation*>* newAnimations, const HashSet<const AnimationPlayer*>* cancelledAnimationPlayers, Animation::Priority priority)
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > AnimationStack::activeInterpolations(AnimationStack* animationStack, const Vector<InertAnimation*>* newAnimations, const HashSet<const AnimationPlayer*>* cancelledAnimationPlayers, Animation::Priority priority, double timelineCurrentTime)
{
// We don't exactly know when new animations will start, but timelineCurrentTime is a good estimate.
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > result;
if (animationStack) {
const Vector<Animation*>& animations = animationStack->m_activeAnimations;
Vector<Animation*>& animations = animationStack->m_activeAnimations;
std::sort(animations.begin(), animations.end(), compareAnimations);
for (size_t i = 0; i < animations.size(); ++i) {
Animation* animation = animations[i];
if (animation->priority() != priority)
continue;
if (cancelledAnimationPlayers && cancelledAnimationPlayers->contains(animation->player()))
continue;
if (animation->player()->startTime() > timelineCurrentTime && newAnimations) {
copyNewAnimationsToActiveInterpolationMap(*newAnimations, result);
newAnimations = 0;
}
copyToActiveInterpolationMap(animation->activeInterpolations(), result);
}
}
if (newAnimations) {
for (size_t i = 0; i < newAnimations->size(); ++i) {
OwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation> > > sample = newAnimations->at(i)->sample();
if (sample) {
copyToActiveInterpolationMap(*sample, result);
}
}
}
if (newAnimations)
copyNewAnimationsToActiveInterpolationMap(*newAnimations, result);
return result;
}
......
......@@ -53,7 +53,7 @@ public:
bool isEmpty() const { return m_activeAnimations.isEmpty(); }
bool affects(CSSPropertyID) const;
bool hasActiveAnimationsOnCompositor(CSSPropertyID) const;
static WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolations(const AnimationStack*, const Vector<InertAnimation*>* newAnimations, const HashSet<const AnimationPlayer*>* cancelledAnimationPlayers, Animation::Priority);
static WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolations(AnimationStack*, const Vector<InertAnimation*>* newAnimations, const HashSet<const AnimationPlayer*>* cancelledAnimationPlayers, Animation::Priority, double timelineCurrentTime);
private:
Vector<Animation*> m_activeAnimations;
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "core/animation/AnimationStack.h"
#include "core/animation/ActiveAnimations.h"
#include "core/animation/AnimatableDouble.h"
#include "core/animation/AnimationClock.h"
#include "core/animation/DocumentTimeline.h"
#include "core/animation/KeyframeEffectModel.h"
#include <gtest/gtest.h>
using namespace WebCore;
namespace {
class AnimationAnimationStackTest : public ::testing::Test {
protected:
virtual void SetUp()
{
document = Document::create();
document->animationClock().resetTimeForTesting();
timeline = DocumentTimeline::create(document.get());
timeline->setZeroTime(0);
element = document->createElement("foo", ASSERT_NO_EXCEPTION);
}
AnimationPlayer* play(Animation* animation, double startTime)
{
AnimationPlayer* player = timeline->createAnimationPlayer(animation);
player->setStartTime(startTime);
player->update();
return player;
}
PassRefPtrWillBeRawPtr<AnimationEffect> makeAnimationEffect(CSSPropertyID id, PassRefPtrWillBeRawPtr<AnimatableValue> value)
{
KeyframeEffectModel::KeyframeVector keyframes(2);
keyframes[0] = Keyframe::create();
keyframes[0]->setOffset(0.0);
keyframes[0]->setPropertyValue(id, value.get());
keyframes[1] = Keyframe::create();
keyframes[1]->setOffset(1.0);
keyframes[1]->setPropertyValue(id, value.get());
return KeyframeEffectModel::create(keyframes);
}
PassRefPtr<InertAnimation> makeInertAnimation(PassRefPtrWillBeRawPtr<AnimationEffect> effect)
{
Timing timing;
timing.fillMode = Timing::FillModeBoth;
return InertAnimation::create(effect, timing, false);
}
PassRefPtr<Animation> makeAnimation(PassRefPtrWillBeRawPtr<AnimationEffect> effect)
{
Timing timing;
timing.fillMode = Timing::FillModeBoth;
return Animation::create(element, effect, timing);
}
AnimatableValue* interpolationValue(Interpolation* interpolation)
{
return toLegacyStyleInterpolation(interpolation)->currentValue();
}
RefPtr<Document> document;
RefPtr<DocumentTimeline> timeline;
RefPtr<Element> element;
};
TEST_F(AnimationAnimationStackTest, ActiveAnimationsSorted)
{
play(makeAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(1))).get(), 10);
play(makeAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(2))).get(), 15);
play(makeAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(3))).get(), 5);
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > result = AnimationStack::activeInterpolations(&element->activeAnimations()->defaultStack(), 0, 0, Animation::DefaultPriority, 0);
EXPECT_EQ(1u, result.size());
EXPECT_TRUE(interpolationValue(result.get(CSSPropertyFontSize))->equals(AnimatableDouble::create(2).get()));
}
TEST_F(AnimationAnimationStackTest, NewAnimations)
{
play(makeAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(1))).get(), 15);
play(makeAnimation(makeAnimationEffect(CSSPropertyZIndex, AnimatableDouble::create(2))).get(), 10);
Vector<InertAnimation*> newAnimations;
RefPtr<InertAnimation> inert1 = makeInertAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(3)));
RefPtr<InertAnimation> inert2 = makeInertAnimation(makeAnimationEffect(CSSPropertyZIndex, AnimatableDouble::create(4)));
newAnimations.append(inert1.get());
newAnimations.append(inert2.get());
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > result = AnimationStack::activeInterpolations(&element->activeAnimations()->defaultStack(), &newAnimations, 0, Animation::DefaultPriority, 10);
EXPECT_EQ(2u, result.size());
EXPECT_TRUE(interpolationValue(result.get(CSSPropertyFontSize))->equals(AnimatableDouble::create(1).get()));
EXPECT_TRUE(interpolationValue(result.get(CSSPropertyZIndex))->equals(AnimatableDouble::create(4).get()));
}
TEST_F(AnimationAnimationStackTest, CancelledAnimationPlayers)
{
HashSet<const AnimationPlayer*> cancelledAnimationPlayers;
cancelledAnimationPlayers.add(play(makeAnimation(makeAnimationEffect(CSSPropertyFontSize, AnimatableDouble::create(1))).get(), 0));
play(makeAnimation(makeAnimationEffect(CSSPropertyZIndex, AnimatableDouble::create(2))).get(), 0);
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > result = AnimationStack::activeInterpolations(&element->activeAnimations()->defaultStack(), 0, &cancelledAnimationPlayers, Animation::DefaultPriority, 0);
EXPECT_EQ(1u, result.size());
EXPECT_TRUE(interpolationValue(result.get(CSSPropertyZIndex))->equals(AnimatableDouble::create(2).get()));
}
}
......@@ -80,7 +80,7 @@ AnimationPlayer* DocumentTimeline::createAnimationPlayer(TimedItem* child)
AnimationPlayer* DocumentTimeline::play(TimedItem* child)
{
AnimationPlayer* player = createAnimationPlayer(child);
player->setStartTime(currentTime());
player->setStartTime(effectiveTime());
return player;
}
......@@ -151,6 +151,12 @@ double DocumentTimeline::currentTime()
return m_document->animationClock().currentTime() - m_zeroTime;
}
double DocumentTimeline::effectiveTime()
{
double time = currentTime();
return std::isnan(time) ? 0 : time;
}
void DocumentTimeline::pauseAnimationsForTesting(double pauseTime)
{
for (HashSet<RefPtr<AnimationPlayer> >::iterator it = m_playersNeedingUpdate.begin(); it != m_playersNeedingUpdate.end(); ++it)
......
......@@ -81,6 +81,7 @@ public:
bool hasPendingUpdates() const { return !m_playersNeedingUpdate.isEmpty(); }
double zeroTime() const { return m_zeroTime; }
double currentTime();
double effectiveTime();
void pauseAnimationsForTesting(double);
size_t numberOfActiveAnimationsForTesting() const;
......
......@@ -323,9 +323,9 @@ PassOwnPtrWillBeRawPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Elemen
{
OwnPtrWillBeRawPtr<CSSAnimationUpdate> update = adoptPtrWillBeNoop(new CSSAnimationUpdate());
calculateAnimationUpdate(update.get(), element, parentElement, style, parentStyle, resolver);
calculateAnimationActiveInterpolations(update.get(), element);
calculateAnimationActiveInterpolations(update.get(), element, parentElement.document().timeline().currentTime());
calculateTransitionUpdate(update.get(), element, style);
calculateTransitionActiveInterpolations(update.get(), element);
calculateTransitionActiveInterpolations(update.get(), element, parentElement.document().transitionTimeline().currentTime());
return update->isEmpty() ? nullptr : update.release();
}
......@@ -652,13 +652,13 @@ void CSSAnimations::cancel()
m_pendingUpdate = nullptr;
}
void CSSAnimations::calculateAnimationActiveInterpolations(CSSAnimationUpdate* update, const Element* element)
void CSSAnimations::calculateAnimationActiveInterpolations(CSSAnimationUpdate* update, const Element* element, double timelineCurrentTime)
{
ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : 0;
if (update->newAnimations().isEmpty() && update->cancelledAnimationAnimationPlayers().isEmpty()) {
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::DefaultPriority));
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::DefaultPriority, timelineCurrentTime));
update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
return;
}
......@@ -669,18 +669,18 @@ void CSSAnimations::calculateAnimationActiveInterpolations(CSSAnimationUpdate* u
for (HashSet<RefPtr<InertAnimation> >::const_iterator animationsIter = animations.begin(); animationsIter != animations.end(); ++animationsIter)
newAnimations.append(animationsIter->get());
}
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, &newAnimations, &update->cancelledAnimationAnimationPlayers(), Animation::DefaultPriority));
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, &newAnimations, &update->cancelledAnimationAnimationPlayers(), Animation::DefaultPriority, timelineCurrentTime));
update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
}
void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate* update, const Element* element)
void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate* update, const Element* element, double timelineCurrentTime)
{
ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : 0;
WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForTransitions;
if (update->newTransitions().isEmpty() && update->cancelledTransitions().isEmpty()) {
activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::TransitionPriority);
activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::TransitionPriority, timelineCurrentTime);
} else {
Vector<InertAnimation*> newTransitions;
for (CSSAnimationUpdate::NewTransitionMap::const_iterator iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter)
......@@ -696,7 +696,7 @@ void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate*
}
}
activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimationPlayers, Animation::TransitionPriority);
activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimationPlayers, Animation::TransitionPriority, timelineCurrentTime);
}
// Properties being animated by animations don't get values from transitions applied.
......
......@@ -200,8 +200,8 @@ private:
static void calculateTransitionUpdate(CSSAnimationUpdate*, const Element*, const RenderStyle&);
static void calculateTransitionUpdateForProperty(CSSPropertyID, const CSSAnimationData*, const RenderStyle& oldStyle, const RenderStyle&, const TransitionMap* activeTransitions, CSSAnimationUpdate*, const Element*);
static void calculateAnimationActiveInterpolations(CSSAnimationUpdate*, const Element*);
static void calculateTransitionActiveInterpolations(CSSAnimationUpdate*, const Element*);
static void calculateAnimationActiveInterpolations(CSSAnimationUpdate*, const Element*, double timelineCurrentTime);
static void calculateTransitionActiveInterpolations(CSSAnimationUpdate*, const Element*, double timelineCurrentTime);
class AnimationEventDelegate FINAL : public TimedItem::EventDelegate {
public:
......
......@@ -3268,6 +3268,7 @@
'animation/AnimatableValueTestHelperTest.cpp',
'animation/AnimationClockTest.cpp',
'animation/AnimationHelpersTest.cpp',
'animation/AnimationStackTest.cpp',
'animation/AnimationTest.cpp',
'animation/AnimationTestHelper.cpp',
'animation/AnimationTestHelper.h',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment