#include "cc/animation/keyframe_effect.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/notreached.h"
#include "base/time/time.h"
#include "cc/animation/animation.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_timeline.h"
#include "cc/animation/scroll_offset_animation_curve.h"
#include "cc/base/features.h"
#include "cc/trees/property_animation_state.h"
#include "ui/gfx/animation/keyframe/animation_curve.h"
#include "ui/gfx/animation/keyframe/target_property.h"
#include "ui/gfx/geometry/transform_operations.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace cc {
namespace {
bool NeedsFinishedEvent(KeyframeModel* keyframe_model) {
if (keyframe_model->is_controlling_instance())
return false;
return !keyframe_model->received_finished_event();
}
std::vector<size_t> FindAnimationsWithSameGroupId(
const std::vector<std::unique_ptr<gfx::KeyframeModel>>& keyframe_models,
int group_id) {
std::vector<size_t> group;
for (size_t i = 0; i < keyframe_models.size(); ++i) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_models[i].get());
if (cc_keyframe_model->group() != group_id)
continue;
group.push_back(i);
}
return group;
}
}
KeyframeEffect::KeyframeEffect(Animation* animation)
: animation_(animation),
element_animations_(),
needs_to_start_keyframe_models_(false),
scroll_offset_animation_was_interrupted_(false),
is_ticking_(false),
awaiting_deletion_(false),
needs_push_properties_(false) {}
KeyframeEffect::~KeyframeEffect() {
DCHECK(!has_bound_element_animations());
}
void KeyframeEffect::SetNeedsPushProperties() {
needs_push_properties_ = true;
if (element_animations()) {
element_animations_->SetNeedsPushProperties();
}
animation_->SetNeedsPushProperties();
}
void KeyframeEffect::ResetNeedsPushProperties() {
needs_push_properties_ = false;
}
void KeyframeEffect::BindElementAnimations(
ElementAnimations* element_animations) {
DCHECK(element_animations);
DCHECK(!element_animations_);
element_animations_ = element_animations;
DCHECK(element_id_);
DCHECK(element_id_ == element_animations->element_id());
if (has_any_keyframe_model())
KeyframeModelAdded();
SetNeedsPushProperties();
}
void KeyframeEffect::UnbindElementAnimations() {
SetNeedsPushProperties();
element_animations_ = nullptr;
}
void KeyframeEffect::AttachElement(ElementId element_id) {
DCHECK(!element_id_);
DCHECK(element_id);
element_id_ = element_id;
}
void KeyframeEffect::DetachElement() {
DCHECK(element_id_);
element_id_ = ElementId();
}
bool KeyframeEffect::Tick(base::TimeTicks monotonic_time) {
DCHECK(has_bound_element_animations());
if (needs_to_start_keyframe_models_)
StartKeyframeModels(monotonic_time);
bool became_inactive = false;
bool is_effect_active = false;
for (auto& keyframe_model : keyframe_models()) {
TickKeyframeModel(monotonic_time, keyframe_model.get());
bool was_active = last_tick_time_.has_value() &&
keyframe_model->HasActiveTime(*last_tick_time_);
bool is_active = keyframe_model->HasActiveTime(monotonic_time);
is_effect_active |= is_active;
became_inactive |= (was_active && !is_active);
}
last_tick_time_ = monotonic_time;
element_animations_->UpdateClientAnimationState();
if (became_inactive) {
animation_->SetNeedsCommit();
}
return is_effect_active;
}
void KeyframeEffect::RemoveFromTicking() {
is_ticking_ = false;
last_tick_time_ = std::nullopt;
animation_->RemoveFromTicking();
}
void KeyframeEffect::UpdateState(bool start_ready_keyframe_models,
AnimationEvents* events) {
DCHECK(has_bound_element_animations());
if (last_tick_time_ == std::nullopt || awaiting_deletion_) {
start_ready_keyframe_models = false;
}
if (start_ready_keyframe_models)
PromoteStartedKeyframeModels(events);
auto last_tick_time = last_tick_time_.value_or(base::TimeTicks());
MarkFinishedKeyframeModels(last_tick_time);
MarkKeyframeModelsForDeletion(last_tick_time, events);
PurgeKeyframeModelsMarkedForDeletion( true);
if (start_ready_keyframe_models) {
if (needs_to_start_keyframe_models_) {
StartKeyframeModels(last_tick_time);
PromoteStartedKeyframeModels(events);
}
}
}
void KeyframeEffect::UpdateTickingState() {
if (!animation_->has_animation_host()) {
return;
}
bool was_ticking = is_ticking_;
is_ticking_ = false;
for (const auto& keyframe_model : keyframe_models()) {
if (keyframe_model->run_state() !=
gfx::KeyframeModel::WAITING_FOR_DELETION) {
is_ticking_ = true;
awaiting_deletion_ = false;
break;
}
}
if (was_ticking && !is_ticking_) {
awaiting_deletion_ = false;
for (const auto& keyframe_model : keyframe_models()) {
KeyframeModel* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
if (keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_DELETION &&
cc_keyframe_model->is_controlling_instance() &&
!cc_keyframe_model->is_impl_only()) {
awaiting_deletion_ = true;
is_ticking_ = true;
break;
}
}
}
if (is_ticking_ && !was_ticking) {
animation_->AddToTicking();
} else if (!is_ticking_ && was_ticking) {
RemoveFromTicking();
}
}
void KeyframeEffect::Pause(base::TimeTicks timeline_time,
PauseCondition pause_condition) {
bool did_pause = false;
for (auto& keyframe_model : keyframe_models()) {
if (pause_condition == PauseCondition::kAfterStart &&
(keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY ||
keyframe_model->run_state() == gfx::KeyframeModel::STARTING))
continue;
keyframe_model->Pause(timeline_time - keyframe_model->start_time());
did_pause = true;
}
if (!did_pause || !has_bound_element_animations())
return;
animation_->SetNeedsCommit();
SetNeedsPushProperties();
}
void KeyframeEffect::AddKeyframeModel(
std::unique_ptr<gfx::KeyframeModel> keyframe_model) {
KeyframeModel* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
DCHECK(!cc_keyframe_model->is_impl_only() ||
keyframe_model->TargetProperty() == TargetProperty::SCROLL_OFFSET);
DCHECK(std::ranges::none_of(
keyframe_models(), [&](const auto& existing_keyframe_model) {
auto* cc_existing_keyframe_model =
KeyframeModel::ToCcKeyframeModel(existing_keyframe_model.get());
bool same_group_and_target =
keyframe_model->TargetProperty() ==
existing_keyframe_model->TargetProperty() &&
cc_keyframe_model->group() == cc_existing_keyframe_model->group();
bool both_active =
cc_keyframe_model->affects_active_elements() &&
cc_existing_keyframe_model->affects_active_elements();
bool both_pending =
cc_keyframe_model->affects_pending_elements() &&
cc_existing_keyframe_model->affects_pending_elements();
return same_group_and_target && (both_active || both_pending);
}));
if (keyframe_model->TargetProperty() == TargetProperty::SCROLL_OFFSET) {
DCHECK(std::ranges::none_of(
keyframe_models(), [&](const auto& existing_keyframe_model) {
auto* cc_existing_keyframe_model =
KeyframeModel::ToCcKeyframeModel(existing_keyframe_model.get());
return cc_existing_keyframe_model->TargetProperty() ==
TargetProperty::SCROLL_OFFSET &&
!cc_existing_keyframe_model->is_finished() &&
(!cc_existing_keyframe_model->is_controlling_instance() ||
cc_existing_keyframe_model->affects_pending_elements());
}));
}
gfx::KeyframeEffect::AddKeyframeModel(std::move(keyframe_model));
if (has_bound_element_animations()) {
KeyframeModelAdded();
SetNeedsPushProperties();
}
}
void KeyframeEffect::PauseKeyframeModel(int keyframe_model_id,
base::TimeDelta time_offset) {
for (auto& keyframe_model : keyframe_models()) {
if (keyframe_model->id() == keyframe_model_id) {
keyframe_model->Pause(time_offset);
}
}
if (has_bound_element_animations()) {
animation_->SetNeedsCommit();
SetNeedsPushProperties();
}
}
void KeyframeEffect::AbortKeyframeModel(int keyframe_model_id) {
if (gfx::KeyframeModel* keyframe_model =
GetKeyframeModelById(keyframe_model_id)) {
if (!keyframe_model->is_finished()) {
keyframe_model->SetRunState(gfx::KeyframeModel::ABORTED,
last_tick_time_.value_or(base::TimeTicks()));
if (has_bound_element_animations())
element_animations_->UpdateClientAnimationState();
}
}
if (has_bound_element_animations()) {
animation_->SetNeedsCommit();
SetNeedsPushProperties();
}
}
void KeyframeEffect::AbortKeyframeModelsWithProperty(
TargetProperty::Type target_property,
bool needs_completion) {
if (needs_completion)
DCHECK(target_property == TargetProperty::SCROLL_OFFSET);
bool aborted_keyframe_model = false;
for (auto& keyframe_model : keyframe_models()) {
if (keyframe_model->TargetProperty() == target_property &&
!keyframe_model->is_finished()) {
if (needs_completion &&
KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->is_impl_only()) {
keyframe_model->SetRunState(
gfx::KeyframeModel::ABORTED_BUT_NEEDS_COMPLETION,
last_tick_time_.value_or(base::TimeTicks()));
} else {
keyframe_model->SetRunState(
gfx::KeyframeModel::ABORTED,
last_tick_time_.value_or(base::TimeTicks()));
}
aborted_keyframe_model = true;
}
}
if (has_bound_element_animations()) {
if (aborted_keyframe_model)
element_animations_->UpdateClientAnimationState();
animation_->SetNeedsCommit();
SetNeedsPushProperties();
}
}
void KeyframeEffect::ActivateKeyframeModels() {
DCHECK(has_bound_element_animations());
bool keyframe_model_activated = false;
for (auto& keyframe_model : keyframe_models()) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
if (replaced_group_ == cc_keyframe_model->group() &&
!cc_keyframe_model->affects_pending_elements()) {
CHECK_NE(cc_keyframe_model->group(), KeyframeModel::kInvalidGroup);
cc_keyframe_model->ungroup();
}
if (cc_keyframe_model->affects_active_elements() !=
cc_keyframe_model->affects_pending_elements()) {
keyframe_model_activated = true;
}
cc_keyframe_model->set_affects_active_elements(
cc_keyframe_model->affects_pending_elements());
}
if (keyframe_model_activated)
element_animations_->UpdateClientAnimationState();
replaced_group_.reset();
scroll_offset_animation_was_interrupted_ = false;
}
void KeyframeEffect::KeyframeModelAdded() {
DCHECK(has_bound_element_animations());
animation_->SetNeedsCommit();
needs_to_start_keyframe_models_ = true;
UpdateTickingState();
for (auto& keyframe_model : keyframe_models()) {
element_animations_->AttachToCurve(keyframe_model->curve());
}
element_animations_->UpdateClientAnimationState();
}
bool KeyframeEffect::DispatchAnimationEventToKeyframeModel(
const AnimationEvent& event) {
DCHECK(!event.is_impl_only);
KeyframeModel* keyframe_model = KeyframeModel::ToCcKeyframeModel(
GetKeyframeModelById(event.uid.model_id));
bool dispatched = false;
switch (event.type) {
case AnimationEvent::Type::kStarted:
if (!keyframe_model) {
KeyframeModel* replacement = KeyframeModel::ToCcKeyframeModel(
GetKeyframeModel(event.target_property));
if (replacement && replacement->group() == event.group_id) {
keyframe_model = replacement;
}
}
if (keyframe_model && keyframe_model->needs_synchronized_start_time()) {
keyframe_model->set_needs_synchronized_start_time(false);
if (!keyframe_model->has_set_start_time())
keyframe_model->set_start_time(event.monotonic_time);
dispatched = true;
}
break;
case AnimationEvent::Type::kFinished:
if (keyframe_model) {
keyframe_model->set_received_finished_event(true);
dispatched = true;
} else {
SetNeedsPushProperties();
}
break;
case AnimationEvent::Type::kAborted:
if (keyframe_model) {
keyframe_model->SetRunState(gfx::KeyframeModel::ABORTED,
event.monotonic_time);
keyframe_model->set_received_finished_event(true);
dispatched = true;
animation_->animation_host()
->UpdateClientAnimationStateForElementAnimations(element_id());
}
break;
case AnimationEvent::Type::kTakeOver:
SetNeedsPushProperties();
dispatched = true;
break;
case AnimationEvent::Type::kTimeUpdated:
NOTREACHED();
}
return dispatched;
}
bool KeyframeEffect::HasTickingKeyframeModel() const {
for (const auto& keyframe_model : keyframe_models()) {
if (!keyframe_model->is_finished())
return true;
}
return false;
}
bool KeyframeEffect::RequiresInvalidation() const {
for (const auto& it : keyframe_models()) {
if (it->TargetProperty() == TargetProperty::NATIVE_PROPERTY ||
it->TargetProperty() == TargetProperty::CSS_CUSTOM_PROPERTY) {
return true;
}
}
return false;
}
bool KeyframeEffect::AffectsNativeProperty() const {
for (const auto& it : keyframe_models()) {
if (it->TargetProperty() != TargetProperty::CSS_CUSTOM_PROPERTY &&
it->TargetProperty() != TargetProperty::NATIVE_PROPERTY)
return true;
}
return false;
}
bool KeyframeEffect::AnimationsPreserveAxisAlignment() const {
for (const auto& keyframe_model : keyframe_models()) {
if (keyframe_model->is_finished())
continue;
if (!keyframe_model->curve()->PreservesAxisAlignment())
return false;
}
return true;
}
float KeyframeEffect::MaximumScale(ElementId element_id,
ElementListType list_type) const {
float maximum_scale = kInvalidScale;
for (const auto& keyframe_model : keyframe_models()) {
if (keyframe_model->is_finished())
continue;
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
ElementId model_element_id = cc_keyframe_model->element_id();
if (!model_element_id)
model_element_id = element_id_;
if (model_element_id != element_id)
continue;
if ((list_type == ElementListType::ACTIVE &&
!cc_keyframe_model->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
!cc_keyframe_model->affects_pending_elements()))
continue;
float curve_maximum_scale = kInvalidScale;
if (keyframe_model->curve()->MaximumScale(&curve_maximum_scale))
maximum_scale = std::max(maximum_scale, curve_maximum_scale);
}
return maximum_scale;
}
bool KeyframeEffect::IsPotentiallyAnimatingProperty(
TargetProperty::Type target_property,
ElementListType list_type) const {
for (const auto& keyframe_model : keyframe_models()) {
if (!keyframe_model->is_finished() &&
keyframe_model->TargetProperty() == target_property) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
if ((list_type == ElementListType::ACTIVE &&
cc_keyframe_model->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
cc_keyframe_model->affects_pending_elements()))
return true;
}
}
return false;
}
bool KeyframeEffect::IsCurrentlyAnimatingProperty(
TargetProperty::Type target_property,
ElementListType list_type) const {
for (const auto& keyframe_model : keyframe_models()) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
if (!keyframe_model->is_finished() &&
cc_keyframe_model->InEffect(
last_tick_time_.value_or(base::TimeTicks())) &&
keyframe_model->TargetProperty() == target_property) {
if ((list_type == ElementListType::ACTIVE &&
cc_keyframe_model->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
cc_keyframe_model->affects_pending_elements()))
return true;
}
}
return false;
}
void KeyframeEffect::GetPropertyAnimationState(
PropertyAnimationState* pending_state,
PropertyAnimationState* active_state) const {
pending_state->Clear();
active_state->Clear();
for (const auto& keyframe_model : keyframe_models()) {
if (!keyframe_model->is_finished()) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
bool in_effect = cc_keyframe_model->InEffect(
last_tick_time_.value_or(base::TimeTicks()));
bool active = cc_keyframe_model->affects_active_elements();
bool pending = cc_keyframe_model->affects_pending_elements();
int property = keyframe_model->TargetProperty();
if (pending)
pending_state->potentially_animating[property] = true;
if (pending && in_effect)
pending_state->currently_running[property] = true;
if (active)
active_state->potentially_animating[property] = true;
if (active && in_effect)
active_state->currently_running[property] = true;
}
}
}
void KeyframeEffect::MarkAbortedKeyframeModelsForDeletion(
KeyframeEffect* keyframe_effect_impl) {
bool keyframe_model_aborted = false;
auto& keyframe_models_impl = keyframe_effect_impl->keyframe_models();
for (const auto& keyframe_model_impl : keyframe_models_impl) {
if (gfx::KeyframeModel* keyframe_model =
GetKeyframeModelById(keyframe_model_impl->id())) {
if (keyframe_model->run_state() == gfx::KeyframeModel::ABORTED) {
keyframe_model_impl->SetRunState(
gfx::KeyframeModel::WAITING_FOR_DELETION,
keyframe_effect_impl->last_tick_time_.value_or(base::TimeTicks()));
keyframe_model->SetRunState(
gfx::KeyframeModel::WAITING_FOR_DELETION,
last_tick_time_.value_or(base::TimeTicks()));
keyframe_model_aborted = true;
}
}
}
if (has_bound_element_animations() && keyframe_model_aborted)
element_animations_->SetNeedsPushProperties();
}
void KeyframeEffect::PurgeKeyframeModelsMarkedForDeletion(bool impl_only) {
std::erase_if(keyframe_models(), [impl_only](const auto& keyframe_model) {
return keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_DELETION &&
(!impl_only || KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->is_impl_only());
});
}
void KeyframeEffect::PurgeDeletedKeyframeModels() {
std::erase_if(keyframe_models(), [](const auto& keyframe_model) {
return keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_DELETION &&
!KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->affects_pending_elements();
});
}
void KeyframeEffect::PushNewKeyframeModelsToImplThread(
KeyframeEffect* keyframe_effect_impl) const {
for (const auto& keyframe_model : keyframe_models()) {
if (keyframe_model->is_finished())
continue;
if (keyframe_effect_impl->GetKeyframeModelById(keyframe_model->id()))
continue;
if (keyframe_model->TargetProperty() == TargetProperty::SCROLL_OFFSET &&
!ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
keyframe_model->curve())
->HasSetInitialValue()) {
std::optional<gfx::PointF> current_scroll_offset;
current_scroll_offset = keyframe_effect_impl->ScrollOffsetForAnimation();
if (!current_scroll_offset.has_value())
current_scroll_offset = ScrollOffsetForAnimation();
DCHECK(current_scroll_offset);
ScrollOffsetAnimationCurve* curve =
ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
keyframe_model->curve());
curve->SetInitialValue(*current_scroll_offset);
}
gfx::KeyframeModel::RunState initial_run_state =
gfx::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY;
std::unique_ptr<KeyframeModel> to_add(
KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->CreateImplInstance(initial_run_state));
DCHECK(!to_add->needs_synchronized_start_time());
to_add->set_affects_active_elements(false);
keyframe_effect_impl->AddKeyframeModel(std::move(to_add));
}
}
namespace {
bool IsCompleted(gfx::KeyframeModel* keyframe_model,
const KeyframeEffect* main_thread_keyframe_effect) {
if (KeyframeModel::ToCcKeyframeModel(keyframe_model)->is_impl_only()) {
return (keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_DELETION);
} else {
gfx::KeyframeModel* main_thread_keyframe_model =
main_thread_keyframe_effect->GetKeyframeModelById(keyframe_model->id());
return !main_thread_keyframe_model ||
main_thread_keyframe_model->is_finished();
}
}
}
void KeyframeEffect::RemoveKeyframeModelsCompletedOnMainThread(
KeyframeEffect* keyframe_effect_impl) const {
bool keyframe_model_completed = false;
for (auto& keyframe_model : keyframe_effect_impl->keyframe_models()) {
if (IsCompleted(keyframe_model.get(), this)) {
KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->set_affects_pending_elements(false);
keyframe_model_completed = true;
}
}
keyframe_effect_impl->PurgeDeletedKeyframeModels();
if (has_bound_element_animations() && keyframe_model_completed)
element_animations_->SetNeedsPushProperties();
}
void KeyframeEffect::PushPropertiesTo(
KeyframeEffect* keyframe_effect_impl,
std::optional<base::TimeTicks> replaced_start_time) {
if (!needs_push_properties_)
return;
needs_push_properties_ = false;
keyframe_effect_impl->SetNeedsPushProperties();
if (element_id_ != keyframe_effect_impl->element_id_) {
if (keyframe_effect_impl->has_attached_element())
keyframe_effect_impl->animation_->DetachElement();
if (element_id_) {
if (element_id_ == kReservedElementIdForPaintWorklet) {
keyframe_effect_impl->animation_->AttachPaintWorkletElement();
} else {
keyframe_effect_impl->animation_->AttachElement(element_id_);
}
}
}
keyframe_effect_impl->scroll_offset_animation_was_interrupted_ =
scroll_offset_animation_was_interrupted_;
scroll_offset_animation_was_interrupted_ = false;
if (!has_any_keyframe_model() &&
!keyframe_effect_impl->has_any_keyframe_model())
return;
if (replaced_start_time) {
for (auto& km : keyframe_models()) {
km->set_start_time(*replaced_start_time);
}
}
MarkAbortedKeyframeModelsForDeletion(keyframe_effect_impl);
PurgeKeyframeModelsMarkedForDeletion( false);
RemoveKeyframeModelsCompletedOnMainThread(keyframe_effect_impl);
PushNewKeyframeModelsToImplThread(keyframe_effect_impl);
for (const auto& keyframe_model : keyframe_models()) {
KeyframeModel* current_impl = KeyframeModel::ToCcKeyframeModel(
keyframe_effect_impl->GetKeyframeModelById(keyframe_model->id()));
if (current_impl)
KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->PushPropertiesTo(current_impl);
}
keyframe_effect_impl->UpdateTickingState();
}
std::string KeyframeEffect::KeyframeModelsToString() const {
std::string str;
for (size_t i = 0; i < keyframe_models().size(); i++) {
if (i > 0)
str.append(", ");
str.append(KeyframeModel::ToCcKeyframeModel(keyframe_models()[i].get())
->ToString());
}
return str;
}
base::TimeDelta KeyframeEffect::MinimumTickInterval() const {
base::TimeDelta min_interval = base::TimeDelta::Max();
for (const auto& model : keyframe_models()) {
base::TimeDelta interval = model->curve()->TickInterval();
if (interval.is_zero())
return interval;
if (interval < min_interval)
min_interval = interval;
}
return min_interval;
}
void KeyframeEffect::RemoveKeyframeModelRange(
typename KeyframeModels::iterator to_remove_begin,
typename KeyframeModels::iterator to_remove_end) {
bool keyframe_model_removed = false;
for (auto it = to_remove_begin; it != to_remove_end; ++it) {
if ((*it)->TargetProperty() == TargetProperty::SCROLL_OFFSET) {
if (has_bound_element_animations())
scroll_offset_animation_was_interrupted_ = true;
} else if (!(*it)->is_finished()) {
keyframe_model_removed = true;
}
}
gfx::KeyframeEffect::RemoveKeyframeModelRange(to_remove_begin, to_remove_end);
if (has_bound_element_animations()) {
UpdateTickingState();
if (keyframe_model_removed)
element_animations_->UpdateClientAnimationState();
animation_->SetNeedsCommit();
SetNeedsPushProperties();
}
}
void KeyframeEffect::StartKeyframeModels(base::TimeTicks monotonic_time) {
DCHECK(needs_to_start_keyframe_models_);
needs_to_start_keyframe_models_ = false;
gfx::TargetProperties blocked_properties_for_active_elements;
gfx::TargetProperties blocked_properties_for_pending_elements;
std::vector<size_t> keyframe_models_waiting_for_target;
keyframe_models_waiting_for_target.reserve(keyframe_models().size());
for (size_t i = 0; i < keyframe_models().size(); ++i) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_models()[i].get());
if (cc_keyframe_model->run_state() == gfx::KeyframeModel::STARTING ||
cc_keyframe_model->run_state() == gfx::KeyframeModel::RUNNING) {
int property = cc_keyframe_model->TargetProperty();
if (cc_keyframe_model->affects_active_elements()) {
blocked_properties_for_active_elements[property] = true;
}
if (cc_keyframe_model->affects_pending_elements()) {
blocked_properties_for_pending_elements[property] = true;
}
} else if (cc_keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY) {
keyframe_models_waiting_for_target.push_back(i);
}
}
for (size_t i = 0; i < keyframe_models_waiting_for_target.size(); ++i) {
size_t keyframe_model_index = keyframe_models_waiting_for_target[i];
KeyframeModel* keyframe_model_waiting_for_target =
KeyframeModel::ToCcKeyframeModel(
keyframe_models()[keyframe_model_index].get());
if (keyframe_model_waiting_for_target->run_state() ==
gfx::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY) {
gfx::TargetProperties enqueued_properties;
bool affects_active_elements =
keyframe_model_waiting_for_target->affects_active_elements();
bool affects_pending_elements =
keyframe_model_waiting_for_target->affects_pending_elements();
enqueued_properties[keyframe_model_waiting_for_target->TargetProperty()] =
true;
for (size_t j = keyframe_model_index + 1; j < keyframe_models().size();
++j) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_models()[j].get());
if (keyframe_model_waiting_for_target->group() ==
cc_keyframe_model->group()) {
enqueued_properties[cc_keyframe_model->TargetProperty()] = true;
affects_active_elements |=
cc_keyframe_model->affects_active_elements();
affects_pending_elements |=
cc_keyframe_model->affects_pending_elements();
}
}
bool null_intersection = true;
for (int property = TargetProperty::FIRST_TARGET_PROPERTY;
property <= TargetProperty::LAST_TARGET_PROPERTY; ++property) {
if (enqueued_properties[property]) {
if (affects_active_elements) {
if (blocked_properties_for_active_elements[property])
null_intersection = false;
else
blocked_properties_for_active_elements[property] = true;
}
if (affects_pending_elements) {
if (blocked_properties_for_pending_elements[property])
null_intersection = false;
else
blocked_properties_for_pending_elements[property] = true;
}
}
}
if (null_intersection) {
keyframe_model_waiting_for_target->SetRunState(
gfx::KeyframeModel::STARTING, monotonic_time);
for (size_t j = keyframe_model_index + 1; j < keyframe_models().size();
++j) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_models()[j].get());
if (keyframe_model_waiting_for_target->group() ==
cc_keyframe_model->group()) {
cc_keyframe_model->SetRunState(gfx::KeyframeModel::STARTING,
monotonic_time);
}
}
} else {
needs_to_start_keyframe_models_ = true;
}
}
}
}
void KeyframeEffect::PromoteStartedKeyframeModels(AnimationEvents* events) {
for (auto& keyframe_model : keyframe_models()) {
if (keyframe_model->run_state() == gfx::KeyframeModel::STARTING &&
KeyframeModel::ToCcKeyframeModel(keyframe_model.get())
->affects_active_elements()) {
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
cc_keyframe_model->SetRunState(
gfx::KeyframeModel::RUNNING,
last_tick_time_.value_or(base::TimeTicks()));
if (!cc_keyframe_model->has_set_start_time() &&
!cc_keyframe_model->needs_synchronized_start_time())
cc_keyframe_model->set_start_time(
last_tick_time_.value_or(base::TimeTicks()));
base::TimeTicks start_time;
if (cc_keyframe_model->has_set_start_time())
start_time = cc_keyframe_model->start_time();
else
start_time = last_tick_time_.value_or(base::TimeTicks());
GenerateEvent(events, *cc_keyframe_model, AnimationEvent::Type::kStarted,
start_time);
}
}
}
void KeyframeEffect::MarkKeyframeModelsForDeletion(
base::TimeTicks monotonic_time,
AnimationEvents* events) {
bool marked_keyframe_model_for_deletion = false;
auto MarkForDeletion = [&](KeyframeModel* keyframe_model) {
keyframe_model->SetRunState(gfx::KeyframeModel::WAITING_FOR_DELETION,
monotonic_time);
marked_keyframe_model_for_deletion = true;
};
for (auto& keyframe_model : keyframe_models()) {
KeyframeModel* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
if (cc_keyframe_model->run_state() == gfx::KeyframeModel::ABORTED) {
GenerateEvent(events, *cc_keyframe_model, AnimationEvent::Type::kAborted,
monotonic_time);
if (!NeedsFinishedEvent(cc_keyframe_model))
MarkForDeletion(cc_keyframe_model);
continue;
}
if (cc_keyframe_model->is_controlling_instance() &&
cc_keyframe_model->run_state() ==
gfx::KeyframeModel::ABORTED_BUT_NEEDS_COMPLETION) {
GenerateTakeoverEventForScrollAnimation(events, *cc_keyframe_model,
monotonic_time);
MarkForDeletion(cc_keyframe_model);
continue;
}
if (cc_keyframe_model->run_state() != gfx::KeyframeModel::FINISHED)
continue;
if (NeedsFinishedEvent(cc_keyframe_model))
continue;
std::vector<size_t> keyframe_models_in_same_group =
FindAnimationsWithSameGroupId(keyframe_models(),
cc_keyframe_model->group());
bool a_keyframe_model_in_same_group_is_not_finished =
std::ranges::any_of(keyframe_models_in_same_group, [&](size_t index) {
auto* keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_models()[index].get());
return !keyframe_model->is_finished() ||
(keyframe_model->run_state() == gfx::KeyframeModel::FINISHED &&
NeedsFinishedEvent(keyframe_model));
});
if (a_keyframe_model_in_same_group_is_not_finished)
continue;
for (size_t j = 0; j < keyframe_models_in_same_group.size(); ++j) {
KeyframeModel* same_group_keyframe_model =
KeyframeModel::ToCcKeyframeModel(
keyframe_models()[keyframe_models_in_same_group[j]].get());
if (same_group_keyframe_model->run_state() ==
gfx::KeyframeModel::WAITING_FOR_DELETION ||
same_group_keyframe_model->run_state() == gfx::KeyframeModel::ABORTED)
continue;
GenerateEvent(events, *same_group_keyframe_model,
AnimationEvent::Type::kFinished, monotonic_time);
MarkForDeletion(same_group_keyframe_model);
}
}
if (marked_keyframe_model_for_deletion)
SetNeedsPushProperties();
}
void KeyframeEffect::MarkFinishedKeyframeModels(
base::TimeTicks monotonic_time) {
DCHECK(has_bound_element_animations());
bool keyframe_model_finished = false;
for (auto& keyframe_model : keyframe_models()) {
if (!animation_->IsScrollLinkedAnimation() &&
!keyframe_model->is_finished() &&
keyframe_model->IsFinishedAt(monotonic_time)) {
keyframe_model->SetRunState(gfx::KeyframeModel::FINISHED, monotonic_time);
keyframe_model_finished = true;
SetNeedsPushProperties();
}
auto* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
if (!cc_keyframe_model->affects_active_elements() &&
!cc_keyframe_model->affects_pending_elements()) {
switch (keyframe_model->run_state()) {
case gfx::KeyframeModel::WAITING_FOR_TARGET_AVAILABILITY:
case gfx::KeyframeModel::STARTING:
case gfx::KeyframeModel::RUNNING:
case gfx::KeyframeModel::PAUSED:
keyframe_model->SetRunState(gfx::KeyframeModel::FINISHED,
monotonic_time);
keyframe_model_finished = true;
break;
default:
break;
}
}
}
if (keyframe_model_finished)
element_animations_->UpdateClientAnimationState();
}
std::optional<gfx::PointF> KeyframeEffect::ScrollOffsetForAnimation() const {
return element_animations_->ScrollOffsetForAnimation();
}
void KeyframeEffect::GenerateEvent(AnimationEvents* events,
const KeyframeModel& keyframe_model,
AnimationEvent::Type type,
base::TimeTicks monotonic_time) {
if (!events || keyframe_model.group() == KeyframeModel::kInvalidGroup) {
return;
}
bool is_impl_only =
KeyframeModel::ToCcKeyframeModel(&keyframe_model)->is_impl_only();
if (is_impl_only) {
AnimationEvent event(type,
{animation_->animation_timeline()->id(),
animation_->id(), keyframe_model.id()},
keyframe_model.group(),
keyframe_model.TargetProperty(), monotonic_time);
event.is_impl_only = true;
animation_->DispatchAndDelegateAnimationEvent(event);
} else {
events->events().emplace_back(type,
AnimationEvent::UniqueKeyframeModelId{
animation_->animation_timeline()->id(),
animation_->id(), keyframe_model.id()},
keyframe_model.group(),
keyframe_model.TargetProperty(),
monotonic_time);
}
}
void KeyframeEffect::GenerateTakeoverEventForScrollAnimation(
AnimationEvents* events,
const KeyframeModel& keyframe_model,
base::TimeTicks monotonic_time) {
DCHECK_EQ(keyframe_model.TargetProperty(), TargetProperty::SCROLL_OFFSET);
if (!events)
return;
events->events().emplace_back(
AnimationEvent::Type::kTakeOver,
AnimationEvent::UniqueKeyframeModelId{
animation_->animation_timeline()->id(), animation_->id(),
keyframe_model.id()},
keyframe_model.group(), keyframe_model.TargetProperty(), monotonic_time);
auto& takeover_event = events->events().back();
takeover_event.animation_start_time = keyframe_model.start_time();
const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
ScrollOffsetAnimationCurve::ToScrollOffsetAnimationCurve(
keyframe_model.curve());
takeover_event.curve = scroll_offset_animation_curve->Clone();
AnimationEvent finished_event(
AnimationEvent::Type::kFinished,
{animation_->animation_timeline()->id(), animation_->id(),
keyframe_model.id()},
keyframe_model.group(), keyframe_model.TargetProperty(), monotonic_time);
finished_event.is_impl_only = true;
animation_->DispatchAndDelegateAnimationEvent(finished_event);
}
}