blob: 44892999bfb5ac886db05c68da5febb7fb5ec08c [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 "third_party/blink/renderer/core/animation/animation_effect.h"
#include "third_party/blink/renderer/core/animation/animation.h"
#include "third_party/blink/renderer/core/animation/animation_input_helpers.h"
#include "third_party/blink/renderer/core/animation/computed_effect_timing.h"
#include "third_party/blink/renderer/core/animation/effect_timing.h"
#include "third_party/blink/renderer/core/animation/optional_effect_timing.h"
#include "third_party/blink/renderer/core/animation/timing_calculations.h"
#include "third_party/blink/renderer/core/animation/timing_input.h"
namespace blink {
namespace {
Timing::FillMode ResolvedFillMode(Timing::FillMode fill_mode,
bool is_animation) {
if (fill_mode != Timing::FillMode::AUTO)
return fill_mode;
if (is_animation)
return Timing::FillMode::NONE;
return Timing::FillMode::BOTH;
}
} // namespace
AnimationEffect::AnimationEffect(const Timing& timing,
EventDelegate* event_delegate)
: owner_(nullptr),
timing_(timing),
event_delegate_(event_delegate),
calculated_(),
needs_update_(true),
last_update_time_(NullValue()) {
timing_.AssertValid();
}
AnimationTimeDelta AnimationEffect::IterationDuration() const {
AnimationTimeDelta result =
timing_.iteration_duration.value_or(IntrinsicIterationDuration());
DCHECK_GE(result, AnimationTimeDelta());
return result;
}
double AnimationEffect::RepeatedDuration() const {
const double result =
MultiplyZeroAlwaysGivesZero(IterationDuration(), timing_.iteration_count);
DCHECK_GE(result, 0);
return result;
}
double AnimationEffect::EndTimeInternal() const {
// Per the spec, the end time has a lower bound of 0.0:
// https://drafts.csswg.org/web-animations-1/#end-time
return std::max(timing_.start_delay + RepeatedDuration() + timing_.end_delay,
0.0);
}
void AnimationEffect::UpdateSpecifiedTiming(const Timing& timing) {
// FIXME: Test whether the timing is actually different?
timing_ = timing;
InvalidateAndNotifyOwner();
}
EffectTiming* AnimationEffect::getTiming() const {
EffectTiming* effect_timing = EffectTiming::Create();
effect_timing->setDelay(SpecifiedTiming().start_delay * 1000);
effect_timing->setEndDelay(SpecifiedTiming().end_delay * 1000);
effect_timing->setFill(Timing::FillModeString(SpecifiedTiming().fill_mode));
effect_timing->setIterationStart(SpecifiedTiming().iteration_start);
effect_timing->setIterations(SpecifiedTiming().iteration_count);
UnrestrictedDoubleOrString duration;
if (SpecifiedTiming().iteration_duration) {
duration.SetUnrestrictedDouble(
SpecifiedTiming().iteration_duration->InMillisecondsF());
} else {
duration.SetString("auto");
}
effect_timing->setDuration(duration);
effect_timing->setDirection(
Timing::PlaybackDirectionString(SpecifiedTiming().direction));
effect_timing->setEasing(SpecifiedTiming().timing_function->ToString());
return effect_timing;
}
ComputedEffectTiming* AnimationEffect::getComputedTiming() const {
ComputedEffectTiming* computed_timing = ComputedEffectTiming::Create();
// ComputedEffectTiming members.
computed_timing->setEndTime(EndTimeInternal() * 1000);
computed_timing->setActiveDuration(RepeatedDuration() * 1000);
if (IsNull(EnsureCalculated().local_time)) {
computed_timing->setLocalTimeToNull();
} else {
computed_timing->setLocalTime(EnsureCalculated().local_time * 1000);
}
if (EnsureCalculated().is_in_effect) {
computed_timing->setProgress(EnsureCalculated().progress.value());
computed_timing->setCurrentIteration(EnsureCalculated().current_iteration);
} else {
computed_timing->setProgressToNull();
computed_timing->setCurrentIterationToNull();
}
// For the EffectTiming members, getComputedTiming is equivalent to getTiming
// except that the fill and duration must be resolved.
//
// https://drafts.csswg.org/web-animations-1/#dom-animationeffect-getcomputedtiming
computed_timing->setDelay(SpecifiedTiming().start_delay * 1000);
computed_timing->setEndDelay(SpecifiedTiming().end_delay * 1000);
computed_timing->setFill(Timing::FillModeString(
ResolvedFillMode(SpecifiedTiming().fill_mode, IsKeyframeEffect())));
computed_timing->setIterationStart(SpecifiedTiming().iteration_start);
computed_timing->setIterations(SpecifiedTiming().iteration_count);
UnrestrictedDoubleOrString duration;
duration.SetUnrestrictedDouble(IterationDuration().InMillisecondsF());
computed_timing->setDuration(duration);
computed_timing->setDirection(
Timing::PlaybackDirectionString(SpecifiedTiming().direction));
computed_timing->setEasing(SpecifiedTiming().timing_function->ToString());
return computed_timing;
}
void AnimationEffect::updateTiming(OptionalEffectTiming* optional_timing,
ExceptionState& exception_state) {
// TODO(crbug.com/827178): Determine whether we should pass a Document in here
// (and which) to resolve the CSS secure/insecure context against.
if (!TimingInput::Update(timing_, optional_timing, nullptr, exception_state))
return;
InvalidateAndNotifyOwner();
}
void AnimationEffect::UpdateInheritedTime(double inherited_time,
TimingUpdateReason reason) const {
bool needs_update =
needs_update_ ||
(last_update_time_ != inherited_time &&
!(IsNull(last_update_time_) && IsNull(inherited_time))) ||
(owner_ && owner_->EffectSuppressed());
needs_update_ = false;
last_update_time_ = inherited_time;
const double local_time = inherited_time;
double time_to_next_iteration = std::numeric_limits<double>::infinity();
if (needs_update) {
const double active_duration = RepeatedDuration();
// TODO(yigu): Direction of WorkletAnimation is always forwards based on
// the calculation. Need to unify the logic to handle it correctly.
// https://crbug.com/896249.
const AnimationDirection direction =
(GetAnimation() && GetAnimation()->playbackRate() < 0) ? kBackwards
: kForwards;
const Phase current_phase =
CalculatePhase(active_duration, local_time, direction, timing_);
// FIXME: parentPhase depends on groups being implemented.
const AnimationEffect::Phase kParentPhase = AnimationEffect::kPhaseActive;
const double active_time = CalculateActiveTime(
active_duration,
ResolvedFillMode(timing_.fill_mode, IsKeyframeEffect()), local_time,
kParentPhase, current_phase, timing_);
double current_iteration;
base::Optional<double> progress;
if (!IterationDuration().is_zero()) {
const double iteration_duration = IterationDuration().InSecondsF();
const double start_offset = MultiplyZeroAlwaysGivesZero(
timing_.iteration_start, iteration_duration);
DCHECK_GE(start_offset, 0);
const double offset_active_time =
CalculateOffsetActiveTime(active_duration, active_time, start_offset);
const double iteration_time = CalculateIterationTime(
iteration_duration, active_duration, offset_active_time, start_offset,
current_phase, timing_);
current_iteration = CalculateCurrentIteration(
iteration_duration, iteration_time, offset_active_time, timing_);
const base::Optional<double> transformed_time = CalculateTransformedTime(
current_iteration, iteration_duration, iteration_time, timing_);
// The infinite iterationDuration case here is a workaround because
// the specified behaviour does not handle infinite durations well.
// There is an open issue against the spec to fix this:
// https://github.com/w3c/web-animations/issues/142
if (!std::isfinite(iteration_duration))
progress = fmod(timing_.iteration_start, 1.0);
else if (transformed_time)
progress = transformed_time.value() / iteration_duration;
if (!IsNull(iteration_time)) {
time_to_next_iteration = iteration_duration - iteration_time;
if (active_duration - active_time < time_to_next_iteration)
time_to_next_iteration = std::numeric_limits<double>::infinity();
}
} else {
const double kLocalIterationDuration = 1;
const double local_active_duration =
kLocalIterationDuration * timing_.iteration_count;
DCHECK_GE(local_active_duration, 0);
const double end_time = std::max(
timing_.start_delay + active_duration + timing_.end_delay, 0.0);
const double before_active_boundary_time =
std::max(std::min(timing_.start_delay, end_time), 0.0);
// local_local_time should be greater than or equal to the
// before_active_boundary_time once the local_time goes past the start
// delay.
const double local_local_time =
local_time < timing_.start_delay
? local_time
: std::max(local_active_duration + timing_.start_delay,
before_active_boundary_time);
const AnimationEffect::Phase local_current_phase = CalculatePhase(
local_active_duration, local_local_time, direction, timing_);
const double local_active_time = CalculateActiveTime(
local_active_duration,
ResolvedFillMode(timing_.fill_mode, IsKeyframeEffect()),
local_local_time, kParentPhase, local_current_phase, timing_);
const double start_offset =
timing_.iteration_start * kLocalIterationDuration;
DCHECK_GE(start_offset, 0);
const double offset_active_time = CalculateOffsetActiveTime(
local_active_duration, local_active_time, start_offset);
const double iteration_time = CalculateIterationTime(
kLocalIterationDuration, local_active_duration, offset_active_time,
start_offset, current_phase, timing_);
current_iteration = CalculateCurrentIteration(
kLocalIterationDuration, iteration_time, offset_active_time, timing_);
progress = CalculateTransformedTime(
current_iteration, kLocalIterationDuration, iteration_time, timing_);
}
const bool was_canceled = current_phase != calculated_.phase &&
current_phase == AnimationEffect::kPhaseNone;
calculated_.phase = current_phase;
// If the animation was canceled, we need to fire the event condition before
// updating the timing so that the cancelation time can be determined.
if (was_canceled && event_delegate_)
event_delegate_->OnEventCondition(*this);
calculated_.current_iteration = current_iteration;
calculated_.progress = progress;
calculated_.is_in_effect = !IsNull(active_time);
calculated_.is_in_play = GetPhase() == kPhaseActive;
calculated_.is_current = GetPhase() == kPhaseBefore || IsInPlay();
calculated_.local_time = last_update_time_;
}
// Test for events even if timing didn't need an update as the animation may
// have gained a start time.
// FIXME: Refactor so that we can DCHECK(owner_) here, this is currently
// required to be nullable for testing.
if (reason == kTimingUpdateForAnimationFrame &&
(!owner_ || owner_->IsEventDispatchAllowed())) {
if (event_delegate_)
event_delegate_->OnEventCondition(*this);
}
if (needs_update) {
// FIXME: This probably shouldn't be recursive.
UpdateChildrenAndEffects();
calculated_.time_to_forwards_effect_change =
CalculateTimeToEffectChange(true, local_time, time_to_next_iteration);
calculated_.time_to_reverse_effect_change =
CalculateTimeToEffectChange(false, local_time, time_to_next_iteration);
}
}
void AnimationEffect::InvalidateAndNotifyOwner() const {
Invalidate();
if (owner_)
owner_->EffectInvalidated();
}
const AnimationEffect::CalculatedTiming& AnimationEffect::EnsureCalculated()
const {
if (!owner_)
return calculated_;
owner_->UpdateIfNecessary();
return calculated_;
}
Animation* AnimationEffect::GetAnimation() {
return owner_ ? owner_->GetAnimation() : nullptr;
}
const Animation* AnimationEffect::GetAnimation() const {
return owner_ ? owner_->GetAnimation() : nullptr;
}
void AnimationEffect::Trace(blink::Visitor* visitor) {
visitor->Trace(owner_);
visitor->Trace(event_delegate_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink