<!DOCTYPE html>
<meta charset=utf-8>
<title>Setting the playback rate of an animation that is using a ScrollTimeline</title>
<link rel="help" href="https://drafts.csswg.org/web-animations/#setting-the-playback-rate-of-an-animation">
<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;
  will-change: transform;
}
.contents {
  height: 1000px;
  width: 100%;
}
</style>
<body>
<script>
  'use strict';

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    // this forces a layout which results in an active timeline
    scroller.scrollTop = 0;
    // Wait for new animation frame  which allows the timeline to compute new
    // current time.
    await waitForNextFrame();

    animation.playbackRate = 0.5;
    animation.play();
    await animation.ready;

    assert_percents_equal(animation.currentTime, 0,
        'Zero current time is not affected by playbackRate change.');
  }, 'Zero current time is not affected by playbackRate set while the ' +
       'animation is in idle state.');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    // this forces a layout which results in an active timeline
    scroller.scrollTop = 0;
    // Wait for new animation frame  which allows the timeline to compute new
    // current time.
    await waitForNextFrame();

    animation.play();
    await animation.ready;
    animation.playbackRate = 0.5;

    assert_percents_equal(animation.currentTime, 0,
        'Zero current time is not affected by playbackRate change.');
  }, 'Zero current time is not affected by playbackRate set while the ' +
      'animation is in play-pending state.');

  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.playbackRate = 0.5;
    animation.play();
    await animation.ready;
    assert_percents_equal(animation.currentTime, 10,
        'Initial current time is scaled by playbackRate change.');
  }, 'Initial current time is scaled by playbackRate set while ' +
      'scroll-linked animation is in running state.');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    const playbackRate = 2;

    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;
    // Set playback rate while the animation is playing.
    animation.playbackRate = playbackRate;
    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 => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;

    // Set playback rate while the animation is in 'idle' state.
    animation.playbackRate = 2;
    animation.play();
    await animation.ready;
    scroller.scrollTop = 0.2 * maxScroll;
    // Wait for new animation frame  which allows the timeline to compute new
    // current time.
    await waitForNextFrame();

    assert_percents_equal(animation.currentTime, 40,
                          'The current time should increase two times faster ' +
                          'than timeline time.');
  }, 'The playback rate set before scroll-linked animation started playing ' +
      'affects the rate of progress of the current time');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    animation.play();

    await animation.ready;

    animation.playbackRate = 2;
    scroller.scrollTop = 0.25 * maxScroll;
    // Wait for new animation frame  which allows the timeline to compute new
    // current time.
    await waitForNextFrame();

    assert_percents_equal(
        animation.currentTime,
        animation.timeline.currentTime.value * animation.playbackRate,
        'The current time should increase two times faster than timeline time');
  }, 'The playback rate affects the rate of progress of the current time' +
     ' when scrolling');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    animation.play();
    // Setting the current time while play-pending sets the hold time and not
    // the start time. currentTime is unaffected by playback rate until no
    // longer pending.
    animation.currentTime = CSSNumericValue.parse("25%");
    animation.playbackRate = 2;

    assert_equals(animation.playState, "running");
    assert_true(animation.pending);
    assert_percents_equal(animation.currentTime, 25);
    await animation.ready;
    assert_percents_equal(animation.currentTime, 25);
  }, 'Setting the playback rate while play-pending does not scale current ' +
     'time.');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    scroller.scrollTop = 0.25 * maxScroll;
    animation.play();
    await animation.ready;
    animation.playbackRate = 2;

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

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);

    animation.play();
    animation.currentTime = CSSNumericValue.parse("25%");
    await animation.ready;
    animation.playbackRate = 2;

    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);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    animation.playbackRate = -1;
    scroller.scrollTop = 0.3 * maxScroll;
    // Wait for new animation frame  which allows the timeline to compute new
    // current time.
    await waitForNextFrame();
    animation.play();

    await animation.ready;
    const expectedCurrentTime = 100 - animation.timeline.currentTime.value;
    assert_percents_equal(animation.currentTime, expectedCurrentTime);
  }, 'Negative initial playback rate should correctly modify initial current' +
    ' time.');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    scroller.scrollTop = 0.5 * maxScroll;
    animation.play();

    await animation.ready;
    const startingTimelineTime = animation.timeline.currentTime;
    const startingCurrentTime = animation.currentTime;
    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.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.timeline.currentTime, 20);
    assert_percents_equal(animation.currentTime, 80);
  }, 'Reversing the playback rate while playing correctly impacts current' +
    ' time during future scrolls');

  promise_test(async t => {
    const animation = createScrollLinkedAnimation(t);
    const scroller = animation.timeline.source;
    const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    animation.playbackRate = 0;
    scroller.scrollTop = 0.3 * 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.currentTime, 0);
  }, 'Zero initial playback rate should correctly modify initial current' +
    ' 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.currentTime, 20);
    animation.playbackRate = 0;
    scroller.scrollTop = 0.5 * maxScroll;
    await waitForNextFrame();

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