blob: 5d480edfc656d7ccdf24d6886d5609b8ca6e5a16 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/animation/KeyframeEffect.h"
#include "bindings/core/v8/Dictionary.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/animation/Animation.h"
#include "core/animation/AnimationTimeline.h"
#include "core/animation/CompositorAnimations.h"
#include "core/animation/ElementAnimations.h"
#include "core/animation/Interpolation.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/KeyframeEffectOptions.h"
#include "core/animation/PropertyHandle.h"
#include "core/dom/Element.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/frame/UseCounter.h"
#include "core/paint/PaintLayer.h"
#include "core/svg/SVGElement.h"
namespace blink {
KeyframeEffect* KeyframeEffect::create(Element* target, EffectModel* model, const Timing& timing, Priority priority, EventDelegate* eventDelegate)
{
return new KeyframeEffect(target, model, timing, priority, eventDelegate);
}
KeyframeEffect* KeyframeEffect::create(ExecutionContext* executionContext, Element* element, const EffectModelOrDictionarySequenceOrDictionary& effectInput, double duration, ExceptionState& exceptionState)
{
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
if (element)
UseCounter::count(element->document(), UseCounter::AnimationConstructorKeyframeListEffectObjectTiming);
Timing timing;
if (!TimingInput::convert(duration, timing, exceptionState))
return nullptr;
return create(element, EffectInput::convert(element, effectInput, executionContext, exceptionState), timing);
}
KeyframeEffect* KeyframeEffect::create(ExecutionContext* executionContext, Element* element, const EffectModelOrDictionarySequenceOrDictionary& effectInput, const KeyframeEffectOptions& timingInput, ExceptionState& exceptionState)
{
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
if (element)
UseCounter::count(element->document(), UseCounter::AnimationConstructorKeyframeListEffectObjectTiming);
Timing timing;
Document* document = element ? &element->document() : nullptr;
if (!TimingInput::convert(timingInput, timing, document, exceptionState))
return nullptr;
return create(element, EffectInput::convert(element, effectInput, executionContext, exceptionState), timing);
}
KeyframeEffect* KeyframeEffect::create(ExecutionContext* executionContext, Element* element, const EffectModelOrDictionarySequenceOrDictionary& effectInput, ExceptionState& exceptionState)
{
ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled());
if (element)
UseCounter::count(element->document(), UseCounter::AnimationConstructorKeyframeListEffectNoTiming);
return create(element, EffectInput::convert(element, effectInput, executionContext, exceptionState), Timing());
}
KeyframeEffect::KeyframeEffect(Element* target, EffectModel* model, const Timing& timing, Priority priority, EventDelegate* eventDelegate)
: AnimationEffect(timing, eventDelegate)
, m_target(target)
, m_model(model)
, m_sampledEffect(nullptr)
, m_priority(priority)
{
}
KeyframeEffect::~KeyframeEffect()
{
}
void KeyframeEffect::attach(Animation* animation)
{
if (m_target) {
m_target->ensureElementAnimations().animations().add(animation);
m_target->setNeedsAnimationStyleRecalc();
if (RuntimeEnabledFeatures::webAnimationsSVGEnabled() && m_target->isSVGElement())
toSVGElement(m_target)->setWebAnimationsPending();
}
AnimationEffect::attach(animation);
}
void KeyframeEffect::detach()
{
if (m_target)
m_target->elementAnimations()->animations().remove(animation());
if (m_sampledEffect)
clearEffects();
AnimationEffect::detach();
}
void KeyframeEffect::specifiedTimingChanged()
{
if (animation()) {
// FIXME: Needs to consider groups when added.
ASSERT(animation()->effect() == this);
animation()->setCompositorPending(true);
}
}
static AnimationStack& ensureAnimationStack(Element* element)
{
return element->ensureElementAnimations().animationStack();
}
bool KeyframeEffect::hasMultipleTransformProperties() const
{
if (!m_target->computedStyle())
return false;
unsigned transformPropertyCount = 0;
if (m_target->computedStyle()->hasTransformOperations())
transformPropertyCount++;
if (m_target->computedStyle()->rotate())
transformPropertyCount++;
if (m_target->computedStyle()->scale())
transformPropertyCount++;
if (m_target->computedStyle()->translate())
transformPropertyCount++;
return transformPropertyCount > 1;
}
// Returns true if transform, translate, rotate or scale is composited
// and a motion path or other transform properties
// has been introduced on the element
bool KeyframeEffect::hasIncompatibleStyle()
{
if (!m_target->computedStyle())
return false;
bool affectsTransform = animation()->affects(*m_target, CSSPropertyTransform)
|| animation()->affects(*m_target, CSSPropertyScale)
|| animation()->affects(*m_target, CSSPropertyRotate)
|| animation()->affects(*m_target, CSSPropertyTranslate);
if (animation()->hasActiveAnimationsOnCompositor()) {
if (m_target->computedStyle()->hasMotionPath() && affectsTransform)
return true;
return hasMultipleTransformProperties();
}
return false;
}
void KeyframeEffect::applyEffects()
{
ASSERT(isInEffect());
ASSERT(animation());
if (!m_target || !m_model)
return;
if (hasIncompatibleStyle())
animation()->cancelAnimationOnCompositor();
double iteration = currentIteration();
ASSERT(iteration >= 0);
bool changed = false;
if (m_sampledEffect) {
changed = m_model->sample(clampTo<int>(iteration, 0), progress(), iterationDuration(), m_sampledEffect->mutableInterpolations());
} else {
Vector<RefPtr<Interpolation>> interpolations;
m_model->sample(clampTo<int>(iteration, 0), progress(), iterationDuration(), interpolations);
if (!interpolations.isEmpty()) {
SampledEffect* sampledEffect = SampledEffect::create(this);
sampledEffect->mutableInterpolations().swap(interpolations);
m_sampledEffect = sampledEffect;
ensureAnimationStack(m_target).add(sampledEffect);
changed = true;
} else {
return;
}
}
if (changed) {
m_target->setNeedsAnimationStyleRecalc();
if (RuntimeEnabledFeatures::webAnimationsSVGEnabled() && m_target->isSVGElement())
toSVGElement(*m_target).setWebAnimationsPending();
}
}
void KeyframeEffect::clearEffects()
{
ASSERT(animation());
ASSERT(m_sampledEffect);
m_sampledEffect->clear();
m_sampledEffect = nullptr;
restartAnimationOnCompositor();
m_target->setNeedsAnimationStyleRecalc();
if (RuntimeEnabledFeatures::webAnimationsSVGEnabled() && m_target->isSVGElement())
toSVGElement(*m_target).clearWebAnimatedAttributes();
invalidate();
}
void KeyframeEffect::updateChildrenAndEffects() const
{
if (!m_model)
return;
ASSERT(animation());
if (isInEffect() && !animation()->effectSuppressed())
const_cast<KeyframeEffect*>(this)->applyEffects();
else if (m_sampledEffect)
const_cast<KeyframeEffect*>(this)->clearEffects();
}
double KeyframeEffect::calculateTimeToEffectChange(bool forwards, double localTime, double timeToNextIteration) const
{
const double startTime = specifiedTiming().startDelay;
const double endTimeMinusEndDelay = startTime + activeDurationInternal();
const double endTime = endTimeMinusEndDelay + specifiedTiming().endDelay;
const double afterTime = std::min(endTimeMinusEndDelay, endTime);
switch (getPhase()) {
case PhaseNone:
return std::numeric_limits<double>::infinity();
case PhaseBefore:
ASSERT(startTime >= localTime);
return forwards
? startTime - localTime
: std::numeric_limits<double>::infinity();
case PhaseActive:
if (forwards) {
// Need service to apply fill / fire events.
const double timeToEnd = afterTime - localTime;
if (requiresIterationEvents()) {
return std::min(timeToEnd, timeToNextIteration);
}
return timeToEnd;
}
return 0;
case PhaseAfter:
ASSERT(localTime >= afterTime);
// If this KeyframeEffect is still in effect then it will need to update
// when its parent goes out of effect. We have no way of knowing when
// that will be, however, so the parent will need to supply it.
return forwards
? std::numeric_limits<double>::infinity()
: localTime - afterTime;
default:
NOTREACHED();
return std::numeric_limits<double>::infinity();
}
}
void KeyframeEffect::notifySampledEffectRemovedFromAnimationStack()
{
m_sampledEffect = nullptr;
}
bool KeyframeEffect::isCandidateForAnimationOnCompositor(double animationPlaybackRate) const
{
// Do not put transforms on compositor if more than one of them are defined
// in computed style because they need to be explicitly ordered
if (!model()
|| !m_target
|| (m_target->computedStyle() && m_target->computedStyle()->hasMotionPath())
|| hasMultipleTransformProperties())
return false;
return CompositorAnimations::isCandidateForAnimationOnCompositor(specifiedTiming(), *m_target, animation(), *model(), animationPlaybackRate);
}
bool KeyframeEffect::maybeStartAnimationOnCompositor(int group, double startTime, double currentTime, double animationPlaybackRate)
{
ASSERT(!hasActiveAnimationsOnCompositor());
if (!isCandidateForAnimationOnCompositor(animationPlaybackRate))
return false;
if (!CompositorAnimations::canStartAnimationOnCompositor(*m_target))
return false;
CompositorAnimations::startAnimationOnCompositor(*m_target, group, startTime, currentTime, specifiedTiming(), *animation(), *model(), m_compositorAnimationIds, animationPlaybackRate);
ASSERT(!m_compositorAnimationIds.isEmpty());
return true;
}
bool KeyframeEffect::hasActiveAnimationsOnCompositor() const
{
return !m_compositorAnimationIds.isEmpty();
}
bool KeyframeEffect::hasActiveAnimationsOnCompositor(CSSPropertyID property) const
{
return hasActiveAnimationsOnCompositor() && affects(PropertyHandle(property));
}
bool KeyframeEffect::affects(PropertyHandle property) const
{
return m_model && m_model->affects(property);
}
bool KeyframeEffect::cancelAnimationOnCompositor()
{
// FIXME: cancelAnimationOnCompositor is called from withins style recalc.
// This queries compositingState, which is not necessarily up to date.
// https://code.google.com/p/chromium/issues/detail?id=339847
DisableCompositingQueryAsserts disabler;
if (!hasActiveAnimationsOnCompositor())
return false;
if (!m_target || !m_target->layoutObject())
return false;
ASSERT(animation());
for (const auto& compositorAnimationId : m_compositorAnimationIds)
CompositorAnimations::cancelAnimationOnCompositor(*m_target, *animation(), compositorAnimationId);
m_compositorAnimationIds.clear();
return true;
}
void KeyframeEffect::restartAnimationOnCompositor()
{
if (cancelAnimationOnCompositor())
animation()->setCompositorPending(true);
}
void KeyframeEffect::cancelIncompatibleAnimationsOnCompositor()
{
if (m_target && animation() && model())
CompositorAnimations::cancelIncompatibleAnimationsOnCompositor(*m_target, *animation(), *model());
}
void KeyframeEffect::pauseAnimationForTestingOnCompositor(double pauseTime)
{
ASSERT(hasActiveAnimationsOnCompositor());
if (!m_target || !m_target->layoutObject())
return;
ASSERT(animation());
for (const auto& compositorAnimationId : m_compositorAnimationIds)
CompositorAnimations::pauseAnimationForTestingOnCompositor(*m_target, *animation(), compositorAnimationId, pauseTime);
}
void KeyframeEffect::attachCompositedLayers()
{
ASSERT(m_target);
ASSERT(animation());
CompositorAnimations::attachCompositedLayers(*m_target, *animation());
}
DEFINE_TRACE(KeyframeEffect)
{
visitor->trace(m_target);
visitor->trace(m_model);
visitor->trace(m_sampledEffect);
AnimationEffect::trace(visitor);
}
} // namespace blink