Eric Seidel e0fd75b5ab Make absolute and sort all Sky headers
This caused us to lose our gn check certification. :(

Turns out gn check was just ignoring all the header
paths it didn't understand and so gn check passing
for sky wasn't meaning much.  I tried to straighten
out some of the mess in this CL, but its going to take
several more rounds of massaging before gn check
passes again.  On the bright side (almost) all of
our headers are absolute now.  Turns out my script
(attached to the bug) didn't notice ../ includes
but I'll fix that in the next patch.

R=abarth@chromium.org
BUG=435361

Review URL: https://codereview.chromium.org/746023002
2014-11-20 17:42:05 -08:00

697 lines
35 KiB
C++

/*
* 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 "sky/engine/config.h"
#include "sky/engine/core/animation/css/CSSAnimations.h"
#include "gen/sky/core/StylePropertyShorthand.h"
#include "sky/engine/core/animation/ActiveAnimations.h"
#include "sky/engine/core/animation/AnimationTimeline.h"
#include "sky/engine/core/animation/CompositorAnimations.h"
#include "sky/engine/core/animation/KeyframeEffectModel.h"
#include "sky/engine/core/animation/LegacyStyleInterpolation.h"
#include "sky/engine/core/animation/css/CSSAnimatableValueFactory.h"
#include "sky/engine/core/animation/css/CSSPropertyEquality.h"
#include "sky/engine/core/css/CSSKeyframeRule.h"
#include "sky/engine/core/css/CSSPropertyMetadata.h"
#include "sky/engine/core/css/CSSValueList.h"
#include "sky/engine/core/css/resolver/CSSToStyleMap.h"
#include "sky/engine/core/css/resolver/StyleResolver.h"
#include "sky/engine/core/dom/Element.h"
#include "sky/engine/core/dom/StyleEngine.h"
#include "sky/engine/core/events/AnimationEvent.h"
#include "sky/engine/core/events/TransitionEvent.h"
#include "sky/engine/core/frame/UseCounter.h"
#include "sky/engine/core/rendering/RenderLayer.h"
#include "sky/engine/core/rendering/RenderObject.h"
#include "sky/engine/core/rendering/style/KeyframeList.h"
#include "sky/engine/platform/animation/TimingFunction.h"
#include "sky/engine/public/platform/Platform.h"
#include "sky/engine/wtf/BitArray.h"
#include "sky/engine/wtf/HashSet.h"
namespace blink {
namespace {
CSSPropertyID propertyForAnimation(CSSPropertyID property)
{
switch (property) {
case CSSPropertyWebkitPerspective:
return CSSPropertyPerspective;
case CSSPropertyWebkitTransform:
return CSSPropertyTransform;
case CSSPropertyWebkitPerspectiveOriginX:
case CSSPropertyWebkitPerspectiveOriginY:
return CSSPropertyPerspectiveOrigin;
case CSSPropertyWebkitTransformOriginX:
case CSSPropertyWebkitTransformOriginY:
case CSSPropertyWebkitTransformOriginZ:
return CSSPropertyTransformOrigin;
default:
break;
}
return property;
}
static void resolveKeyframes(StyleResolver* resolver, Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, const AtomicString& name, TimingFunction* defaultTimingFunction,
AnimatableValueKeyframeVector& keyframes)
{
// When the element is null, use its parent for scoping purposes.
const Element* elementForScoping = element ? element : &parentElement;
const StyleRuleKeyframes* keyframesRule = CSSAnimations::matchScopedKeyframesRule(resolver, elementForScoping, name.impl());
if (!keyframesRule)
return;
const Vector<RefPtr<StyleKeyframe> >& styleKeyframes = keyframesRule->keyframes();
if (styleKeyframes.isEmpty())
return;
// Construct and populate the style for each keyframe
PropertySet specifiedPropertiesForUseCounter;
for (size_t i = 0; i < styleKeyframes.size(); ++i) {
const StyleKeyframe* styleKeyframe = styleKeyframes[i].get();
// It's OK to pass a null element here.
RefPtr<RenderStyle> keyframeStyle = resolver->styleForKeyframe(element, style, parentStyle, styleKeyframe, name);
RefPtr<AnimatableValueKeyframe> keyframe = AnimatableValueKeyframe::create();
const Vector<double>& offsets = styleKeyframe->keys();
ASSERT(!offsets.isEmpty());
keyframe->setOffset(offsets[0]);
keyframe->setEasing(defaultTimingFunction);
const StylePropertySet& properties = styleKeyframe->properties();
for (unsigned j = 0; j < properties.propertyCount(); j++) {
specifiedPropertiesForUseCounter.add(properties.propertyAt(j).id());
CSSPropertyID property = propertyForAnimation(properties.propertyAt(j).id());
if (property == CSSPropertyWebkitAnimationTimingFunction || property == CSSPropertyAnimationTimingFunction) {
CSSValue* value = properties.propertyAt(j).value();
RefPtr<TimingFunction> timingFunction;
if (value->isInheritedValue() && parentStyle->animations())
timingFunction = parentStyle->animations()->timingFunctionList()[0];
else if (value->isInheritedValue() || value->isInitialValue())
timingFunction = CSSTimingData::initialTimingFunction();
else
timingFunction = CSSToStyleMap::mapAnimationTimingFunction(toCSSValueList(value)->item(0));
keyframe->setEasing(timingFunction.release());
} else if (CSSPropertyMetadata::isAnimatableProperty(property)) {
keyframe->setPropertyValue(property, CSSAnimatableValueFactory::create(property, *keyframeStyle).get());
}
}
keyframes.append(keyframe);
// The last keyframe specified at a given offset is used.
for (size_t j = 1; j < offsets.size(); ++j) {
keyframes.append(toAnimatableValueKeyframe(keyframe->cloneWithOffset(offsets[j]).get()));
}
}
ASSERT(!keyframes.isEmpty());
// Remove duplicate keyframes. In CSS the last keyframe at a given offset takes priority.
std::stable_sort(keyframes.begin(), keyframes.end(), Keyframe::compareOffsets);
size_t targetIndex = 0;
for (size_t i = 1; i < keyframes.size(); i++) {
if (keyframes[i]->offset() != keyframes[targetIndex]->offset())
targetIndex++;
if (targetIndex != i)
keyframes[targetIndex] = keyframes[i];
}
keyframes.shrink(targetIndex + 1);
// Add 0% and 100% keyframes if absent.
RefPtr<AnimatableValueKeyframe> startKeyframe = keyframes[0];
if (startKeyframe->offset()) {
startKeyframe = AnimatableValueKeyframe::create();
startKeyframe->setOffset(0);
startKeyframe->setEasing(defaultTimingFunction);
keyframes.prepend(startKeyframe);
}
RefPtr<AnimatableValueKeyframe> endKeyframe = keyframes[keyframes.size() - 1];
if (endKeyframe->offset() != 1) {
endKeyframe = AnimatableValueKeyframe::create();
endKeyframe->setOffset(1);
endKeyframe->setEasing(defaultTimingFunction);
keyframes.append(endKeyframe);
}
ASSERT(keyframes.size() >= 2);
ASSERT(!keyframes.first()->offset());
ASSERT(keyframes.last()->offset() == 1);
// Snapshot current property values for 0% and 100% if missing.
PropertySet allProperties;
size_t numKeyframes = keyframes.size();
for (size_t i = 0; i < numKeyframes; i++) {
const PropertySet& keyframeProperties = keyframes[i]->properties();
for (PropertySet::const_iterator iter = keyframeProperties.begin(); iter != keyframeProperties.end(); ++iter)
allProperties.add(*iter);
}
const PropertySet& startKeyframeProperties = startKeyframe->properties();
const PropertySet& endKeyframeProperties = endKeyframe->properties();
bool missingStartValues = startKeyframeProperties.size() < allProperties.size();
bool missingEndValues = endKeyframeProperties.size() < allProperties.size();
if (missingStartValues || missingEndValues) {
for (PropertySet::const_iterator iter = allProperties.begin(); iter != allProperties.end(); ++iter) {
const CSSPropertyID property = *iter;
bool startNeedsValue = missingStartValues && !startKeyframeProperties.contains(property);
bool endNeedsValue = missingEndValues && !endKeyframeProperties.contains(property);
if (!startNeedsValue && !endNeedsValue)
continue;
RefPtr<AnimatableValue> snapshotValue = CSSAnimatableValueFactory::create(property, style);
if (startNeedsValue)
startKeyframe->setPropertyValue(property, snapshotValue.get());
if (endNeedsValue)
endKeyframe->setPropertyValue(property, snapshotValue.get());
}
}
ASSERT(startKeyframe->properties().size() == allProperties.size());
ASSERT(endKeyframe->properties().size() == allProperties.size());
}
} // namespace
const StyleRuleKeyframes* CSSAnimations::matchScopedKeyframesRule(StyleResolver* resolver, const Element* element, const StringImpl* animationName)
{
// FIXME: This is all implementation detail of style resolver, CSSAnimations shouldn't be reaching into any of it.
if (resolver->document().styleEngine()->hasOnlyScopedResolverForDocument())
return element->document().scopedStyleResolver()->keyframeStylesForAnimation(animationName);
Vector<RawPtr<ScopedStyleResolver>, 8> stack;
resolver->styleTreeResolveScopedKeyframesRules(element, stack);
if (stack.isEmpty())
return 0;
for (size_t i = 0; i < stack.size(); ++i) {
if (const StyleRuleKeyframes* keyframesRule = stack.at(i)->keyframeStylesForAnimation(animationName))
return keyframesRule;
}
return 0;
}
CSSAnimations::CSSAnimations()
{
}
PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, StyleResolver* resolver)
{
OwnPtr<CSSAnimationUpdate> update = adoptPtr(new CSSAnimationUpdate());
calculateAnimationUpdate(update.get(), element, parentElement, style, parentStyle, resolver);
calculateAnimationActiveInterpolations(update.get(), element, parentElement.document().timeline().currentTimeInternal());
calculateTransitionUpdate(update.get(), element, style);
calculateTransitionActiveInterpolations(update.get(), element, parentElement.document().timeline().currentTimeInternal());
return update->isEmpty() ? nullptr : update.release();
}
void CSSAnimations::calculateAnimationUpdate(CSSAnimationUpdate* update, Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, StyleResolver* resolver)
{
const ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
#if !ENABLE(ASSERT)
// If we're in an animation style change, no animations can have started, been cancelled or changed play state.
// When ASSERT is enabled, we verify this optimization.
if (activeAnimations && activeAnimations->isAnimationStyleChange())
return;
#endif
const CSSAnimationData* animationData = style.animations();
const CSSAnimations* cssAnimations = activeAnimations ? &activeAnimations->cssAnimations() : 0;
HashSet<AtomicString> inactive;
if (cssAnimations)
for (AnimationMap::const_iterator iter = cssAnimations->m_animations.begin(); iter != cssAnimations->m_animations.end(); ++iter)
inactive.add(iter->key);
if (style.display() != NONE) {
for (size_t i = 0; animationData && i < animationData->nameList().size(); ++i) {
AtomicString animationName(animationData->nameList()[i]);
if (animationName == CSSAnimationData::initialName())
continue;
bool isPaused = CSSTimingData::getRepeated(animationData->playStateList(), i) == AnimPlayStatePaused;
// Keyframes and animation properties are snapshotted when the
// animation starts, so we don't need to track changes to these,
// with the exception of play-state.
if (cssAnimations) {
AnimationMap::const_iterator existing(cssAnimations->m_animations.find(animationName));
if (existing != cssAnimations->m_animations.end()) {
inactive.remove(animationName);
AnimationPlayer* player = existing->value.get();
if (isPaused != player->paused()) {
ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
update->toggleAnimationPaused(animationName);
}
continue;
}
}
Timing timing = animationData->convertToTiming(i);
RefPtr<TimingFunction> keyframeTimingFunction = timing.timingFunction;
timing.timingFunction = Timing::defaults().timingFunction;
AnimatableValueKeyframeVector resolvedKeyframes;
resolveKeyframes(resolver, element, parentElement, style, parentStyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes);
if (!resolvedKeyframes.isEmpty()) {
ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
update->startAnimation(animationName, InertAnimation::create(AnimatableValueKeyframeEffectModel::create(resolvedKeyframes), timing, isPaused));
}
}
}
ASSERT(inactive.isEmpty() || cssAnimations);
for (HashSet<AtomicString>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter) {
ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
update->cancelAnimation(*iter, *cssAnimations->m_animations.get(*iter));
}
}
void CSSAnimations::maybeApplyPendingUpdate(Element* element)
{
if (!m_pendingUpdate) {
m_previousActiveInterpolationsForAnimations.clear();
return;
}
OwnPtr<CSSAnimationUpdate> update = m_pendingUpdate.release();
m_previousActiveInterpolationsForAnimations.swap(update->activeInterpolationsForAnimations());
// FIXME: cancelling, pausing, unpausing animations all query compositingState, which is not necessarily up to date here
// since we call this from recalc style.
// https://code.google.com/p/chromium/issues/detail?id=339847
DisableCompositingQueryAsserts disabler;
for (Vector<AtomicString>::const_iterator iter = update->cancelledAnimationNames().begin(); iter != update->cancelledAnimationNames().end(); ++iter) {
RefPtr<AnimationPlayer> player = m_animations.take(*iter);
player->cancel();
player->update(TimingUpdateOnDemand);
}
for (Vector<AtomicString>::const_iterator iter = update->animationsWithPauseToggled().begin(); iter != update->animationsWithPauseToggled().end(); ++iter) {
AnimationPlayer* player = m_animations.get(*iter);
if (player->paused())
player->unpause();
else
player->pause();
if (player->outdated())
player->update(TimingUpdateOnDemand);
}
for (Vector<CSSAnimationUpdate::NewAnimation>::const_iterator iter = update->newAnimations().begin(); iter != update->newAnimations().end(); ++iter) {
const InertAnimation* inertAnimation = iter->animation.get();
OwnPtr<AnimationEventDelegate> eventDelegate = adoptPtr(new AnimationEventDelegate(element, iter->name));
RefPtr<Animation> animation = Animation::create(element, inertAnimation->effect(), inertAnimation->specifiedTiming(), Animation::DefaultPriority, eventDelegate.release());
RefPtr<AnimationPlayer> player = element->document().timeline().createAnimationPlayer(animation.get());
if (inertAnimation->paused())
player->pause();
player->update(TimingUpdateOnDemand);
m_animations.set(iter->name, player.get());
}
// Transitions that are run on the compositor only update main-thread state
// lazily. However, we need the new state to know what the from state shoud
// be when transitions are retargeted. Instead of triggering complete style
// recalculation, we find these cases by searching for new transitions that
// have matching cancelled animation property IDs on the compositor.
HashMap<CSSPropertyID, std::pair<RefPtr<Animation>, double> > retargetedCompositorTransitions;
for (HashSet<CSSPropertyID>::iterator iter = update->cancelledTransitions().begin(); iter != update->cancelledTransitions().end(); ++iter) {
CSSPropertyID id = *iter;
ASSERT(m_transitions.contains(id));
RefPtr<AnimationPlayer> player = m_transitions.take(id).player;
Animation* animation = toAnimation(player->source());
if (animation->hasActiveAnimationsOnCompositor(id) && update->newTransitions().find(id) != update->newTransitions().end())
retargetedCompositorTransitions.add(id, std::pair<RefPtr<Animation>, double>(animation, player->startTimeInternal()));
player->cancel();
player->update(TimingUpdateOnDemand);
}
for (CSSAnimationUpdate::NewTransitionMap::const_iterator iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter) {
const CSSAnimationUpdate::NewTransition& newTransition = iter->value;
RunningTransition runningTransition;
runningTransition.from = newTransition.from;
runningTransition.to = newTransition.to;
CSSPropertyID id = newTransition.id;
InertAnimation* inertAnimation = newTransition.animation.get();
OwnPtr<TransitionEventDelegate> eventDelegate = adoptPtr(new TransitionEventDelegate(element, newTransition.eventId));
RefPtr<AnimationEffect> effect = inertAnimation->effect();
if (retargetedCompositorTransitions.contains(id)) {
const std::pair<RefPtr<Animation>, double>& oldTransition = retargetedCompositorTransitions.get(id);
RefPtr<Animation> oldAnimation = oldTransition.first;
double oldStartTime = oldTransition.second;
double inheritedTime = isNull(oldStartTime) ? 0 : element->document().timeline().currentTimeInternal() - oldStartTime;
AnimatableValueKeyframeEffectModel* oldEffect = toAnimatableValueKeyframeEffectModel(inertAnimation->effect());
const KeyframeVector& frames = oldEffect->getFrames();
AnimatableValueKeyframeVector newFrames;
newFrames.append(toAnimatableValueKeyframe(frames[0]->clone().get()));
newFrames.append(toAnimatableValueKeyframe(frames[1]->clone().get()));
newFrames[0]->clearPropertyValue(id);
RefPtr<InertAnimation> inertAnimationForSampling = InertAnimation::create(oldAnimation->effect(), oldAnimation->specifiedTiming(), false);
OwnPtr<Vector<RefPtr<Interpolation> > > sample = inertAnimationForSampling->sample(inheritedTime);
ASSERT(sample->size() == 1);
newFrames[0]->setPropertyValue(id, toLegacyStyleInterpolation(sample->at(0).get())->currentValue());
effect = AnimatableValueKeyframeEffectModel::create(newFrames);
}
RefPtr<Animation> transition = Animation::create(element, effect, inertAnimation->specifiedTiming(), Animation::TransitionPriority, eventDelegate.release());
RefPtr<AnimationPlayer> player = element->document().timeline().createAnimationPlayer(transition.get());
player->update(TimingUpdateOnDemand);
runningTransition.player = player;
m_transitions.set(id, runningTransition);
ASSERT(id != CSSPropertyInvalid);
}
}
void CSSAnimations::calculateTransitionUpdateForProperty(CSSPropertyID id, CSSPropertyID eventId, const CSSTransitionData& transitionData, size_t transitionIndex, const RenderStyle& oldStyle, const RenderStyle& style, const TransitionMap* activeTransitions, CSSAnimationUpdate* update, const Element* element)
{
RefPtr<AnimatableValue> to = nullptr;
if (activeTransitions) {
TransitionMap::const_iterator activeTransitionIter = activeTransitions->find(id);
if (activeTransitionIter != activeTransitions->end()) {
to = CSSAnimatableValueFactory::create(id, style);
const AnimatableValue* activeTo = activeTransitionIter->value.to;
if (to->equals(activeTo))
return;
update->cancelTransition(id);
ASSERT(!element->activeAnimations() || !element->activeAnimations()->isAnimationStyleChange());
}
}
if (CSSPropertyEquality::propertiesEqual(id, oldStyle, style))
return;
if (!to)
to = CSSAnimatableValueFactory::create(id, style);
RefPtr<AnimatableValue> from = CSSAnimatableValueFactory::create(id, oldStyle);
// If we have multiple transitions on the same property, we will use the
// last one since we iterate over them in order.
if (AnimatableValue::usesDefaultInterpolation(to.get(), from.get()))
return;
Timing timing = transitionData.convertToTiming(transitionIndex);
if (timing.startDelay + timing.iterationDuration <= 0)
return;
AnimatableValueKeyframeVector keyframes;
RefPtr<AnimatableValueKeyframe> startKeyframe = AnimatableValueKeyframe::create();
startKeyframe->setPropertyValue(id, from.get());
startKeyframe->setOffset(0);
startKeyframe->setEasing(timing.timingFunction.release());
timing.timingFunction = LinearTimingFunction::shared();
keyframes.append(startKeyframe);
RefPtr<AnimatableValueKeyframe> endKeyframe = AnimatableValueKeyframe::create();
endKeyframe->setPropertyValue(id, to.get());
endKeyframe->setOffset(1);
keyframes.append(endKeyframe);
RefPtr<AnimatableValueKeyframeEffectModel> effect = AnimatableValueKeyframeEffectModel::create(keyframes);
update->startTransition(id, eventId, from.get(), to.get(), InertAnimation::create(effect, timing, false));
ASSERT(!element->activeAnimations() || !element->activeAnimations()->isAnimationStyleChange());
}
void CSSAnimations::calculateTransitionUpdate(CSSAnimationUpdate* update, const Element* element, const RenderStyle& style)
{
if (!element)
return;
ActiveAnimations* activeAnimations = element->activeAnimations();
const TransitionMap* activeTransitions = activeAnimations ? &activeAnimations->cssAnimations().m_transitions : 0;
const CSSTransitionData* transitionData = style.transitions();
#if ENABLE(ASSERT)
// In debug builds we verify that it would have been safe to avoid populating and testing listedProperties if the style recalc is due to animation.
const bool animationStyleRecalc = false;
#else
// In release builds we avoid the cost of checking for new and interrupted transitions if the style recalc is due to animation.
const bool animationStyleRecalc = activeAnimations && activeAnimations->isAnimationStyleChange();
#endif
BitArray<numCSSProperties> listedProperties;
bool anyTransitionHadTransitionAll = false;
const RenderObject* renderer = element->renderer();
if (!animationStyleRecalc && style.display() != NONE && renderer && renderer->style() && transitionData) {
const RenderStyle& oldStyle = *renderer->style();
for (size_t i = 0; i < transitionData->propertyList().size(); ++i) {
const CSSTransitionData::TransitionProperty& transitionProperty = transitionData->propertyList()[i];
CSSTransitionData::TransitionPropertyType mode = transitionProperty.propertyType;
CSSPropertyID property = transitionProperty.propertyId;
if (mode == CSSTransitionData::TransitionNone || mode == CSSTransitionData::TransitionUnknown)
continue;
bool animateAll = mode == CSSTransitionData::TransitionAll;
ASSERT(animateAll || mode == CSSTransitionData::TransitionSingleProperty);
if (animateAll)
anyTransitionHadTransitionAll = true;
const StylePropertyShorthand& propertyList = animateAll ? CSSAnimations::animatableProperties() : shorthandForProperty(property);
// If not a shorthand we only execute one iteration of this loop, and refer to the property directly.
for (unsigned j = 0; !j || j < propertyList.length(); ++j) {
CSSPropertyID id = propertyList.length() ? propertyList.properties()[j] : property;
CSSPropertyID eventId = id;
if (!animateAll) {
id = propertyForAnimation(id);
if (CSSPropertyMetadata::isAnimatableProperty(id))
listedProperties.set(id);
else
continue;
}
// FIXME: We should transition if an !important property changes even when an animation is running,
// but this is a bit hard to do with the current applyMatchedProperties system.
if (!update->activeInterpolationsForAnimations().contains(id)
&& (!activeAnimations || !activeAnimations->cssAnimations().m_previousActiveInterpolationsForAnimations.contains(id))) {
calculateTransitionUpdateForProperty(id, eventId, *transitionData, i, oldStyle, style, activeTransitions, update, element);
}
}
}
}
if (activeTransitions) {
for (TransitionMap::const_iterator iter = activeTransitions->begin(); iter != activeTransitions->end(); ++iter) {
const AnimationPlayer& player = *iter->value.player;
CSSPropertyID id = iter->key;
if (player.finishedInternal() || (!anyTransitionHadTransitionAll && !animationStyleRecalc && !listedProperties.get(id))) {
// TODO: Figure out why this fails on Chrome OS login page. crbug.com/365507
// ASSERT(player.finishedInternal() || !(activeAnimations && activeAnimations->isAnimationStyleChange()));
update->cancelTransition(id);
}
}
}
}
void CSSAnimations::cancel()
{
for (AnimationMap::iterator iter = m_animations.begin(); iter != m_animations.end(); ++iter) {
iter->value->cancel();
iter->value->update(TimingUpdateOnDemand);
}
for (TransitionMap::iterator iter = m_transitions.begin(); iter != m_transitions.end(); ++iter) {
iter->value.player->cancel();
iter->value.player->update(TimingUpdateOnDemand);
}
m_animations.clear();
m_transitions.clear();
m_pendingUpdate = nullptr;
}
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()) {
HashMap<CSSPropertyID, RefPtr<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::DefaultPriority, timelineCurrentTime));
update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
return;
}
Vector<RawPtr<InertAnimation> > newAnimations;
for (size_t i = 0; i < update->newAnimations().size(); ++i) {
newAnimations.append(update->newAnimations()[i].animation.get());
}
HashMap<CSSPropertyID, RefPtr<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, &newAnimations, &update->cancelledAnimationAnimationPlayers(), Animation::DefaultPriority, timelineCurrentTime));
update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
}
void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate* update, const Element* element, double timelineCurrentTime)
{
ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : 0;
HashMap<CSSPropertyID, RefPtr<Interpolation> > activeInterpolationsForTransitions;
if (update->newTransitions().isEmpty() && update->cancelledTransitions().isEmpty()) {
activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::TransitionPriority, timelineCurrentTime);
} else {
Vector<RawPtr<InertAnimation> > newTransitions;
for (CSSAnimationUpdate::NewTransitionMap::const_iterator iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter)
newTransitions.append(iter->value.animation.get());
HashSet<RawPtr<const AnimationPlayer> > cancelledAnimationPlayers;
if (!update->cancelledTransitions().isEmpty()) {
ASSERT(activeAnimations);
const TransitionMap& transitionMap = activeAnimations->cssAnimations().m_transitions;
for (HashSet<CSSPropertyID>::iterator iter = update->cancelledTransitions().begin(); iter != update->cancelledTransitions().end(); ++iter) {
ASSERT(transitionMap.contains(*iter));
cancelledAnimationPlayers.add(transitionMap.get(*iter).player.get());
}
}
activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimationPlayers, Animation::TransitionPriority, timelineCurrentTime);
}
// Properties being animated by animations don't get values from transitions applied.
if (!update->activeInterpolationsForAnimations().isEmpty() && !activeInterpolationsForTransitions.isEmpty()) {
for (HashMap<CSSPropertyID, RefPtr<Interpolation> >::const_iterator iter = update->activeInterpolationsForAnimations().begin(); iter != update->activeInterpolationsForAnimations().end(); ++iter)
activeInterpolationsForTransitions.remove(iter->key);
}
update->adoptActiveInterpolationsForTransitions(activeInterpolationsForTransitions);
}
void CSSAnimations::AnimationEventDelegate::maybeDispatch(Document::ListenerType listenerType, const AtomicString& eventName, double elapsedTime)
{
if (m_target->document().hasListenerType(listenerType)) {
RefPtr<AnimationEvent> event = AnimationEvent::create(eventName, m_name, elapsedTime);
event->setTarget(m_target);
m_target->document().enqueueAnimationFrameEvent(event);
}
}
void CSSAnimations::AnimationEventDelegate::onEventCondition(const AnimationNode* animationNode)
{
const AnimationNode::Phase currentPhase = animationNode->phase();
const double currentIteration = animationNode->currentIteration();
if (m_previousPhase != currentPhase
&& (currentPhase == AnimationNode::PhaseActive || currentPhase == AnimationNode::PhaseAfter)
&& (m_previousPhase == AnimationNode::PhaseNone || m_previousPhase == AnimationNode::PhaseBefore)) {
// The spec states that the elapsed time should be
// 'delay < 0 ? -delay : 0', but we always use 0 to match the existing
// implementation. See crbug.com/279611
maybeDispatch(Document::ANIMATIONSTART_LISTENER, EventTypeNames::animationstart, 0);
}
if (currentPhase == AnimationNode::PhaseActive && m_previousPhase == currentPhase && m_previousIteration != currentIteration) {
// We fire only a single event for all iterations thast terminate
// between a single pair of samples. See http://crbug.com/275263. For
// compatibility with the existing implementation, this event uses
// the elapsedTime for the first iteration in question.
ASSERT(!std::isnan(animationNode->specifiedTiming().iterationDuration));
const double elapsedTime = animationNode->specifiedTiming().iterationDuration * (m_previousIteration + 1);
maybeDispatch(Document::ANIMATIONITERATION_LISTENER, EventTypeNames::animationiteration, elapsedTime);
}
if (currentPhase == AnimationNode::PhaseAfter && m_previousPhase != AnimationNode::PhaseAfter)
maybeDispatch(Document::ANIMATIONEND_LISTENER, EventTypeNames::animationend, animationNode->activeDurationInternal());
m_previousPhase = currentPhase;
m_previousIteration = currentIteration;
}
void CSSAnimations::TransitionEventDelegate::onEventCondition(const AnimationNode* animationNode)
{
const AnimationNode::Phase currentPhase = animationNode->phase();
if (currentPhase == AnimationNode::PhaseAfter && currentPhase != m_previousPhase && m_target->document().hasListenerType(Document::TRANSITIONEND_LISTENER)) {
String propertyName = getPropertyNameString(m_property);
const Timing& timing = animationNode->specifiedTiming();
double elapsedTime = timing.iterationDuration;
const AtomicString& eventType = EventTypeNames::transitionend;
RefPtr<TransitionEvent> event = TransitionEvent::create(eventType, propertyName, elapsedTime);
event->setTarget(m_target);
m_target->document().enqueueAnimationFrameEvent(event);
}
m_previousPhase = currentPhase;
}
const StylePropertyShorthand& CSSAnimations::animatableProperties()
{
DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
DEFINE_STATIC_LOCAL(StylePropertyShorthand, propertyShorthand, ());
if (properties.isEmpty()) {
for (int i = firstCSSProperty; i < lastCSSProperty; ++i) {
CSSPropertyID id = convertToCSSPropertyID(i);
if (CSSPropertyMetadata::isAnimatableProperty(id))
properties.append(id);
}
propertyShorthand = StylePropertyShorthand(CSSPropertyInvalid, properties.begin(), properties.size());
}
return propertyShorthand;
}
// Animation properties are not allowed to be affected by Web Animations.
// http://dev.w3.org/fxtf/web-animations/#not-animatable
bool CSSAnimations::isAllowedAnimation(CSSPropertyID property)
{
switch (property) {
case CSSPropertyAnimation:
case CSSPropertyAnimationDelay:
case CSSPropertyAnimationDirection:
case CSSPropertyAnimationDuration:
case CSSPropertyAnimationFillMode:
case CSSPropertyAnimationIterationCount:
case CSSPropertyAnimationName:
case CSSPropertyAnimationPlayState:
case CSSPropertyAnimationTimingFunction:
case CSSPropertyDisplay:
case CSSPropertyTransition:
case CSSPropertyTransitionDelay:
case CSSPropertyTransitionDuration:
case CSSPropertyTransitionProperty:
case CSSPropertyTransitionTimingFunction:
case CSSPropertyWebkitAnimation:
case CSSPropertyWebkitAnimationDelay:
case CSSPropertyWebkitAnimationDirection:
case CSSPropertyWebkitAnimationDuration:
case CSSPropertyWebkitAnimationFillMode:
case CSSPropertyWebkitAnimationIterationCount:
case CSSPropertyWebkitAnimationName:
case CSSPropertyWebkitAnimationPlayState:
case CSSPropertyWebkitAnimationTimingFunction:
case CSSPropertyWebkitTransition:
case CSSPropertyWebkitTransitionDelay:
case CSSPropertyWebkitTransitionDuration:
case CSSPropertyWebkitTransitionProperty:
case CSSPropertyWebkitTransitionTimingFunction:
return false;
default:
return true;
}
}
} // namespace blink