blob: 8d57548ca6cb59d1de2cd78e3bf4ddb68ffa2891 [file] [log] [blame]
// Copyright 2014 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/timing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_computed_effect_timing.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_effect_timing.h"
#include "third_party/blink/renderer/core/animation/timing_calculations.h"
#include "third_party/blink/renderer/core/css/cssom/css_numeric_value.h"
namespace blink {
String Timing::FillModeString(FillMode fill_mode) {
switch (fill_mode) {
case FillMode::NONE:
return "none";
case FillMode::FORWARDS:
return "forwards";
case FillMode::BACKWARDS:
return "backwards";
case FillMode::BOTH:
return "both";
case FillMode::AUTO:
return "auto";
}
NOTREACHED();
return "none";
}
Timing::FillMode Timing::StringToFillMode(const String& fill_mode) {
if (fill_mode == "none")
return Timing::FillMode::NONE;
if (fill_mode == "backwards")
return Timing::FillMode::BACKWARDS;
if (fill_mode == "both")
return Timing::FillMode::BOTH;
if (fill_mode == "forwards")
return Timing::FillMode::FORWARDS;
DCHECK_EQ(fill_mode, "auto");
return Timing::FillMode::AUTO;
}
String Timing::PlaybackDirectionString(PlaybackDirection playback_direction) {
switch (playback_direction) {
case PlaybackDirection::NORMAL:
return "normal";
case PlaybackDirection::REVERSE:
return "reverse";
case PlaybackDirection::ALTERNATE_NORMAL:
return "alternate";
case PlaybackDirection::ALTERNATE_REVERSE:
return "alternate-reverse";
}
NOTREACHED();
return "normal";
}
Timing::FillMode Timing::ResolvedFillMode(bool is_keyframe_effect) const {
if (fill_mode != Timing::FillMode::AUTO)
return fill_mode;
// https://drafts.csswg.org/web-animations/#the-effecttiming-dictionaries
if (is_keyframe_effect)
return Timing::FillMode::NONE;
return Timing::FillMode::BOTH;
}
AnimationTimeDelta Timing::IterationDuration() const {
AnimationTimeDelta result = iteration_duration.value_or(AnimationTimeDelta());
DCHECK_GE(result, AnimationTimeDelta());
return result;
}
double Timing::ActiveDuration() const {
const double result =
MultiplyZeroAlwaysGivesZero(IterationDuration(), iteration_count);
DCHECK_GE(result, 0);
return result;
}
double Timing::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(start_delay + ActiveDuration() + end_delay, 0.0);
}
EffectTiming* Timing::ConvertToEffectTiming() const {
EffectTiming* effect_timing = EffectTiming::Create();
effect_timing->setDelay(start_delay * 1000);
effect_timing->setEndDelay(end_delay * 1000);
effect_timing->setFill(FillModeString(fill_mode));
effect_timing->setIterationStart(iteration_start);
effect_timing->setIterations(iteration_count);
UnrestrictedDoubleOrString duration;
if (iteration_duration) {
duration.SetUnrestrictedDouble(iteration_duration->InMillisecondsF());
} else {
duration.SetString("auto");
}
effect_timing->setDuration(duration);
effect_timing->setDirection(PlaybackDirectionString(direction));
effect_timing->setEasing(timing_function->ToString());
return effect_timing;
}
ComputedEffectTiming* Timing::getComputedTiming(
const CalculatedTiming& calculated_timing,
bool is_keyframe_effect) const {
ComputedEffectTiming* computed_timing = ComputedEffectTiming::Create();
// ComputedEffectTiming members.
computed_timing->setEndTime(
CSSNumberish::FromDouble(EndTimeInternal() * 1000));
computed_timing->setActiveDuration(
CSSNumberish::FromDouble(ActiveDuration() * 1000));
if (calculated_timing.local_time) {
computed_timing->setLocalTime(
CSSNumberish::FromDouble(calculated_timing.local_time.value() * 1000));
} else {
computed_timing->setLocalTime(CSSNumberish());
}
if (calculated_timing.is_in_effect) {
DCHECK(calculated_timing.current_iteration);
DCHECK(calculated_timing.progress);
computed_timing->setProgress(calculated_timing.progress.value());
computed_timing->setCurrentIteration(
calculated_timing.current_iteration.value());
} 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(start_delay * 1000);
computed_timing->setEndDelay(end_delay * 1000);
computed_timing->setFill(
Timing::FillModeString(ResolvedFillMode(is_keyframe_effect)));
computed_timing->setIterationStart(iteration_start);
computed_timing->setIterations(iteration_count);
UnrestrictedDoubleOrString duration;
duration.SetUnrestrictedDouble(IterationDuration().InMillisecondsF());
computed_timing->setDuration(duration);
computed_timing->setDirection(Timing::PlaybackDirectionString(direction));
computed_timing->setEasing(timing_function->ToString());
return computed_timing;
}
Timing::CalculatedTiming Timing::CalculateTimings(
base::Optional<double> local_time,
base::Optional<Phase> timeline_phase,
AnimationDirection animation_direction,
bool is_keyframe_effect,
base::Optional<double> playback_rate) const {
const double active_duration = ActiveDuration();
Timing::Phase current_phase = CalculatePhase(
active_duration, local_time, timeline_phase, animation_direction, *this);
const base::Optional<AnimationTimeDelta> active_time =
CalculateActiveTime(active_duration, ResolvedFillMode(is_keyframe_effect),
local_time, current_phase, *this);
base::Optional<double> progress;
const double local_iteration_duration = IterationDuration().InSecondsF();
const base::Optional<double> overall_progress = CalculateOverallProgress(
current_phase, active_time, local_iteration_duration, iteration_count,
iteration_start);
const base::Optional<double> simple_iteration_progress =
CalculateSimpleIterationProgress(current_phase, overall_progress,
iteration_start, active_time,
active_duration, iteration_count);
const base::Optional<double> current_iteration =
CalculateCurrentIteration(current_phase, active_time, iteration_count,
overall_progress, simple_iteration_progress);
const bool current_direction_is_forwards =
IsCurrentDirectionForwards(current_iteration, direction);
const base::Optional<double> directed_progress = CalculateDirectedProgress(
simple_iteration_progress, current_iteration, direction);
progress = CalculateTransformedProgress(current_phase, directed_progress,
current_direction_is_forwards,
timing_function);
AnimationTimeDelta time_to_next_iteration = AnimationTimeDelta::Max();
// Conditionally compute the time to next iteration, which is only
// applicable if the iteration duration is non-zero.
if (local_iteration_duration) {
const double start_offset =
MultiplyZeroAlwaysGivesZero(iteration_start, local_iteration_duration);
DCHECK_GE(start_offset, 0);
const base::Optional<AnimationTimeDelta> offset_active_time =
CalculateOffsetActiveTime(active_duration, active_time, start_offset);
const base::Optional<AnimationTimeDelta> iteration_time =
CalculateIterationTime(local_iteration_duration, active_duration,
offset_active_time, start_offset, current_phase,
*this);
if (iteration_time) {
// active_time cannot be null if iteration_time is not null.
DCHECK(active_time);
time_to_next_iteration =
AnimationTimeDelta::FromSecondsD(local_iteration_duration) -
iteration_time.value();
if (AnimationTimeDelta::FromSecondsD(active_duration) -
active_time.value() <
time_to_next_iteration)
time_to_next_iteration = AnimationTimeDelta::Max();
}
}
CalculatedTiming calculated = CalculatedTiming();
calculated.phase = current_phase;
calculated.current_iteration = current_iteration;
calculated.progress = progress;
calculated.is_in_effect = active_time.has_value();
// If active_time is not null then current_iteration and (transformed)
// progress are also non-null).
DCHECK(!calculated.is_in_effect ||
(current_iteration.has_value() && progress.has_value()));
calculated.is_in_play = calculated.phase == Timing::kPhaseActive;
// https://drafts.csswg.org/web-animations-1/#current
calculated.is_current = calculated.is_in_play ||
(playback_rate.has_value() && playback_rate > 0 &&
calculated.phase == Timing::kPhaseBefore) ||
(playback_rate.has_value() && playback_rate < 0 &&
calculated.phase == Timing::kPhaseAfter);
calculated.local_time = local_time;
calculated.time_to_next_iteration = time_to_next_iteration;
return calculated;
}
} // namespace blink