tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

playback-rate.https.html (13359B)


      1 <!DOCTYPE html>
      2 <meta charset=utf-8>
      3 <title>The playback rate of a worklet animation</title>
      4 <link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script>
      8 'use strict';
      9 // Presence of playback rate adds FP operations to calculating start_time
     10 // and current_time of animations. That's why it's needed to increase FP error
     11 // for comparing times in these tests.
     12 window.assert_times_equal = (actual, expected, description) => {
     13  assert_approx_equals(actual, expected, 0.002, description);
     14 };
     15 </script>
     16 <script src="/web-animations/testcommon.js"></script>
     17 <script src="common.js"></script>
     18 <style>
     19  .scroller {
     20    overflow: auto;
     21    height: 100px;
     22    width: 100px;
     23  }
     24  .contents {
     25    height: 1000px;
     26    width: 100%;
     27  }
     28 </style>
     29 <body>
     30 <div id="log"></div>
     31 <script>
     32 'use strict';
     33 
     34 function createWorkletAnimation(test) {
     35  const DURATION = 10000; // ms
     36  const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] };
     37  return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
     38        KEYFRAMES, DURATION), document.timeline);
     39 }
     40 
     41 function createScroller(test) {
     42  var scroller = createDiv(test);
     43  scroller.innerHTML = "<div class='contents'></div>";
     44  scroller.classList.add('scroller');
     45  return scroller;
     46 }
     47 
     48 function createScrollLinkedWorkletAnimation(test) {
     49  const timeline = new ScrollTimeline({
     50    scrollSource: createScroller(test)
     51  });
     52  const DURATION = 10000; // ms
     53  const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] };
     54  return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
     55        KEYFRAMES, DURATION), timeline);
     56 }
     57 
     58 setup(setupAndRegisterTests, {explicit_done: true});
     59 
     60 function setupAndRegisterTests() {
     61  registerPassthroughAnimator().then(() => {
     62 
     63    promise_test(async t => {
     64      const animation = createWorkletAnimation(t);
     65 
     66      animation.playbackRate = 0.5;
     67      animation.play();
     68      assert_equals(animation.currentTime, 0,
     69        'Zero current time is not affected by playbackRate.');
     70    }, 'Zero current time is not affected by playbackRate set while the ' +
     71       'animation is in idle state.');
     72 
     73    promise_test(async t => {
     74      const animation = createWorkletAnimation(t);
     75 
     76      animation.play();
     77      animation.playbackRate = 0.5;
     78      assert_equals(animation.currentTime, 0,
     79        'Zero current time is not affected by playbackRate.');
     80    }, 'Zero current time is not affected by playbackRate set while the ' +
     81       'animation is in play-pending state.');
     82 
     83    promise_test(async t => {
     84      const animation = createWorkletAnimation(t);
     85      const playbackRate = 2;
     86 
     87      animation.play();
     88 
     89      await waitForAnimationFrameWithCondition(_=> {
     90        return animation.playState == "running"
     91      });
     92      // Make sure the current time is not Zero.
     93      await waitForDocumentTimelineAdvance();
     94 
     95      // Set playback rate while the animation is playing.
     96      const prevCurrentTime = animation.currentTime;
     97      animation.playbackRate = playbackRate;
     98 
     99      assert_times_equal(animation.currentTime, prevCurrentTime,
    100        'The current time should stay unaffected by setting playback rate.');
    101    }, 'Non zero current time is not affected by playbackRate set while the ' +
    102       'animation is in play state.');
    103 
    104    promise_test(async t => {
    105      const animation = createWorkletAnimation(t);
    106      const playbackRate = 0.2;
    107 
    108      animation.play();
    109 
    110      await waitForAnimationFrameWithCondition(_=> {
    111        return animation.playState == "running"
    112      });
    113 
    114      // Set playback rate while the animation is playing.
    115      const prevCurrentTime = animation.currentTime;
    116      const prevTimelineTime = document.timeline.currentTime;
    117      animation.playbackRate = playbackRate;
    118 
    119      // Play the animation some more.
    120      await waitForDocumentTimelineAdvance();
    121 
    122      const currentTime = animation.currentTime;
    123      const currentTimelineTime = document.timeline.currentTime;
    124 
    125      assert_times_equal(
    126        currentTime - prevCurrentTime,
    127        (currentTimelineTime - prevTimelineTime) * playbackRate,
    128        'The current time should increase 0.2 times faster than timeline.');
    129    }, 'The playback rate affects the rate of progress of the current time.');
    130 
    131    promise_test(async t => {
    132      const animation = createWorkletAnimation(t);
    133      const playbackRate = 2;
    134 
    135      // Set playback rate while the animation is in 'idle' state.
    136      animation.playbackRate = playbackRate;
    137      const prevTimelineTime = document.timeline.currentTime;
    138      animation.play();
    139 
    140      await waitForAnimationFrameWithCondition(_=> {
    141        return animation.playState == "running"
    142      });
    143      await waitForDocumentTimelineAdvance();
    144 
    145      const currentTime = animation.currentTime;
    146      const timelineTime = document.timeline.currentTime;
    147      assert_times_equal(
    148        currentTime,
    149        (timelineTime - prevTimelineTime) * playbackRate,
    150        'The current time should increase two times faster than timeline.');
    151    }, 'The playback rate set before the animation started playing affects ' +
    152       'the rate of progress of the current time');
    153 
    154    promise_test(async t => {
    155      const timing = { duration: 100,
    156                      easing: 'linear',
    157                      fill: 'none',
    158                      iterations: 1
    159                     };
    160      // TODO(crbug.com/937382): Currently composited
    161      // workletAnimation.currentTime and the corresponding
    162      // effect.getComputedTiming().localTime are computed by main and
    163      // compositing threads respectively and, as a result, don't match.
    164      // To workaround this limitation we compare the output of two identical
    165      // animations that only differ in playback rate. The expectation is that
    166      // their output matches after taking their playback rates into
    167      // consideration. This works since these two animations start at the same
    168      // time on the same thread.
    169      // Once the issue is fixed, this test needs to change so expected
    170      // effect.getComputedTiming().localTime is compared against
    171      // workletAnimation.currentTime.
    172      const target = createDiv(t);
    173      const targetRef = createDiv(t);
    174      const keyframeEffect = new KeyframeEffect(
    175        target, { opacity: [1, 0] }, timing);
    176      const keyframeEffectRef = new KeyframeEffect(
    177        targetRef, { opacity: [1, 0] }, timing);
    178      const animation = new WorkletAnimation(
    179        'passthrough', keyframeEffect, document.timeline);
    180      const animationRef = new WorkletAnimation(
    181        'passthrough', keyframeEffectRef, document.timeline);
    182      const playbackRate = 2;
    183      animation.playbackRate = playbackRate;
    184      animation.play();
    185      animationRef.play();
    186 
    187      // wait until local times are synced back to the main thread.
    188      await waitForAnimationFrameWithCondition(_ => {
    189        return getComputedStyle(target).opacity != '1';
    190      });
    191 
    192      assert_times_equal(
    193        keyframeEffect.getComputedTiming().localTime,
    194        keyframeEffectRef.getComputedTiming().localTime * playbackRate,
    195        'When playback rate is set on WorkletAnimation, the underlying ' +
    196        'effect\'s timing should be properly updated.');
    197 
    198      assert_approx_equals(
    199        1 - Number(getComputedStyle(target).opacity),
    200        (1 - Number(getComputedStyle(targetRef).opacity)) * playbackRate,
    201        0.001,
    202        'When playback rate is set on WorkletAnimation, the underlying effect' +
    203        ' should produce correct visual result.');
    204    }, 'When playback rate is updated, the underlying effect is properly ' +
    205       'updated with the current time of its WorkletAnimation and produces ' +
    206       'correct visual result.');
    207 
    208    promise_test(async t => {
    209      const animation = createScrollLinkedWorkletAnimation(t);
    210      const scroller = animation.timeline.scrollSource;
    211      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    212      scroller.scrollTop = 0.2 * maxScroll;
    213 
    214      animation.playbackRate = 0.5;
    215      animation.play();
    216      await waitForAnimationFrameWithCondition(_=> {
    217        return animation.playState == "running"
    218      });
    219      assert_percents_equal(animation.currentTime, 10,
    220        'Initial current time is scaled by playbackRate.');
    221    }, 'Initial current time is scaled by playbackRate set while ' +
    222       'scroll-linked animation is in idle state.');
    223 
    224    promise_test(async t => {
    225      const animation = createScrollLinkedWorkletAnimation(t);
    226      const scroller = animation.timeline.scrollSource;
    227      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    228      scroller.scrollTop = 0.2 * maxScroll;
    229 
    230      animation.play();
    231      animation.playbackRate = 0.5;
    232 
    233      assert_percents_equal(animation.currentTime, 20,
    234        'Initial current time is not affected by playbackRate.');
    235    }, 'Initial current time is not affected by playbackRate set while '+
    236       'scroll-linked animation is in play-pending state.');
    237 
    238    promise_test(async t => {
    239      const animation = createScrollLinkedWorkletAnimation(t);
    240      const scroller = animation.timeline.scrollSource;
    241      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    242      const playbackRate = 2;
    243 
    244      animation.play();
    245      scroller.scrollTop = 0.2 * maxScroll;
    246      await waitForAnimationFrameWithCondition(_=> {
    247        return animation.playState == "running"
    248      });
    249      // Set playback rate while the animation is playing.
    250      animation.playbackRate = playbackRate;
    251      assert_percents_equal(animation.currentTime, 20,
    252        'The current time should stay unaffected by setting playback rate.');
    253    }, 'The current time is not affected by playbackRate set while the ' +
    254       'scroll-linked animation is in play state.');
    255 
    256    promise_test(async t => {
    257      const animation = createScrollLinkedWorkletAnimation(t);
    258      const scroller = animation.timeline.scrollSource;
    259      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    260      const playbackRate = 2;
    261 
    262      animation.play();
    263      await waitForAnimationFrameWithCondition(_=> {
    264        return animation.playState == "running"
    265      });
    266      scroller.scrollTop = 0.1 * maxScroll;
    267 
    268      // Set playback rate while the animation is playing.
    269      animation.playbackRate = playbackRate;
    270 
    271      scroller.scrollTop = 0.2 * maxScroll;
    272 
    273      assert_equals(
    274        animation.currentTime.value - 10, 10 * playbackRate,
    275        'The current time should increase twice faster than scroll timeline.');
    276    }, 'Scroll-linked animation playback rate affects the rate of progress ' +
    277       'of the current time.');
    278 
    279    promise_test(async t => {
    280      const animation = createScrollLinkedWorkletAnimation(t);
    281      const scroller = animation.timeline.scrollSource;
    282      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    283      const playbackRate = 2;
    284 
    285      // Set playback rate while the animation is in 'idle' state.
    286      animation.playbackRate = playbackRate;
    287      animation.play();
    288      await waitForAnimationFrameWithCondition(_=> {
    289        return animation.playState == "running"
    290      });
    291      scroller.scrollTop = 0.2 * maxScroll;
    292 
    293      assert_percents_equal(animation.currentTime, 20 * playbackRate,
    294        'The current time should increase two times faster than timeline.');
    295    }, 'The playback rate set before scroll-linked animation started playing ' +
    296       'affects the rate of progress of the current time');
    297 
    298    promise_test(async t => {
    299      const scroller = createScroller(t);
    300      const timeline = new ScrollTimeline({
    301        scrollSource: scroller
    302      });
    303      const timing = { duration: 1000,
    304                      easing: 'linear',
    305                      fill: 'none',
    306                      iterations: 1
    307                    };
    308      const target = createDiv(t);
    309      const keyframeEffect = new KeyframeEffect(
    310        target, { opacity: [1, 0] }, timing);
    311      const animation = new WorkletAnimation(
    312        'passthrough', keyframeEffect, timeline);
    313      const playbackRate = 2;
    314      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
    315 
    316      animation.play();
    317      animation.playbackRate = playbackRate;
    318      await waitForAnimationFrameWithCondition(_=> {
    319        return animation.playState == "running"
    320      });
    321 
    322      scroller.scrollTop = 0.2 * maxScroll;
    323      // wait until local times are synced back to the main thread.
    324      await waitForAnimationFrameWithCondition(_ => {
    325        return getComputedStyle(target).opacity != '1';
    326      });
    327 
    328      assert_percents_equal(
    329        keyframeEffect.getComputedTiming().localTime,
    330        20 * playbackRate,
    331        'When playback rate is set on WorkletAnimation, the underlying ' +
    332        'effect\'s timing should be properly updated.');
    333      assert_approx_equals(
    334        Number(getComputedStyle(target).opacity),
    335        1 - 20 * playbackRate / 1000, 0.001,
    336        'When playback rate is set on WorkletAnimation, the underlying ' +
    337        'effect should produce correct visual result.');
    338    }, 'When playback rate is updated, the underlying effect is properly ' +
    339       'updated with the current time of its scroll-linked WorkletAnimation ' +
    340       'and produces correct visual result.');
    341    done();
    342  });
    343 }
    344 </script>
    345 </body>