Commit 6829205c authored by Olga Gerchikov's avatar Olga Gerchikov Committed by Commit Bot

Initialize start time of scroll animations to zero.

Implemented web-animations-1 spec changes introduces in [1].

- Update play and pause procedures to initialize start time of scroll
  animations to zero.
- Updated calculate play state procedure to return "running" state for
  animations that has start time resolved.
- Added/modified tests reflecting spec changes.


[1] https://github.com/w3c/csswg-drafts/pull/4842

Bug: 1070637
Change-Id: Ic83995899b2f3f8d8f985f84b8a2b438bbad7c35
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2150687
Commit-Queue: Olga Gerchikov <gerchiko@microsoft.com>
Reviewed-by: default avatarMajid Valipour <majidvp@chromium.org>
Reviewed-by: default avatarKevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/master@{#761974}
parent eb7d2a53
...@@ -276,8 +276,6 @@ base::Optional<double> Animation::TimelineTime() const { ...@@ -276,8 +276,6 @@ base::Optional<double> Animation::TimelineTime() const {
// https://drafts.csswg.org/web-animations/#setting-the-current-time-of-an-animation. // https://drafts.csswg.org/web-animations/#setting-the-current-time-of-an-animation.
void Animation::setCurrentTime(base::Optional<double> new_current_time, void Animation::setCurrentTime(base::Optional<double> new_current_time,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// TODO(crbug.com/924159): Update this after we add support for inactive
// timelines and unresolved timeline.currentTime
if (!new_current_time) { if (!new_current_time) {
// If the current time is resolved, then throw a TypeError. // If the current time is resolved, then throw a TypeError.
if (CurrentTimeInternal()) { if (CurrentTimeInternal()) {
...@@ -446,7 +444,7 @@ bool Animation::PreCommit( ...@@ -446,7 +444,7 @@ bool Animation::PreCommit(
return true; return true;
} }
void Animation::PostCommit(double timeline_time) { void Animation::PostCommit() {
compositor_pending_ = false; compositor_pending_ = false;
if (!compositor_state_ || compositor_state_->pending_action == kNone) if (!compositor_state_ || compositor_state_->pending_action == kNone)
...@@ -896,10 +894,11 @@ const char* Animation::PlayStateString(AnimationPlayState play_state) { ...@@ -896,10 +894,11 @@ const char* Animation::PlayStateString(AnimationPlayState play_state) {
Animation::AnimationPlayState Animation::CalculateAnimationPlayState() const { Animation::AnimationPlayState Animation::CalculateAnimationPlayState() const {
// 1. All of the following conditions are true: // 1. All of the following conditions are true:
// * The current time of animation is unresolved, and // * The current time of animation is unresolved, and
// * the start time of animation is unresolved, and
// * animation does not have either a pending play task or a pending pause // * animation does not have either a pending play task or a pending pause
// task, // task,
// then idle. // then idle.
if (!CurrentTimeInternal() && !PendingInternal()) if (!CurrentTimeInternal() && !start_time_ && !PendingInternal())
return kIdle; return kIdle;
// 2. Either of the following conditions are true: // 2. Either of the following conditions are true:
...@@ -974,18 +973,36 @@ void Animation::pause(ExceptionState& exception_state) { ...@@ -974,18 +973,36 @@ void Animation::pause(ExceptionState& exception_state) {
if (pending_pause_ || CalculateAnimationPlayState() == kPaused) if (pending_pause_ || CalculateAnimationPlayState() == kPaused)
return; return;
// 3. If the animation’s current time is unresolved, perform the steps // 3. Let has finite timeline be true if animation has an associated timeline
// that is not monotonically increasing.
bool has_finite_timeline =
timeline_ && !timeline_->IsMonotonicallyIncreasing();
// 4. If the animation’s current time is unresolved, perform the steps
// according to the first matching condition from below: // according to the first matching condition from below:
// 3a. If animation’s playback rate is ≥ 0, // 4a. If animation’s playback rate is ≥ 0,
// Let animation’s hold time be zero. // Update either animation’s start time or hold time as follows:
// 3b. Otherwise, // If has finite timeline is true,
// If associated effect end for animation is positive infinity, throw an // Set animation’s start time to zero.
// "InvalidStateError" DOMException and abort these steps. Otherwise, // Otherwise,
// let animation’s hold time be associated effect end. // Set animation’s hold time to zero.
// 4b. Otherwise,
// If associated effect end for animation is positive infinity,
// throw an "InvalidStateError" DOMException and abort these
// steps.
// Otherwise,
// Update either animation’s start time or hold time as follows:
// If has finite timeline is true,
// let animation’s start time be associated effect end.
// Otherwise,
// let animation’s hold time be associated effect end.
base::Optional<double> current_time = CurrentTimeInternal(); base::Optional<double> current_time = CurrentTimeInternal();
if (!current_time) { if (!current_time) {
if (playback_rate_ >= 0) { if (playback_rate_ >= 0) {
hold_time_ = 0; if (has_finite_timeline)
start_time_ = 0;
else
hold_time_ = 0;
} else { } else {
if (EffectEnd() == std::numeric_limits<double>::infinity()) { if (EffectEnd() == std::numeric_limits<double>::infinity()) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
...@@ -993,30 +1010,35 @@ void Animation::pause(ExceptionState& exception_state) { ...@@ -993,30 +1010,35 @@ void Animation::pause(ExceptionState& exception_state) {
"Cannot play reversed Animation with infinite target effect end."); "Cannot play reversed Animation with infinite target effect end.");
return; return;
} }
hold_time_ = EffectEnd(); if (has_finite_timeline)
start_time_ = EffectEnd();
else
hold_time_ = EffectEnd();
} }
} }
// 4. Let has pending ready promise be a boolean flag that is initially false. // 5. Let has pending ready promise be a boolean flag that is initially false.
// 5. If animation has a pending play task, cancel that task and let has // 6. If animation has a pending play task, cancel that task and let has
// pending ready promise be true. // pending ready promise be true.
// 6. If has pending ready promise is false, set animation’s current ready // 7. If has pending ready promise is false, set animation’s current ready
// promise to a new promise in the relevant Realm of animation. // promise to a new promise in the relevant Realm of animation.
if (pending_play_) if (pending_play_)
pending_play_ = false; pending_play_ = false;
else if (ready_promise_) else if (ready_promise_)
ready_promise_->Reset(); ready_promise_->Reset();
// 7. Schedule a task to be executed at the first possible moment after the // 8. Schedule a task to be executed at the first possible moment where both
// user agent has performed any processing necessary to suspend the // of the following conditions are true:
// playback of animation’s associated effect, if any. // 8a. the user agent has performed any processing necessary to suspend
// the playback of animation’s associated effect, if any.
// 8b. the animation is associated with a timeline that is not inactive.
pending_pause_ = true; pending_pause_ = true;
pending_play_ = false; pending_play_ = false;
SetOutdated(); SetOutdated();
SetCompositorPending(false); SetCompositorPending(false);
// 8. Run the procedure to update an animation’s finished state for animation // 9. Run the procedure to update an animation’s finished state for animation
// with the did seek flag set to false (continuous) , and thesynchronously // with the did seek flag set to false (continuous) , and thesynchronously
// notify flag set to false. // notify flag set to false.
UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync); UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
...@@ -1049,51 +1071,63 @@ void Animation::PlayInternal(AutoRewind auto_rewind, ...@@ -1049,51 +1071,63 @@ void Animation::PlayInternal(AutoRewind auto_rewind,
// 1. Let aborted pause be a boolean flag that is true if animation has a // 1. Let aborted pause be a boolean flag that is true if animation has a
// pending pause task, and false otherwise. // pending pause task, and false otherwise.
// 2. Let has pending ready promise be a boolean flag that is initially false. // 2. Let has pending ready promise be a boolean flag that is initially false.
// 3. Let performed seek be a boolean flag that is initially false.
// 4. Let has finite timeline be true if animation has an associated timeline
// that is not monotonically increasing.
bool aborted_pause = pending_pause_; bool aborted_pause = pending_pause_;
bool has_pending_ready_promise = false; bool has_pending_ready_promise = false;
bool performed_seek = false;
bool has_finite_timeline =
timeline_ && !timeline_->IsMonotonicallyIncreasing();
// 3. Perform the steps corresponding to the first matching condition from the // 5. Perform the steps corresponding to the first matching condition from the
// following, if any: // following, if any:
// //
// 3a If animation’s effective playback rate > 0, the auto-rewind flag is true // 5a If animation’s effective playback rate > 0, the auto-rewind flag is true
// and either animation’s: // and either animation’s:
// current time is unresolved, or // current time is unresolved, or
// current time < zero, or // current time < zero, or
// current time ≥ target effect end, // current time ≥ target effect end,
// Set animation’s hold time to zero. // 5a1. Set performed seek to true.
// 5a2. Update either animation’s start time or hold time as follows:
// If has finite timeline is true,
// Set animation’s start time to zero.
// Otherwise,
// Set animation’s hold time to zero.
// //
// 3b If animation’s effective playback rate < 0, the auto-rewind flag is true // 5b If animation’s effective playback rate < 0, the auto-rewind flag is true
// and either animation’s: // and either animation’s:
// current time is unresolved, or // current time is unresolved, or
// current time ≤ zero, or // current time ≤ zero, or
// current time > target effect end, // current time > target effect end,
// If target effect end is positive infinity, throw an "InvalidStateError" // 5b1.If associated effect end is positive infinity,
// DOMException and abort these steps. Otherwise, set animation’s hold time // throw an "InvalidStateError" DOMException and abort these steps.
// to target effect end. // 5b2.Otherwise,
// 5b2a Set performed seek to true.
// 5b2b Update either animation’s start time or hold time as follows:
// If has finite timeline is true,
// Set animation’s start time to associated effect end.
// Otherwise,
// Set animation’s hold time to associated effect end.
// //
// 3c If animation’s effective playback rate = 0 and animation’s current time // 5c If animation’s effective playback rate = 0 and animation’s current time
// is unresolved, // is unresolved,
// Set animation’s hold time to zero. // 5c1. Set performed seek to true.
// 5c2. Update either animation’s start time or hold time as follows:
// If has finite timeline is true,
// Set animation’s start time to zero.
// Otherwise,
// Set animation’s hold time to zero.
double effective_playback_rate = EffectivePlaybackRate(); double effective_playback_rate = EffectivePlaybackRate();
base::Optional<double> current_time = CurrentTimeInternal(); base::Optional<double> current_time = CurrentTimeInternal();
// TODO(crbug.com/1012073): This should be able to be extracted into a
// function in AnimationTimeline that each child class can override for their
// own special behavior.
double initial_hold_time = 0;
if (timeline_ && timeline_->IsScrollTimeline() && timeline_->IsActive()) {
base::Optional<double> timeline_time = timeline_->CurrentTimeSeconds();
if (timeline_time) {
// TODO(crbug.com/924159): Once inactive timelines are supported we need
// to re-evaluate if it is desired behavior to adjust the hold time when
// playback rate is set before play().
initial_hold_time = timeline_time.value() * effective_playback_rate;
}
}
if (effective_playback_rate > 0 && auto_rewind == AutoRewind::kEnabled && if (effective_playback_rate > 0 && auto_rewind == AutoRewind::kEnabled &&
(!current_time || current_time < 0 || current_time >= EffectEnd())) { (!current_time || current_time < 0 || current_time >= EffectEnd())) {
hold_time_ = initial_hold_time; performed_seek = true;
if (has_finite_timeline)
start_time_ = 0;
else
hold_time_ = 0;
} else if (effective_playback_rate < 0 && } else if (effective_playback_rate < 0 &&
auto_rewind == AutoRewind::kEnabled && auto_rewind == AutoRewind::kEnabled &&
(!current_time || current_time <= 0 || (!current_time || current_time <= 0 ||
...@@ -1104,44 +1138,54 @@ void Animation::PlayInternal(AutoRewind auto_rewind, ...@@ -1104,44 +1138,54 @@ void Animation::PlayInternal(AutoRewind auto_rewind,
"Cannot play reversed Animation with infinite target effect end."); "Cannot play reversed Animation with infinite target effect end.");
return; return;
} }
hold_time_ = initial_hold_time + EffectEnd(); performed_seek = true;
if (has_finite_timeline)
start_time_ = EffectEnd();
else
hold_time_ = EffectEnd();
} else if (effective_playback_rate == 0 && !current_time) { } else if (effective_playback_rate == 0 && !current_time) {
hold_time_ = initial_hold_time; performed_seek = true;
if (has_finite_timeline)
start_time_ = 0;
else
hold_time_ = 0;
} }
// 4. If animation has a pending play task or a pending pause task, // 6. If animation has a pending play task or a pending pause task,
// 4.1 Cancel that task. // 6.1 Cancel that task.
// 4.2 Set has pending ready promise to true. // 6.2 Set has pending ready promise to true.
if (pending_play_ || pending_pause_) { if (pending_play_ || pending_pause_) {
pending_play_ = pending_pause_ = false; pending_play_ = pending_pause_ = false;
has_pending_ready_promise = true; has_pending_ready_promise = true;
} }
// 5. If the following three conditions are all satisfied: // 7. If the following three conditions are all satisfied:
// animation’s hold time is unresolved, and // animation’s hold time is unresolved, and
// performed seek is false, and
// aborted pause is false, and // aborted pause is false, and
// animation does not have a pending playback rate, // animation does not have a pending playback rate,
// abort this procedure. // abort this procedure.
if (!hold_time_ && !aborted_pause && !pending_playback_rate_) if (!hold_time_ && !performed_seek && !aborted_pause &&
!pending_playback_rate_)
return; return;
// 6. If animation’s hold time is resolved, let its start time be unresolved. // 8. If animation’s hold time is resolved, let its start time be unresolved.
if (hold_time_) if (hold_time_)
start_time_ = base::nullopt; start_time_ = base::nullopt;
// 7. If has pending ready promise is false, let animation’s current ready // 9. If has pending ready promise is false, let animation’s current ready
// promise be a new promise in the relevant Realm of animation. // promise be a new promise in the relevant Realm of animation.
if (ready_promise_ && !has_pending_ready_promise) if (ready_promise_ && !has_pending_ready_promise)
ready_promise_->Reset(); ready_promise_->Reset();
// 8. Schedule a task to run as soon as animation is ready. // 10. Schedule a task to run as soon as animation is ready.
pending_play_ = true; pending_play_ = true;
finished_ = false; finished_ = false;
committed_finish_notification_ = false; committed_finish_notification_ = false;
SetOutdated(); SetOutdated();
SetCompositorPending(/*effect_changed=*/false); SetCompositorPending(/*effect_changed=*/false);
// 9. Run the procedure to update an animation’s finished state for animation // 11. Run the procedure to update an animation’s finished state for animation
// with the did seek flag set to false, and the synchronously notify flag // with the did seek flag set to false, and the synchronously notify flag
// set to false. // set to false.
// Boolean valued arguments replaced with enumerated values for clarity. // Boolean valued arguments replaced with enumerated values for clarity.
...@@ -1198,6 +1242,13 @@ void Animation::reverse(ExceptionState& exception_state) { ...@@ -1198,6 +1242,13 @@ void Animation::reverse(ExceptionState& exception_state) {
// https://drafts.csswg.org/web-animations/#finishing-an-animation-section // https://drafts.csswg.org/web-animations/#finishing-an-animation-section
void Animation::finish(ExceptionState& exception_state) { void Animation::finish(ExceptionState& exception_state) {
// TODO(crbug.com/916117): Implement finish for scroll-linked animations.
if (timeline_ && timeline_->IsScrollTimeline()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Scroll-linked WebAnimation currently does not support finish.");
return;
}
if (!EffectivePlaybackRate()) { if (!EffectivePlaybackRate()) {
exception_state.ThrowDOMException( exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError, DOMExceptionCode::kInvalidStateError,
...@@ -1527,9 +1578,6 @@ void Animation::ApplyPendingPlaybackRate() { ...@@ -1527,9 +1578,6 @@ void Animation::ApplyPendingPlaybackRate() {
void Animation::setPlaybackRate(double playback_rate, void Animation::setPlaybackRate(double playback_rate,
ExceptionState& exception_state) { ExceptionState& exception_state) {
// TODO(crbug.com/924159): Update this after we add support for inactive
// timelines and unresolved timeline.currentTime
base::Optional<double> start_time_before = start_time_; base::Optional<double> start_time_before = start_time_;
// 1. Clear any pending playback rate on animation. // 1. Clear any pending playback rate on animation.
......
...@@ -250,7 +250,7 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData, ...@@ -250,7 +250,7 @@ class CORE_EXPORT Animation : public EventTargetWithInlineData,
bool PreCommit(int compositor_group, bool PreCommit(int compositor_group,
const PaintArtifactCompositor*, const PaintArtifactCompositor*,
bool start_on_compositor); bool start_on_compositor);
void PostCommit(double timeline_time); void PostCommit();
unsigned SequenceNumber() const override { return sequence_number_; } unsigned SequenceNumber() const override { return sequence_number_; }
......
...@@ -1439,7 +1439,7 @@ TEST_F(AnimationAnimationTestNoCompositing, ScrollLinkedAnimationCreation) { ...@@ -1439,7 +1439,7 @@ TEST_F(AnimationAnimationTestNoCompositing, ScrollLinkedAnimationCreation) {
scroll_animation->play(); scroll_animation->play();
// Verify start and current times in Pending state. // Verify start and current times in Pending state.
EXPECT_FALSE(scroll_animation->startTime().has_value()); EXPECT_EQ(0, scroll_animation->startTime());
EXPECT_EQ(20, scroll_animation->currentTime()); EXPECT_EQ(20, scroll_animation->currentTime());
UpdateAllLifecyclePhasesForTest(); UpdateAllLifecyclePhasesForTest();
......
...@@ -95,6 +95,8 @@ bool PendingAnimations::Update( ...@@ -95,6 +95,8 @@ bool PendingAnimations::Update(
if (animation->Playing() && !animation->startTime()) { if (animation->Playing() && !animation->startTime()) {
waiting_for_start_time.push_back(animation.Get()); waiting_for_start_time.push_back(animation.Get());
} else if (animation->PendingInternal()) { } else if (animation->PendingInternal()) {
DCHECK(animation->timeline()->IsActive() &&
animation->timeline()->CurrentTimeSeconds());
// A pending animation that is not waiting on a start time does not need // A pending animation that is not waiting on a start time does not need
// to be synchronized with animations that are starting up. Nonetheless, // to be synchronized with animations that are starting up. Nonetheless,
// it needs to notify the animation to resolve the ready promise and // it needs to notify the animation to resolve the ready promise and
...@@ -117,18 +119,16 @@ bool PendingAnimations::Update( ...@@ -117,18 +119,16 @@ bool PendingAnimations::Update(
} else { } else {
for (auto& animation : waiting_for_start_time) { for (auto& animation : waiting_for_start_time) {
DCHECK(!animation->startTime()); DCHECK(!animation->startTime());
// TODO(crbug.com/916117): Handle start time of scroll-linked animations. DCHECK(animation->timeline()->IsActive() &&
animation->timeline()->CurrentTimeSeconds());
animation->NotifyReady( animation->NotifyReady(
animation->timeline()->CurrentTimeSeconds().value_or(0)); animation->timeline()->CurrentTimeSeconds().value_or(0));
} }
} }
// FIXME: The postCommit should happen *after* the commit, not before. // FIXME: The postCommit should happen *after* the commit, not before.
for (auto& animation : animations) { for (auto& animation : animations)
// TODO(crbug.com/916117): Handle NaN current time of scroll timeline. animation->PostCommit();
animation->PostCommit(
animation->timeline()->CurrentTimeSeconds().value_or(0));
}
DCHECK(pending_.IsEmpty()); DCHECK(pending_.IsEmpty());
for (auto& animation : deferred) for (auto& animation : deferred)
...@@ -208,8 +208,8 @@ void PendingAnimations::FlushWaitingNonCompositedAnimations() { ...@@ -208,8 +208,8 @@ void PendingAnimations::FlushWaitingNonCompositedAnimations() {
if (animation->HasActiveAnimationsOnCompositor()) { if (animation->HasActiveAnimationsOnCompositor()) {
waiting_for_compositor_animation_start_.push_back(animation); waiting_for_compositor_animation_start_.push_back(animation);
} else { } else {
// TODO(crbug.com/916117): Handle start time of scroll-linked DCHECK(animation->timeline()->IsActive() &&
// animations. animation->timeline()->CurrentTimeSeconds());
animation->NotifyReady( animation->NotifyReady(
animation->timeline()->CurrentTimeSeconds().value_or(0)); animation->timeline()->CurrentTimeSeconds().value_or(0));
} }
......
...@@ -89,6 +89,10 @@ ...@@ -89,6 +89,10 @@
endScrollOffset: {target: end, ...config.end } endScrollOffset: {target: end, ...config.end }
}); });
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
const animation = createScrollLinkedAnimation(t, timeline); const animation = createScrollLinkedAnimation(t, timeline);
const scrollRange = end.offsetTop - start.offsetTop; const scrollRange = end.offsetTop - start.offsetTop;
const timeRange = animation.timeline.timeRange; const timeRange = animation.timeline.timeRange;
...@@ -100,11 +104,12 @@ ...@@ -100,11 +104,12 @@
"The start time is null in Idle state."); "The start time is null in Idle state.");
animation.play(); animation.play();
assert_true(animation.pending, "Animation is in pending state.");
// Verify initial start and current times in Pending state. // Verify initial start and current times in Pending state.
assert_times_equal(animation.currentTime, 0, assert_times_equal(animation.currentTime, 0,
"The current time is a hold time in Pending state."); "The current time is zero in Pending state.");
assert_equals(animation.startTime, null, assert_equals(animation.startTime, 0,
"The start time is null in Pending state."); "The start time is zero in Pending state.");
await animation.ready; await animation.ready;
// Verify initial start and current times in Playing state. // Verify initial start and current times in Playing state.
......
<!DOCTYPE html>
<meta charset=utf-8>
<title>Test basic functionality of scroll linked animation.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="testcommon.js"></script>
<style>
.scroller {
overflow: auto;
height: 100px;
width: 100px;
}
.contents {
height: 1000px;
width: 100%;
}
</style>
<div id="log"></div>
<script>
'use strict';
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
// Make the scroll timeline inactive.
scroller.style.overflow = 'visible';
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
// Play the animation when the timeline is inactive.
animation.play();
assert_equals(animation.currentTime, null,
'The current time is null when the timeline is inactive.');
assert_equals(animation.startTime, 0,
'The start time is zero in Pending state.');
await waitForNextFrame();
assert_true(animation.pending,
'Animation has play pending task while the timeline is inactive.');
assert_equals(animation.playState, 'running',
'State is \'running\' in Pending state.');
}, 'Play pending task doesn\'t run when the timeline is inactive.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
// Make the scroll timeline inactive.
scroller.style.overflow = 'visible';
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
// Play the animation when the timeline is inactive.
animation.play();
// Make the scroll timeline active.
scroller.style.overflow = 'auto';
await animation.ready;
// Ready promise is resolved as a result of the timeline becoming active.
assert_equals(animation.currentTime, 0,
'Animation current time is resolved when the animation is ready.');
assert_equals(animation.startTime, 0,
'Animation start time is resolved when the animation is ready.');
}, 'Animation start and current times are correct if scroll timeline is ' +
'activated after animation.play call.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const target = animation.effect.target;
// Advance the scroller.
scroller.scrollTop = 0.2 * maxScroll;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
// Play the animation when the timeline is active.
animation.play();
await animation.ready;
// Make the scroll timeline inactive.
scroller.style.overflow = 'visible';
scroller.scrollTop;
await waitForNextFrame();
assert_equals(animation.playState, 'running',
'State is \'running\' when the timeline is inactive.');
assert_equals(animation.currentTime, null,
'Current time is unresolved when the timeline is inactive.');
assert_equals(animation.startTime, 0,
'Start time is zero when the timeline is inactive.');
assert_equals(
animation.effect.getComputedTiming().localTime,
null,
'Effect local time is null when the timeline is inactive.');
assert_equals(Number(getComputedStyle(target).opacity), 1,
'Animation does not have an effect when the timeline is inactive.');
// Make the scroll timeline active.
scroller.style.overflow = 'auto';
await waitForNextFrame();
assert_equals(animation.playState, 'running',
'State is \'running\' when the timeline is active.');
assert_equals(animation.currentTime, 200,
'Current time is resolved when the timeline is active.');
assert_equals(animation.startTime, 0,
'Start time is zero when the timeline is active.');
assert_times_equal(
animation.effect.getComputedTiming().localTime,
200,
'Effect local time is resolved when the timeline is active.');
assert_equals(Number(getComputedStyle(target).opacity), 0.2,
'Animation has an effect when the timeline is active.');
}, 'Animation current time is correct when the timeline becomes newly ' +
'inactive and then active again.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
scroller.scrollTop;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
animation.play();
await animation.ready;
// Make the scroll timeline inactive.
scroller.style.overflow = 'visible';
scroller.scrollTop;
await waitForNextFrame();
const eventWatcher = new EventWatcher(t, animation, 'cancel');
animation.cancel();
const cancelEvent = await eventWatcher.wait_for('cancel');
assert_equals(cancelEvent.currentTime, null,
'event.currentTime should be unresolved when the timeline is inactive.');
assert_equals(cancelEvent.timelineTime, null,
'event.timelineTime should be unresolved when the timeline is inactive');
}, 'oncancel event is fired when the timeline is inactive.');
</script>
\ No newline at end of file
...@@ -23,7 +23,10 @@ ...@@ -23,7 +23,10 @@
const animation = createScrollLinkedAnimation(t); const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource; const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight; const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const timeRange = animation.timeline.timeRange;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
// Verify initial start and current times in Idle state. // Verify initial start and current times in Idle state.
assert_equals(animation.currentTime, null, assert_equals(animation.currentTime, null,
...@@ -31,11 +34,12 @@ ...@@ -31,11 +34,12 @@
assert_equals(animation.startTime, null, assert_equals(animation.startTime, null,
"The start time is null in Idle state."); "The start time is null in Idle state.");
animation.play(); animation.play();
assert_true(animation.pending, "Animation is in pending state.");
// Verify initial start and current times in Pending state. // Verify initial start and current times in Pending state.
assert_equals(animation.currentTime, 0, assert_equals(animation.currentTime, 0,
"The current time is a hold time in Pending state."); "The current time is zero in Pending state.");
assert_equals(animation.startTime, null, assert_equals(animation.startTime, 0,
"The start time is null in Pending state."); "The start time is zero in Pending state.");
await animation.ready; await animation.ready;
// Verify initial start and current times in Playing state. // Verify initial start and current times in Playing state.
...@@ -62,7 +66,7 @@ promise_test(async t => { ...@@ -62,7 +66,7 @@ promise_test(async t => {
const animation = createScrollLinkedAnimation(t); const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource; const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight; const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const timeRange = animation.timeline.timeRange;
// Advance the scroller. // Advance the scroller.
scroller.scrollTop = 0.2 * maxScroll; scroller.scrollTop = 0.2 * maxScroll;
// Wait for new animation frame which allows the timeline to compute new // Wait for new animation frame which allows the timeline to compute new
...@@ -78,8 +82,8 @@ promise_test(async t => { ...@@ -78,8 +82,8 @@ promise_test(async t => {
// Verify initial start and current times in Pending state. // Verify initial start and current times in Pending state.
assert_equals(animation.currentTime, animation.timeline.currentTime, assert_equals(animation.currentTime, animation.timeline.currentTime,
"The current time is a hold time in Pending state."); "The current time is a hold time in Pending state.");
assert_equals(animation.startTime, null, assert_equals(animation.startTime, 0,
"The start time is null in Pending state."); "The start time is zero in Pending state.");
await animation.ready; await animation.ready;
// Verify initial start and current times in Playing state. // Verify initial start and current times in Playing state.
...@@ -96,7 +100,7 @@ promise_test(async t => { ...@@ -96,7 +100,7 @@ promise_test(async t => {
const animation2 = createScrollLinkedAnimation(t, timeline); const animation2 = createScrollLinkedAnimation(t, timeline);
const scroller = timeline.scrollSource; const scroller = timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight; const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const timeRange = timeline.timeRange;
// Advance the scroller. // Advance the scroller.
scroller.scrollTop = 0.2 * maxScroll; scroller.scrollTop = 0.2 * maxScroll;
// Wait for new animation frame which allows the timeline to compute new // Wait for new animation frame which allows the timeline to compute new
...@@ -118,13 +122,13 @@ promise_test(async t => { ...@@ -118,13 +122,13 @@ promise_test(async t => {
assert_equals(animation1.currentTime, timeline.currentTime, assert_equals(animation1.currentTime, timeline.currentTime,
"The current time corresponds to the scroll position of the scroller" + "The current time corresponds to the scroll position of the scroller" +
" in Pending state."); " in Pending state.");
assert_equals(animation1.startTime, null, assert_equals(animation1.startTime, 0,
"The start time is null in Pending state."); "The start time is zero in Pending state.");
assert_equals(animation2.currentTime, timeline.currentTime, assert_equals(animation2.currentTime, timeline.currentTime,
"The current time corresponds to the scroll position of the scroller" + "The current time corresponds to the scroll position of the scroller" +
" in Pending state."); " in Pending state.");
assert_equals(animation2.startTime, null, assert_equals(animation2.startTime, 0,
"The start time is null in Pending state."); "The start time is zero in Pending state.");
await animation1.ready; await animation1.ready;
await animation2.ready; await animation2.ready;
...@@ -140,30 +144,6 @@ promise_test(async t => { ...@@ -140,30 +144,6 @@ promise_test(async t => {
}, 'Animation start and current times are correct when multiple animations' + }, 'Animation start and current times are correct when multiple animations' +
' are attached to the same timeline.'); ' are attached to the same timeline.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
// Make the scroll timeline inactive.
scroller.style.overflow = "visible";
// Trigger layout;
scroller.scrollTop;
assert_equals(animation.timeline.currentTime, null,
"Timeline current time is null in inactive state.");
// Play the animation when the timeline is inactive.
animation.play();
// Make the scroll timeline active.
scroller.style.overflow = "auto";
await animation.ready;
// Ready promise is resolved as a result of the timeline becoming active.
assert_equals(animation.timeline.currentTime, 0,
"Timeline current time is resolved in active state.");
assert_equals(animation.currentTime, 0,
"Animation current time is resolved when the animation is ready.");
assert_equals(animation.startTime, 0,
"Animation start time is resolved when the animation is ready.");
}, 'Animation start and current times are correct if scroll timeline is ' +
'activated after animation.play call.');
promise_test(async t => { promise_test(async t => {
const animation = createScrollLinkedAnimation(t); const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource; const scroller = animation.timeline.scrollSource;
......
...@@ -223,5 +223,71 @@ ...@@ -223,5 +223,71 @@
" source has been scrolled." " source has been scrolled."
); );
}, 'Set Animation current time then scroll.'); }, 'Set Animation current time then scroll.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
animation.play();
await animation.ready;
// Make the timeline inactive.
scroller.style.overflow = 'visible';
scroller.scrollTop;
await waitForNextFrame();
assert_equals(animation.currentTime, null,
'Current time is unresolved when the timeline is inactive.');
animation.currentTime = 300;
assert_equals(animation.currentTime, 300,
'Animation current time should be equal to the set value.');
assert_equals(animation.playState, 'paused',
'Animation play state is \'paused\' when current time is set and ' +
'timeline is inactive.');
}, 'Animation current time and play state are correct when current time is ' +
'set while the timeline is inactive.');
promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.scrollSource;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
animation.play();
await animation.ready;
// Make the timeline inactive.
scroller.style.overflow = 'visible';
scroller.scrollTop;
await waitForNextFrame();
assert_equals(animation.timeline.currentTime, null,
'Current time is unresolved when the timeline is inactive.');
animation.currentTime = 300;
assert_equals(animation.currentTime, 300,
'Animation current time should be equal to the set value.');
assert_equals(animation.playState, 'paused',
'Animation play state is \'paused\' when current time is set and ' +
'timeline is inactive.');
// Make the timeline active.
scroller.style.overflow = 'auto';
scroller.scrollTop;
await waitForNextFrame();
assert_equals(animation.timeline.currentTime, 0,
'Current time is resolved when the timeline is active.');
assert_equals(animation.currentTime, 300,
'Animation current time holds the set value.');
assert_equals(animation.playState, 'paused',
'Animation holds \'paused\' state.');
}, 'Animation current time set while the timeline is inactive holds when the ' +
'timeline becomes active again.');
</script> </script>
</body> </body>
...@@ -27,7 +27,7 @@ function createScrollLinkedAnimation(test, timeline) { ...@@ -27,7 +27,7 @@ function createScrollLinkedAnimation(test, timeline) {
if (timeline === undefined) if (timeline === undefined)
timeline = createScrollTimeline(test); timeline = createScrollTimeline(test);
const DURATION = 1000; // ms const DURATION = 1000; // ms
const KEYFRAMES = { opacity: [1, 0] }; const KEYFRAMES = { opacity: [0, 1] };
return new Animation( return new Animation(
new KeyframeEffect(createDiv(test), KEYFRAMES, DURATION), timeline); new KeyframeEffect(createDiv(test), KEYFRAMES, DURATION), timeline);
} }
...@@ -31,5 +31,25 @@ promise_test(t => { ...@@ -31,5 +31,25 @@ promise_test(t => {
}); });
}, 'reports true -> false when paused'); }, 'reports true -> false when paused');
promise_test(async t => {
const animation =
new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
null);
animation.play();
assert_true(animation.pending);
await waitForAnimationFrames(2);
assert_true(animation.pending);
}, 'reports true -> true when played without a timeline');
promise_test(async t => {
const animation =
new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
null);
animation.pause();
assert_true(animation.pending);
await waitForAnimationFrames(2);
assert_true(animation.pending);
}, 'reports true -> true when paused without a timeline');
</script> </script>
</body> </body>
...@@ -60,7 +60,7 @@ test(t => { ...@@ -60,7 +60,7 @@ test(t => {
new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
null); null);
animation.startTime = document.timeline.currentTime; animation.startTime = document.timeline.currentTime;
assert_equals(animation.playState, 'idle'); assert_equals(animation.playState, 'running');
animation.timeline = document.timeline; animation.timeline = document.timeline;
...@@ -73,7 +73,7 @@ test(t => { ...@@ -73,7 +73,7 @@ test(t => {
new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC), new Animation(new KeyframeEffect(createDiv(t), null, 100 * MS_PER_SEC),
null); null);
animation.startTime = document.timeline.currentTime - 200 * MS_PER_SEC; animation.startTime = document.timeline.currentTime - 200 * MS_PER_SEC;
assert_equals(animation.playState, 'idle'); assert_equals(animation.playState, 'running');
animation.timeline = document.timeline; animation.timeline = document.timeline;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment