blob: 68eb4f9c27cd767f3c3d34732b8513e045427013 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/animation/animation_host.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/auto_reset.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "cc/animation/animation.h"
#include "cc/animation/animation_delegate.h"
#include "cc/animation/animation_events.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/animation_timeline.h"
#include "cc/animation/element_animations.h"
#include "cc/animation/keyframe_effect.h"
#include "cc/animation/scroll_offset_animation_curve.h"
#include "cc/animation/scroll_offset_animations.h"
#include "cc/animation/scroll_offset_animations_impl.h"
#include "cc/animation/scroll_timeline.h"
#include "cc/animation/worklet_animation.h"
#include "ui/gfx/animation/keyframe/timing_function.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace cc {
namespace {
AnimationWorkletMutationState ToAnimationWorkletMutationState(
MutateStatus status) {
switch (status) {
case MutateStatus::kCompletedWithUpdate:
return AnimationWorkletMutationState::COMPLETED_WITH_UPDATE;
case MutateStatus::kCompletedNoUpdate:
return AnimationWorkletMutationState::COMPLETED_NO_UPDATE;
case MutateStatus::kCanceled:
return AnimationWorkletMutationState::CANCELED;
}
}
} // namespace
std::unique_ptr<AnimationHost> AnimationHost::CreateMainInstance() {
return base::WrapUnique(new AnimationHost(ThreadInstance::kMain));
}
std::unique_ptr<AnimationHost> AnimationHost::CreateForTesting(
ThreadInstance thread_instance) {
auto animation_host = base::WrapUnique(new AnimationHost(thread_instance));
return animation_host;
}
AnimationHost::AnimationHost(ThreadInstance thread_instance)
: thread_instance_(thread_instance) {}
AnimationHost::~AnimationHost() {
ClearMutators();
DCHECK(!mutator_host_client());
}
std::unique_ptr<MutatorHost> AnimationHost::CreateImplInstance() const {
DCHECK_EQ(thread_instance_, ThreadInstance::kMain);
auto mutator_host_impl =
base::WrapUnique<MutatorHost>(new AnimationHost(ThreadInstance::kImpl));
return mutator_host_impl;
}
const AnimationTimeline* AnimationHost::GetTimelineById(int timeline_id) const {
auto f = id_to_timeline_map_.Read(*this).find(timeline_id);
return f == id_to_timeline_map_.Read(*this).end() ? nullptr : f->second.get();
}
AnimationTimeline* AnimationHost::GetTimelineById(int timeline_id) {
auto f = id_to_timeline_map_.Write(*this).find(timeline_id);
return f == id_to_timeline_map_.Write(*this).end() ? nullptr
: f->second.get();
}
void AnimationHost::ClearMutators() {
for (auto& kv : id_to_timeline_map_.Read(*this))
EraseTimeline(kv.second);
id_to_timeline_map_.Write(*this).clear();
}
base::TimeDelta AnimationHost::MinimumTickInterval() const {
base::TimeDelta min_interval = base::TimeDelta::Max();
for (const auto& animation : ticking_animations_.Read(*this)) {
DCHECK(animation->keyframe_effect());
base::TimeDelta interval =
animation->keyframe_effect()->MinimumTickInterval();
if (interval.is_zero())
return interval;
if (interval < min_interval)
min_interval = interval;
}
return min_interval;
}
void AnimationHost::EraseTimeline(scoped_refptr<AnimationTimeline> timeline) {
timeline->ClearAnimations();
timeline->SetAnimationHost(nullptr);
}
void AnimationHost::AddAnimationTimeline(
scoped_refptr<AnimationTimeline> timeline) {
DCHECK(timeline->id());
id_to_timeline_map_.Write(*this).insert(
std::make_pair(timeline->id(), timeline));
timeline->SetAnimationHost(this);
SetNeedsPushProperties();
}
void AnimationHost::RemoveAnimationTimeline(
scoped_refptr<AnimationTimeline> timeline) {
DCHECK(timeline->id());
EraseTimeline(timeline);
id_to_timeline_map_.Write(*this).erase(timeline->id());
SetNeedsPushProperties();
}
void AnimationHost::DetachAnimationTimeline(
scoped_refptr<AnimationTimeline> timeline) {
if (InProtectedSequence()) {
// Defer cleanup until post-commit.
detached_timeline_map_.Write(*this).insert(
std::make_pair(timeline->id(), timeline));
} else {
RemoveAnimationTimeline(timeline);
}
}
void AnimationHost::SetHasCanvasInvalidation(bool has_canvas_invalidation) {
has_canvas_invalidation_.Write(*this) = has_canvas_invalidation;
}
bool AnimationHost::HasCanvasInvalidation() const {
return has_canvas_invalidation_.Read(*this);
}
bool AnimationHost::HasJSAnimation() const {
return has_inline_style_mutation_.Read(*this);
}
void AnimationHost::SetHasInlineStyleMutation(bool has_inline_style_mutation) {
has_inline_style_mutation_.Write(*this) = has_inline_style_mutation;
}
bool AnimationHost::HasSmilAnimation() const {
return has_smil_animation_.Read(*this);
}
void AnimationHost::SetHasSmilAnimation(bool has_smil_animation) {
has_smil_animation_.Write(*this) = has_smil_animation;
}
bool AnimationHost::HasViewTransition() const {
return has_view_transition_.Read(*this);
}
void AnimationHost::SetHasViewTransition(bool has_view_transition) {
has_view_transition_.Write(*this) = has_view_transition;
}
void AnimationHost::SetCurrentFrameHadRaf(bool current_frame_had_raf) {
current_frame_had_raf_.Write(*this) = current_frame_had_raf;
}
bool AnimationHost::CurrentFrameHadRAF() const {
return current_frame_had_raf_.Read(*this);
}
void AnimationHost::SetNextFrameHasPendingRaf(bool next_frame_has_pending_raf) {
next_frame_has_pending_raf_.Write(*this) = next_frame_has_pending_raf;
}
bool AnimationHost::NextFrameHasPendingRAF() const {
return next_frame_has_pending_raf_.Read(*this);
}
void AnimationHost::InitClientAnimationState() {
for (auto map_entry : element_to_animations_map_.Write(*this))
map_entry.second->InitClientAnimationState();
}
void AnimationHost::RemoveElementId(ElementId element_id) {
scoped_refptr<ElementAnimations> element_animations =
GetElementAnimationsForElementId(element_id);
if (element_animations) {
DCHECK(!element_animations->HasTickingKeyframeEffect());
element_animations->RemoveKeyframeEffects();
}
}
void AnimationHost::RegisterAnimationForElement(ElementId element_id,
Animation* animation) {
DCHECK(element_id);
DCHECK(animation);
#if DCHECK_IS_ON()
for (const auto& keyframe_model :
animation->keyframe_effect()->keyframe_models()) {
KeyframeModel* cc_keyframe_model =
KeyframeModel::ToCcKeyframeModel(keyframe_model.get());
ElementId model_element_id = cc_keyframe_model->element_id()
? cc_keyframe_model->element_id()
: element_id;
DCHECK(cc_keyframe_model->affects_active_elements() ||
cc_keyframe_model->affects_pending_elements());
DCHECK(!cc_keyframe_model->affects_active_elements() ||
mutator_host_client()->IsElementInPropertyTrees(
model_element_id, ElementListType::ACTIVE));
// Test thread_instance_ because LayerTreeHost has no pending tree.
DCHECK(thread_instance_ == ThreadInstance::kMain ||
!cc_keyframe_model->affects_pending_elements() ||
mutator_host_client()->IsElementInPropertyTrees(
model_element_id, ElementListType::PENDING));
}
#endif
scoped_refptr<ElementAnimations> element_animations =
GetElementAnimationsForElementId(element_id);
if (!element_animations) {
element_animations = ElementAnimations::Create(this, element_id);
element_to_animations_map_.Write(*this)[element_animations->element_id()] =
element_animations;
}
DCHECK(element_animations->AnimationHostIs(this));
element_animations->AddKeyframeEffect(animation->keyframe_effect());
}
void AnimationHost::UnregisterAnimationForElement(ElementId element_id,
Animation* animation) {
DCHECK(element_id);
DCHECK(animation);
scoped_refptr<ElementAnimations> element_animations =
GetElementAnimationsForElementId(element_id);
DCHECK(element_animations);
// |ClearAffectedElementTypes| requires an ElementId map in order to update
// the property trees. Generating that map requires walking the keyframe
// effects, so we have to do it before removing this one.
PropertyToElementIdMap element_id_map =
element_animations->GetPropertyToElementIdMap();
element_animations->RemoveKeyframeEffect(animation->keyframe_effect());
if (element_animations->IsEmpty()) {
element_animations->ClearAffectedElementTypes(element_id_map);
element_to_animations_map_.Write(*this).erase(
element_animations->element_id());
element_animations->ClearAnimationHost();
}
RemoveFromTicking(animation);
}
void AnimationHost::UpdateClientAnimationStateForElementAnimations(
ElementId element_id) {
auto* element_animations = GetElementAnimationsForElementId(element_id).get();
if (element_animations)
element_animations->UpdateClientAnimationState();
}
void AnimationHost::SetMutatorHostClient(MutatorHostClient* client) {
if (mutator_host_client() == client)
return;
WaitForProtectedSequenceCompletion();
if (!client) {
scroll_offset_animations_impl_.Write(*this).reset();
scroll_offset_animations_.Write(*this).reset();
ClearMutators();
}
mutator_host_client_ = client;
// Creating ScrollOffsetAnimationsImpl calls back into this, triggering
// DCHECKs that are easier to verify once `mutator_host_client_` has been
// set.
if (mutator_host_client() && !scroll_offset_animations_impl_.Read(*this)) {
if (thread_instance_ == ThreadInstance::kImpl) {
scroll_offset_animations_impl_.Write(*this) =
std::make_unique<ScrollOffsetAnimationsImpl>(this);
} else {
scroll_offset_animations_.Write(*this) =
std::make_unique<ScrollOffsetAnimations>(this);
}
}
if (mutator_host_client() && needs_push_properties_.Read(*this))
mutator_host_client()->SetMutatorsNeedCommit();
}
bool AnimationHost::IsOwnerThread() const {
return !mutator_host_client_ || mutator_host_client_->IsOwnerThread();
}
bool AnimationHost::InProtectedSequence() const {
return !mutator_host_client_ || mutator_host_client_->InProtectedSequence();
}
void AnimationHost::WaitForProtectedSequenceCompletion() const {
if (mutator_host_client_)
mutator_host_client_->WaitForProtectedSequenceCompletion();
}
void AnimationHost::SetNeedsCommit() {
DCHECK(mutator_host_client());
DCHECK(IsOwnerThread());
DCHECK(!InProtectedSequence());
mutator_host_client()->SetMutatorsNeedCommit();
// TODO(loyso): Invalidate property trees only if really needed.
mutator_host_client()->SetMutatorsNeedRebuildPropertyTrees();
}
void AnimationHost::SetNeedsPushProperties() {
if (needs_push_properties())
return;
needs_push_properties_.Write(*this) = true;
if (mutator_host_client())
mutator_host_client()->SetMutatorsNeedCommit();
}
void AnimationHost::ResetNeedsPushProperties() {
needs_push_properties_.Write(*this) = false;
}
void AnimationHost::PushPropertiesTo(MutatorHost* mutator_host_impl,
const PropertyTrees& property_trees) {
auto* host_impl = static_cast<AnimationHost*>(mutator_host_impl);
base::AutoReset<raw_ptr<const PropertyTrees>> properties(&property_trees_,
&property_trees);
// Update animation counts and whether raf was requested. These explicitly
// do not request push properties and are pushed as part of the next commit
// when it happens as requesting a commit leads to performance issues:
// https://crbug.com/1083244
host_impl->main_thread_animations_count_.Write(*host_impl) =
main_thread_animations_count_.Read(*this);
host_impl->SetCurrentFrameHadRaf(CurrentFrameHadRAF());
host_impl->SetNextFrameHasPendingRaf(NextFrameHasPendingRAF());
host_impl->SetHasCanvasInvalidation(HasCanvasInvalidation());
host_impl->SetHasInlineStyleMutation(HasJSAnimation());
host_impl->SetHasSmilAnimation(HasSmilAnimation());
host_impl->SetHasViewTransition(HasViewTransition());
if (needs_push_properties()) {
needs_push_properties_.Write(*this) = false;
PushTimelinesToImplThread(host_impl);
RemoveTimelinesFromImplThread(host_impl);
PushPropertiesToImplThread(host_impl);
// When using a display tree this ensures that any new animation updates are
// pushed to Viz on next display tree update. When not using display trees,
// setting this flag here is meaningless.
host_impl->needs_push_properties_.Write(*host_impl) = true;
}
}
void AnimationHost::RemoveStaleTimelines() {
DCHECK(!InProtectedSequence());
if (detached_timeline_map_.Read(*this).empty()) {
return;
}
for (auto& kv : detached_timeline_map_.Read(*this)) {
RemoveAnimationTimeline(kv.second);
}
detached_timeline_map_.Write(*this).clear();
}
void AnimationHost::PushTimelinesToImplThread(AnimationHost* host_impl) const {
for (auto& kv : id_to_timeline_map_.Read(*this)) {
auto& timeline = kv.second;
const AnimationTimeline* timeline_impl =
host_impl->GetTimelineById(timeline->id());
if (timeline_impl)
continue;
scoped_refptr<AnimationTimeline> to_add = timeline->CreateImplInstance();
host_impl->AddAnimationTimeline(std::move(to_add));
}
}
void AnimationHost::RemoveTimelinesFromImplThread(
AnimationHost* host_impl) const {
IdToTimelineMap& timelines_impl =
host_impl->id_to_timeline_map_.Write(*host_impl);
// Erase all the impl timelines which |this| doesn't have.
for (auto it = timelines_impl.begin(); it != timelines_impl.end();) {
auto& timeline_impl = it->second;
if (timeline_impl->is_impl_only() || GetTimelineById(timeline_impl->id())) {
++it;
} else {
host_impl->EraseTimeline(it->second);
it = timelines_impl.erase(it);
}
}
}
void AnimationHost::PushPropertiesToImplThread(AnimationHost* host_impl) {
base::AutoReset<raw_ptr<const PropertyTrees>> properties(
&host_impl->property_trees_, property_trees_);
// Sync all animations with impl thread to create ElementAnimations. This
// needs to happen before the element animations are synced below.
for (auto& kv : id_to_timeline_map_.Read(*this)) {
AnimationTimeline* timeline = kv.second.get();
if (AnimationTimeline* timeline_impl =
host_impl->GetTimelineById(timeline->id())) {
timeline->PushPropertiesTo(timeline_impl);
}
}
// Sync properties for created ElementAnimations.
for (auto& kv : element_to_animations_map_.Read(*this)) {
const auto& element_animations = kv.second;
if (auto element_animations_impl =
host_impl->GetElementAnimationsForElementId(kv.first)) {
element_animations->PushPropertiesTo(std::move(element_animations_impl));
}
}
// Update the impl-only scroll offset animations.
scroll_offset_animations_.Write(*this)->PushPropertiesTo(
host_impl->scroll_offset_animations_impl_.Write(*host_impl).get());
// The pending info list is cleared in LayerTreeHostImpl::CommitComplete
// and should be empty when pushing properties.
DCHECK(host_impl->pending_compositor_metrics_tracker_infos_.Read(*host_impl)
.empty());
host_impl->pending_compositor_metrics_tracker_infos_.Write(*host_impl) =
TakePendingCompositorMetricsTrackerInfos();
}
const ElementAnimations* AnimationHost::GetElementAnimationsForElementId(
ElementId element_id) const {
if (!element_id)
return nullptr;
auto iter = element_to_animations_map_.Read(*this).find(element_id);
return iter == element_to_animations_map_.Read(*this).end()
? nullptr
: iter->second.get();
}
scoped_refptr<ElementAnimations>
AnimationHost::GetElementAnimationsForElementId(ElementId element_id) {
if (!element_id)
return nullptr;
auto iter = element_to_animations_map_.Write(*this).find(element_id);
return iter == element_to_animations_map_.Write(*this).end() ? nullptr
: iter->second;
}
scoped_refptr<const ElementAnimations>
AnimationHost::GetElementAnimationsForElementIdForTesting(
ElementId element_id) const {
return GetElementAnimationsForElementId(element_id);
}
gfx::PointF AnimationHost::GetScrollOffsetForAnimation(
ElementId element_id) const {
DCHECK(property_trees_);
return property_trees_->scroll_tree().current_scroll_offset(element_id);
}
void AnimationHost::SetScrollAnimationDurationForTesting(
base::TimeDelta duration) {
ScrollOffsetAnimationCurve::SetAnimationDurationForTesting(duration);
}
bool AnimationHost::NeedsTickAnimations() const {
for (auto& animation : ticking_animations_.Read(*this)) {
if (!animation->keyframe_effect()->awaiting_deletion()) {
return true;
}
}
return false;
}
void AnimationHost::TickMutator(base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
LayerTreeMutator* mutator = mutator_.Write(*this).get();
if (!mutator || !mutator->HasMutators())
return;
DCHECK(IsOwnerThread());
DCHECK(!InProtectedSequence());
std::unique_ptr<MutatorInputState> state = CollectWorkletAnimationsState(
monotonic_time, scroll_tree, is_active_tree);
if (state->IsEmpty())
return;
ElementListType tree_type =
is_active_tree ? ElementListType::ACTIVE : ElementListType::PENDING;
auto on_done = base::BindOnce(
[](base::WeakPtr<AnimationHost> animation_host, ElementListType tree_type,
MutateStatus status) {
if (animation_host->mutator_host_client()) {
animation_host->mutator_host_client()
->NotifyAnimationWorkletStateChange(
ToAnimationWorkletMutationState(status), tree_type);
}
},
weak_factory_.GetWeakPtr(), tree_type);
MutateQueuingStrategy queuing_strategy =
is_active_tree ? MutateQueuingStrategy::kQueueAndReplaceNormalPriority
: MutateQueuingStrategy::kQueueHighPriority;
if (mutator->Mutate(std::move(state), queuing_strategy, std::move(on_done))) {
mutator_host_client()->NotifyAnimationWorkletStateChange(
AnimationWorkletMutationState::STARTED, tree_type);
}
}
bool AnimationHost::ActivateAnimations(MutatorEvents* mutator_events) {
if (!NeedsTickAnimations())
return false;
auto* animation_events = static_cast<AnimationEvents*>(mutator_events);
TRACE_EVENT0("cc", "AnimationHost::ActivateAnimations");
AnimationsList ticking_animations_copy = ticking_animations_.Read(*this);
for (auto& it : ticking_animations_copy) {
it->ActivateKeyframeModels();
// Finish animations which no longer affect active or pending elements.
it->UpdateState(false, animation_events);
}
return true;
}
bool AnimationHost::TickAnimations(base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
TRACE_EVENT0("cc", "AnimationHost::TickAnimations");
// We tick animations in the following order:
// 1. regular animations 2. mutator 3. worklet animations
//
// Mutator may depend on scroll offset as its time input e.g., when there is
// a worklet animation attached to a scroll timeline.
// This ordering ensures we use the latest scroll offset as the input to the
// mutator even if there are active scroll animations.
// The ticking of worklet animations is deferred until draw to ensure that
// mutator output takes effect in the same impl frame that it was mutated.
if (is_active_tree && !NeedsTickAnimations()) {
return false;
}
TRACE_EVENT_INSTANT0("cc", "NeedsTickAnimations", TRACE_EVENT_SCOPE_THREAD);
bool animated = false;
std::vector<AnimationTimeline*> scroll_timelines;
for (auto& kv : id_to_timeline_map_.Read(*this)) {
AnimationTimeline* timeline = kv.second.get();
if (timeline->IsScrollTimeline()) {
scroll_timelines.push_back(timeline);
} else {
animated |= timeline->TickTimeLinkedAnimations(
ticking_animations_.Read(*this), monotonic_time, !is_active_tree);
}
}
// Tick the scroll-linked animations last, since a smooth scroll (time-linked)
// might update the scroll offset.
for (auto* timeline : scroll_timelines) {
animated |= timeline->TickScrollLinkedAnimations(
ticking_animations_.Read(*this), scroll_tree, is_active_tree);
}
// TODO(majidvp): At the moment we call this for both active and pending
// trees similar to other animations. However our final goal is to only call
// it once, ideally after activation, and only when the input
// to an active timeline has changed. http://crbug.com/767210
// Note that the TickMutator does not set the animated flag since these
// mutations are processed asynchronously. Additional actions required to
// handle these mutations are performed on receiving the asynchronous results.
TickMutator(monotonic_time, scroll_tree, is_active_tree);
return animated;
}
void AnimationHost::TickScrollAnimations(base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree) {
// TODO(majidvp): We need to return a boolean here so that LTHI knows
// whether it needs to schedule another frame.
TickMutator(monotonic_time, scroll_tree, true /* is_active_tree */);
}
void AnimationHost::TickWorkletAnimations() {
for (auto& animation : ticking_animations_.Read(*this)) {
if (!animation->IsWorkletAnimation())
continue;
animation->Tick(base::TimeTicks());
}
}
std::unique_ptr<MutatorInputState> AnimationHost::CollectWorkletAnimationsState(
base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
TRACE_EVENT0("cc", "AnimationHost::CollectWorkletAnimationsState");
std::unique_ptr<MutatorInputState> result =
std::make_unique<MutatorInputState>();
for (auto& animation : ticking_animations_.Read(*this)) {
if (!animation->IsWorkletAnimation())
continue;
ToWorkletAnimation(animation.get())
->UpdateInputState(result.get(), monotonic_time, scroll_tree,
is_active_tree);
}
return result;
}
bool AnimationHost::UpdateAnimationState(bool start_ready_animations,
MutatorEvents* mutator_events) {
if (!NeedsTickAnimations())
return false;
auto* animation_events = static_cast<AnimationEvents*>(mutator_events);
TRACE_EVENT0("cc", "AnimationHost::UpdateAnimationState");
AnimationsList ticking_animations_copy = ticking_animations_.Read(*this);
for (auto& it : ticking_animations_copy)
it->UpdateState(start_ready_animations, animation_events);
return true;
}
void AnimationHost::TakeTimeUpdatedEvents(MutatorEvents* events) {
auto* animation_events = static_cast<AnimationEvents*>(events);
if (!animation_events->needs_time_updated_events())
return;
for (auto& it : ticking_animations_.Read(*this))
it->TakeTimeUpdatedEvent(animation_events);
animation_events->set_needs_time_updated_events(false);
}
void AnimationHost::PromoteScrollTimelinesPendingToActive() {
for (auto& kv : id_to_timeline_map_.Read(*this)) {
auto& timeline = kv.second;
timeline->ActivateTimeline();
}
}
std::unique_ptr<MutatorEvents> AnimationHost::CreateEvents() {
return std::make_unique<AnimationEvents>();
}
void AnimationHost::SetAnimationEvents(
std::unique_ptr<MutatorEvents> mutator_events) {
DCHECK_EQ(thread_instance_, ThreadInstance::kMain);
auto events =
base::WrapUnique(static_cast<AnimationEvents*>(mutator_events.release()));
for (const AnimationEvent& event : events->events()) {
AnimationTimeline* timeline = GetTimelineById(event.uid.timeline_id);
if (timeline) {
Animation* animation = timeline->GetAnimationById(event.uid.animation_id);
if (animation)
animation->DispatchAndDelegateAnimationEvent(event);
}
}
}
bool AnimationHost::ScrollOffsetAnimationWasInterrupted(
ElementId element_id) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return element_animations &&
element_animations->ScrollOffsetAnimationWasInterrupted();
}
bool AnimationHost::IsAnimatingProperty(ElementId element_id,
ElementListType list_type,
TargetProperty::Type property) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return element_animations &&
element_animations->IsCurrentlyAnimatingProperty(property, list_type);
}
bool AnimationHost::HasPotentiallyRunningAnimationForProperty(
ElementId element_id,
ElementListType list_type,
TargetProperty::Type property) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return element_animations &&
element_animations->IsPotentiallyAnimatingProperty(property,
list_type);
}
bool AnimationHost::HasAnyAnimationTargetingProperty(
ElementId element_id,
TargetProperty::Type property) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return element_animations &&
element_animations->HasAnyAnimationTargetingProperty(property,
element_id);
}
bool AnimationHost::AnimationsPreserveAxisAlignment(
ElementId element_id) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return !element_animations ||
element_animations->AnimationsPreserveAxisAlignment();
}
float AnimationHost::MaximumScale(ElementId element_id,
ElementListType list_type) const {
if (const auto* element_animations =
GetElementAnimationsForElementId(element_id)) {
return element_animations->MaximumScale(element_id, list_type);
}
return kInvalidScale;
}
bool AnimationHost::IsElementAnimating(ElementId element_id) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return element_animations && element_animations->HasAnyKeyframeModel();
}
bool AnimationHost::HasTickingKeyframeModelForTesting(
ElementId element_id) const {
const auto* element_animations = GetElementAnimationsForElementId(element_id);
return element_animations && element_animations->HasTickingKeyframeEffect();
}
void AnimationHost::ImplOnlyAutoScrollAnimationCreate(
ElementId element_id,
const gfx::PointF& target_offset,
const gfx::PointF& current_offset,
float autoscroll_velocity,
base::TimeDelta animation_start_offset) {
DCHECK(scroll_offset_animations_impl_.Read(*this));
scroll_offset_animations_impl_.Write(*this)->AutoScrollAnimationCreate(
element_id, target_offset, current_offset, autoscroll_velocity,
animation_start_offset);
}
void AnimationHost::ImplOnlyScrollAnimationCreate(
ElementId element_id,
const gfx::PointF& target_offset,
const gfx::PointF& current_offset,
base::TimeDelta delayed_by,
base::TimeDelta animation_start_offset) {
DCHECK(scroll_offset_animations_impl_.Read(*this));
scroll_offset_animations_impl_.Write(*this)->MouseWheelScrollAnimationCreate(
element_id, target_offset, current_offset, delayed_by,
animation_start_offset);
}
std::optional<gfx::PointF> AnimationHost::ImplOnlyScrollAnimationUpdateTarget(
const gfx::Vector2dF& scroll_delta,
const gfx::PointF& max_scroll_offset,
base::TimeTicks frame_monotonic_time,
base::TimeDelta delayed_by,
ElementId element_id) {
DCHECK(scroll_offset_animations_impl_.Read(*this));
return scroll_offset_animations_impl_.Write(*this)
->ScrollAnimationUpdateTarget(scroll_delta, max_scroll_offset,
frame_monotonic_time, delayed_by,
element_id);
}
ScrollOffsetAnimations& AnimationHost::scroll_offset_animations() {
DCHECK(scroll_offset_animations_.Read(*this));
return *scroll_offset_animations_.Write(*this).get();
}
void AnimationHost::ScrollAnimationAbort(ElementId element_id) {
DCHECK(scroll_offset_animations_impl_.Read(*this));
scroll_offset_animations_impl_.Write(*this)->ScrollAnimationAbort(
false /* needs_completion */, element_id);
}
bool AnimationHost::ElementHasImplOnlyScrollAnimation(
ElementId element_id) const {
return scroll_offset_animations_impl_.Read(*this)
->ElementHasImplOnlyScrollAnimation(element_id);
}
bool AnimationHost::HasImplOnlyScrollAnimatingElement() const {
return scroll_offset_animations_impl_.Read(*this)
->HasImplOnlyScrollAnimatingElement();
}
bool AnimationHost::HasImplOnlyAutoScrollAnimatingElement() const {
return scroll_offset_animations_impl_.Read(*this)
->HasImplOnlyAutoScrollAnimatingElement();
}
bool AnimationHost::IsElementInPropertyTrees(ElementId element_id,
bool commits_to_active) const {
return mutator_host_client()->IsElementInPropertyTrees(
element_id,
commits_to_active ? ElementListType::ACTIVE : ElementListType::PENDING);
}
void AnimationHost::HandleRemovedScrollAnimatingElements(
bool commits_to_active) {
scroll_offset_animations_impl_.Write(*this)
->HandleRemovedScrollAnimatingElements(commits_to_active);
}
void AnimationHost::AddToTicking(scoped_refptr<Animation> animation) {
DCHECK(!base::Contains(ticking_animations_.Read(*this), animation));
ticking_animations_.Write(*this).push_back(animation);
}
void AnimationHost::RemoveFromTicking(scoped_refptr<Animation> animation) {
auto to_erase =
std::ranges::find(ticking_animations_.Write(*this), animation);
if (to_erase != ticking_animations_.Write(*this).end()) {
ticking_animations_.Write(*this).erase(to_erase);
}
}
const AnimationHost::AnimationsList&
AnimationHost::ticking_animations_for_testing() const {
return ticking_animations_.Read(*this);
}
const AnimationHost::ElementToAnimationsMap&
AnimationHost::element_animations_for_testing() const {
return element_to_animations_map_.Read(*this);
}
void AnimationHost::SetLayerTreeMutator(
std::unique_ptr<LayerTreeMutator> mutator) {
mutator_.Write(*this) = std::move(mutator);
mutator_.Write(*this)->SetClient(this);
}
WorkletAnimation* AnimationHost::FindWorkletAnimation(WorkletAnimationId id) {
// TODO(majidvp): Use a map to make lookup O(1)
auto animation =
std::ranges::find_if(ticking_animations_.Read(*this), [id](auto& it) {
return it->IsWorkletAnimation() &&
ToWorkletAnimation(it.get())->worklet_animation_id() == id;
});
if (animation == ticking_animations_.Read(*this).end())
return nullptr;
return ToWorkletAnimation(animation->get());
}
void AnimationHost::SetMutationUpdate(
std::unique_ptr<MutatorOutputState> output_state) {
if (!output_state)
return;
TRACE_EVENT0("cc", "AnimationHost::SetMutationUpdate");
for (auto& animation_state : output_state->animations) {
WorkletAnimationId id = animation_state.worklet_animation_id;
WorkletAnimation* to_update = FindWorkletAnimation(id);
if (to_update)
to_update->SetOutputState(animation_state);
}
}
void AnimationHost::SetAnimationCounts(size_t total_animations_count) {
// Though these changes are pushed as part of AnimationHost::PushPropertiesTo
// we don't SetNeedsPushProperties as pushing the values requires a commit.
// Instead we allow them to be pushed whenever the next required commit
// happens to avoid unnecessary work. See https://crbug.com/1083244.
// If an animation is being run on the compositor, it will have a ticking
// Animation (which will have a corresponding impl-thread version). Therefore
// to find the count of main-only animations, we can simply subtract the
// number of ticking animations from the total count.
size_t ticking_animations_count = ticking_animations_.Read(*this).size();
main_thread_animations_count_.Write(*this) =
total_animations_count - ticking_animations_count;
DCHECK_GE(main_thread_animations_count_.Read(*this), 0u);
}
size_t AnimationHost::MainThreadAnimationsCount() const {
return main_thread_animations_count_.Read(*this);
}
bool AnimationHost::HasInvalidationAnimation() const {
for (const auto& it : ticking_animations_.Read(*this))
if (it->RequiresInvalidation())
return true;
return false;
}
bool AnimationHost::HasNativePropertyAnimation() const {
for (const auto& it : ticking_animations_.Read(*this))
if (it->AffectsNativeProperty())
return true;
return false;
}
AnimationHost::PendingCompositorMetricsTrackerInfos
AnimationHost::TakePendingCompositorMetricsTrackerInfos() {
PendingCompositorMetricsTrackerInfos infos =
std::move(pending_compositor_metrics_tracker_infos_.Write(*this));
pending_compositor_metrics_tracker_infos_.Write(*this) = {};
return infos;
}
void AnimationHost::StartCompositorMetricsTracking(
TrackedAnimationSequenceId sequence_id) {
pending_compositor_metrics_tracker_infos_.Write(*this).push_back(
{sequence_id, true});
SetNeedsPushProperties();
}
void AnimationHost::StopCompositorMetricsTracking(
TrackedAnimationSequenceId sequence_id) {
pending_compositor_metrics_tracker_infos_.Write(*this).push_back(
{sequence_id, false});
SetNeedsPushProperties();
}
bool AnimationHost::HasScrollLinkedAnimation(ElementId for_scroller) const {
for (auto& animation : ticking_animations_.Read(*this)) {
if (auto* timeline = animation->animation_timeline()) {
if (timeline->IsLinkedToScroller(for_scroller)) {
return true;
}
}
}
return false;
}
} // namespace cc