Skip to content

Commit

Permalink
Added support for scroll timelines that do not provide a time_range
Browse files Browse the repository at this point in the history
Integrated progress based scroll timelines with animations and
effects. Effect timing model updated to handle both time based
timelines and progress based timelines

Spec change this CL is based on:
w3c/csswg-drafts#4890

Bug: 1140602

Change-Id: If4bc889f8fdcc1ebe393f41b8788f6bd1ddec051
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2925975
Reviewed-by: Kevin Ellis <[email protected]>
Reviewed-by: Rune Lillesveen <[email protected]>
Commit-Queue: Jordan Taylor <[email protected]>
Cr-Commit-Position: refs/heads/master@{#895253}
  • Loading branch information
Jordan Taylor authored and Chromium LUCI CQ committed Jun 23, 2021
1 parent a099147 commit cae357d
Show file tree
Hide file tree
Showing 19 changed files with 1,452 additions and 70 deletions.
115 changes: 93 additions & 22 deletions third_party/blink/renderer/core/animation/animation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -199,18 +199,40 @@ Animation* Animation::Create(AnimationEffect* effect,
}
DCHECK(IsA<DocumentTimeline>(timeline) || timeline->IsScrollTimeline());

// TODO(crbug.com/1097041): Support 'auto' value.
if (timeline->IsScrollTimeline()) {
auto* time_range = To<ScrollTimeline>(timeline)->timeRange();
// TODO(crbug.com/1140602): Support progress based animations
// We are currently abusing the intended use of the "auto" keyword. We are
// using it here as a signal to use progress based timeline instead of
// having a range based current time. We are doing this maintain backwards
// compatibility with existing tests.
if (time_range->IsScrollTimelineAutoKeyword()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"progress based animations are not supported");
if (timeline->IsProgressBasedTimeline()) {
if (effect->timing_.iteration_duration) {
if (effect->timing_.iteration_duration->is_inf()) {
exception_state.ThrowTypeError(
"Effect duration cannot be Infinity when used with Scroll "
"Timelines");
return nullptr;
}
} else {
// TODO(crbug.com/1216527)
// Eventually we hope to be able to be more flexible with
// iteration_duration "auto" and its interaction with start_delay and
// end_delay. For now we will throw an exception if either delay is set.
// Once the spec (https://github.com/w3c/csswg-drafts/pull/6337) has been
// ratified, we will be able to better handle mixed scenarios like "auto"
// and time based delays.

// If either delay or end_delay are non-zero, we can't yet handle "auto"
if (!effect->timing_.start_delay.is_zero() ||
!effect->timing_.end_delay.is_zero()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Effect duration \"auto\" with delays is not yet implemented when "
"used with Scroll Timelines");
return nullptr;
}
}

if (effect->timing_.iteration_count ==
std::numeric_limits<double>::infinity()) {
// iteration count of infinity makes no sense for scroll timelines
exception_state.ThrowTypeError(
"Effect iterations cannot be Infinity when used with Scroll "
"Timelines");
return nullptr;
}
}
Expand Down Expand Up @@ -273,14 +295,19 @@ Animation::Animation(ExecutionContext* execution_context,
}
content_->Attach(this);
}
document_ = timeline_ ? timeline_->GetDocument()
: To<LocalDOMWindow>(execution_context)->document();
DCHECK(document_);

if (timeline_)
if (timeline_) {
document_ = timeline_->GetDocument();
DCHECK(document_);
timeline_->AnimationAttached(this);
else

if (content_)
content_->SetTimingTimelineDuration(timeline_->GetDuration());
} else {
document_ = To<LocalDOMWindow>(execution_context)->document();
DCHECK(document_);
document_->Timeline().AnimationAttached(this);
}

probe::DidCreateAnimation(document_, sequence_number_);
}
Expand All @@ -301,6 +328,11 @@ void Animation::Dispose() {
}

AnimationTimeDelta Animation::EffectEnd() const {
if (timeline_ && timeline_->IsProgressBasedTimeline()) {
// For progress based timelines, timeline times are mapped to be relative to
// Effect times, which means effect end maps to timeline duration.
return timeline_->GetDuration().value();
}
return content_ ? content_->SpecifiedTiming().EndTimeInternal()
: AnimationTimeDelta();
}
Expand Down Expand Up @@ -340,7 +372,22 @@ void Animation::setCurrentTime(const V8CSSNumberish* current_time,
// Throw exception for CSSNumberish that is a CSSNumericValue
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Invalid startTime. CSSNumericValue not yet supported.");
"Invalid currentTime. CSSNumericValue not yet supported.");
return;
}

// TODO (crbug.com/1218963):
// Once current_time can be set as a CSSNumberish, we need to support
// setting it for progress based timelines. This will involve conversions
// between the type of input and the type of timeline being used.
// (i.e. input is a time relative to effect, but using progress based timeline
// would result in converting the input time to be timeline relative since
// that would be the expected type of data being used internally)
if (timeline_ && timeline_->IsProgressBasedTimeline()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Setting currentTime is not yet supported for progress based "
"animations");
return;
}

Expand Down Expand Up @@ -421,13 +468,20 @@ V8CSSNumberish* Animation::startTime() const {
return nullptr;
}

V8CSSNumberish* Animation::ConvertTimeToCSSNumberish(
AnimationTimeDelta time) const {
if (timeline_ && timeline_->IsProgressBasedTimeline()) {
return To<ScrollTimeline>(*timeline_).ConvertTimeToProgress(time);
}
return MakeGarbageCollected<V8CSSNumberish>(time.InMillisecondsF());
}

// https://drafts.csswg.org/web-animations/#the-current-time-of-an-animation
V8CSSNumberish* Animation::currentTime() const {
// 1. If the animation’s hold time is resolved,
// The current time is the animation’s hold time.
if (hold_time_.has_value()) {
return MakeGarbageCollected<V8CSSNumberish>(
hold_time_.value().InMillisecondsF());
return ConvertTimeToCSSNumberish(hold_time_.value());
}

// 2. If any of the following are true:
Expand All @@ -449,8 +503,7 @@ V8CSSNumberish* Animation::currentTime() const {
AnimationTimeDelta calculated_current_time =
(timeline_time.value() - start_time_.value()) * playback_rate_;

return MakeGarbageCollected<V8CSSNumberish>(
calculated_current_time.InMillisecondsF());
return ConvertTimeToCSSNumberish(calculated_current_time);
}

bool Animation::ValidateHoldTimeAndPhase() const {
Expand Down Expand Up @@ -828,6 +881,10 @@ void Animation::setTimeline(AnimationTimeline* timeline) {
document_->Timeline().AnimationAttached(this);
SetOutdated();

// Update content timing to be based on new timeline type
if (content_ && timeline_)
content_->SetTimingTimelineDuration(timeline_->GetDuration());

reset_current_time_on_resume_ = false;

if (timeline) {
Expand Down Expand Up @@ -931,6 +988,20 @@ void Animation::setStartTime(const V8CSSNumberish* start_time,
return;
}

// TODO (crbug.com/1218963):
// Once startTime can be set as a CSSNumberish, we need to support
// setting it for progress based timelines. This will involve conversions
// between the type of input and the type of timeline being used.
// (i.e. input is a time relative to effect, but using progress based timeline
// would result in converting the input time to be timeline relative since
// that would be the expected type of data being used internally)
if (timeline_ && timeline_->IsProgressBasedTimeline()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Setting startTime is not yet supported for progress based animations");
return;
}

absl::optional<AnimationTimeDelta> new_start_time;
if (start_time) {
new_start_time =
Expand Down
3 changes: 3 additions & 0 deletions third_party/blink/renderer/core/animation/animation.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,

void cancel();

V8CSSNumberish* ConvertTimeToCSSNumberish(AnimationTimeDelta) const;

V8CSSNumberish* currentTime() const;
absl::optional<AnimationTimeDelta> CurrentTimeInternal() const;
void setCurrentTime(const V8CSSNumberish* current_time,
Expand Down Expand Up @@ -205,6 +207,7 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
double playbackRate() const;
void setPlaybackRate(double, ExceptionState& = ASSERT_NO_EXCEPTION);
AnimationTimeline* timeline() { return timeline_; }
AnimationTimeline* timeline() const { return timeline_; }
virtual void setTimeline(AnimationTimeline* timeline);
Document* GetDocument() const;

Expand Down
81 changes: 81 additions & 0 deletions third_party/blink/renderer/core/animation/animation_effect.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,28 @@ void AnimationEffect::UpdateSpecifiedTiming(const Timing& timing) {
if (!timing_.HasTimingOverride(Timing::kOverrideTimingFunction))
timing_.timing_function = timing.timing_function;
}

// Changing timings can impact the intrinsic iteration duration.
if (GetAnimation() && GetAnimation()->timeline()) {
timing_.intrinsic_iteration_duration =
GetAnimation()->timeline()->CalculateIntrinsicIterationDuration(
timing_);
}
InvalidateAndNotifyOwner();
}

void AnimationEffect::SetTimingTimelineDuration(
absl::optional<AnimationTimeDelta> timeline_duration) {
timing_.timeline_duration = timeline_duration;
if (timeline_duration) {
timing_.intrinsic_iteration_duration =
GetAnimation()->timeline()->CalculateIntrinsicIterationDuration(
timing_);
} else {
timing_.intrinsic_iteration_duration = AnimationTimeDelta();
}
}

void AnimationEffect::SetIgnoreCssTimingProperties() {
timing_.SetTimingOverride(Timing::kOverrideAll);
}
Expand All @@ -101,10 +120,60 @@ ComputedEffectTiming* AnimationEffect::getComputedTiming() const {

void AnimationEffect::updateTiming(OptionalEffectTiming* optional_timing,
ExceptionState& exception_state) {
if (GetAnimation() && GetAnimation()->timeline() &&
GetAnimation()->timeline()->IsProgressBasedTimeline()) {
if (optional_timing->hasDuration()) {
if (optional_timing->duration()->IsUnrestrictedDouble()) {
double duration =
optional_timing->duration()->GetAsUnrestrictedDouble();
if (duration == std::numeric_limits<double>::infinity()) {
exception_state.ThrowTypeError(
"Effect duration cannot be Infinity when used with Scroll "
"Timelines");
return;
}
} else if (optional_timing->duration()->GetAsString() == "auto") {
// TODO(crbug.com/1216527)
// Eventually we hope to be able to be more flexible with
// iteration_duration "auto" and its interaction with start_delay and
// end_delay. For now we will throw an exception if either delay is set.
// Once delays are changed to CSSNumberish, we will need to adjust logic
// here to allow for percentage values but not time values.

// If either delay or end_delay are non-zero, we can't handle "auto"
if (!SpecifiedTiming().start_delay.is_zero() ||
!SpecifiedTiming().end_delay.is_zero()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Effect duration \"auto\" with delays is not yet implemented "
"when used with Scroll Timelines");
return;
}
}
}

if (optional_timing->hasIterations() &&
optional_timing->iterations() ==
std::numeric_limits<double>::infinity()) {
// iteration count of infinity makes no sense for scroll timelines
exception_state.ThrowTypeError(
"Effect iterations cannot be Infinity when used with Scroll "
"Timelines");
return;
}
}

// 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;

// Changing timings can impact the intrinsic iteration duration.
if (GetAnimation() && GetAnimation()->timeline()) {
timing_.intrinsic_iteration_duration =
GetAnimation()->timeline()->CalculateIntrinsicIterationDuration(
timing_);
}
InvalidateAndNotifyOwner();
}

Expand Down Expand Up @@ -145,6 +214,18 @@ void AnimationEffect::UpdateInheritedTime(
absl::optional<Timing::Phase> timeline_phase =
TimelinePhaseToTimingPhase(inherited_timeline_phase);

// TODO (crbug.com/1222387): Once normalized timing values have been added,
// we will no longer need to convert inherited_time to be effect time relative
// since all effect times will be timeline relative based on the normalized
// timing values.
if (inherited_time && GetAnimation() && GetAnimation()->timeline() &&
GetAnimation()->timeline()->IsProgressBasedTimeline()) {
// map inherited time [0,timeline_duration] to effect end time [0,end_time]
inherited_time = (inherited_time.value() /
GetAnimation()->timeline()->GetDuration().value()) *
SpecifiedTiming().EndTimeInternal();
}

bool needs_update = needs_update_ || last_update_time_ != inherited_time ||
(owner_ && owner_->EffectSuppressed()) ||
last_update_phase_ != timeline_phase;
Expand Down
2 changes: 2 additions & 0 deletions third_party/blink/renderer/core/animation/animation_effect.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class CORE_EXPORT AnimationEffect : public ScriptWrappable {
virtual bool IsKeyframeEffect() const { return false; }
virtual bool IsInertEffect() const { return false; }

void SetTimingTimelineDuration(absl::optional<AnimationTimeDelta>);

Timing::Phase GetPhase() const { return EnsureCalculated().phase; }
bool IsCurrent() const { return EnsureCalculated().is_current; }
bool IsInEffect() const { return EnsureCalculated().is_in_effect; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
virtual bool IsDocumentTimeline() const { return false; }
virtual bool IsScrollTimeline() const { return false; }
virtual bool IsCSSScrollTimeline() const { return false; }
virtual bool IsProgressBasedTimeline() const { return false; }
virtual bool IsActive() const = 0;
virtual AnimationTimeDelta ZeroTime() = 0;
// https://drafts.csswg.org/web-animations/#monotonically-increasing-timeline
Expand All @@ -63,6 +64,10 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {
// Changing scroll-linked animation start_time initialization is under
// consideration here: https://github.com/w3c/csswg-drafts/issues/2075.
virtual absl::optional<base::TimeDelta> InitialStartTimeForAnimations() = 0;
virtual AnimationTimeDelta CalculateIntrinsicIterationDuration(
const Timing&) {
return AnimationTimeDelta();
}
Document* GetDocument() { return document_; }
virtual void AnimationAttached(Animation*);
virtual void AnimationDetached(Animation*);
Expand Down Expand Up @@ -111,6 +116,10 @@ class CORE_EXPORT AnimationTimeline : public ScriptWrappable {

void Trace(Visitor*) const override;

virtual absl::optional<AnimationTimeDelta> GetDuration() const {
return absl::nullopt;
}

protected:
virtual PhaseAndTime CurrentPhaseAndTime() = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,8 @@ CSSScrollTimeline::CSSScrollTimeline(Document* document, Options&& options)
options.source_,
options.direction_,
std::move(options.offsets_),
*options.time_range_),
options.time_range_),
rule_(options.rule_) {
DCHECK(options.IsValid());
DCHECK(rule_);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class CORE_EXPORT CSSScrollTimeline : public ScrollTimeline {
public:
Options(Document&, StyleRuleScrollTimeline&);

// TODO(crbug.com/1097041): Support 'auto' value.
bool IsValid() const { return time_range_.has_value(); }

private:
friend class CSSScrollTimeline;

Expand Down
Loading

0 comments on commit cae357d

Please sign in to comment.