blob: 00602a4f8b90f77c4bd20250666a05f0a4414266 [file] [log] [blame]
// Copyright 2015 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 "cc/animation/element_animations.h"
#include <stddef.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "base/cxx17_backports.h"
#include "cc/animation/animation_delegate.h"
#include "cc/animation/animation_events.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/keyframe_effect.h"
#include "cc/animation/keyframe_model.h"
#include "cc/paint/filter_operations.h"
#include "cc/trees/mutator_host_client.h"
#include "ui/gfx/animation/keyframe/keyframed_animation_curve.h"
#include "ui/gfx/geometry/box_f.h"
#include "ui/gfx/geometry/transform_operations.h"
namespace cc {
namespace {
// After BlinkGenPropertyTrees, the targeted ElementId depends on the property
// being mutated. If an ElementId is set on the KeyframeModel, we should apply
// the mutation to the specific element.
// TODO(flackr): Remove ElementId from ElementAnimations once all element
// tracking is done on the KeyframeModel - https://crbug.com/900241
ElementId CalculateTargetElementId(const ElementAnimations* element_animations,
const gfx::KeyframeModel* keyframe_model) {
if (LIKELY(KeyframeModel::ToCcKeyframeModel(keyframe_model)->element_id()))
return KeyframeModel::ToCcKeyframeModel(keyframe_model)->element_id();
return element_animations->element_id();
}
bool UsingPaintWorklet(int property_index) {
// The set of properties where its animation uses paint worklet infra.
return property_index == TargetProperty::CSS_CUSTOM_PROPERTY ||
property_index == TargetProperty::NATIVE_PROPERTY;
}
} // namespace
scoped_refptr<ElementAnimations> ElementAnimations::Create(
AnimationHost* host,
ElementId element_id) {
DCHECK(element_id);
DCHECK(host);
return base::WrapRefCounted(new ElementAnimations(host, element_id));
}
ElementAnimations::ElementAnimations(AnimationHost* host, ElementId element_id)
: animation_host_(host),
element_id_(element_id),
has_element_in_active_list_(false),
has_element_in_pending_list_(false),
needs_push_properties_(false),
active_maximum_scale_(kInvalidScale),
pending_maximum_scale_(kInvalidScale) {
InitAffectedElementTypes();
}
ElementAnimations::~ElementAnimations() = default;
void ElementAnimations::InitAffectedElementTypes() {
DCHECK(element_id_);
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
if (animation_host_->mutator_host_client()->IsElementInPropertyTrees(
element_id_, ElementListType::ACTIVE)) {
set_has_element_in_active_list(true);
}
if (animation_host_->mutator_host_client()->IsElementInPropertyTrees(
element_id_, ElementListType::PENDING)) {
set_has_element_in_pending_list(true);
}
}
gfx::TargetProperties ElementAnimations::GetPropertiesMaskForAnimationState() {
gfx::TargetProperties properties;
properties[TargetProperty::TRANSFORM] = true;
properties[TargetProperty::OPACITY] = true;
properties[TargetProperty::FILTER] = true;
properties[TargetProperty::BACKDROP_FILTER] = true;
return properties;
}
void ElementAnimations::ClearAffectedElementTypes(
const PropertyToElementIdMap& element_id_map) {
DCHECK(animation_host_);
gfx::TargetProperties disable_properties =
GetPropertiesMaskForAnimationState();
PropertyAnimationState disabled_state_mask, disabled_state;
disabled_state_mask.currently_running = disable_properties;
disabled_state_mask.potentially_animating = disable_properties;
// This method may get called from AnimationHost dtor so it is possible for
// mutator_host_client() to be null.
if (has_element_in_active_list() && animation_host_->mutator_host_client()) {
animation_host_->mutator_host_client()->ElementIsAnimatingChanged(
element_id_map, ElementListType::ACTIVE, disabled_state_mask,
disabled_state);
}
set_has_element_in_active_list(false);
if (has_element_in_pending_list() && animation_host_->mutator_host_client()) {
animation_host_->mutator_host_client()->ElementIsAnimatingChanged(
element_id_map, ElementListType::PENDING, disabled_state_mask,
disabled_state);
}
set_has_element_in_pending_list(false);
RemoveKeyframeEffectsFromTicking();
}
// TODO(crbug.com/1240712): the ReservedElementId should always be 'registered'.
// Instead of calling this from AnimationHost::UpdateRegisteredElementIds, we
// can ensure that the |has_element_in_active_list_| and the
// |has_element_in_pending_list_| are true for ReservedElementId, and this
// should result in animations ticking right away. With that, we do not need to
// add anything to the |keyframe_effects_list_| for ReservedElementId.
void ElementAnimations::ElementIdRegistered(ElementId element_id,
ElementListType list_type) {
DCHECK_EQ(element_id_, element_id);
bool had_element_in_any_list = has_element_in_any_list();
if (list_type == ElementListType::ACTIVE)
set_has_element_in_active_list(true);
else
set_has_element_in_pending_list(true);
if (!had_element_in_any_list)
UpdateKeyframeEffectsTickingState();
}
void ElementAnimations::ElementIdUnregistered(ElementId element_id,
ElementListType list_type) {
DCHECK_EQ(this->element_id(), element_id);
if (list_type == ElementListType::ACTIVE)
set_has_element_in_active_list(false);
else
set_has_element_in_pending_list(false);
}
void ElementAnimations::AddKeyframeEffect(KeyframeEffect* keyframe_effect) {
keyframe_effects_list_.AddObserver(keyframe_effect);
keyframe_effect->BindElementAnimations(this);
}
void ElementAnimations::RemoveKeyframeEffect(KeyframeEffect* keyframe_effect) {
keyframe_effects_list_.RemoveObserver(keyframe_effect);
keyframe_effect->UnbindElementAnimations();
}
bool ElementAnimations::IsEmpty() const {
return keyframe_effects_list_.empty();
}
void ElementAnimations::SetNeedsPushProperties() {
needs_push_properties_ = true;
}
void ElementAnimations::PushPropertiesTo(
scoped_refptr<ElementAnimations> element_animations_impl) const {
DCHECK_NE(this, element_animations_impl);
if (!needs_push_properties_)
return;
needs_push_properties_ = false;
element_animations_impl->UpdateClientAnimationState();
}
void ElementAnimations::UpdateKeyframeEffectsTickingState() const {
for (auto& keyframe_effect : keyframe_effects_list_)
keyframe_effect.UpdateTickingState();
}
void ElementAnimations::RemoveKeyframeEffectsFromTicking() const {
for (auto& keyframe_effect : keyframe_effects_list_)
keyframe_effect.RemoveFromTicking();
}
bool ElementAnimations::AnimationsPreserveAxisAlignment() const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (!keyframe_effect.AnimationsPreserveAxisAlignment())
return false;
}
return true;
}
float ElementAnimations::MaximumScale(ElementListType list_type) const {
float maximum_scale = kInvalidScale;
for (auto& keyframe_effect : keyframe_effects_list_) {
maximum_scale =
std::max(maximum_scale, keyframe_effect.MaximumScale(list_type));
}
return maximum_scale;
}
bool ElementAnimations::ScrollOffsetAnimationWasInterrupted() const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (keyframe_effect.scroll_offset_animation_was_interrupted())
return true;
}
return false;
}
void ElementAnimations::OnFloatAnimated(const float& value,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
switch (keyframe_model->TargetProperty()) {
case TargetProperty::CSS_CUSTOM_PROPERTY:
case TargetProperty::NATIVE_PROPERTY:
// Custom properties are only tracked on the pending tree, where they may
// be used as inputs for PaintWorklets (which are only dispatched from the
// pending tree). As such, we don't need to notify in the case where a
// KeyframeModel only affects active elements.
if (KeyframeModelAffectsPendingElements(keyframe_model))
OnCustomPropertyAnimated(
PaintWorkletInput::PropertyValue(value),
KeyframeModel::ToCcKeyframeModel(keyframe_model),
target_property_id);
break;
case TargetProperty::OPACITY: {
float opacity = base::clamp(value, 0.0f, 1.0f);
if (KeyframeModelAffectsActiveElements(keyframe_model))
OnOpacityAnimated(ElementListType::ACTIVE, opacity, keyframe_model);
if (KeyframeModelAffectsPendingElements(keyframe_model))
OnOpacityAnimated(ElementListType::PENDING, opacity, keyframe_model);
break;
}
default:
NOTREACHED();
}
}
void ElementAnimations::OnFilterAnimated(const FilterOperations& filters,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
switch (keyframe_model->TargetProperty()) {
case TargetProperty::BACKDROP_FILTER:
if (KeyframeModelAffectsActiveElements(keyframe_model))
OnBackdropFilterAnimated(ElementListType::ACTIVE, filters,
keyframe_model);
if (KeyframeModelAffectsPendingElements(keyframe_model))
OnBackdropFilterAnimated(ElementListType::PENDING, filters,
keyframe_model);
break;
case TargetProperty::FILTER:
if (KeyframeModelAffectsActiveElements(keyframe_model))
OnFilterAnimated(ElementListType::ACTIVE, filters, keyframe_model);
if (KeyframeModelAffectsPendingElements(keyframe_model))
OnFilterAnimated(ElementListType::PENDING, filters, keyframe_model);
break;
default:
NOTREACHED();
}
}
void ElementAnimations::OnColorAnimated(const SkColor& value,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
DCHECK_EQ(keyframe_model->TargetProperty(),
TargetProperty::CSS_CUSTOM_PROPERTY);
OnCustomPropertyAnimated(PaintWorkletInput::PropertyValue(value),
KeyframeModel::ToCcKeyframeModel(keyframe_model),
target_property_id);
}
void ElementAnimations::OnTransformAnimated(
const gfx::TransformOperations& operations,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
gfx::Transform transform = operations.Apply();
if (KeyframeModelAffectsActiveElements(keyframe_model))
OnTransformAnimated(ElementListType::ACTIVE, transform, keyframe_model);
if (KeyframeModelAffectsPendingElements(keyframe_model))
OnTransformAnimated(ElementListType::PENDING, transform, keyframe_model);
}
void ElementAnimations::OnScrollOffsetAnimated(
const gfx::PointF& scroll_offset,
int target_property_id,
gfx::KeyframeModel* keyframe_model) {
if (KeyframeModelAffectsActiveElements(keyframe_model))
OnScrollOffsetAnimated(ElementListType::ACTIVE, scroll_offset,
keyframe_model);
if (KeyframeModelAffectsPendingElements(keyframe_model))
OnScrollOffsetAnimated(ElementListType::PENDING, scroll_offset,
keyframe_model);
}
void ElementAnimations::InitClientAnimationState() {
// Clear current states so that UpdateClientAnimationState() will send all
// (instead of only changed) recalculated current states to the client.
pending_state_.Clear();
active_state_.Clear();
active_maximum_scale_ = kInvalidScale;
pending_maximum_scale_ = kInvalidScale;
UpdateClientAnimationState();
}
void ElementAnimations::UpdateClientAnimationState() {
if (!element_id())
return;
// For a custom property animation, or an animation that uses paint worklet,
// it is not associated with any property node, and thus this function is not
// needed.
if (element_id().GetStableId() == ElementId::kReservedElementId)
return;
DCHECK(animation_host_);
if (!animation_host_->mutator_host_client())
return;
PropertyAnimationState prev_pending = pending_state_;
PropertyAnimationState prev_active = active_state_;
pending_state_.Clear();
active_state_.Clear();
for (auto& keyframe_effect : keyframe_effects_list_) {
PropertyAnimationState keyframe_effect_pending_state,
keyframe_effect_active_state;
keyframe_effect.GetPropertyAnimationState(&keyframe_effect_pending_state,
&keyframe_effect_active_state);
pending_state_ |= keyframe_effect_pending_state;
active_state_ |= keyframe_effect_active_state;
}
gfx::TargetProperties allowed_properties =
GetPropertiesMaskForAnimationState();
PropertyAnimationState allowed_state;
allowed_state.currently_running = allowed_properties;
allowed_state.potentially_animating = allowed_properties;
pending_state_ &= allowed_state;
active_state_ &= allowed_state;
DCHECK(pending_state_.IsValid());
DCHECK(active_state_.IsValid());
PropertyToElementIdMap element_id_map = GetPropertyToElementIdMap();
ElementId transform_element_id = element_id_map[TargetProperty::TRANSFORM];
if (has_element_in_active_list()) {
if (prev_active != active_state_) {
PropertyAnimationState diff_active = prev_active ^ active_state_;
animation_host_->mutator_host_client()->ElementIsAnimatingChanged(
element_id_map, ElementListType::ACTIVE, diff_active, active_state_);
}
float maximum_scale = transform_element_id
? MaximumScale(ElementListType::ACTIVE)
: kInvalidScale;
if (maximum_scale != active_maximum_scale_) {
animation_host_->mutator_host_client()->MaximumScaleChanged(
transform_element_id, ElementListType::ACTIVE, maximum_scale);
active_maximum_scale_ = maximum_scale;
}
}
if (has_element_in_pending_list()) {
if (prev_pending != pending_state_) {
PropertyAnimationState diff_pending = prev_pending ^ pending_state_;
animation_host_->mutator_host_client()->ElementIsAnimatingChanged(
element_id_map, ElementListType::PENDING, diff_pending,
pending_state_);
}
float maximum_scale = transform_element_id
? MaximumScale(ElementListType::PENDING)
: kInvalidScale;
if (maximum_scale != pending_maximum_scale_) {
animation_host_->mutator_host_client()->MaximumScaleChanged(
transform_element_id, ElementListType::PENDING, maximum_scale);
pending_maximum_scale_ = maximum_scale;
}
}
}
void ElementAnimations::AttachToCurve(gfx::AnimationCurve* c) {
switch (c->Type()) {
case gfx::AnimationCurve::COLOR:
gfx::ColorAnimationCurve::ToColorAnimationCurve(c)->set_target(this);
break;
case gfx::AnimationCurve::FLOAT:
gfx::FloatAnimationCurve::ToFloatAnimationCurve(c)->set_target(this);
break;
case gfx::AnimationCurve::TRANSFORM:
gfx::TransformAnimationCurve::ToTransformAnimationCurve(c)->set_target(
this);
break;
case gfx::AnimationCurve::FILTER:
FilterAnimationCurve::ToFilterAnimationCurve(c)->set_target(this);
break;
case gfx::AnimationCurve::SCROLL_OFFSET:
ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(c)->set_target(
this);
break;
default:
NOTREACHED();
break;
}
}
bool ElementAnimations::HasTickingKeyframeEffect() const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (keyframe_effect.HasTickingKeyframeModel())
return true;
}
return false;
}
bool ElementAnimations::HasAnyKeyframeModel() const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (keyframe_effect.has_any_keyframe_model())
return true;
}
return false;
}
bool ElementAnimations::HasAnyAnimationTargetingProperty(
TargetProperty::Type property) const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (keyframe_effect.GetKeyframeModel(property))
return true;
}
return false;
}
bool ElementAnimations::IsPotentiallyAnimatingProperty(
TargetProperty::Type target_property,
ElementListType list_type) const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (keyframe_effect.IsPotentiallyAnimatingProperty(target_property,
list_type))
return true;
}
return false;
}
bool ElementAnimations::IsCurrentlyAnimatingProperty(
TargetProperty::Type target_property,
ElementListType list_type) const {
for (auto& keyframe_effect : keyframe_effects_list_) {
if (keyframe_effect.IsCurrentlyAnimatingProperty(target_property,
list_type))
return true;
}
return false;
}
void ElementAnimations::OnFilterAnimated(ElementListType list_type,
const FilterOperations& filters,
gfx::KeyframeModel* keyframe_model) {
ElementId target_element_id = CalculateTargetElementId(this, keyframe_model);
DCHECK(target_element_id);
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
animation_host_->mutator_host_client()->SetElementFilterMutated(
target_element_id, list_type, filters);
}
void ElementAnimations::OnBackdropFilterAnimated(
ElementListType list_type,
const FilterOperations& backdrop_filters,
gfx::KeyframeModel* keyframe_model) {
ElementId target_element_id = CalculateTargetElementId(this, keyframe_model);
DCHECK(target_element_id);
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
animation_host_->mutator_host_client()->SetElementBackdropFilterMutated(
target_element_id, list_type, backdrop_filters);
}
void ElementAnimations::OnOpacityAnimated(ElementListType list_type,
float opacity,
gfx::KeyframeModel* keyframe_model) {
ElementId target_element_id = CalculateTargetElementId(this, keyframe_model);
DCHECK(target_element_id);
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
animation_host_->mutator_host_client()->SetElementOpacityMutated(
target_element_id, list_type, opacity);
}
void ElementAnimations::OnCustomPropertyAnimated(
PaintWorkletInput::PropertyValue property_value,
KeyframeModel* keyframe_model,
int target_property_id) {
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
// No-op background-color animations can have no unique_id. See
// CompositorAnimations::IsNoOpBackgroundColorAnimation for details.
if (!ElementId::IsValid(keyframe_model->element_id().GetStableId())) {
return;
}
ElementId id = CalculateTargetElementId(this, keyframe_model);
PaintWorkletInput::PropertyKey property_key =
target_property_id == TargetProperty::NATIVE_PROPERTY
? PaintWorkletInput::PropertyKey(
keyframe_model->native_property_type(), id)
: PaintWorkletInput::PropertyKey(
keyframe_model->custom_property_name(), id);
animation_host_->mutator_host_client()->OnCustomPropertyMutated(
std::move(property_key), std::move(property_value));
}
void ElementAnimations::OnTransformAnimated(
ElementListType list_type,
const gfx::Transform& transform,
gfx::KeyframeModel* keyframe_model) {
ElementId target_element_id = CalculateTargetElementId(this, keyframe_model);
DCHECK(target_element_id);
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
animation_host_->mutator_host_client()->SetElementTransformMutated(
target_element_id, list_type, transform);
}
void ElementAnimations::OnScrollOffsetAnimated(
ElementListType list_type,
const gfx::PointF& scroll_offset,
gfx::KeyframeModel* keyframe_model) {
ElementId target_element_id = CalculateTargetElementId(this, keyframe_model);
DCHECK(target_element_id);
DCHECK(animation_host_);
DCHECK(animation_host_->mutator_host_client());
animation_host_->mutator_host_client()->SetElementScrollOffsetMutated(
target_element_id, list_type, scroll_offset);
}
gfx::PointF ElementAnimations::ScrollOffsetForAnimation() const {
if (animation_host_)
return animation_host_->GetScrollOffsetForAnimation(element_id());
return gfx::PointF();
}
PropertyToElementIdMap ElementAnimations::GetPropertyToElementIdMap() const {
// As noted in the header documentation, this method assumes that each
// property type maps to at most one ElementId. This is not conceptually true
// for cc/animations, but it is true for the current clients:
//
// * ui/ does not set per-keyframe-model ElementIds, so this map will be
// each property type mapping to the same ElementId (i.e. element_id()).
//
// * blink guarantees that any two keyframe models that it creates which
// target the same property on the same target will have the same ElementId.
//
// In order to make this as little of a footgun as possible for future-us,
// this method DCHECKs that the assumption holds.
std::vector<PropertyToElementIdMap::value_type> entries;
for (int property_index = TargetProperty::FIRST_TARGET_PROPERTY;
property_index <= TargetProperty::LAST_TARGET_PROPERTY;
++property_index) {
// We skip the set of properties that uses paint worklet, because the
// animation is not directly associated with the element its compositing
// layer targets and we use reserved element id when we attach a layer for
// the animation. In that case, the DCHECK here is no longer applicable.
// For example, when we have two paint worklet elements with two different
// custom property animations, then these two KeyframeModels would have
// different element_id and thus fail the first DCHECK here.
// It is not valid to include these properties in the PropertyToElementIdMap
// as they do not map to a single element id. Therefore, these properties
// should not be included in the map.
if (UsingPaintWorklet(property_index))
continue;
TargetProperty::Type property =
static_cast<TargetProperty::Type>(property_index);
ElementId element_id_for_property;
for (auto& keyframe_effect : keyframe_effects_list_) {
KeyframeModel* model = KeyframeModel::ToCcKeyframeModel(
keyframe_effect.GetKeyframeModel(property));
if (model) {
// We deliberately use two branches here so that the DCHECK can
// differentiate between models with different element ids, and the case
// where some models don't have an element id.
// TODO(crbug.com/900241): All KeyframeModels should have an ElementId.
if (model->element_id()) {
DCHECK(!element_id_for_property ||
element_id_for_property == model->element_id())
<< "Different KeyframeModels for the same target must have the "
<< "same ElementId";
element_id_for_property = model->element_id();
} else {
// This DCHECK isn't perfect; you could have a case where one model
// has an ElementId and the other doesn't, but model->element_id() ==
// this->element_id() and so the DCHECK passes. That is unlikely
// enough that we don't bother guarding against it specifically.
DCHECK(!element_id_for_property ||
element_id_for_property == element_id())
<< "Either all models should have an ElementId or none should";
element_id_for_property = element_id();
}
}
}
if (element_id_for_property)
entries.emplace_back(property, element_id_for_property);
}
return PropertyToElementIdMap(std::move(entries));
}
unsigned int ElementAnimations::CountKeyframesForTesting() const {
unsigned int count = 0;
for (auto it = keyframe_effects_list_.begin();
it != keyframe_effects_list_.end(); it++)
count++;
return count;
}
KeyframeEffect* ElementAnimations::FirstKeyframeEffectForTesting() const {
DCHECK(!keyframe_effects_list_.empty());
return &*keyframe_effects_list_.begin();
}
bool ElementAnimations::HasKeyframeEffectForTesting(
const KeyframeEffect* keyframe) const {
return keyframe_effects_list_.HasObserver(keyframe);
}
bool ElementAnimations::KeyframeModelAffectsActiveElements(
gfx::KeyframeModel* keyframe_model) const {
// When we force a keyframe_model update due to a notification, we do not have
// a KeyframeModel instance. In this case, we force an update of active
// elements.
if (!keyframe_model)
return true;
return KeyframeModel::ToCcKeyframeModel(keyframe_model)
->affects_active_elements() &&
has_element_in_active_list();
}
bool ElementAnimations::KeyframeModelAffectsPendingElements(
gfx::KeyframeModel* keyframe_model) const {
// When we force a keyframe_model update due to a notification, we do not have
// a KeyframeModel instance. In this case, we force an update of pending
// elements.
if (!keyframe_model)
return true;
return KeyframeModel::ToCcKeyframeModel(keyframe_model)
->affects_pending_elements() &&
has_element_in_pending_list();
}
} // namespace cc