blob: 3c5a2a93e825cb8a07a393242832608ab58c87e9 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/animation/Animation.h"
#include "core/animation/AnimationTimeline.h"
#include "core/animation/DocumentTimeline.h"
#include "core/animation/KeyframeEffectReadOnly.h"
#include "core/animation/PendingAnimations.h"
#include "core/animation/css/CSSAnimations.h"
#include "core/css/StyleChangeReason.h"
#include "core/dom/DOMNodeIds.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/events/AnimationPlaybackEvent.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "core/paint/PaintLayer.h"
#include "core/probe/CoreProbes.h"
#include "platform/WebTaskRunner.h"
#include "platform/animation/CompositorAnimationPlayer.h"
#include "platform/bindings/ScriptForbiddenScope.h"
#include "platform/heap/Persistent.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/runtime_enabled_features.h"
#include "platform/wtf/MathExtras.h"
#include "platform/wtf/PtrUtil.h"
#include "public/platform/Platform.h"
#include "public/platform/TaskType.h"
#include "public/platform/WebCompositorSupport.h"
namespace blink {
namespace {
static unsigned NextSequenceNumber() {
static unsigned next = 0;
return ++next;
}
}
Animation* Animation::Create(AnimationEffectReadOnly* effect,
AnimationTimeline* timeline) {
if (!timeline || !timeline->IsDocumentTimeline()) {
// FIXME: Support creating animations without a timeline.
NOTREACHED();
return nullptr;
}
DocumentTimeline* subtimeline = ToDocumentTimeline(timeline);
Animation* animation = new Animation(
subtimeline->GetDocument()->ContextDocument(), *subtimeline, effect);
if (subtimeline) {
subtimeline->AnimationAttached(*animation);
animation->AttachCompositorTimeline();
}
return animation;
}
Animation* Animation::Create(ExecutionContext* execution_context,
AnimationEffectReadOnly* effect,
ExceptionState& exception_state) {
DCHECK(RuntimeEnabledFeatures::WebAnimationsAPIEnabled());
Document* document = ToDocument(execution_context);
return Create(effect, &document->Timeline());
}
Animation* Animation::Create(ExecutionContext* execution_context,
AnimationEffectReadOnly* effect,
AnimationTimeline* timeline,
ExceptionState& exception_state) {
DCHECK(RuntimeEnabledFeatures::WebAnimationsAPIEnabled());
if (!timeline) {
return Create(execution_context, effect, exception_state);
}
return Create(effect, timeline);
}
Animation::Animation(ExecutionContext* execution_context,
DocumentTimeline& timeline,
AnimationEffectReadOnly* content)
: ContextLifecycleObserver(execution_context),
play_state_(kIdle),
playback_rate_(1),
start_time_(NullValue()),
hold_time_(0),
sequence_number_(NextSequenceNumber()),
content_(content),
timeline_(&timeline),
paused_(false),
held_(false),
is_paused_for_testing_(false),
is_composited_animation_disabled_for_testing_(false),
outdated_(false),
finished_(true),
compositor_state_(nullptr),
compositor_pending_(false),
compositor_group_(0),
current_time_pending_(false),
state_is_being_updated_(false),
effect_suppressed_(false) {
if (content_) {
if (content_->GetAnimation()) {
content_->GetAnimation()->cancel();
content_->GetAnimation()->setEffect(nullptr);
}
content_->Attach(this);
}
probe::didCreateAnimation(timeline_->GetDocument(), sequence_number_);
}
Animation::~Animation() {
// Verify that m_compositorPlayer has been disposed of.
DCHECK(!compositor_player_);
}
void Animation::Dispose() {
DestroyCompositorPlayer();
// If the DocumentTimeline and its Animation objects are
// finalized by the same GC, we have to eagerly clear out
// this Animation object's compositor player registration.
DCHECK(!compositor_player_);
}
double Animation::EffectEnd() const {
return content_ ? content_->EndTimeInternal() : 0;
}
bool Animation::Limited(double current_time) const {
return (playback_rate_ < 0 && current_time <= 0) ||
(playback_rate_ > 0 && current_time >= EffectEnd());
}
void Animation::setCurrentTime(double new_current_time, bool is_null) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
if (PlayStateInternal() == kIdle)
paused_ = true;
current_time_pending_ = false;
play_state_ = kUnset;
SetCurrentTimeInternal(new_current_time / 1000, kTimingUpdateOnDemand);
if (CalculatePlayState() == kFinished)
start_time_ = CalculateStartTime(new_current_time);
}
void Animation::SetCurrentTimeInternal(double new_current_time,
TimingUpdateReason reason) {
DCHECK(std::isfinite(new_current_time));
bool old_held = held_;
bool outdated = false;
bool is_limited = Limited(new_current_time);
held_ = paused_ || !playback_rate_ || is_limited || std::isnan(start_time_);
if (held_) {
if (!old_held || hold_time_ != new_current_time)
outdated = true;
hold_time_ = new_current_time;
if (paused_ || !playback_rate_) {
start_time_ = NullValue();
} else if (is_limited && std::isnan(start_time_) &&
reason == kTimingUpdateForAnimationFrame) {
start_time_ = CalculateStartTime(new_current_time);
}
} else {
hold_time_ = NullValue();
start_time_ = CalculateStartTime(new_current_time);
finished_ = false;
outdated = true;
}
if (outdated) {
SetOutdated();
}
}
// Update timing to reflect updated animation clock due to tick
void Animation::UpdateCurrentTimingState(TimingUpdateReason reason) {
if (play_state_ == kIdle)
return;
if (held_) {
double new_current_time = hold_time_;
if (play_state_ == kFinished && !IsNull(start_time_) && timeline_) {
// Add hystersis due to floating point error accumulation
if (!Limited(CalculateCurrentTime() + 0.001 * playback_rate_)) {
// The current time became unlimited, eg. due to a backwards
// seek of the timeline.
new_current_time = CalculateCurrentTime();
} else if (!Limited(hold_time_)) {
// The hold time became unlimited, eg. due to the effect
// becoming longer.
new_current_time =
clampTo<double>(CalculateCurrentTime(), 0, EffectEnd());
}
}
SetCurrentTimeInternal(new_current_time, reason);
} else if (Limited(CalculateCurrentTime())) {
held_ = true;
hold_time_ = playback_rate_ < 0 ? 0 : EffectEnd();
}
}
double Animation::startTime(bool& is_null) const {
double result = startTime();
is_null = std::isnan(result);
return result;
}
double Animation::startTime() const {
return start_time_ * 1000;
}
double Animation::currentTime(bool& is_null) {
double result = currentTime();
is_null = std::isnan(result);
return result;
}
double Animation::currentTime() {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
if (PlayStateInternal() == kIdle || (!held_ && !HasStartTime()))
return std::numeric_limits<double>::quiet_NaN();
return CurrentTimeInternal() * 1000;
}
double Animation::CurrentTimeInternal() const {
double result = held_ ? hold_time_ : CalculateCurrentTime();
#if DCHECK_IS_ON()
// We can't enforce this check during Unset due to other
// assertions.
if (play_state_ != kUnset) {
const_cast<Animation*>(this)->UpdateCurrentTimingState(
kTimingUpdateOnDemand);
DCHECK_EQ(result, (held_ ? hold_time_ : CalculateCurrentTime()));
}
#endif
return result;
}
double Animation::UnlimitedCurrentTimeInternal() const {
#if DCHECK_IS_ON()
CurrentTimeInternal();
#endif
return PlayStateInternal() == kPaused || IsNull(start_time_)
? CurrentTimeInternal()
: CalculateCurrentTime();
}
bool Animation::PreCommit(
int compositor_group,
const Optional<CompositorElementIdSet>& composited_element_ids,
bool start_on_compositor) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand,
kDoNotSetCompositorPending);
bool soft_change =
compositor_state_ &&
(Paused() || compositor_state_->playback_rate != playback_rate_);
bool hard_change =
compositor_state_ && (compositor_state_->effect_changed ||
compositor_state_->start_time != start_time_);
// FIXME: softChange && !hardChange should generate a Pause/ThenStart,
// not a Cancel, but we can't communicate these to the compositor yet.
bool changed = soft_change || hard_change;
bool should_cancel = (!Playing() && compositor_state_) || changed;
bool should_start = Playing() && (!compositor_state_ || changed);
if (start_on_compositor && should_cancel && should_start &&
compositor_state_ && compositor_state_->pending_action == kStart) {
// Restarting but still waiting for a start time.
return false;
}
if (should_cancel) {
CancelAnimationOnCompositor();
compositor_state_ = nullptr;
}
DCHECK(!compositor_state_ || !std::isnan(compositor_state_->start_time));
if (!should_start) {
current_time_pending_ = false;
}
if (should_start) {
compositor_group_ = compositor_group;
if (start_on_compositor) {
CompositorAnimations::FailureCode failure_code =
CheckCanStartAnimationOnCompositor(composited_element_ids);
if (failure_code.Ok()) {
CreateCompositorPlayer();
StartAnimationOnCompositor(composited_element_ids);
compositor_state_ = WTF::WrapUnique(new CompositorState(*this));
} else {
// failure_code.Ok() is equivalent of |will_composite| = true, so if the
// |can_composite| is true here, then we know that it is a main thread
// compositable animation.
// The |will_composite| is set at
// CompositorAnimations::CheckCanStartElementOnCompositor. Please refer
// to that function for more details.
//
// In the CompositingRequirementsUpdater::UpdateRecursive, the
// (direct_reasons & CompositingReason::kComboActiveAnimation) can be
// non-zero which indicates that there is a compositor animation.
// However, the PaintLayerCompositor::CanBeComposited could still return
// false because the LocalFrameView is not visible. And in that case,
// the code path will get here because there is a compositor animation
// but it won't be composited. We have to account for this case.
if (failure_code.can_composite &&
TimelineInternal()->GetDocument()->View()->IsVisible()) {
is_non_composited_compositable_ = true;
}
CancelIncompatibleAnimationsOnCompositor();
}
}
}
return true;
}
void Animation::PostCommit(double timeline_time) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand,
kDoNotSetCompositorPending);
compositor_pending_ = false;
if (!compositor_state_ || compositor_state_->pending_action == kNone)
return;
switch (compositor_state_->pending_action) {
case kStart:
if (!std::isnan(compositor_state_->start_time)) {
DCHECK_EQ(start_time_, compositor_state_->start_time);
compositor_state_->pending_action = kNone;
}
break;
case kPause:
case kPauseThenStart:
DCHECK(std::isnan(start_time_));
compositor_state_->pending_action = kNone;
SetCurrentTimeInternal(
(timeline_time - compositor_state_->start_time) * playback_rate_,
kTimingUpdateForAnimationFrame);
current_time_pending_ = false;
break;
default:
NOTREACHED();
}
}
void Animation::NotifyCompositorStartTime(double timeline_time) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand,
kDoNotSetCompositorPending);
if (compositor_state_) {
DCHECK_EQ(compositor_state_->pending_action, kStart);
DCHECK(std::isnan(compositor_state_->start_time));
double initial_compositor_hold_time = compositor_state_->hold_time;
compositor_state_->pending_action = kNone;
compositor_state_->start_time =
timeline_time + CurrentTimeInternal() / -playback_rate_;
if (start_time_ == timeline_time) {
// The start time was set to the incoming compositor start time.
// Unlikely, but possible.
// FIXME: Depending on what changed above this might still be pending.
// Maybe...
current_time_pending_ = false;
return;
}
if (!std::isnan(start_time_) ||
CurrentTimeInternal() != initial_compositor_hold_time) {
// A new start time or current time was set while starting.
SetCompositorPending(true);
return;
}
}
NotifyStartTime(timeline_time);
}
void Animation::NotifyStartTime(double timeline_time) {
if (Playing()) {
DCHECK(std::isnan(start_time_));
DCHECK(held_);
if (playback_rate_ == 0) {
SetStartTimeInternal(timeline_time);
} else {
SetStartTimeInternal(timeline_time +
CurrentTimeInternal() / -playback_rate_);
}
// FIXME: This avoids marking this animation as outdated needlessly when a
// start time is notified, but we should refactor how outdating works to
// avoid this.
ClearOutdated();
current_time_pending_ = false;
}
}
bool Animation::Affects(const Element& element,
const CSSProperty& property) const {
if (!content_ || !content_->IsKeyframeEffectReadOnly())
return false;
const KeyframeEffectReadOnly* effect =
ToKeyframeEffectReadOnly(content_.Get());
return (effect->Target() == &element) &&
effect->Affects(PropertyHandle(property));
}
double Animation::CalculateStartTime(double current_time) const {
return timeline_->EffectiveTime() - current_time / playback_rate_;
}
double Animation::CalculateCurrentTime() const {
if (IsNull(start_time_) || !timeline_)
return 0;
return (timeline_->EffectiveTime() - start_time_) * playback_rate_;
}
void Animation::setStartTime(double start_time, bool is_null) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
if (start_time == start_time_)
return;
current_time_pending_ = false;
play_state_ = kUnset;
paused_ = false;
SetStartTimeInternal(start_time / 1000);
}
void Animation::SetStartTimeInternal(double new_start_time) {
DCHECK(!paused_);
DCHECK(std::isfinite(new_start_time));
DCHECK_NE(new_start_time, start_time_);
bool had_start_time = HasStartTime();
double previous_current_time = CurrentTimeInternal();
start_time_ = new_start_time;
if (held_ && playback_rate_) {
// If held, the start time would still be derrived from the hold time.
// Force a new, limited, current time.
held_ = false;
double current_time = CalculateCurrentTime();
if (playback_rate_ > 0 && current_time > EffectEnd()) {
current_time = EffectEnd();
} else if (playback_rate_ < 0 && current_time < 0) {
current_time = 0;
}
SetCurrentTimeInternal(current_time, kTimingUpdateOnDemand);
}
UpdateCurrentTimingState(kTimingUpdateOnDemand);
double new_current_time = CurrentTimeInternal();
if (previous_current_time != new_current_time) {
SetOutdated();
} else if (!had_start_time && timeline_) {
// Even though this animation is not outdated, time to effect change is
// infinity until start time is set.
ForceServiceOnNextFrame();
}
}
void Animation::setEffect(AnimationEffectReadOnly* new_effect) {
if (content_ == new_effect)
return;
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand,
kSetCompositorPendingWithEffectChanged);
double stored_current_time = CurrentTimeInternal();
if (content_)
content_->Detach();
content_ = new_effect;
if (new_effect) {
// FIXME: This logic needs to be updated once groups are implemented
if (new_effect->GetAnimation()) {
new_effect->GetAnimation()->cancel();
new_effect->GetAnimation()->setEffect(nullptr);
}
new_effect->Attach(this);
SetOutdated();
}
SetCurrentTimeInternal(stored_current_time, kTimingUpdateOnDemand);
}
const char* Animation::PlayStateString(AnimationPlayState play_state) {
switch (play_state) {
case kIdle:
return "idle";
case kPending:
return "pending";
case kRunning:
return "running";
case kPaused:
return "paused";
case kFinished:
return "finished";
default:
NOTREACHED();
return "";
}
}
Animation::AnimationPlayState Animation::PlayStateInternal() const {
DCHECK_NE(play_state_, kUnset);
return play_state_;
}
Animation::AnimationPlayState Animation::CalculatePlayState() {
if (paused_ && !current_time_pending_)
return kPaused;
if (play_state_ == kIdle)
return kIdle;
if (current_time_pending_ || (IsNull(start_time_) && playback_rate_ != 0))
return kPending;
if (Limited())
return kFinished;
return kRunning;
}
void Animation::pause(ExceptionState& exception_state) {
if (paused_)
return;
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
double new_current_time = CurrentTimeInternal();
if (CalculatePlayState() == kIdle) {
if (playback_rate_ < 0 &&
EffectEnd() == std::numeric_limits<double>::infinity()) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Cannot pause, Animation has infinite target effect end.");
return;
}
new_current_time = playback_rate_ < 0 ? EffectEnd() : 0;
}
play_state_ = kUnset;
paused_ = true;
current_time_pending_ = true;
SetCurrentTimeInternal(new_current_time, kTimingUpdateOnDemand);
}
void Animation::Unpause() {
if (!paused_)
return;
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
current_time_pending_ = true;
UnpauseInternal();
}
void Animation::UnpauseInternal() {
if (!paused_)
return;
paused_ = false;
SetCurrentTimeInternal(CurrentTimeInternal(), kTimingUpdateOnDemand);
}
void Animation::play(ExceptionState& exception_state) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
double current_time = this->CurrentTimeInternal();
if (playback_rate_ < 0 && current_time <= 0 &&
EffectEnd() == std::numeric_limits<double>::infinity()) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Cannot play reversed Animation with infinite target effect end.");
return;
}
if (!Playing()) {
start_time_ = NullValue();
}
if (PlayStateInternal() == kIdle) {
held_ = true;
hold_time_ = 0;
}
play_state_ = kUnset;
finished_ = false;
UnpauseInternal();
if (playback_rate_ > 0 && (current_time < 0 || current_time >= EffectEnd())) {
start_time_ = NullValue();
SetCurrentTimeInternal(0, kTimingUpdateOnDemand);
} else if (playback_rate_ < 0 &&
(current_time <= 0 || current_time > EffectEnd())) {
start_time_ = NullValue();
SetCurrentTimeInternal(EffectEnd(), kTimingUpdateOnDemand);
}
}
void Animation::reverse(ExceptionState& exception_state) {
if (!playback_rate_) {
return;
}
SetPlaybackRateInternal(-playback_rate_);
play(exception_state);
}
void Animation::finish(ExceptionState& exception_state) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
if (!playback_rate_) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Cannot finish Animation with a playbackRate of 0.");
return;
}
if (playback_rate_ > 0 &&
EffectEnd() == std::numeric_limits<double>::infinity()) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Cannot finish Animation with an infinite target effect end.");
return;
}
// Avoid updating start time when already finished.
if (CalculatePlayState() == kFinished)
return;
double new_current_time = playback_rate_ < 0 ? 0 : EffectEnd();
SetCurrentTimeInternal(new_current_time, kTimingUpdateOnDemand);
paused_ = false;
current_time_pending_ = false;
start_time_ = CalculateStartTime(new_current_time);
play_state_ = kFinished;
ForceServiceOnNextFrame();
}
ScriptPromise Animation::finished(ScriptState* script_state) {
if (!finished_promise_) {
finished_promise_ =
new AnimationPromise(ExecutionContext::From(script_state), this,
AnimationPromise::kFinished);
if (PlayStateInternal() == kFinished)
finished_promise_->Resolve(this);
}
return finished_promise_->Promise(script_state->World());
}
ScriptPromise Animation::ready(ScriptState* script_state) {
if (!ready_promise_) {
ready_promise_ = new AnimationPromise(ExecutionContext::From(script_state),
this, AnimationPromise::kReady);
if (PlayStateInternal() != kPending)
ready_promise_->Resolve(this);
}
return ready_promise_->Promise(script_state->World());
}
const AtomicString& Animation::InterfaceName() const {
return EventTargetNames::AnimationPlayer;
}
ExecutionContext* Animation::GetExecutionContext() const {
return ContextLifecycleObserver::GetExecutionContext();
}
bool Animation::HasPendingActivity() const {
bool has_pending_promise =
finished_promise_ &&
finished_promise_->GetState() == ScriptPromisePropertyBase::kPending;
return pending_finished_event_ || has_pending_promise ||
(!finished_ && HasEventListeners(EventTypeNames::finish));
}
void Animation::ContextDestroyed(ExecutionContext*) {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
finished_ = true;
pending_finished_event_ = nullptr;
}
DispatchEventResult Animation::DispatchEventInternal(Event* event) {
if (pending_finished_event_ == event)
pending_finished_event_ = nullptr;
return EventTargetWithInlineData::DispatchEventInternal(event);
}
double Animation::playbackRate() const {
return playback_rate_;
}
void Animation::setPlaybackRate(double playback_rate) {
if (playback_rate == playback_rate_)
return;
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
double start_time_before = start_time_;
SetPlaybackRateInternal(playback_rate);
// Adds a UseCounter to check if setting playbackRate causes a compensatory
// seek forcing a change in start_time_
if (!std::isnan(start_time_before) && start_time_ != start_time_before &&
play_state_ != kFinished) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kAnimationSetPlaybackRateCompensatorySeek);
}
}
void Animation::SetPlaybackRateInternal(double playback_rate) {
DCHECK(std::isfinite(playback_rate));
DCHECK_NE(playback_rate, playback_rate_);
if (!Limited() && !Paused() && HasStartTime())
current_time_pending_ = true;
double stored_current_time = CurrentTimeInternal();
if ((playback_rate_ < 0 && playback_rate >= 0) ||
(playback_rate_ > 0 && playback_rate <= 0))
finished_ = false;
playback_rate_ = playback_rate;
start_time_ = std::numeric_limits<double>::quiet_NaN();
SetCurrentTimeInternal(stored_current_time, kTimingUpdateOnDemand);
}
void Animation::ClearOutdated() {
if (!outdated_)
return;
outdated_ = false;
if (timeline_)
timeline_->ClearOutdatedAnimation(this);
}
void Animation::SetOutdated() {
if (outdated_)
return;
outdated_ = true;
if (timeline_)
timeline_->SetOutdatedAnimation(this);
}
void Animation::ForceServiceOnNextFrame() {
timeline_->Wake();
}
CompositorAnimations::FailureCode Animation::CheckCanStartAnimationOnCompositor(
const Optional<CompositorElementIdSet>& composited_element_ids) const {
CompositorAnimations::FailureCode code =
CheckCanStartAnimationOnCompositorInternal(composited_element_ids);
if (!code.Ok()) {
return code;
}
return ToKeyframeEffectReadOnly(content_.Get())
->CheckCanStartAnimationOnCompositor(playback_rate_);
}
CompositorAnimations::FailureCode
Animation::CheckCanStartAnimationOnCompositorInternal(
const Optional<CompositorElementIdSet>& composited_element_ids) const {
if (is_composited_animation_disabled_for_testing_) {
return CompositorAnimations::FailureCode::NonActionable(
"Accelerated animations disabled for testing");
}
if (EffectSuppressed()) {
return CompositorAnimations::FailureCode::NonActionable(
"Animation effect suppressed by DevTools");
}
if (playback_rate_ == 0) {
return CompositorAnimations::FailureCode::Actionable(
"Animation is not playing");
}
if (std::isinf(EffectEnd()) && playback_rate_ < 0) {
return CompositorAnimations::FailureCode::Actionable(
"Accelerated animations do not support reversed infinite duration "
"animations");
}
// FIXME: Timeline playback rates should be compositable
if (TimelineInternal() && TimelineInternal()->PlaybackRate() != 1) {
return CompositorAnimations::FailureCode::NonActionable(
"Accelerated animations do not support timelines with playback rates "
"other than 1");
}
if (!timeline_) {
return CompositorAnimations::FailureCode::Actionable(
"Animation is not attached to a timeline");
}
if (!content_) {
return CompositorAnimations::FailureCode::Actionable(
"Animation has no animation effect");
}
if (!content_->IsKeyframeEffectReadOnly()) {
return CompositorAnimations::FailureCode::NonActionable(
"Animation effect is not keyframe-based");
}
// If the optional element id set has no value we must be in SPv1 mode in
// which case we trust the compositing logic will create a layer if needed.
if (composited_element_ids.has_value()) {
DCHECK(RuntimeEnabledFeatures::SlimmingPaintV2Enabled());
Element* target_element =
ToKeyframeEffectReadOnly(content_.Get())->Target();
if (!target_element) {
return CompositorAnimations::FailureCode::Actionable(
"Animation is not attached to an element");
}
bool has_own_layer_id = false;
if (target_element->GetLayoutObject() &&
target_element->GetLayoutObject()->IsBoxModelObject() &&
target_element->GetLayoutObject()->HasLayer()) {
CompositorElementId target_element_id =
CompositorElementIdFromUniqueObjectId(
target_element->GetLayoutObject()->UniqueId(),
CompositorElementIdNamespace::kPrimary);
if (composited_element_ids->Contains(target_element_id)) {
has_own_layer_id = true;
}
}
if (!has_own_layer_id) {
return CompositorAnimations::FailureCode::NonActionable(
"Target element does not have its own compositing layer");
}
}
if (!Playing()) {
return CompositorAnimations::FailureCode::Actionable(
"Animation is not playing");
}
return CompositorAnimations::FailureCode::None();
}
void Animation::StartAnimationOnCompositor(
const Optional<CompositorElementIdSet>& composited_element_ids) {
DCHECK(CheckCanStartAnimationOnCompositor(composited_element_ids).Ok());
bool reversed = playback_rate_ < 0;
double start_time = TimelineInternal()->ZeroTime() + StartTimeInternal();
if (reversed) {
start_time -= EffectEnd() / fabs(playback_rate_);
}
double time_offset = 0;
if (std::isnan(start_time)) {
time_offset =
reversed ? EffectEnd() - CurrentTimeInternal() : CurrentTimeInternal();
time_offset = time_offset / fabs(playback_rate_);
}
DCHECK_NE(compositor_group_, 0);
ToKeyframeEffectReadOnly(content_.Get())
->StartAnimationOnCompositor(compositor_group_, start_time, time_offset,
playback_rate_);
}
void Animation::SetCompositorPending(bool effect_changed) {
// FIXME: KeyframeEffect could notify this directly?
if (!HasActiveAnimationsOnCompositor()) {
DestroyCompositorPlayer();
compositor_state_.reset();
}
if (effect_changed && compositor_state_) {
compositor_state_->effect_changed = true;
}
if (compositor_pending_ || is_paused_for_testing_) {
return;
}
if (!compositor_state_ || compositor_state_->effect_changed ||
compositor_state_->playback_rate != playback_rate_ ||
compositor_state_->start_time != start_time_) {
compositor_pending_ = true;
TimelineInternal()->GetDocument()->GetPendingAnimations().Add(this);
}
}
void Animation::CancelAnimationOnCompositor() {
if (HasActiveAnimationsOnCompositor())
ToKeyframeEffectReadOnly(content_.Get())->CancelAnimationOnCompositor();
DestroyCompositorPlayer();
}
void Animation::RestartAnimationOnCompositor() {
if (HasActiveAnimationsOnCompositor())
ToKeyframeEffectReadOnly(content_.Get())->RestartAnimationOnCompositor();
}
void Animation::CancelIncompatibleAnimationsOnCompositor() {
if (content_ && content_->IsKeyframeEffectReadOnly())
ToKeyframeEffectReadOnly(content_.Get())
->CancelIncompatibleAnimationsOnCompositor();
}
bool Animation::HasActiveAnimationsOnCompositor() {
if (!content_ || !content_->IsKeyframeEffectReadOnly())
return false;
return ToKeyframeEffectReadOnly(content_.Get())
->HasActiveAnimationsOnCompositor();
}
bool Animation::Update(TimingUpdateReason reason) {
if (!timeline_)
return false;
PlayStateUpdateScope update_scope(*this, reason, kDoNotSetCompositorPending);
ClearOutdated();
bool idle = PlayStateInternal() == kIdle;
if (content_) {
double inherited_time = idle || IsNull(timeline_->CurrentTimeInternal())
? NullValue()
: CurrentTimeInternal();
// Special case for end-exclusivity when playing backwards.
if (inherited_time == 0 && playback_rate_ < 0)
inherited_time = -1;
content_->UpdateInheritedTime(inherited_time, reason);
}
if ((idle || Limited()) && !finished_) {
if (reason == kTimingUpdateForAnimationFrame && (idle || HasStartTime())) {
if (idle) {
const AtomicString& event_type = EventTypeNames::cancel;
if (GetExecutionContext() && HasEventListeners(event_type)) {
double event_current_time = NullValue();
pending_cancelled_event_ =
AnimationPlaybackEvent::Create(event_type, event_current_time,
TimelineInternal()->currentTime());
pending_cancelled_event_->SetTarget(this);
pending_cancelled_event_->SetCurrentTarget(this);
timeline_->GetDocument()->EnqueueAnimationFrameEvent(
pending_cancelled_event_);
}
} else {
const AtomicString& event_type = EventTypeNames::finish;
if (GetExecutionContext() && HasEventListeners(event_type)) {
double event_current_time = CurrentTimeInternal() * 1000;
pending_finished_event_ =
AnimationPlaybackEvent::Create(event_type, event_current_time,
TimelineInternal()->currentTime());
pending_finished_event_->SetTarget(this);
pending_finished_event_->SetCurrentTarget(this);
timeline_->GetDocument()->EnqueueAnimationFrameEvent(
pending_finished_event_);
}
}
finished_ = true;
}
}
DCHECK(!outdated_);
return !finished_ || std::isfinite(TimeToEffectChange());
}
double Animation::TimeToEffectChange() {
DCHECK(!outdated_);
if (!HasStartTime() || held_)
return std::numeric_limits<double>::infinity();
if (!content_)
return -CurrentTimeInternal() / playback_rate_;
double result = playback_rate_ > 0
? content_->TimeToForwardsEffectChange() / playback_rate_
: content_->TimeToReverseEffectChange() / -playback_rate_;
return !HasActiveAnimationsOnCompositor() &&
content_->GetPhase() == AnimationEffectReadOnly::kPhaseActive
? 0
: result;
}
void Animation::cancel() {
PlayStateUpdateScope update_scope(*this, kTimingUpdateOnDemand);
if (PlayStateInternal() == kIdle)
return;
held_ = false;
paused_ = false;
play_state_ = kIdle;
start_time_ = NullValue();
current_time_pending_ = false;
ForceServiceOnNextFrame();
}
void Animation::BeginUpdatingState() {
// Nested calls are not allowed!
DCHECK(!state_is_being_updated_);
state_is_being_updated_ = true;
}
void Animation::EndUpdatingState() {
DCHECK(state_is_being_updated_);
state_is_being_updated_ = false;
}
void Animation::CreateCompositorPlayer() {
if (Platform::Current()->IsThreadedAnimationEnabled() &&
!compositor_player_) {
DCHECK(Platform::Current()->CompositorSupport());
compositor_player_ = CompositorAnimationPlayerHolder::Create(this);
DCHECK(compositor_player_);
AttachCompositorTimeline();
}
AttachCompositedLayers();
}
void Animation::DestroyCompositorPlayer() {
DetachCompositedLayers();
if (compositor_player_) {
DetachCompositorTimeline();
compositor_player_->Detach();
compositor_player_ = nullptr;
}
}
void Animation::AttachCompositorTimeline() {
if (compositor_player_) {
CompositorAnimationTimeline* timeline =
timeline_ ? timeline_->CompositorTimeline() : nullptr;
if (timeline)
timeline->PlayerAttached(*this);
}
}
void Animation::DetachCompositorTimeline() {
if (compositor_player_) {
CompositorAnimationTimeline* timeline =
timeline_ ? timeline_->CompositorTimeline() : nullptr;
if (timeline)
timeline->PlayerDestroyed(*this);
}
}
void Animation::AttachCompositedLayers() {
if (!compositor_player_)
return;
DCHECK(content_);
DCHECK(content_->IsKeyframeEffectReadOnly());
ToKeyframeEffectReadOnly(content_.Get())->AttachCompositedLayers();
}
void Animation::DetachCompositedLayers() {
if (compositor_player_ && compositor_player_->Player()->IsElementAttached())
compositor_player_->Player()->DetachElement();
}
void Animation::NotifyAnimationStarted(double monotonic_time, int group) {
TimelineInternal()
->GetDocument()
->GetPendingAnimations()
.NotifyCompositorAnimationStarted(monotonic_time, group);
}
Animation::PlayStateUpdateScope::PlayStateUpdateScope(
Animation& animation,
TimingUpdateReason reason,
CompositorPendingChange compositor_pending_change)
: animation_(animation),
initial_play_state_(animation_->PlayStateInternal()),
compositor_pending_change_(compositor_pending_change) {
DCHECK_NE(initial_play_state_, kUnset);
animation_->BeginUpdatingState();
animation_->UpdateCurrentTimingState(reason);
}
Animation::PlayStateUpdateScope::~PlayStateUpdateScope() {
AnimationPlayState old_play_state = initial_play_state_;
AnimationPlayState new_play_state = animation_->CalculatePlayState();
animation_->play_state_ = new_play_state;
if (old_play_state != new_play_state) {
bool was_active = old_play_state == kPending || old_play_state == kRunning;
bool is_active = new_play_state == kPending || new_play_state == kRunning;
if (!was_active && is_active)
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
"blink.animations,devtools.timeline,benchmark,rail", "Animation",
animation_, "data", InspectorAnimationEvent::Data(*animation_));
else if (was_active && !is_active)
TRACE_EVENT_NESTABLE_ASYNC_END1(
"blink.animations,devtools.timeline,benchmark,rail", "Animation",
animation_, "endData",
InspectorAnimationStateEvent::Data(*animation_));
else
TRACE_EVENT_NESTABLE_ASYNC_INSTANT1(
"blink.animations,devtools.timeline,benchmark,rail", "Animation",
animation_, "data", InspectorAnimationStateEvent::Data(*animation_));
}
// Ordering is important, the ready promise should resolve/reject before
// the finished promise.
if (animation_->ready_promise_ && new_play_state != old_play_state) {
if (new_play_state == kIdle) {
if (animation_->ready_promise_->GetState() ==
AnimationPromise::kPending) {
animation_->RejectAndResetPromiseMaybeAsync(
animation_->ready_promise_.Get());
} else {
animation_->ready_promise_->Reset();
}
animation_->ResolvePromiseMaybeAsync(animation_->ready_promise_.Get());
} else if (old_play_state == kPending) {
animation_->ResolvePromiseMaybeAsync(animation_->ready_promise_.Get());
} else if (new_play_state == kPending) {
DCHECK_NE(animation_->ready_promise_->GetState(),
AnimationPromise::kPending);
animation_->ready_promise_->Reset();
}
}
if (animation_->finished_promise_ && new_play_state != old_play_state) {
if (new_play_state == kIdle) {
if (animation_->finished_promise_->GetState() ==
AnimationPromise::kPending) {
animation_->RejectAndResetPromiseMaybeAsync(
animation_->finished_promise_.Get());
} else {
animation_->finished_promise_->Reset();
}
} else if (new_play_state == kFinished) {
animation_->ResolvePromiseMaybeAsync(animation_->finished_promise_.Get());
} else if (old_play_state == kFinished) {
animation_->finished_promise_->Reset();
}
}
if (old_play_state != new_play_state &&
(old_play_state == kIdle || new_play_state == kIdle)) {
animation_->SetOutdated();
}
#if DCHECK_IS_ON()
// Verify that current time is up to date.
animation_->CurrentTimeInternal();
#endif
switch (compositor_pending_change_) {
case kSetCompositorPending:
animation_->SetCompositorPending();
break;
case kSetCompositorPendingWithEffectChanged:
animation_->SetCompositorPending(true);
break;
case kDoNotSetCompositorPending:
break;
default:
NOTREACHED();
break;
}
animation_->EndUpdatingState();
if (old_play_state != new_play_state) {
probe::animationPlayStateChanged(
animation_->TimelineInternal()->GetDocument(), animation_,
old_play_state, new_play_state);
}
}
void Animation::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::AddedEventListener(event_type,
registered_listener);
if (event_type == EventTypeNames::finish)
UseCounter::Count(GetExecutionContext(), WebFeature::kAnimationFinishEvent);
}
void Animation::PauseForTesting(double pause_time) {
SetCurrentTimeInternal(pause_time, kTimingUpdateOnDemand);
if (HasActiveAnimationsOnCompositor())
ToKeyframeEffectReadOnly(content_.Get())
->PauseAnimationForTestingOnCompositor(CurrentTimeInternal());
is_paused_for_testing_ = true;
pause();
}
void Animation::SetEffectSuppressed(bool suppressed) {
effect_suppressed_ = suppressed;
if (suppressed)
CancelAnimationOnCompositor();
}
void Animation::DisableCompositedAnimationForTesting() {
is_composited_animation_disabled_for_testing_ = true;
CancelAnimationOnCompositor();
}
void Animation::InvalidateKeyframeEffect(const TreeScope& tree_scope) {
if (!content_ || !content_->IsKeyframeEffectReadOnly())
return;
Element* target = ToKeyframeEffectReadOnly(content_.Get())->Target();
// TODO(alancutter): Remove dependency of this function on CSSAnimations.
// This function makes the incorrect assumption that the animation uses
// @keyframes for its effect model when it may instead be using JS provided
// keyframes.
if (target &&
CSSAnimations::IsAffectedByKeyframesFromScope(*target, tree_scope)) {
target->SetNeedsStyleRecalc(kLocalStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kStyleSheetChange));
}
}
void Animation::ResolvePromiseMaybeAsync(AnimationPromise* promise) {
if (ScriptForbiddenScope::IsScriptForbidden()) {
GetExecutionContext()
->GetTaskRunner(TaskType::kDOMManipulation)
->PostTask(FROM_HERE,
WTF::Bind(&AnimationPromise::Resolve<Animation*>,
WrapPersistent(promise), WrapPersistent(this)));
} else {
promise->Resolve(this);
}
}
void Animation::RejectAndResetPromise(AnimationPromise* promise) {
promise->Reject(DOMException::Create(kAbortError));
promise->Reset();
}
void Animation::RejectAndResetPromiseMaybeAsync(AnimationPromise* promise) {
if (ScriptForbiddenScope::IsScriptForbidden()) {
GetExecutionContext()
->GetTaskRunner(TaskType::kDOMManipulation)
->PostTask(FROM_HERE,
WTF::Bind(&Animation::RejectAndResetPromise,
WrapPersistent(this), WrapPersistent(promise)));
} else {
RejectAndResetPromise(promise);
}
}
void Animation::Trace(blink::Visitor* visitor) {
visitor->Trace(content_);
visitor->Trace(timeline_);
visitor->Trace(pending_finished_event_);
visitor->Trace(pending_cancelled_event_);
visitor->Trace(finished_promise_);
visitor->Trace(ready_promise_);
visitor->Trace(compositor_player_);
EventTargetWithInlineData::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
Animation::CompositorAnimationPlayerHolder*
Animation::CompositorAnimationPlayerHolder::Create(Animation* animation) {
return new CompositorAnimationPlayerHolder(animation);
}
Animation::CompositorAnimationPlayerHolder::CompositorAnimationPlayerHolder(
Animation* animation)
: animation_(animation) {
compositor_player_ = CompositorAnimationPlayer::Create();
compositor_player_->SetAnimationDelegate(animation_);
}
void Animation::CompositorAnimationPlayerHolder::Dispose() {
if (!animation_)
return;
animation_->Dispose();
DCHECK(!animation_);
DCHECK(!compositor_player_);
}
void Animation::CompositorAnimationPlayerHolder::Detach() {
DCHECK(compositor_player_);
compositor_player_->SetAnimationDelegate(nullptr);
animation_ = nullptr;
compositor_player_.reset();
}
} // namespace blink