blob: df6a5297fe708b64cfc00411a0a53551cc002fa8 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/core/animation/animation_timeline.h"
#include "third_party/blink/renderer/core/animation/document_animations.h"
#include "third_party/blink/renderer/core/animation/keyframe_effect.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/page/page.h"
namespace blink {
AnimationTimeline::AnimationTimeline(Document* document)
: document_(document), outdated_animation_count_(0) {
document_->GetDocumentAnimations().AddTimeline(*this);
}
void AnimationTimeline::AnimationAttached(Animation* animation) {
DCHECK(!animations_.Contains(animation));
animations_.insert(animation);
}
void AnimationTimeline::AnimationDetached(Animation* animation) {
animations_.erase(animation);
animations_needing_update_.erase(animation);
if (animation->Outdated())
outdated_animation_count_--;
}
bool CompareAnimations(const Member<Animation>& left,
const Member<Animation>& right) {
// This uses pointer order comparision because it is less expensive and
// element order doesn't affect the animation result(http://crbug.com/1047316)
return Animation::HasLowerCompositeOrdering(
left.Get(), right.Get(),
Animation::CompareAnimationsOrdering::kPointerOrder);
}
double AnimationTimeline::currentTime(bool& is_null) {
base::Optional<base::TimeDelta> result = CurrentPhaseAndTime().time;
is_null = !result.has_value();
return result ? result->InMillisecondsF()
: std::numeric_limits<double>::quiet_NaN();
}
double AnimationTimeline::currentTime() {
base::Optional<base::TimeDelta> result = CurrentPhaseAndTime().time;
return result ? result->InMillisecondsF()
: std::numeric_limits<double>::quiet_NaN();
}
base::Optional<double> AnimationTimeline::CurrentTime() {
base::Optional<base::TimeDelta> result = CurrentPhaseAndTime().time;
return result ? base::make_optional(result->InMillisecondsF())
: base::nullopt;
}
base::Optional<double> AnimationTimeline::CurrentTimeSeconds() {
base::Optional<base::TimeDelta> result = CurrentPhaseAndTime().time;
return result ? base::make_optional(result->InSecondsF()) : base::nullopt;
}
String AnimationTimeline::phase() {
switch (CurrentPhaseAndTime().phase) {
case TimelinePhase::kInactive:
return "inactive";
case TimelinePhase::kBefore:
return "before";
case TimelinePhase::kActive:
return "active";
case TimelinePhase::kAfter:
return "after";
}
}
void AnimationTimeline::ClearOutdatedAnimation(Animation* animation) {
DCHECK(!animation->Outdated());
outdated_animation_count_--;
}
bool AnimationTimeline::NeedsAnimationTimingUpdate() {
PhaseAndTime current_phase_and_time = CurrentPhaseAndTime();
if (current_phase_and_time == last_current_phase_and_time_)
return false;
// We allow |last_current_phase_and_time_| to advance here when there
// are no animations to allow animations spawned during style
// recalc to not invalidate this flag.
if (animations_needing_update_.IsEmpty())
last_current_phase_and_time_ = current_phase_and_time;
return !animations_needing_update_.IsEmpty();
}
void AnimationTimeline::ServiceAnimations(TimingUpdateReason reason) {
TRACE_EVENT0("blink", "AnimationTimeline::serviceAnimations");
auto current_phase_and_time = CurrentPhaseAndTime();
bool maybe_update_compositor_scroll_timeline = false;
if (IsScrollTimeline() &&
last_current_phase_and_time_ != current_phase_and_time) {
maybe_update_compositor_scroll_timeline = true;
}
last_current_phase_and_time_ = current_phase_and_time;
HeapVector<Member<Animation>> animations;
animations.ReserveInitialCapacity(animations_needing_update_.size());
for (Animation* animation : animations_needing_update_)
animations.push_back(animation);
std::sort(animations.begin(), animations.end(), CompareAnimations);
for (Animation* animation : animations) {
if (!animation->Update(reason)) {
animations_needing_update_.erase(animation);
continue;
}
if (maybe_update_compositor_scroll_timeline)
animation->UpdateCompositorScrollTimeline();
}
DCHECK_EQ(outdated_animation_count_, 0U);
DCHECK(last_current_phase_and_time_ == CurrentPhaseAndTime());
#if DCHECK_IS_ON()
for (const auto& animation : animations_needing_update_)
DCHECK(!animation->Outdated());
#endif
// Explicitly free the backing store to avoid memory regressions.
// TODO(bikineev): Revisit when young generation is done.
animations.clear();
if (RuntimeEnabledFeatures::WebAnimationsAPIEnabled() &&
reason == kTimingUpdateForAnimationFrame) {
RemoveReplacedAnimations();
}
}
// https://drafts.csswg.org/web-animations-1/#removing-replaced-animations
void AnimationTimeline::RemoveReplacedAnimations() {
// Group replaceable animations by target element.
HeapHashMap<Member<Element>, Member<HeapVector<Member<Animation>>>>
replaceable_animations;
for (Animation* animation : animations_) {
// Initial conditions for removal:
// * has an associated animation effect whose effect target is a descendant
// of doc, and
// * is replaceable
if (!animation->IsReplaceable())
continue;
DCHECK(animation->effect());
Element* target = To<KeyframeEffect>(animation->effect())->target();
DCHECK(target);
if (target->GetDocument() != animation->GetDocument())
continue;
auto inserted = replaceable_animations.insert(target, nullptr);
if (inserted.is_new_entry) {
inserted.stored_value->value =
MakeGarbageCollected<HeapVector<Member<Animation>>>();
}
inserted.stored_value->value->push_back(animation);
}
HeapVector<Member<Animation>> animations_to_remove;
for (auto& elem_it : replaceable_animations) {
HeapVector<Member<Animation>>* animations = elem_it.value;
// Only elements with multiple animations in the replaceable state need to
// be checked.
if (animations->size() == 1)
continue;
// By processing in decreasing order by priority, we can perform a single
// pass for discovery of replaced properties.
std::sort(animations->begin(), animations->end(), CompareAnimations);
PropertyHandleSet replaced_properties;
for (auto anim_it = animations->rbegin(); anim_it != animations->rend();
anim_it++) {
// Remaining conditions for removal:
// * has a replace state of active, and
// * for which there exists for each target property of every animation
// effect associated with animation, an animation effect associated with
// a replaceable animation with a higher composite order than animation
// that includes the same target property.
// Only active animations can be removed. We still need to go through
// the process of iterating over properties if not removable to update
// the set of properties being replaced.
bool replace = (*anim_it)->ReplaceStateActive();
PropertyHandleSet animation_properties =
To<KeyframeEffect>((*anim_it)->effect())->Model()->Properties();
for (const auto& property : animation_properties) {
auto inserted = replaced_properties.insert(property);
if (inserted.is_new_entry) {
// Top-most compositor order animation affecting this property.
replace = false;
}
}
if (replace)
animations_to_remove.push_back(*anim_it);
}
}
// The list of animations for removal is constructed in reverse composite
// ordering for efficiency. Flip the ordering to ensure that events are
// dispatched in composite order.
// TODO(crbug.com/981905): Add test for ordering once onremove is implemented.
for (auto it = animations_to_remove.rbegin();
it != animations_to_remove.rend(); it++) {
(*it)->RemoveReplacedAnimation();
}
}
void AnimationTimeline::SetOutdatedAnimation(Animation* animation) {
DCHECK(animation->Outdated());
outdated_animation_count_++;
animations_needing_update_.insert(animation);
if (IsActive() && !document_->GetPage()->Animator().IsServicingAnimations())
ScheduleServiceOnNextFrame();
}
void AnimationTimeline::ScheduleServiceOnNextFrame() {
if (document_->View())
document_->View()->ScheduleAnimation();
}
void AnimationTimeline::Trace(Visitor* visitor) {
visitor->Trace(document_);
visitor->Trace(animations_needing_update_);
visitor->Trace(animations_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink