blob: 404e9a0aebb44a8530eed346e8f2393334996d20 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/animation/element_animations.h"
#include <stddef.h>
#include <algorithm>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "cc/animation/animation_delegate.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_player.h"
#include "cc/animation/keyframed_animation_curve.h"
#include "cc/animation/scroll_offset_animation_curve.h"
#include "cc/output/filter_operations.h"
#include "cc/trees/mutator_host_client.h"
#include "ui/gfx/geometry/box_f.h"
namespace cc {
scoped_refptr<ElementAnimations> ElementAnimations::Create() {
return make_scoped_refptr(new ElementAnimations());
}
ElementAnimations::ElementAnimations()
: players_list_(new PlayersList()),
animation_host_(),
element_id_(),
is_active_(false),
has_element_in_active_list_(false),
has_element_in_pending_list_(false),
needs_to_start_animations_(false),
scroll_offset_animation_was_interrupted_(false) {}
ElementAnimations::~ElementAnimations() {}
void ElementAnimations::SetAnimationHost(AnimationHost* host) {
animation_host_ = host;
}
void ElementAnimations::SetElementId(ElementId element_id) {
element_id_ = element_id;
}
void ElementAnimations::InitAffectedElementTypes() {
DCHECK(element_id_);
DCHECK(animation_host_);
UpdateActivation(FORCE_ACTIVATION);
DCHECK(animation_host_->mutator_host_client());
if (animation_host_->mutator_host_client()->IsElementInList(
element_id_, ElementListType::ACTIVE)) {
set_has_element_in_active_list(true);
}
if (animation_host_->mutator_host_client()->IsElementInList(
element_id_, ElementListType::PENDING)) {
set_has_element_in_pending_list(true);
}
}
void ElementAnimations::ClearAffectedElementTypes() {
DCHECK(animation_host_);
if (has_element_in_active_list()) {
IsAnimatingChanged(ElementListType::ACTIVE, TargetProperty::TRANSFORM,
AnimationChangeType::BOTH, false);
IsAnimatingChanged(ElementListType::ACTIVE, TargetProperty::OPACITY,
AnimationChangeType::BOTH, false);
}
set_has_element_in_active_list(false);
if (has_element_in_pending_list()) {
IsAnimatingChanged(ElementListType::PENDING, TargetProperty::TRANSFORM,
AnimationChangeType::BOTH, false);
IsAnimatingChanged(ElementListType::PENDING, TargetProperty::OPACITY,
AnimationChangeType::BOTH, false);
}
set_has_element_in_pending_list(false);
animation_host_->DidDeactivateElementAnimations(this);
UpdateActivation(FORCE_ACTIVATION);
}
void ElementAnimations::ElementRegistered(ElementId element_id,
ElementListType list_type) {
DCHECK_EQ(element_id_, element_id);
if (list_type == ElementListType::ACTIVE)
set_has_element_in_active_list(true);
else
set_has_element_in_pending_list(true);
}
void ElementAnimations::ElementUnregistered(ElementId element_id,
ElementListType list_type) {
DCHECK_EQ(this->element_id(), element_id);
if (list_type == ElementListType::ACTIVE)
set_has_element_in_active_list(false);
else
set_has_element_in_pending_list(false);
}
void ElementAnimations::AddPlayer(AnimationPlayer* player) {
players_list_->Append(player);
}
void ElementAnimations::RemovePlayer(AnimationPlayer* player) {
for (PlayersListNode* node = players_list_->head();
node != players_list_->end(); node = node->next()) {
if (node->value() == player) {
node->RemoveFromList();
return;
}
}
}
bool ElementAnimations::IsEmpty() const {
return players_list_->empty();
}
void ElementAnimations::PushPropertiesTo(
scoped_refptr<ElementAnimations> element_animations_impl) {
DCHECK_NE(this, element_animations_impl);
if (!has_any_animation() && !element_animations_impl->has_any_animation())
return;
MarkAbortedAnimationsForDeletion(element_animations_impl.get());
PurgeAnimationsMarkedForDeletion();
PushNewAnimationsToImplThread(element_animations_impl.get());
// Remove finished impl side animations only after pushing,
// and only after the animations are deleted on the main thread
// this insures we will never push an animation twice.
RemoveAnimationsCompletedOnMainThread(element_animations_impl.get());
PushPropertiesToImplThread(element_animations_impl.get());
element_animations_impl->UpdateActivation(NORMAL_ACTIVATION);
UpdateActivation(NORMAL_ACTIVATION);
}
void ElementAnimations::AddAnimation(std::unique_ptr<Animation> animation) {
DCHECK(!animation->is_impl_only() ||
animation->target_property() == TargetProperty::SCROLL_OFFSET);
bool added_transform_animation =
animation->target_property() == TargetProperty::TRANSFORM;
bool added_opacity_animation =
animation->target_property() == TargetProperty::OPACITY;
animations_.push_back(std::move(animation));
needs_to_start_animations_ = true;
UpdateActivation(NORMAL_ACTIVATION);
if (added_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (added_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
void ElementAnimations::Animate(base::TimeTicks monotonic_time) {
DCHECK(!monotonic_time.is_null());
if (!has_element_in_active_list() && !has_element_in_pending_list())
return;
if (needs_to_start_animations_)
StartAnimations(monotonic_time);
TickAnimations(monotonic_time);
last_tick_time_ = monotonic_time;
UpdateClientAnimationState(TargetProperty::OPACITY);
UpdateClientAnimationState(TargetProperty::TRANSFORM);
}
void ElementAnimations::UpdateState(bool start_ready_animations,
AnimationEvents* events) {
if (!has_element_in_active_list())
return;
// Animate hasn't been called, this happens if an element has been added
// between the Commit and Draw phases.
if (last_tick_time_ == base::TimeTicks())
return;
if (start_ready_animations)
PromoteStartedAnimations(last_tick_time_, events);
MarkFinishedAnimations(last_tick_time_);
MarkAnimationsForDeletion(last_tick_time_, events);
if (needs_to_start_animations_ && start_ready_animations) {
StartAnimations(last_tick_time_);
PromoteStartedAnimations(last_tick_time_, events);
}
UpdateActivation(NORMAL_ACTIVATION);
}
void ElementAnimations::ActivateAnimations() {
bool changed_transform_animation = false;
bool changed_opacity_animation = false;
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->affects_active_elements() !=
animations_[i]->affects_pending_elements()) {
if (animations_[i]->target_property() == TargetProperty::TRANSFORM)
changed_transform_animation = true;
else if (animations_[i]->target_property() == TargetProperty::OPACITY)
changed_opacity_animation = true;
}
animations_[i]->set_affects_active_elements(
animations_[i]->affects_pending_elements());
}
auto affects_no_elements = [](const std::unique_ptr<Animation>& animation) {
return !animation->affects_active_elements() &&
!animation->affects_pending_elements();
};
animations_.erase(std::remove_if(animations_.begin(), animations_.end(),
affects_no_elements),
animations_.end());
scroll_offset_animation_was_interrupted_ = false;
UpdateActivation(NORMAL_ACTIVATION);
if (changed_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (changed_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
void ElementAnimations::NotifyAnimationStarted(const AnimationEvent& event) {
if (event.is_impl_only) {
NotifyPlayersAnimationStarted(event.monotonic_time, event.target_property,
event.group_id);
return;
}
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->group() == event.group_id &&
animations_[i]->target_property() == event.target_property &&
animations_[i]->needs_synchronized_start_time()) {
animations_[i]->set_needs_synchronized_start_time(false);
if (!animations_[i]->has_set_start_time())
animations_[i]->set_start_time(event.monotonic_time);
NotifyPlayersAnimationStarted(event.monotonic_time, event.target_property,
event.group_id);
return;
}
}
}
void ElementAnimations::NotifyAnimationFinished(const AnimationEvent& event) {
if (event.is_impl_only) {
NotifyPlayersAnimationFinished(event.monotonic_time, event.target_property,
event.group_id);
return;
}
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->group() == event.group_id &&
animations_[i]->target_property() == event.target_property) {
animations_[i]->set_received_finished_event(true);
NotifyPlayersAnimationFinished(event.monotonic_time,
event.target_property, event.group_id);
return;
}
}
}
void ElementAnimations::NotifyAnimationTakeover(const AnimationEvent& event) {
DCHECK(event.target_property == TargetProperty::SCROLL_OFFSET);
if (!players_list_->empty()) {
std::unique_ptr<AnimationCurve> animation_curve = event.curve->Clone();
NotifyPlayersAnimationTakeover(event.monotonic_time, event.target_property,
event.animation_start_time,
std::move(animation_curve));
}
}
void ElementAnimations::NotifyAnimationAborted(const AnimationEvent& event) {
bool aborted_transform_animation = false;
bool aborted_opacity_animation = false;
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->group() == event.group_id &&
animations_[i]->target_property() == event.target_property) {
animations_[i]->SetRunState(Animation::ABORTED, event.monotonic_time);
animations_[i]->set_received_finished_event(true);
NotifyPlayersAnimationAborted(event.monotonic_time, event.target_property,
event.group_id);
if (event.target_property == TargetProperty::TRANSFORM)
aborted_transform_animation = true;
else if (event.target_property == TargetProperty::OPACITY)
aborted_opacity_animation = true;
break;
}
}
if (aborted_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (aborted_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
void ElementAnimations::NotifyAnimationPropertyUpdate(
const AnimationEvent& event) {
bool notify_active_elements = true;
bool notify_pending_elements = true;
switch (event.target_property) {
case TargetProperty::OPACITY:
NotifyClientOpacityAnimated(event.opacity, notify_active_elements,
notify_pending_elements);
break;
case TargetProperty::TRANSFORM:
NotifyClientTransformAnimated(event.transform, notify_active_elements,
notify_pending_elements);
break;
default:
NOTREACHED();
}
}
bool ElementAnimations::HasFilterAnimationThatInflatesBounds() const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (!animations_[i]->is_finished() &&
animations_[i]->target_property() == TargetProperty::FILTER &&
animations_[i]
->curve()
->ToFilterAnimationCurve()
->HasFilterThatMovesPixels())
return true;
}
return false;
}
bool ElementAnimations::HasTransformAnimationThatInflatesBounds() const {
return IsCurrentlyAnimatingProperty(TargetProperty::TRANSFORM,
ElementListType::ACTIVE) ||
IsCurrentlyAnimatingProperty(TargetProperty::TRANSFORM,
ElementListType::PENDING);
}
bool ElementAnimations::FilterAnimationBoundsForBox(const gfx::BoxF& box,
gfx::BoxF* bounds) const {
// TODO(avallee): Implement.
return false;
}
bool ElementAnimations::TransformAnimationBoundsForBox(
const gfx::BoxF& box,
gfx::BoxF* bounds) const {
DCHECK(HasTransformAnimationThatInflatesBounds())
<< "TransformAnimationBoundsForBox will give incorrect results if there "
<< "are no transform animations affecting bounds, non-animated transform "
<< "is not known";
// Compute bounds based on animations for which is_finished() is false.
// Do nothing if there are no such animations; in this case, it is assumed
// that callers will take care of computing bounds based on the owning layer's
// actual transform.
*bounds = gfx::BoxF();
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->is_finished() ||
animations_[i]->target_property() != TargetProperty::TRANSFORM)
continue;
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
gfx::BoxF animation_bounds;
bool success =
transform_animation_curve->AnimatedBoundsForBox(box, &animation_bounds);
if (!success)
return false;
bounds->Union(animation_bounds);
}
return true;
}
bool ElementAnimations::HasAnimationThatAffectsScale() const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->is_finished() ||
animations_[i]->target_property() != TargetProperty::TRANSFORM)
continue;
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
if (transform_animation_curve->AffectsScale())
return true;
}
return false;
}
bool ElementAnimations::HasOnlyTranslationTransforms(
ElementListType list_type) const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->is_finished() ||
animations_[i]->target_property() != TargetProperty::TRANSFORM)
continue;
if ((list_type == ElementListType::ACTIVE &&
!animations_[i]->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
!animations_[i]->affects_pending_elements()))
continue;
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
if (!transform_animation_curve->IsTranslation())
return false;
}
return true;
}
bool ElementAnimations::AnimationsPreserveAxisAlignment() const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->is_finished() ||
animations_[i]->target_property() != TargetProperty::TRANSFORM)
continue;
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
if (!transform_animation_curve->PreservesAxisAlignment())
return false;
}
return true;
}
bool ElementAnimations::AnimationStartScale(ElementListType list_type,
float* start_scale) const {
*start_scale = 0.f;
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->is_finished() ||
animations_[i]->target_property() != TargetProperty::TRANSFORM)
continue;
if ((list_type == ElementListType::ACTIVE &&
!animations_[i]->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
!animations_[i]->affects_pending_elements()))
continue;
bool forward_direction = true;
switch (animations_[i]->direction()) {
case Animation::Direction::NORMAL:
case Animation::Direction::ALTERNATE_NORMAL:
forward_direction = animations_[i]->playback_rate() >= 0.0;
break;
case Animation::Direction::REVERSE:
case Animation::Direction::ALTERNATE_REVERSE:
forward_direction = animations_[i]->playback_rate() < 0.0;
break;
}
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
float animation_start_scale = 0.f;
if (!transform_animation_curve->AnimationStartScale(forward_direction,
&animation_start_scale))
return false;
*start_scale = std::max(*start_scale, animation_start_scale);
}
return true;
}
bool ElementAnimations::MaximumTargetScale(ElementListType list_type,
float* max_scale) const {
*max_scale = 0.f;
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->is_finished() ||
animations_[i]->target_property() != TargetProperty::TRANSFORM)
continue;
if ((list_type == ElementListType::ACTIVE &&
!animations_[i]->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
!animations_[i]->affects_pending_elements()))
continue;
bool forward_direction = true;
switch (animations_[i]->direction()) {
case Animation::Direction::NORMAL:
case Animation::Direction::ALTERNATE_NORMAL:
forward_direction = animations_[i]->playback_rate() >= 0.0;
break;
case Animation::Direction::REVERSE:
case Animation::Direction::ALTERNATE_REVERSE:
forward_direction = animations_[i]->playback_rate() < 0.0;
break;
}
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
float animation_scale = 0.f;
if (!transform_animation_curve->MaximumTargetScale(forward_direction,
&animation_scale))
return false;
*max_scale = std::max(*max_scale, animation_scale);
}
return true;
}
void ElementAnimations::PushNewAnimationsToImplThread(
ElementAnimations* element_animations_impl) const {
// Any new animations owned by the main thread's ElementAnimations are cloned
// and added to the impl thread's ElementAnimations.
for (size_t i = 0; i < animations_.size(); ++i) {
// If the animation is already running on the impl thread, there is no
// need to copy it over.
if (element_animations_impl->GetAnimationById(animations_[i]->id()))
continue;
if (animations_[i]->target_property() == TargetProperty::SCROLL_OFFSET &&
!animations_[i]
->curve()
->ToScrollOffsetAnimationCurve()
->HasSetInitialValue()) {
gfx::ScrollOffset current_scroll_offset;
if (element_animations_impl->has_element_in_active_list()) {
current_scroll_offset =
element_animations_impl->ScrollOffsetForAnimation();
} else {
// The owning layer isn't yet in the active tree, so the main thread
// scroll offset will be up-to-date.
current_scroll_offset = ScrollOffsetForAnimation();
}
animations_[i]->curve()->ToScrollOffsetAnimationCurve()->SetInitialValue(
current_scroll_offset);
}
// The new animation should be set to run as soon as possible.
Animation::RunState initial_run_state =
Animation::WAITING_FOR_TARGET_AVAILABILITY;
std::unique_ptr<Animation> to_add(
animations_[i]->CloneAndInitialize(initial_run_state));
DCHECK(!to_add->needs_synchronized_start_time());
to_add->set_affects_active_elements(false);
element_animations_impl->AddAnimation(std::move(to_add));
}
}
static bool IsCompleted(
Animation* animation,
const ElementAnimations* main_thread_element_animations) {
if (animation->is_impl_only()) {
return (animation->run_state() == Animation::WAITING_FOR_DELETION);
} else {
return !main_thread_element_animations->GetAnimationById(animation->id());
}
}
void ElementAnimations::RemoveAnimationsCompletedOnMainThread(
ElementAnimations* element_animations_impl) const {
bool removed_transform_animation = false;
bool removed_opacity_animation = false;
// Animations removed on the main thread should no longer affect pending
// elements, and should stop affecting active elements after the next call
// to ActivateAnimations. If already WAITING_FOR_DELETION, they can be removed
// immediately.
auto& animations = element_animations_impl->animations_;
for (const auto& animation : animations) {
if (IsCompleted(animation.get(), this)) {
animation->set_affects_pending_elements(false);
if (animation->target_property() == TargetProperty::TRANSFORM)
removed_transform_animation = true;
else if (animation->target_property() == TargetProperty::OPACITY)
removed_opacity_animation = true;
}
}
auto affects_active_only_and_is_waiting_for_deletion =
[](const std::unique_ptr<Animation>& animation) {
return animation->run_state() == Animation::WAITING_FOR_DELETION &&
!animation->affects_pending_elements();
};
animations.erase(
std::remove_if(animations.begin(), animations.end(),
affects_active_only_and_is_waiting_for_deletion),
animations.end());
if (removed_transform_animation)
element_animations_impl->UpdateClientAnimationState(
TargetProperty::TRANSFORM);
if (removed_opacity_animation)
element_animations_impl->UpdateClientAnimationState(
TargetProperty::OPACITY);
}
void ElementAnimations::PushPropertiesToImplThread(
ElementAnimations* element_animations_impl) {
for (size_t i = 0; i < animations_.size(); ++i) {
Animation* current_impl =
element_animations_impl->GetAnimationById(animations_[i]->id());
if (current_impl)
animations_[i]->PushPropertiesTo(current_impl);
}
element_animations_impl->scroll_offset_animation_was_interrupted_ =
scroll_offset_animation_was_interrupted_;
scroll_offset_animation_was_interrupted_ = false;
}
void ElementAnimations::StartAnimations(base::TimeTicks monotonic_time) {
DCHECK(needs_to_start_animations_);
needs_to_start_animations_ = false;
// First collect running properties affecting each type of element.
TargetProperties blocked_properties_for_active_elements;
TargetProperties blocked_properties_for_pending_elements;
std::vector<size_t> animations_waiting_for_target;
animations_waiting_for_target.reserve(animations_.size());
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->run_state() == Animation::STARTING ||
animations_[i]->run_state() == Animation::RUNNING) {
if (animations_[i]->affects_active_elements()) {
blocked_properties_for_active_elements[animations_[i]
->target_property()] = true;
}
if (animations_[i]->affects_pending_elements()) {
blocked_properties_for_pending_elements[animations_[i]
->target_property()] = true;
}
} else if (animations_[i]->run_state() ==
Animation::WAITING_FOR_TARGET_AVAILABILITY) {
animations_waiting_for_target.push_back(i);
}
}
for (size_t i = 0; i < animations_waiting_for_target.size(); ++i) {
// Collect all properties for animations with the same group id (they
// should all also be in the list of animations).
size_t animation_index = animations_waiting_for_target[i];
Animation* animation_waiting_for_target =
animations_[animation_index].get();
// Check for the run state again even though the animation was waiting
// for target because it might have changed the run state while handling
// previous animation in this loop (if they belong to same group).
if (animation_waiting_for_target->run_state() ==
Animation::WAITING_FOR_TARGET_AVAILABILITY) {
TargetProperties enqueued_properties;
bool affects_active_elements =
animation_waiting_for_target->affects_active_elements();
bool affects_pending_elements =
animation_waiting_for_target->affects_pending_elements();
enqueued_properties[animation_waiting_for_target->target_property()] =
true;
for (size_t j = animation_index + 1; j < animations_.size(); ++j) {
if (animation_waiting_for_target->group() == animations_[j]->group()) {
enqueued_properties[animations_[j]->target_property()] = true;
affects_active_elements |= animations_[j]->affects_active_elements();
affects_pending_elements |=
animations_[j]->affects_pending_elements();
}
}
// Check to see if intersection of the list of properties affected by
// the group and the list of currently blocked properties is null, taking
// into account the type(s) of elements affected by the group. In any
// case, the group's target properties need to be added to the lists of
// blocked properties.
bool null_intersection = true;
static_assert(TargetProperty::FIRST_TARGET_PROPERTY == 0,
"TargetProperty must be 0-based enum");
for (int property = TargetProperty::FIRST_TARGET_PROPERTY;
property <= TargetProperty::LAST_TARGET_PROPERTY; ++property) {
if (enqueued_properties[property]) {
if (affects_active_elements) {
if (blocked_properties_for_active_elements[property])
null_intersection = false;
else
blocked_properties_for_active_elements[property] = true;
}
if (affects_pending_elements) {
if (blocked_properties_for_pending_elements[property])
null_intersection = false;
else
blocked_properties_for_pending_elements[property] = true;
}
}
}
// If the intersection is null, then we are free to start the animations
// in the group.
if (null_intersection) {
animation_waiting_for_target->SetRunState(Animation::STARTING,
monotonic_time);
for (size_t j = animation_index + 1; j < animations_.size(); ++j) {
if (animation_waiting_for_target->group() ==
animations_[j]->group()) {
animations_[j]->SetRunState(Animation::STARTING, monotonic_time);
}
}
} else {
needs_to_start_animations_ = true;
}
}
}
}
void ElementAnimations::PromoteStartedAnimations(base::TimeTicks monotonic_time,
AnimationEvents* events) {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->run_state() == Animation::STARTING &&
animations_[i]->affects_active_elements()) {
animations_[i]->SetRunState(Animation::RUNNING, monotonic_time);
if (!animations_[i]->has_set_start_time() &&
!animations_[i]->needs_synchronized_start_time())
animations_[i]->set_start_time(monotonic_time);
if (events) {
base::TimeTicks start_time;
if (animations_[i]->has_set_start_time())
start_time = animations_[i]->start_time();
else
start_time = monotonic_time;
AnimationEvent started_event(
AnimationEvent::STARTED, element_id_, animations_[i]->group(),
animations_[i]->target_property(), start_time);
started_event.is_impl_only = animations_[i]->is_impl_only();
if (started_event.is_impl_only)
NotifyAnimationStarted(started_event);
else
events->events_.push_back(started_event);
}
}
}
}
void ElementAnimations::MarkFinishedAnimations(base::TimeTicks monotonic_time) {
bool finished_transform_animation = false;
bool finished_opacity_animation = false;
for (size_t i = 0; i < animations_.size(); ++i) {
if (!animations_[i]->is_finished() &&
animations_[i]->IsFinishedAt(monotonic_time)) {
animations_[i]->SetRunState(Animation::FINISHED, monotonic_time);
if (animations_[i]->target_property() == TargetProperty::TRANSFORM)
finished_transform_animation = true;
else if (animations_[i]->target_property() == TargetProperty::OPACITY)
finished_opacity_animation = true;
}
}
if (finished_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (finished_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
void ElementAnimations::MarkAnimationsForDeletion(
base::TimeTicks monotonic_time,
AnimationEvents* events) {
bool marked_animations_for_deletions = false;
std::vector<size_t> animations_with_same_group_id;
animations_with_same_group_id.reserve(animations_.size());
// Non-aborted animations are marked for deletion after a corresponding
// AnimationEvent::FINISHED event is sent or received. This means that if
// we don't have an events vector, we must ensure that non-aborted animations
// have received a finished event before marking them for deletion.
for (size_t i = 0; i < animations_.size(); i++) {
int group_id = animations_[i]->group();
if (animations_[i]->run_state() == Animation::ABORTED) {
if (events && !animations_[i]->is_impl_only()) {
AnimationEvent aborted_event(
AnimationEvent::ABORTED, element_id_, group_id,
animations_[i]->target_property(), monotonic_time);
events->events_.push_back(aborted_event);
}
// If on the compositor or on the main thread and received finish event,
// animation can be marked for deletion.
if (events || animations_[i]->received_finished_event()) {
animations_[i]->SetRunState(Animation::WAITING_FOR_DELETION,
monotonic_time);
marked_animations_for_deletions = true;
}
continue;
}
// If running on the compositor and need to complete an aborted animation
// on the main thread.
if (events &&
animations_[i]->run_state() ==
Animation::ABORTED_BUT_NEEDS_COMPLETION) {
AnimationEvent aborted_event(AnimationEvent::TAKEOVER, element_id_,
group_id, animations_[i]->target_property(),
monotonic_time);
aborted_event.animation_start_time =
(animations_[i]->start_time() - base::TimeTicks()).InSecondsF();
const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
animations_[i]->curve()->ToScrollOffsetAnimationCurve();
aborted_event.curve = scroll_offset_animation_curve->Clone();
// Notify the compositor that the animation is finished.
NotifyPlayersAnimationFinished(aborted_event.monotonic_time,
aborted_event.target_property,
aborted_event.group_id);
// Notify main thread.
events->events_.push_back(aborted_event);
// Remove the animation from the compositor.
animations_[i]->SetRunState(Animation::WAITING_FOR_DELETION,
monotonic_time);
marked_animations_for_deletions = true;
continue;
}
bool all_anims_with_same_id_are_finished = false;
// Since deleting an animation on the main thread leads to its deletion
// on the impl thread, we only mark a FINISHED main thread animation for
// deletion once it has received a FINISHED event from the impl thread.
bool animation_i_will_send_or_has_received_finish_event =
animations_[i]->is_controlling_instance() ||
animations_[i]->is_impl_only() ||
animations_[i]->received_finished_event();
// If an animation is finished, and not already marked for deletion,
// find out if all other animations in the same group are also finished.
if (animations_[i]->run_state() == Animation::FINISHED &&
animation_i_will_send_or_has_received_finish_event) {
// Clear the animations_with_same_group_id if it was added for
// the previous animation's iteration.
if (animations_with_same_group_id.size() > 0)
animations_with_same_group_id.clear();
all_anims_with_same_id_are_finished = true;
for (size_t j = 0; j < animations_.size(); ++j) {
bool animation_j_will_send_or_has_received_finish_event =
animations_[j]->is_controlling_instance() ||
animations_[j]->is_impl_only() ||
animations_[j]->received_finished_event();
if (group_id == animations_[j]->group()) {
if (!animations_[j]->is_finished() ||
(animations_[j]->run_state() == Animation::FINISHED &&
!animation_j_will_send_or_has_received_finish_event)) {
all_anims_with_same_id_are_finished = false;
break;
} else if (j >= i &&
animations_[j]->run_state() != Animation::ABORTED) {
// Mark down the animations which belong to the same group
// and is not yet aborted. If this current iteration finds that all
// animations with same ID are finished, then the marked
// animations below will be set to WAITING_FOR_DELETION in next
// iteration.
animations_with_same_group_id.push_back(j);
}
}
}
}
if (all_anims_with_same_id_are_finished) {
// We now need to remove all animations with the same group id as
// group_id (and send along animation finished notifications, if
// necessary).
for (size_t j = 0; j < animations_with_same_group_id.size(); j++) {
size_t animation_index = animations_with_same_group_id[j];
if (events) {
AnimationEvent finished_event(
AnimationEvent::FINISHED, element_id_,
animations_[animation_index]->group(),
animations_[animation_index]->target_property(), monotonic_time);
finished_event.is_impl_only =
animations_[animation_index]->is_impl_only();
if (finished_event.is_impl_only)
NotifyAnimationFinished(finished_event);
else
events->events_.push_back(finished_event);
}
animations_[animation_index]->SetRunState(
Animation::WAITING_FOR_DELETION, monotonic_time);
}
marked_animations_for_deletions = true;
}
}
if (marked_animations_for_deletions)
NotifyClientAnimationWaitingForDeletion();
}
void ElementAnimations::MarkAbortedAnimationsForDeletion(
ElementAnimations* element_animations_impl) const {
bool aborted_transform_animation = false;
bool aborted_opacity_animation = false;
auto& animations_impl = element_animations_impl->animations_;
for (const auto& animation_impl : animations_impl) {
// If the animation has been aborted on the main thread, mark it for
// deletion.
if (Animation* animation = GetAnimationById(animation_impl->id())) {
if (animation->run_state() == Animation::ABORTED) {
animation_impl->SetRunState(Animation::WAITING_FOR_DELETION,
element_animations_impl->last_tick_time_);
animation->SetRunState(Animation::WAITING_FOR_DELETION,
last_tick_time_);
if (animation_impl->target_property() == TargetProperty::TRANSFORM)
aborted_transform_animation = true;
else if (animation_impl->target_property() == TargetProperty::OPACITY)
aborted_opacity_animation = true;
}
}
}
if (aborted_transform_animation)
element_animations_impl->UpdateClientAnimationState(
TargetProperty::TRANSFORM);
if (aborted_opacity_animation)
element_animations_impl->UpdateClientAnimationState(
TargetProperty::OPACITY);
}
void ElementAnimations::PurgeAnimationsMarkedForDeletion() {
animations_.erase(
std::remove_if(animations_.begin(), animations_.end(),
[](const std::unique_ptr<Animation>& animation) {
return animation->run_state() ==
Animation::WAITING_FOR_DELETION;
}),
animations_.end());
}
void ElementAnimations::TickAnimations(base::TimeTicks monotonic_time) {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->run_state() == Animation::STARTING ||
animations_[i]->run_state() == Animation::RUNNING ||
animations_[i]->run_state() == Animation::PAUSED) {
if (!animations_[i]->InEffect(monotonic_time))
continue;
base::TimeDelta trimmed =
animations_[i]->TrimTimeToCurrentIteration(monotonic_time);
switch (animations_[i]->target_property()) {
case TargetProperty::TRANSFORM: {
const TransformAnimationCurve* transform_animation_curve =
animations_[i]->curve()->ToTransformAnimationCurve();
const gfx::Transform transform =
transform_animation_curve->GetValue(trimmed);
NotifyClientTransformAnimated(
transform, animations_[i]->affects_active_elements(),
animations_[i]->affects_pending_elements());
break;
}
case TargetProperty::OPACITY: {
const FloatAnimationCurve* float_animation_curve =
animations_[i]->curve()->ToFloatAnimationCurve();
const float opacity = std::max(
std::min(float_animation_curve->GetValue(trimmed), 1.0f), 0.f);
NotifyClientOpacityAnimated(
opacity, animations_[i]->affects_active_elements(),
animations_[i]->affects_pending_elements());
break;
}
case TargetProperty::FILTER: {
const FilterAnimationCurve* filter_animation_curve =
animations_[i]->curve()->ToFilterAnimationCurve();
const FilterOperations filter =
filter_animation_curve->GetValue(trimmed);
NotifyClientFilterAnimated(
filter, animations_[i]->affects_active_elements(),
animations_[i]->affects_pending_elements());
break;
}
case TargetProperty::BACKGROUND_COLOR: {
// Not yet implemented.
break;
}
case TargetProperty::SCROLL_OFFSET: {
const ScrollOffsetAnimationCurve* scroll_offset_animation_curve =
animations_[i]->curve()->ToScrollOffsetAnimationCurve();
const gfx::ScrollOffset scroll_offset =
scroll_offset_animation_curve->GetValue(trimmed);
NotifyClientScrollOffsetAnimated(
scroll_offset, animations_[i]->affects_active_elements(),
animations_[i]->affects_pending_elements());
break;
}
}
}
}
}
void ElementAnimations::UpdateActivation(UpdateActivationType type) {
bool force = type == FORCE_ACTIVATION;
if (animation_host_) {
bool was_active = is_active_;
is_active_ = false;
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->run_state() != Animation::WAITING_FOR_DELETION) {
is_active_ = true;
break;
}
}
if (is_active_ && (!was_active || force))
animation_host_->DidActivateElementAnimations(this);
else if (!is_active_ && (was_active || force))
animation_host_->DidDeactivateElementAnimations(this);
}
}
void ElementAnimations::NotifyClientOpacityAnimated(
float opacity,
bool notify_active_elements,
bool notify_pending_elements) {
if (notify_active_elements && has_element_in_active_list())
OnOpacityAnimated(ElementListType::ACTIVE, opacity);
if (notify_pending_elements && has_element_in_pending_list())
OnOpacityAnimated(ElementListType::PENDING, opacity);
}
void ElementAnimations::NotifyClientTransformAnimated(
const gfx::Transform& transform,
bool notify_active_elements,
bool notify_pending_elements) {
if (notify_active_elements && has_element_in_active_list())
OnTransformAnimated(ElementListType::ACTIVE, transform);
if (notify_pending_elements && has_element_in_pending_list())
OnTransformAnimated(ElementListType::PENDING, transform);
}
void ElementAnimations::NotifyClientFilterAnimated(
const FilterOperations& filters,
bool notify_active_elements,
bool notify_pending_elements) {
if (notify_active_elements && has_element_in_active_list())
OnFilterAnimated(ElementListType::ACTIVE, filters);
if (notify_pending_elements && has_element_in_pending_list())
OnFilterAnimated(ElementListType::PENDING, filters);
}
void ElementAnimations::NotifyClientScrollOffsetAnimated(
const gfx::ScrollOffset& scroll_offset,
bool notify_active_elements,
bool notify_pending_elements) {
if (notify_active_elements && has_element_in_active_list())
OnScrollOffsetAnimated(ElementListType::ACTIVE, scroll_offset);
if (notify_pending_elements && has_element_in_pending_list())
OnScrollOffsetAnimated(ElementListType::PENDING, scroll_offset);
}
void ElementAnimations::NotifyClientAnimationWaitingForDeletion() {
OnAnimationWaitingForDeletion();
}
void ElementAnimations::NotifyClientAnimationChanged(
TargetProperty::Type property,
ElementListType list_type,
bool notify_elements_about_potential_animation,
bool notify_elements_about_running_animation) {
struct PropertyAnimationState* animation_state = nullptr;
switch (property) {
case TargetProperty::OPACITY:
animation_state = &opacity_animation_state_;
break;
case TargetProperty::TRANSFORM:
animation_state = &transform_animation_state_;
break;
default:
NOTREACHED();
break;
}
bool notify_elements_about_potential_and_running_animation =
notify_elements_about_potential_animation &&
notify_elements_about_running_animation;
bool active = list_type == ElementListType::ACTIVE;
if (notify_elements_about_potential_and_running_animation) {
bool potentially_animating =
active ? animation_state->potentially_animating_for_active_elements
: animation_state->potentially_animating_for_pending_elements;
bool currently_animating =
active ? animation_state->currently_running_for_active_elements
: animation_state->currently_running_for_pending_elements;
DCHECK_EQ(potentially_animating, currently_animating);
IsAnimatingChanged(list_type, property, AnimationChangeType::BOTH,
potentially_animating);
} else if (notify_elements_about_potential_animation) {
bool potentially_animating =
active ? animation_state->potentially_animating_for_active_elements
: animation_state->potentially_animating_for_pending_elements;
IsAnimatingChanged(list_type, property, AnimationChangeType::POTENTIAL,
potentially_animating);
} else if (notify_elements_about_running_animation) {
bool currently_animating =
active ? animation_state->currently_running_for_active_elements
: animation_state->currently_running_for_pending_elements;
IsAnimatingChanged(list_type, property, AnimationChangeType::RUNNING,
currently_animating);
}
}
void ElementAnimations::UpdateClientAnimationState(
TargetProperty::Type property) {
struct PropertyAnimationState* animation_state = nullptr;
switch (property) {
case TargetProperty::OPACITY:
animation_state = &opacity_animation_state_;
break;
case TargetProperty::TRANSFORM:
animation_state = &transform_animation_state_;
break;
default:
NOTREACHED();
break;
}
bool was_currently_running_animation_for_active_elements =
animation_state->currently_running_for_active_elements;
bool was_currently_running_animation_for_pending_elements =
animation_state->currently_running_for_pending_elements;
bool was_potentially_animating_for_active_elements =
animation_state->potentially_animating_for_active_elements;
bool was_potentially_animating_for_pending_elements =
animation_state->potentially_animating_for_pending_elements;
animation_state->Clear();
DCHECK(was_potentially_animating_for_active_elements ||
!was_currently_running_animation_for_active_elements);
DCHECK(was_potentially_animating_for_pending_elements ||
!was_currently_running_animation_for_pending_elements);
for (const auto& animation : animations_) {
if (!animation->is_finished() && animation->target_property() == property) {
animation_state->potentially_animating_for_active_elements |=
animation->affects_active_elements();
animation_state->potentially_animating_for_pending_elements |=
animation->affects_pending_elements();
animation_state->currently_running_for_active_elements =
animation_state->potentially_animating_for_active_elements &&
animation->InEffect(last_tick_time_);
animation_state->currently_running_for_pending_elements =
animation_state->potentially_animating_for_pending_elements &&
animation->InEffect(last_tick_time_);
}
}
bool potentially_animating_changed_for_active_elements =
was_potentially_animating_for_active_elements !=
animation_state->potentially_animating_for_active_elements;
bool potentially_animating_changed_for_pending_elements =
was_potentially_animating_for_pending_elements !=
animation_state->potentially_animating_for_pending_elements;
bool currently_running_animation_changed_for_active_elements =
was_currently_running_animation_for_active_elements !=
animation_state->currently_running_for_active_elements;
bool currently_running_animation_changed_for_pending_elements =
was_currently_running_animation_for_pending_elements !=
animation_state->currently_running_for_pending_elements;
if (!potentially_animating_changed_for_active_elements &&
!potentially_animating_changed_for_pending_elements &&
!currently_running_animation_changed_for_active_elements &&
!currently_running_animation_changed_for_pending_elements)
return;
if (has_element_in_active_list())
NotifyClientAnimationChanged(
property, ElementListType::ACTIVE,
potentially_animating_changed_for_active_elements,
currently_running_animation_changed_for_active_elements);
if (has_element_in_pending_list())
NotifyClientAnimationChanged(
property, ElementListType::PENDING,
potentially_animating_changed_for_pending_elements,
currently_running_animation_changed_for_pending_elements);
}
bool ElementAnimations::HasActiveAnimation() const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (!animations_[i]->is_finished())
return true;
}
return false;
}
bool ElementAnimations::IsPotentiallyAnimatingProperty(
TargetProperty::Type target_property,
ElementListType list_type) const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (!animations_[i]->is_finished() &&
animations_[i]->target_property() == target_property) {
if ((list_type == ElementListType::ACTIVE &&
animations_[i]->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
animations_[i]->affects_pending_elements()))
return true;
}
}
return false;
}
bool ElementAnimations::IsCurrentlyAnimatingProperty(
TargetProperty::Type target_property,
ElementListType list_type) const {
for (size_t i = 0; i < animations_.size(); ++i) {
if (!animations_[i]->is_finished() &&
animations_[i]->InEffect(last_tick_time_) &&
animations_[i]->target_property() == target_property) {
if ((list_type == ElementListType::ACTIVE &&
animations_[i]->affects_active_elements()) ||
(list_type == ElementListType::PENDING &&
animations_[i]->affects_pending_elements()))
return true;
}
}
return false;
}
void ElementAnimations::PauseAnimation(int animation_id,
base::TimeDelta time_offset) {
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->id() == animation_id) {
animations_[i]->SetRunState(Animation::PAUSED,
time_offset + animations_[i]->start_time() +
animations_[i]->time_offset());
}
}
}
void ElementAnimations::RemoveAnimation(int animation_id) {
bool removed_transform_animation = false;
bool removed_opacity_animation = false;
// Since we want to use the animations that we're going to remove, we need to
// use a stable_parition here instead of remove_if. Remove_if leaves the
// removed items in an unspecified state.
auto animations_to_remove = std::stable_partition(
animations_.begin(), animations_.end(),
[animation_id](const std::unique_ptr<Animation>& animation) {
return animation->id() != animation_id;
});
for (auto it = animations_to_remove; it != animations_.end(); ++it) {
if ((*it)->target_property() == TargetProperty::SCROLL_OFFSET) {
scroll_offset_animation_was_interrupted_ = true;
} else if ((*it)->target_property() == TargetProperty::TRANSFORM &&
!(*it)->is_finished()) {
removed_transform_animation = true;
} else if ((*it)->target_property() == TargetProperty::OPACITY &&
!(*it)->is_finished()) {
removed_opacity_animation = true;
}
}
animations_.erase(animations_to_remove, animations_.end());
UpdateActivation(NORMAL_ACTIVATION);
if (removed_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (removed_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
void ElementAnimations::AbortAnimation(int animation_id) {
bool aborted_transform_animation = false;
bool aborted_opacity_animation = false;
if (Animation* animation = GetAnimationById(animation_id)) {
if (!animation->is_finished()) {
animation->SetRunState(Animation::ABORTED, last_tick_time_);
if (animation->target_property() == TargetProperty::TRANSFORM)
aborted_transform_animation = true;
else if (animation->target_property() == TargetProperty::OPACITY)
aborted_opacity_animation = true;
}
}
if (aborted_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (aborted_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
void ElementAnimations::AbortAnimations(TargetProperty::Type target_property,
bool needs_completion) {
if (needs_completion)
DCHECK(target_property == TargetProperty::SCROLL_OFFSET);
bool aborted_transform_animation = false;
bool aborted_opacity_animation = false;
for (size_t i = 0; i < animations_.size(); ++i) {
if (animations_[i]->target_property() == target_property &&
!animations_[i]->is_finished()) {
// Currently only impl-only scroll offset animations can be completed on
// the main thread.
if (needs_completion && animations_[i]->is_impl_only()) {
animations_[i]->SetRunState(Animation::ABORTED_BUT_NEEDS_COMPLETION,
last_tick_time_);
} else {
animations_[i]->SetRunState(Animation::ABORTED, last_tick_time_);
}
if (target_property == TargetProperty::TRANSFORM)
aborted_transform_animation = true;
else if (target_property == TargetProperty::OPACITY)
aborted_opacity_animation = true;
}
}
if (aborted_transform_animation)
UpdateClientAnimationState(TargetProperty::TRANSFORM);
if (aborted_opacity_animation)
UpdateClientAnimationState(TargetProperty::OPACITY);
}
Animation* ElementAnimations::GetAnimation(
TargetProperty::Type target_property) const {
for (size_t i = 0; i < animations_.size(); ++i) {
size_t index = animations_.size() - i - 1;
if (animations_[index]->target_property() == target_property)
return animations_[index].get();
}
return nullptr;
}
Animation* ElementAnimations::GetAnimationById(int animation_id) const {
for (size_t i = 0; i < animations_.size(); ++i)
if (animations_[i]->id() == animation_id)
return animations_[i].get();
return nullptr;
}
void ElementAnimations::OnFilterAnimated(ElementListType list_type,
const FilterOperations& filters) {
DCHECK(element_id());
DCHECK(animation_host());
DCHECK(animation_host()->mutator_host_client());
animation_host()->mutator_host_client()->SetElementFilterMutated(
element_id(), list_type, filters);
}
void ElementAnimations::OnOpacityAnimated(ElementListType list_type,
float opacity) {
DCHECK(element_id());
DCHECK(animation_host());
DCHECK(animation_host()->mutator_host_client());
animation_host()->mutator_host_client()->SetElementOpacityMutated(
element_id(), list_type, opacity);
}
void ElementAnimations::OnTransformAnimated(ElementListType list_type,
const gfx::Transform& transform) {
DCHECK(element_id());
DCHECK(animation_host());
DCHECK(animation_host()->mutator_host_client());
animation_host()->mutator_host_client()->SetElementTransformMutated(
element_id(), list_type, transform);
}
void ElementAnimations::OnScrollOffsetAnimated(
ElementListType list_type,
const gfx::ScrollOffset& scroll_offset) {
DCHECK(element_id());
DCHECK(animation_host());
DCHECK(animation_host()->mutator_host_client());
animation_host()->mutator_host_client()->SetElementScrollOffsetMutated(
element_id(), list_type, scroll_offset);
}
void ElementAnimations::OnAnimationWaitingForDeletion() {
// TODO(loyso): Invalidate AnimationHost::SetNeedsPushProperties here.
// But we always do PushProperties in AnimationHost for now. crbug.com/604280
DCHECK(animation_host());
animation_host()->OnAnimationWaitingForDeletion();
}
void ElementAnimations::IsAnimatingChanged(ElementListType list_type,
TargetProperty::Type property,
AnimationChangeType change_type,
bool is_animating) {
if (!element_id())
return;
DCHECK(animation_host());
if (animation_host()->mutator_host_client()) {
switch (property) {
case TargetProperty::OPACITY:
animation_host()
->mutator_host_client()
->ElementOpacityIsAnimatingChanged(element_id(), list_type,
change_type, is_animating);
break;
case TargetProperty::TRANSFORM:
animation_host()
->mutator_host_client()
->ElementTransformIsAnimatingChanged(element_id(), list_type,
change_type, is_animating);
break;
default:
NOTREACHED();
break;
}
}
}
void ElementAnimations::NotifyPlayersAnimationStarted(
base::TimeTicks monotonic_time,
TargetProperty::Type target_property,
int group) {
for (PlayersListNode* node = players_list_->head();
node != players_list_->end(); node = node->next()) {
AnimationPlayer* player = node->value();
player->NotifyAnimationStarted(monotonic_time, target_property, group);
}
}
void ElementAnimations::NotifyPlayersAnimationFinished(
base::TimeTicks monotonic_time,
TargetProperty::Type target_property,
int group) {
for (PlayersListNode* node = players_list_->head();
node != players_list_->end(); node = node->next()) {
AnimationPlayer* player = node->value();
player->NotifyAnimationFinished(monotonic_time, target_property, group);
}
}
void ElementAnimations::NotifyPlayersAnimationAborted(
base::TimeTicks monotonic_time,
TargetProperty::Type target_property,
int group) {
for (PlayersListNode* node = players_list_->head();
node != players_list_->end(); node = node->next()) {
AnimationPlayer* player = node->value();
player->NotifyAnimationAborted(monotonic_time, target_property, group);
}
}
void ElementAnimations::NotifyPlayersAnimationTakeover(
base::TimeTicks monotonic_time,
TargetProperty::Type target_property,
double animation_start_time,
std::unique_ptr<AnimationCurve> curve) {
DCHECK(curve);
for (PlayersListNode* node = players_list_->head();
node != players_list_->end(); node = node->next()) {
std::unique_ptr<AnimationCurve> animation_curve = curve->Clone();
AnimationPlayer* player = node->value();
player->NotifyAnimationTakeover(monotonic_time, target_property,
animation_start_time,
std::move(animation_curve));
}
}
gfx::ScrollOffset ElementAnimations::ScrollOffsetForAnimation() const {
if (animation_host()) {
DCHECK(animation_host()->mutator_host_client());
return animation_host()->mutator_host_client()->GetScrollOffsetForAnimation(
element_id());
}
return gfx::ScrollOffset();
}
} // namespace cc