910e62b5创建于 1月15日历史提交
// 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/animation_trigger.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();
}

scoped_refptr<AnimationTimeline> AnimationHost::GetScopedRefTimelineById(
    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;
}

const AnimationTrigger* AnimationHost::GetTriggerById(int id) const {
  const auto& it = id_to_trigger_map_.Read(*this).find(id);
  return it == id_to_trigger_map_.Read(*this).end() ? nullptr
                                                    : it->second.get();
}

void AnimationHost::ClearMutators() {
  for (auto& kv : id_to_timeline_map_.Read(*this))
    EraseTimeline(kv.second);
  id_to_timeline_map_.Write(*this).clear();
  for (auto& it : id_to_trigger_map_.Write(*this)) {
    it.second->SetAnimationHost(nullptr);
  }
  id_to_trigger_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::AddTrigger(scoped_refptr<AnimationTrigger> trigger) {
  trigger->SetAnimationHost(this);
  id_to_trigger_map_.Write(*this).insert(
      std::make_pair(trigger->id(), trigger));
  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::RemoveTrigger(scoped_refptr<AnimationTrigger> trigger) {
  id_to_trigger_map_.Write(*this).erase(trigger->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::DetachTrigger(scoped_refptr<AnimationTrigger> trigger) {
  if (InProtectedSequence()) {
    // Defer cleanup until post-commit.
    detached_trigger_map_.Write(*this).insert(
        std::make_pair(trigger->id(), trigger));
  } else {
    RemoveTrigger(trigger);
  }
}

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);
    PushTriggersToImplThread(host_impl);
    RemoveTriggersFromImplThread(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::RemoveStaleTriggers() {
  DCHECK(!InProtectedSequence());
  if (detached_trigger_map_.Read(*this).empty()) {
    return;
  }

  for (auto& kv : detached_trigger_map_.Read(*this)) {
    RemoveTrigger(kv.second);
  }
  detached_trigger_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::PushTriggersToImplThread(AnimationHost* host_impl) const {
  for (auto& it : id_to_trigger_map_.Read(*this)) {
    const scoped_refptr<AnimationTrigger>& trigger = it.second;
    if (host_impl->GetTriggerById(trigger->id())) {
      continue;
    }

    auto to_add = trigger->CreateImplInstance(*host_impl);
    host_impl->AddTrigger(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.
  // TODO(crbug.com/459538550): It might be too early to remove a timeline here
  // as the timeline might still be relevant to the active tree until the
  // pending tree becomes active.
  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::RemoveTriggersFromImplThread(
    AnimationHost* host_impl) const {
  IdToTriggerMap& impl_list = host_impl->id_to_trigger_map_.Write(*host_impl);
  // TODO(crbug.com/459538550): It might be too early to remove a trigger here
  // as the trigger might still be relevant to the active tree until the pending
  // tree becomes active.
  std::erase_if(
      impl_list,
      [&](const std::pair<int, scoped_refptr<AnimationTrigger>>& pair) {
        return !GetTriggerById(pair.second->id());
      });
}

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