Skip to content

Commit

Permalink
Update handling of changes to playback rate for a scroll-linked anima…
Browse files Browse the repository at this point in the history
…tion

* Preserve start time when updating playback rate for a scroll-linked
  animation.
* If changing playback rate for < 0 to >= 0 or vice versa, and start
  time is resolved:
  start time = effect end - start time.

Spec issue is here: w3c/csswg-drafts#2075

Bug: 1393060
Change-Id: I1afc5618decc8fdf245a230ae4af7de87ee8caf7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4060553
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1078080}
  • Loading branch information
kevers-google authored and Chromium LUCI CQ committed Dec 1, 2022
1 parent 9de4536 commit 6faafc4
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 30 deletions.
23 changes: 19 additions & 4 deletions third_party/blink/renderer/core/animation/animation.cc
Expand Up @@ -1936,20 +1936,35 @@ void Animation::setPlaybackRate(double playback_rate,
// 2. Let previous time be the value of the current time of animation before
// changing the playback rate.
// 3. Set the playback rate to new playback rate.
// 4. If previous time is resolved, set the current time of animation to
// previous time
// 4. If the timeline is monotonically increasing and the previous time is
// resolved, set the current time of animation to previous time.
// 5. If the timeline is not monotonically increasing, the start time is
// resolved and either:
// * the previous playback rate < 0 and the new playback rate >= 0, or
// * the previous playback rate >= 0 and the new playback rate < 0,
// Set animation's start time to the result of evaluating:
// associated effect end - start time
bool preserve_current_time =
timeline() && timeline()->IsMonotonicallyIncreasing();

bool reversal = (EffectivePlaybackRate() < 0) != (playback_rate < 0);
pending_playback_rate_ = absl::nullopt;
V8CSSNumberish* previous_current_time = currentTime();
playback_rate_ = playback_rate;
if (previous_current_time) {
if (previous_current_time && preserve_current_time) {
setCurrentTime(previous_current_time, exception_state);
}

if (timeline() && !timeline()->IsMonotonicallyIncreasing() && reversal &&
start_time_) {
start_time_ = EffectEnd() - start_time_.value();
}

// Adds a UseCounter to check if setting playbackRate causes a compensatory
// seek forcing a change in start_time_
// We use an epsilon (1 microsecond) to handle precision issue.
double epsilon = 1e-6;
if (start_time_before && start_time_ &&
if (preserve_current_time && start_time_before && start_time_ &&
fabs(start_time_.value().InMillisecondsF() -
start_time_before.value().InMillisecondsF()) > epsilon &&
CalculateAnimationPlayState() != kFinished) {
Expand Down
Expand Up @@ -88,9 +88,9 @@
await animation.ready;
// Set playback rate while the animation is playing.
animation.playbackRate = playbackRate;
assert_percents_equal(animation.currentTime, 20,
'The current time should stay unaffected by setting playback rate.');
}, 'The current time is not affected by playbackRate set while the ' +
assert_percents_equal(animation.currentTime, 40,
'The current time is scaled by the playback rate.');
}, 'The current time is scaled by playbackRate set while the ' +
'scroll-linked animation is in play state.');

promise_test(async t => {
Expand Down Expand Up @@ -148,9 +148,8 @@

assert_equals(animation.playState, "running");
assert_true(animation.pending);
assert_percents_equal(
animation.currentTime, animation.timeline.currentTime);
}, 'Setting the playback rate while play-pending preserves the current time' +
assert_percents_equal(animation.currentTime, 50);
}, 'Setting the playback rate while play-pending scales the current time' +
' from scrollTimeline.');

test(t => {
Expand All @@ -161,8 +160,8 @@

assert_equals(animation.playState, "running");
assert_true(animation.pending);
assert_percents_equal(animation.currentTime, 25);
}, 'Setting the playback rate while play-pending preserves the set current' +
assert_percents_equal(animation.currentTime, 50);
}, 'Setting the playback rate while play-pending scales the set current' +
' time.');

promise_test(async t => {
Expand All @@ -174,13 +173,13 @@
// current time.
await waitForNextFrame();
animation.play();
assert_percents_equal(animation.currentTime, 25);

await animation.ready;
animation.playbackRate = 2;

assert_percents_equal(
animation.currentTime, animation.timeline.currentTime);
}, 'Setting the playback rate while playing preserves the current time' +
assert_percents_equal(animation.currentTime, 50);
}, 'Setting the playback rate while playing scales the current time' +
' from scrollTimeline.');

promise_test(async t => {
Expand All @@ -196,8 +195,8 @@
await animation.ready;
animation.playbackRate = 2;

assert_percents_equal(animation.currentTime, 25);
}, 'Setting the playback rate while playing preserves the set current time.');
assert_percents_equal(animation.currentTime, 50);
}, 'Setting the playback rate while playing scales the set current time.');

promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
Expand Down Expand Up @@ -229,27 +228,31 @@
await animation.ready;
const startingTimelineTime = animation.timeline.currentTime;
const startingCurrentTime = animation.currentTime;
assert_percents_equal(startingCurrentTime, startingTimelineTime);
assert_percents_equal(startingCurrentTime, 50);
assert_percents_equal(startingTimelineTime, 50);

animation.playbackRate = -1;

scroller.scrollTop = 0.8 * maxScroll;
await waitForNextFrame();
// -300 = 500 - 800
let timelineDiff =
startingTimelineTime.value - animation.timeline.currentTime.value;
// 200 = 500 + (-300)
let expected = startingCurrentTime.value + timelineDiff;
assert_percents_equal(animation.currentTime, expected);

// let timelineDiff =
// startingTimelineTime.value - animation.timeline.currentTime.value;
// // 200 = 500 + (-300)
// let expected = startingCurrentTime.value + timelineDiff;
assert_percents_equal(animation.timeline.currentTime, 80);
assert_percents_equal(animation.currentTime, 20);

scroller.scrollTop = 0.2 * maxScroll;
await waitForNextFrame();
// 300 = 500 - 200
timelineDiff =
startingTimelineTime.value - animation.timeline.currentTime.value;
// 800 = 500 + 300
expected = startingCurrentTime.value + timelineDiff;
assert_percents_equal(animation.currentTime, expected);
// // 300 = 500 - 200
// timelineDiff =
// startingTimelineTime.value - animation.timeline.currentTime.value;
// // 800 = 500 + 300
// expected = startingCurrentTime.value + timelineDiff;
assert_percents_equal(animation.timeline.currentTime, 20);
assert_percents_equal(animation.currentTime, 80);
}, 'Reversing the playback rate while playing correctly impacts current' +
' time during future scrolls');

Expand Down Expand Up @@ -287,7 +290,34 @@

// Ensure that current time does not change.
assert_percents_equal(animation.timeline.currentTime, 50);
assert_percents_equal(animation.currentTime, 0);
}, 'Setting a zero playback rate while running preserves the start time');


promise_test(async t => {
const animation = createScrollLinkedAnimation(t);
const scroller = animation.timeline.source;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.2 * maxScroll;
// Wait for new animation frame which allows the timeline to compute new
// current time.
await waitForNextFrame();
animation.play();

await animation.ready;
assert_percents_equal(animation.timeline.currentTime, 20);
assert_percents_equal(animation.currentTime, 20);
}, 'Setting a zero playback rate while running preserves the current time');
animation.startTime = animation.currentTime;
// timeline current time [0%, 100%] --> animation current time [-20%, 80%].
assert_percents_equal(animation.currentTime, 0);

animation.playbackRate = -1;
// timeline current time [0%, 100%] --> animation current time [80%, -20%].
// timeline @ 20% --> animation current time @ (20% - 80%) * (-1) = 60%.
assert_percents_equal(animation.timeline.currentTime, 20);
assert_percents_equal(animation.currentTime, 60);
}, 'Reversing an animation with non-boundary aligned start time ' +
'symmetrically adjusts the start time');

</script>
</body>

0 comments on commit 6faafc4

Please sign in to comment.