summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/animation-worklet/inactive-timeline.https.html
blob: 3938cb3092c092c33df75869b227eb3844e505be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<!DOCTYPE html>
<meta charset=utf-8>
<title>Correctness of worklet animation state when timeline becomes newly
         active or inactive.</title>
<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="common.js"></script>
<style>
  .scroller {
    overflow: auto;
    height: 100px;
    width: 100px;
  }
  .contents {
    height: 1000px;
    width: 100%;
  }
</style>
<body>
<div id="log"></div>
<script>
'use strict';

function createScroller(test) {
  var scroller = createDiv(test);
  scroller.innerHTML = "<div class='contents'></div>";
  scroller.classList.add('scroller');
  return scroller;
}

function createScrollLinkedWorkletAnimation(test) {
  const timeline = new ScrollTimeline({
    scrollSource: createScroller(test),
  });
  const DURATION = 1000; // ms
  const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] };
  return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
        KEYFRAMES, DURATION), timeline);
}

setup(setupAndRegisterTests, {explicit_done: true});

function setupAndRegisterTests() {
  registerPassthroughAnimator().then(() => {

    promise_test(async t => {
      const animation = createScrollLinkedWorkletAnimation(t);
      const scroller = animation.timeline.scrollSource;
      const target = animation.effect.target;

      // There is no direct way to control when local times of composited
      // animations are synced to the main thread. This test uses another
      // composited worklet animation with an always active timeline as an
      // indicator of when the sync is ready. The sync is done when animation
      // effect's output has changed as a result of advancing the timeline.
      const animationRef = createScrollLinkedWorkletAnimation(t);
      const scrollerRef = animationRef.timeline.scrollSource;
      const targetRef = animationRef.effect.target;

      const maxScroll = scroller.scrollHeight - scroller.clientHeight;
      scroller.scrollTop = 0.2 * maxScroll;

      // Make the timeline inactive.
      scroller.style.display = "none"
      // Force relayout.
      scroller.scrollTop;

      animation.play();
      animationRef.play();
      assert_equals(animation.currentTime, null,
        'Initial current time must be unresolved in idle state.');
      assert_equals(animation.startTime, null,
        'Initial start time must be unresolved in idle state.');
      waitForAnimationFrameWithCondition(_=> {
        return animation.playState == "running"
      });
      assert_equals(animation.currentTime, null,
        'Initial current time must be unresolved in playing state.');
      assert_equals(animation.startTime, null,
        'Initial start time must be unresolved in playing state.');

      scrollerRef.scrollTop = 0.2 * maxScroll;

      // Wait until local times are synced back to the main thread.
      await waitForAnimationFrameWithCondition(_ => {
        return animationRef.effect.getComputedTiming().localTime == 200;
      });

      assert_equals(animation.effect.getComputedTiming().localTime, null,
        'The underlying effect local time must be undefined while the ' +
        'timeline is inactive.');

      // Make the timeline active.
      scroller.style.display = "";
      // Wait for new animation frame  which allows the timeline to compute new
      // current time.
      await waitForNextFrame();

      assert_times_equal(animation.currentTime, 200,
        'Current time must be initialized.');
      assert_times_equal(animation.startTime, 0,
        'Start time must be initialized.');

      scrollerRef.scrollTop = 0.4 * maxScroll;
      // Wait until local times are synced back to the main thread.
      await waitForAnimationFrameWithCondition(_ => {
        return animationRef.effect.getComputedTiming().localTime == 400;
      });
      assert_times_equal(animation.effect.getComputedTiming().localTime, 200,
        'When the timeline becomes newly active, the underlying effect\'s ' +
        'timing should be properly updated.');

      // Make the timeline inactive again.
      scroller.style.display = "none"
      await waitForNextFrame();

      assert_times_equal(animation.currentTime, 200,
        'Current time must be the previous current time.');
      assert_equals(animation.startTime, null,
        'Initial start time must be unresolved.');

      scrollerRef.scrollTop = 0.6 * maxScroll;
      // Wait until local times are synced back to the main thread.
      await waitForAnimationFrameWithCondition(_ => {
        return animationRef.effect.getComputedTiming().localTime == 600;
      });

      assert_times_equal(animation.effect.getComputedTiming().localTime, 200,
        'When the timeline becomes newly inactive, the underlying effect\'s ' +
        'timing should stay unchanged.');
    }, 'When timeline time becomes inactive previous current time must be ' +
       'the current time and start time unresolved');
    done();
  });
}
</script>
</body>