// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/animation/animation_host.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/contains.h"
#include "base/memory/ptr_util.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/box_f.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::MAIN));
}

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::MAIN);
  auto mutator_host_impl =
      base::WrapUnique<MutatorHost>(new AnimationHost(ThreadInstance::IMPL));
  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::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;
}

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) {
    element_animations->RemoveKeyframeEffects();
  }
}

void AnimationHost::RegisterAnimationForElement(ElementId element_id,
                                                Animation* animation) {
  DCHECK(element_id);
  DCHECK(animation);

  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::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::IMPL) {
      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::PushPropertiesTo(MutatorHost* mutator_host_impl,
                                     const PropertyTrees& property_trees) {
  auto* host_impl = static_cast<AnimationHost*>(mutator_host_impl);

  base::AutoReset<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());

  if (needs_push_properties()) {
    needs_push_properties_.Write(*this) = false;
    PushTimelinesToImplThread(host_impl);
    RemoveTimelinesFromImplThread(host_impl);
    PushPropertiesToImplThread(host_impl);
    // This is redundant but used in tests.
    host_impl->needs_push_properties_.Write(*host_impl) = false;
  }
}

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<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_throughput_tracker_infos_.Read(*host_impl).empty());
  host_impl->pending_throughput_tracker_infos_.Write(*host_impl) =
      TakePendingThroughputTrackerInfos();
}

scoped_refptr<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;
}

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;
}

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 {
  return !ticking_animations_.Read(*this).empty();
}

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 (!NeedsTickAnimations())
    return false;

  TRACE_EVENT_INSTANT0("cc", "NeedsTickAnimations", TRACE_EVENT_SCOPE_THREAD);

  bool animated = false;
  for (auto& kv : id_to_timeline_map_.Read(*this)) {
    AnimationTimeline* timeline = kv.second.get();
    if (timeline->IsScrollTimeline()) {
      animated |= timeline->TickScrollLinkedAnimations(
          ticking_animations_.Read(*this), scroll_tree, is_active_tree);
    } else {
      animated |= timeline->TickTimeLinkedAnimations(
          ticking_animations_.Read(*this), monotonic_time);
    }
  }

  // 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::MAIN);
  auto events =
      base::WrapUnique(static_cast<AnimationEvents*>(mutator_events.release()));

  for (size_t event_index = 0; event_index < events->events_.size();
       ++event_index) {
    AnimationEvent& event = events->events_[event_index];
    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 {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->ScrollOffsetAnimationWasInterrupted()
             : false;
}

bool AnimationHost::IsAnimatingFilterProperty(ElementId element_id,
                                              ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsCurrentlyAnimatingProperty(
                   TargetProperty::FILTER, list_type)
             : false;
}

bool AnimationHost::IsAnimatingBackdropFilterProperty(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations ? element_animations->IsCurrentlyAnimatingProperty(
                                  TargetProperty::BACKDROP_FILTER, list_type)
                            : false;
}

bool AnimationHost::IsAnimatingOpacityProperty(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsCurrentlyAnimatingProperty(
                   TargetProperty::OPACITY, list_type)
             : false;
}

bool AnimationHost::IsAnimatingTransformProperty(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsCurrentlyAnimatingProperty(
                   TargetProperty::TRANSFORM, list_type)
             : false;
}

bool AnimationHost::HasPotentiallyRunningFilterAnimation(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsPotentiallyAnimatingProperty(
                   TargetProperty::FILTER, list_type)
             : false;
}

bool AnimationHost::HasPotentiallyRunningBackdropFilterAnimation(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsPotentiallyAnimatingProperty(
                   TargetProperty::BACKDROP_FILTER, list_type)
             : false;
}

bool AnimationHost::HasPotentiallyRunningOpacityAnimation(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsPotentiallyAnimatingProperty(
                   TargetProperty::OPACITY, list_type)
             : false;
}

bool AnimationHost::HasPotentiallyRunningTransformAnimation(
    ElementId element_id,
    ElementListType list_type) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->IsPotentiallyAnimatingProperty(
                   TargetProperty::TRANSFORM, list_type)
             : false;
}

bool AnimationHost::HasAnyAnimationTargetingProperty(
    ElementId element_id,
    TargetProperty::Type property) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  if (!element_animations)
    return false;

  return element_animations->HasAnyAnimationTargetingProperty(property);
}

bool AnimationHost::AnimationsPreserveAxisAlignment(
    ElementId element_id) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations
             ? element_animations->AnimationsPreserveAxisAlignment()
             : true;
}

float AnimationHost::MaximumScale(ElementId element_id,
                                  ElementListType list_type) const {
  if (auto element_animations = GetElementAnimationsForElementId(element_id))
    return element_animations->MaximumScale(list_type);
  return kInvalidScale;
}

bool AnimationHost::IsElementAnimating(ElementId element_id) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations ? element_animations->HasAnyKeyframeModel() : false;
}

bool AnimationHost::HasTickingKeyframeModelForTesting(
    ElementId element_id) const {
  auto element_animations = GetElementAnimationsForElementId(element_id);
  return element_animations ? element_animations->HasTickingKeyframeEffect()
                            : false;
}

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);
}

bool AnimationHost::ImplOnlyScrollAnimationUpdateTarget(
    const gfx::Vector2dF& scroll_delta,
    const gfx::PointF& max_scroll_offset,
    base::TimeTicks frame_monotonic_time,
    base::TimeDelta delayed_by) {
  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);
}

ScrollOffsetAnimations& AnimationHost::scroll_offset_animations() {
  DCHECK(scroll_offset_animations_.Read(*this));
  return *scroll_offset_animations_.Write(*this).get();
}

void AnimationHost::ScrollAnimationAbort() {
  DCHECK(scroll_offset_animations_impl_.Read(*this));
  scroll_offset_animations_impl_.Write(*this)->ScrollAnimationAbort(
      false /* needs_completion */);
}

ElementId AnimationHost::ImplOnlyScrollAnimatingElement() const {
  DCHECK(scroll_offset_animations_impl_.Read(*this));
  if (!scroll_offset_animations_impl_.Read(*this)->IsAnimating())
    return ElementId();

  return scroll_offset_animations_impl_.Read(*this)->GetElementId();
}

void AnimationHost::ImplOnlyScrollAnimatingElementRemoved() {
  scroll_offset_animations_impl_.Write(*this)
      ->AnimatingElementRemovedByCommit();
}

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::find(ticking_animations_.Write(*this).begin(),
                            ticking_animations_.Write(*this).end(), 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::find_if(
      ticking_animations_.Read(*this).begin(),
      ticking_animations_.Read(*this).end(), [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::PendingThroughputTrackerInfos
AnimationHost::TakePendingThroughputTrackerInfos() {
  PendingThroughputTrackerInfos infos =
      std::move(pending_throughput_tracker_infos_.Write(*this));
  pending_throughput_tracker_infos_.Write(*this) = {};
  return infos;
}

void AnimationHost::StartThroughputTracking(
    TrackedAnimationSequenceId sequence_id) {
  pending_throughput_tracker_infos_.Write(*this).push_back({sequence_id, true});
  SetNeedsPushProperties();
}

void AnimationHost::StopThroughputTracking(
    TrackedAnimationSequenceId sequnece_id) {
  pending_throughput_tracker_infos_.Write(*this).push_back(
      {sequnece_id, false});
  SetNeedsPushProperties();
}

}  // namespace cc
