blob: 1a0909aedb4d57648757c06f1b0e0ef626746a9f [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/CompositorAnimations.h"
#include "core/animation/AnimationEffectReadOnly.h"
#include "core/animation/ElementAnimations.h"
#include "core/animation/KeyframeEffectModel.h"
#include "core/animation/animatable/AnimatableDouble.h"
#include "core/animation/animatable/AnimatableFilterOperations.h"
#include "core/animation/animatable/AnimatableTransform.h"
#include "core/animation/animatable/AnimatableValue.h"
#include "core/dom/DOMNodeIds.h"
#include "core/layout/LayoutBoxModelObject.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/paint/FilterEffectBuilder.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintLayer.h"
#include "platform/animation/AnimationTranslationUtil.h"
#include "platform/animation/CompositorAnimation.h"
#include "platform/animation/CompositorAnimationPlayer.h"
#include "platform/animation/CompositorFilterAnimationCurve.h"
#include "platform/animation/CompositorFilterKeyframe.h"
#include "platform/animation/CompositorFloatAnimationCurve.h"
#include "platform/animation/CompositorFloatKeyframe.h"
#include "platform/animation/CompositorTransformAnimationCurve.h"
#include "platform/animation/CompositorTransformKeyframe.h"
#include "platform/geometry/FloatBox.h"
#include "public/platform/Platform.h"
#include "wtf/PtrUtil.h"
#include <algorithm>
#include <cmath>
#include <memory>
namespace blink {
namespace {
bool considerAnimationAsIncompatible(const Animation& animation,
const Animation& animationToAdd) {
if (&animation == &animationToAdd)
return false;
switch (animation.playStateInternal()) {
case Animation::Idle:
return false;
case Animation::Pending:
case Animation::Running:
return true;
case Animation::Paused:
case Animation::Finished:
return Animation::hasLowerPriority(&animationToAdd, &animation);
default:
NOTREACHED();
return true;
}
}
bool isTransformRelatedCSSProperty(const PropertyHandle property) {
return property.isCSSProperty() &&
(property.cssProperty() == CSSPropertyRotate ||
property.cssProperty() == CSSPropertyScale ||
property.cssProperty() == CSSPropertyTransform ||
property.cssProperty() == CSSPropertyTranslate);
}
bool isTransformRelatedAnimation(const Element& targetElement,
const Animation* animation) {
return animation->affects(targetElement, CSSPropertyTransform) ||
animation->affects(targetElement, CSSPropertyRotate) ||
animation->affects(targetElement, CSSPropertyScale) ||
animation->affects(targetElement, CSSPropertyTranslate);
}
bool hasIncompatibleAnimations(const Element& targetElement,
const Animation& animationToAdd,
const EffectModel& effectToAdd) {
const bool affectsOpacity =
effectToAdd.affects(PropertyHandle(CSSPropertyOpacity));
const bool affectsTransform = effectToAdd.isTransformRelatedEffect();
const bool affectsFilter =
effectToAdd.affects(PropertyHandle(CSSPropertyFilter));
const bool affectsBackdropFilter =
effectToAdd.affects(PropertyHandle(CSSPropertyBackdropFilter));
if (!targetElement.hasAnimations())
return false;
ElementAnimations* elementAnimations = targetElement.elementAnimations();
DCHECK(elementAnimations);
for (const auto& entry : elementAnimations->animations()) {
const Animation* attachedAnimation = entry.key;
if (!considerAnimationAsIncompatible(*attachedAnimation, animationToAdd))
continue;
if ((affectsOpacity &&
attachedAnimation->affects(targetElement, CSSPropertyOpacity)) ||
(affectsTransform &&
isTransformRelatedAnimation(targetElement, attachedAnimation)) ||
(affectsFilter &&
attachedAnimation->affects(targetElement, CSSPropertyFilter)) ||
(affectsBackdropFilter &&
attachedAnimation->affects(targetElement, CSSPropertyBackdropFilter)))
return true;
}
return false;
}
} // namespace
bool CompositorAnimations::isCompositableProperty(CSSPropertyID property) {
for (CSSPropertyID id : compositableProperties) {
if (property == id)
return true;
}
return false;
}
const CSSPropertyID CompositorAnimations::compositableProperties[7] = {
CSSPropertyOpacity, CSSPropertyRotate, CSSPropertyScale,
CSSPropertyTransform, CSSPropertyTranslate, CSSPropertyFilter,
CSSPropertyBackdropFilter};
bool CompositorAnimations::getAnimatedBoundingBox(FloatBox& box,
const EffectModel& effect,
double minValue,
double maxValue) {
const KeyframeEffectModelBase& keyframeEffect =
toKeyframeEffectModelBase(effect);
PropertyHandleSet properties = keyframeEffect.properties();
if (properties.isEmpty())
return true;
minValue = std::min(minValue, 0.0);
maxValue = std::max(maxValue, 1.0);
for (const auto& property : properties) {
if (!property.isCSSProperty())
continue;
// TODO: Add the ability to get expanded bounds for filters as well.
if (!isTransformRelatedCSSProperty(property))
continue;
const PropertySpecificKeyframeVector& frames =
keyframeEffect.getPropertySpecificKeyframes(property);
if (frames.isEmpty() || frames.size() < 2)
continue;
FloatBox originalBox(box);
for (size_t j = 0; j < frames.size() - 1; ++j) {
const AnimatableTransform* startTransform =
toAnimatableTransform(frames[j]->getAnimatableValue().get());
const AnimatableTransform* endTransform =
toAnimatableTransform(frames[j + 1]->getAnimatableValue().get());
if (!startTransform || !endTransform)
return false;
// TODO: Add support for inflating modes other than Replace.
if (frames[j]->composite() != EffectModel::CompositeReplace)
return false;
const TimingFunction& timing = frames[j]->easing();
double min = 0;
double max = 1;
if (j == 0) {
float frameLength = frames[j + 1]->offset();
if (frameLength > 0) {
min = minValue / frameLength;
}
}
if (j == frames.size() - 2) {
float frameLength = frames[j + 1]->offset() - frames[j]->offset();
if (frameLength > 0) {
max = 1 + (maxValue - 1) / frameLength;
}
}
FloatBox bounds;
timing.range(&min, &max);
if (!endTransform->transformOperations().blendedBoundsForBox(
originalBox, startTransform->transformOperations(), min, max,
&bounds))
return false;
box.expandTo(bounds);
}
}
return true;
}
bool CompositorAnimations::isCandidateForAnimationOnCompositor(
const Timing& timing,
const Element& targetElement,
const Animation* animationToAdd,
const EffectModel& effect,
double animationPlaybackRate) {
const KeyframeEffectModelBase& keyframeEffect =
toKeyframeEffectModelBase(effect);
PropertyHandleSet properties = keyframeEffect.properties();
if (properties.isEmpty())
return false;
unsigned transformPropertyCount = 0;
for (const auto& property : properties) {
if (!property.isCSSProperty())
return false;
if (isTransformRelatedCSSProperty(property)) {
if (targetElement.layoutObject() &&
!targetElement.layoutObject()->isTransformApplicable()) {
return false;
}
transformPropertyCount++;
}
const PropertySpecificKeyframeVector& keyframes =
keyframeEffect.getPropertySpecificKeyframes(property);
DCHECK_GE(keyframes.size(), 2U);
for (const auto& keyframe : keyframes) {
// FIXME: Determine candidacy based on the CSSValue instead of a snapshot
// AnimatableValue.
bool isNeutralKeyframe =
keyframe->isCSSPropertySpecificKeyframe() &&
!toCSSPropertySpecificKeyframe(keyframe.get())->value() &&
keyframe->composite() == EffectModel::CompositeAdd;
if ((keyframe->composite() != EffectModel::CompositeReplace &&
!isNeutralKeyframe) ||
!keyframe->getAnimatableValue())
return false;
switch (property.cssProperty()) {
case CSSPropertyOpacity:
break;
case CSSPropertyRotate:
case CSSPropertyScale:
case CSSPropertyTranslate:
case CSSPropertyTransform:
if (toAnimatableTransform(keyframe->getAnimatableValue().get())
->transformOperations()
.dependsOnBoxSize())
return false;
break;
case CSSPropertyFilter:
case CSSPropertyBackdropFilter: {
const FilterOperations& operations =
toAnimatableFilterOperations(keyframe->getAnimatableValue().get())
->operations();
if (operations.hasFilterThatMovesPixels())
return false;
break;
}
default:
// any other types are not allowed to run on compositor.
return false;
}
}
}
// TODO: Support multiple transform property animations on the compositor
if (transformPropertyCount > 1)
return false;
if (animationToAdd &&
hasIncompatibleAnimations(targetElement, *animationToAdd, effect))
return false;
CompositorTiming out;
if (!convertTimingForCompositor(timing, 0, out, animationPlaybackRate))
return false;
return true;
}
void CompositorAnimations::cancelIncompatibleAnimationsOnCompositor(
const Element& targetElement,
const Animation& animationToAdd,
const EffectModel& effectToAdd) {
const bool affectsOpacity =
effectToAdd.affects(PropertyHandle(CSSPropertyOpacity));
const bool affectsTransform = effectToAdd.isTransformRelatedEffect();
const bool affectsFilter =
effectToAdd.affects(PropertyHandle(CSSPropertyFilter));
const bool affectsBackdropFilter =
effectToAdd.affects(PropertyHandle(CSSPropertyBackdropFilter));
if (!targetElement.hasAnimations())
return;
ElementAnimations* elementAnimations = targetElement.elementAnimations();
DCHECK(elementAnimations);
for (const auto& entry : elementAnimations->animations()) {
Animation* attachedAnimation = entry.key;
if (!considerAnimationAsIncompatible(*attachedAnimation, animationToAdd))
continue;
if ((affectsOpacity &&
attachedAnimation->affects(targetElement, CSSPropertyOpacity)) ||
(affectsTransform &&
isTransformRelatedAnimation(targetElement, attachedAnimation)) ||
(affectsFilter &&
attachedAnimation->affects(targetElement, CSSPropertyFilter)) ||
(affectsBackdropFilter &&
attachedAnimation->affects(targetElement, CSSPropertyBackdropFilter)))
attachedAnimation->cancelAnimationOnCompositor();
}
}
bool CompositorAnimations::canStartAnimationOnCompositor(
const Element& element) {
if (!Platform::current()->isThreadedAnimationEnabled())
return false;
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
// We query paint property tree state below to determine whether the
// animation is compositable. There is a known lifecycle violation where an
// animation can be cancelled during style update. See
// CompositorAnimations::cancelAnimationOnCompositor and
// http://crbug.com/676456. When this is fixed we would like to enable
// the DCHECK below.
// DCHECK(document().lifecycle().state() >=
// DocumentLifecycle::PrePaintClean);
const ObjectPaintProperties* paintProperties =
element.layoutObject()->paintProperties();
const TransformPaintPropertyNode* transformNode =
paintProperties->transform();
const EffectPaintPropertyNode* effectNode = paintProperties->effect();
return (transformNode && transformNode->hasDirectCompositingReasons()) ||
(effectNode && effectNode->hasDirectCompositingReasons());
}
return element.layoutObject() &&
element.layoutObject()->compositingState() == PaintsIntoOwnBacking;
}
void CompositorAnimations::startAnimationOnCompositor(
const Element& element,
int group,
double startTime,
double timeOffset,
const Timing& timing,
const Animation& animation,
const EffectModel& effect,
Vector<int>& startedAnimationIds,
double animationPlaybackRate) {
DCHECK(startedAnimationIds.isEmpty());
DCHECK(isCandidateForAnimationOnCompositor(timing, element, &animation,
effect, animationPlaybackRate));
DCHECK(canStartAnimationOnCompositor(element));
const KeyframeEffectModelBase& keyframeEffect =
toKeyframeEffectModelBase(effect);
Vector<std::unique_ptr<CompositorAnimation>> animations;
getAnimationOnCompositor(timing, group, startTime, timeOffset, keyframeEffect,
animations, animationPlaybackRate);
DCHECK(!animations.isEmpty());
for (auto& compositorAnimation : animations) {
int id = compositorAnimation->id();
CompositorAnimationPlayer* compositorPlayer = animation.compositorPlayer();
DCHECK(compositorPlayer);
compositorPlayer->addAnimation(std::move(compositorAnimation));
startedAnimationIds.push_back(id);
}
DCHECK(!startedAnimationIds.isEmpty());
}
void CompositorAnimations::cancelAnimationOnCompositor(
const Element& element,
const Animation& animation,
int id) {
if (!canStartAnimationOnCompositor(element)) {
// When an element is being detached, we cancel any associated
// Animations for CSS animations. But by the time we get
// here the mapping will have been removed.
// FIXME: Defer remove/pause operations until after the
// compositing update.
return;
}
CompositorAnimationPlayer* compositorPlayer = animation.compositorPlayer();
if (compositorPlayer)
compositorPlayer->removeAnimation(id);
}
void CompositorAnimations::pauseAnimationForTestingOnCompositor(
const Element& element,
const Animation& animation,
int id,
double pauseTime) {
// FIXME: canStartAnimationOnCompositor queries compositingState, which is not
// necessarily up to date.
// https://code.google.com/p/chromium/issues/detail?id=339847
DisableCompositingQueryAsserts disabler;
if (!canStartAnimationOnCompositor(element)) {
NOTREACHED();
return;
}
CompositorAnimationPlayer* compositorPlayer = animation.compositorPlayer();
DCHECK(compositorPlayer);
compositorPlayer->pauseAnimation(id, pauseTime);
}
void CompositorAnimations::attachCompositedLayers(Element& element,
const Animation& animation) {
if (!animation.compositorPlayer())
return;
if (!element.layoutObject() || !element.layoutObject()->isBoxModelObject())
return;
// Composited animations do not depend on a composited layer mapping for SPv2.
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
PaintLayer* layer = toLayoutBoxModelObject(element.layoutObject())->layer();
if (!layer || !layer->isAllowedToQueryCompositingState() ||
!layer->compositedLayerMapping() ||
!layer->compositedLayerMapping()->mainGraphicsLayer())
return;
if (!layer->compositedLayerMapping()->mainGraphicsLayer()->platformLayer())
return;
}
CompositorAnimationPlayer* compositorPlayer = animation.compositorPlayer();
compositorPlayer->attachElement(createCompositorElementId(
DOMNodeIds::idForNode(&element), CompositorSubElementId::Primary));
}
bool CompositorAnimations::convertTimingForCompositor(
const Timing& timing,
double timeOffset,
CompositorTiming& out,
double animationPlaybackRate) {
timing.assertValid();
// FIXME: Compositor does not know anything about endDelay.
if (timing.endDelay != 0)
return false;
if (std::isnan(timing.iterationDuration) || !timing.iterationCount ||
!timing.iterationDuration)
return false;
out.adjustedIterationCount =
std::isfinite(timing.iterationCount) ? timing.iterationCount : -1;
out.scaledDuration = timing.iterationDuration;
out.direction = timing.direction;
// Compositor's time offset is positive for seeking into the animation.
out.scaledTimeOffset =
-timing.startDelay / animationPlaybackRate + timeOffset;
out.playbackRate = timing.playbackRate * animationPlaybackRate;
out.fillMode = timing.fillMode == Timing::FillMode::AUTO
? Timing::FillMode::NONE
: timing.fillMode;
out.iterationStart = timing.iterationStart;
DCHECK_GT(out.scaledDuration, 0);
DCHECK(std::isfinite(out.scaledTimeOffset));
DCHECK(out.adjustedIterationCount > 0 || out.adjustedIterationCount == -1);
DCHECK(std::isfinite(out.playbackRate) && out.playbackRate);
DCHECK_GE(out.iterationStart, 0);
return true;
}
namespace {
void addKeyframeToCurve(CompositorFilterAnimationCurve& curve,
Keyframe::PropertySpecificKeyframe* keyframe,
const AnimatableValue* value,
const TimingFunction& keyframeTimingFunction) {
FilterEffectBuilder builder(nullptr, FloatRect(), 1);
CompositorFilterKeyframe filterKeyframe(
keyframe->offset(),
builder.buildFilterOperations(
toAnimatableFilterOperations(value)->operations()),
keyframeTimingFunction);
curve.addKeyframe(filterKeyframe);
}
void addKeyframeToCurve(CompositorFloatAnimationCurve& curve,
Keyframe::PropertySpecificKeyframe* keyframe,
const AnimatableValue* value,
const TimingFunction& keyframeTimingFunction) {
CompositorFloatKeyframe floatKeyframe(keyframe->offset(),
toAnimatableDouble(value)->toDouble(),
keyframeTimingFunction);
curve.addKeyframe(floatKeyframe);
}
void addKeyframeToCurve(CompositorTransformAnimationCurve& curve,
Keyframe::PropertySpecificKeyframe* keyframe,
const AnimatableValue* value,
const TimingFunction& keyframeTimingFunction) {
CompositorTransformOperations ops;
toCompositorTransformOperations(
toAnimatableTransform(value)->transformOperations(), &ops);
CompositorTransformKeyframe transformKeyframe(
keyframe->offset(), std::move(ops), keyframeTimingFunction);
curve.addKeyframe(transformKeyframe);
}
template <typename PlatformAnimationCurveType>
void addKeyframesToCurve(PlatformAnimationCurveType& curve,
const PropertySpecificKeyframeVector& keyframes) {
auto* lastKeyframe = keyframes.back().get();
for (const auto& keyframe : keyframes) {
const TimingFunction* keyframeTimingFunction = 0;
// Ignore timing function of last frame.
if (keyframe == lastKeyframe)
keyframeTimingFunction = LinearTimingFunction::shared();
else
keyframeTimingFunction = &keyframe->easing();
const AnimatableValue* value = keyframe->getAnimatableValue().get();
addKeyframeToCurve(curve, keyframe.get(), value, *keyframeTimingFunction);
}
}
} // namespace
void CompositorAnimations::getAnimationOnCompositor(
const Timing& timing,
int group,
double startTime,
double timeOffset,
const KeyframeEffectModelBase& effect,
Vector<std::unique_ptr<CompositorAnimation>>& animations,
double animationPlaybackRate) {
DCHECK(animations.isEmpty());
CompositorTiming compositorTiming;
bool timingValid = convertTimingForCompositor(
timing, timeOffset, compositorTiming, animationPlaybackRate);
ALLOW_UNUSED_LOCAL(timingValid);
PropertyHandleSet properties = effect.properties();
DCHECK(!properties.isEmpty());
for (const auto& property : properties) {
// If the animation duration is infinite, it doesn't make sense to scale
// the keyframe offset, so use a scale of 1.0. This is connected to
// the known issue of how the Web Animations spec handles infinite
// durations. See https://github.com/w3c/web-animations/issues/142
double scale = compositorTiming.scaledDuration;
if (!std::isfinite(scale))
scale = 1.0;
const PropertySpecificKeyframeVector& values =
effect.getPropertySpecificKeyframes(property);
CompositorTargetProperty::Type targetProperty;
std::unique_ptr<CompositorAnimationCurve> curve;
DCHECK(timing.timingFunction);
switch (property.cssProperty()) {
case CSSPropertyOpacity: {
targetProperty = CompositorTargetProperty::OPACITY;
std::unique_ptr<CompositorFloatAnimationCurve> floatCurve =
CompositorFloatAnimationCurve::create();
addKeyframesToCurve(*floatCurve, values);
floatCurve->setTimingFunction(*timing.timingFunction);
floatCurve->setScaledDuration(scale);
curve = std::move(floatCurve);
break;
}
case CSSPropertyFilter:
case CSSPropertyBackdropFilter: {
targetProperty = CompositorTargetProperty::FILTER;
std::unique_ptr<CompositorFilterAnimationCurve> filterCurve =
CompositorFilterAnimationCurve::create();
addKeyframesToCurve(*filterCurve, values);
filterCurve->setTimingFunction(*timing.timingFunction);
filterCurve->setScaledDuration(scale);
curve = std::move(filterCurve);
break;
}
case CSSPropertyRotate:
case CSSPropertyScale:
case CSSPropertyTranslate:
case CSSPropertyTransform: {
targetProperty = CompositorTargetProperty::TRANSFORM;
std::unique_ptr<CompositorTransformAnimationCurve> transformCurve =
CompositorTransformAnimationCurve::create();
addKeyframesToCurve(*transformCurve, values);
transformCurve->setTimingFunction(*timing.timingFunction);
transformCurve->setScaledDuration(scale);
curve = std::move(transformCurve);
break;
}
default:
NOTREACHED();
continue;
}
DCHECK(curve.get());
std::unique_ptr<CompositorAnimation> animation =
CompositorAnimation::create(*curve, targetProperty, group, 0);
if (!std::isnan(startTime))
animation->setStartTime(startTime);
animation->setIterations(compositorTiming.adjustedIterationCount);
animation->setIterationStart(compositorTiming.iterationStart);
animation->setTimeOffset(compositorTiming.scaledTimeOffset);
animation->setDirection(compositorTiming.direction);
animation->setPlaybackRate(compositorTiming.playbackRate);
animation->setFillMode(compositorTiming.fillMode);
animations.push_back(std::move(animation));
}
DCHECK(!animations.isEmpty());
}
} // namespace blink