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 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
|
<!DOCTYPE html>
<meta charset=utf-8>
<title>The playback rate of a worklet animation</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>
'use strict';
// Presence of playback rate adds FP operations to calculating start_time
// and current_time of animations. That's why it's needed to increase FP error
// for comparing times in these tests.
window.assert_times_equal = (actual, expected, description) => {
assert_approx_equals(actual, expected, 0.002, description);
};
</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 createWorkletAnimation(test) {
const DURATION = 10000; // ms
const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] };
return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
KEYFRAMES, DURATION), document.timeline);
}
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 = 10000; // 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 = createWorkletAnimation(t);
animation.playbackRate = 0.5;
animation.play();
assert_equals(animation.currentTime, 0,
'Zero current time is not affected by playbackRate.');
}, 'Zero current time is not affected by playbackRate set while the ' +
'animation is in idle state.');
promise_test(async t => {
const animation = createWorkletAnimation(t);
animation.play();
animation.playbackRate = 0.5;
assert_equals(animation.currentTime, 0,
'Zero current time is not affected by playbackRate.');
}, 'Zero current time is not affected by playbackRate set while the ' +
'animation is in play-pending state.');
promise_test(async t => {
const animation = createWorkletAnimation(t);
const playbackRate = 2;
animation.play();
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
// Make sure the current time is not Zero.
await waitForDocumentTimelineAdvance();
// Set playback rate while the animation is playing.
const prevCurrentTime = animation.currentTime;
animation.playbackRate = playbackRate;
assert_times_equal(animation.currentTime, prevCurrentTime,
'The current time should stay unaffected by setting playback rate.');
}, 'Non zero current time is not affected by playbackRate set while the ' +
'animation is in play state.');
promise_test(async t => {
const animation = createWorkletAnimation(t);
const playbackRate = 0.2;
animation.play();
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
// Set playback rate while the animation is playing.
const prevCurrentTime = animation.currentTime;
const prevTimelineTime = document.timeline.currentTime;
animation.playbackRate = playbackRate;
// Play the animation some more.
await waitForDocumentTimelineAdvance();
const currentTime = animation.currentTime;
const currentTimelineTime = document.timeline.currentTime;
assert_times_equal(
currentTime - prevCurrentTime,
(currentTimelineTime - prevTimelineTime) * playbackRate,
'The current time should increase 0.2 times faster than timeline.');
}, 'The playback rate affects the rate of progress of the current time.');
promise_test(async t => {
const animation = createWorkletAnimation(t);
const playbackRate = 2;
// Set playback rate while the animation is in 'idle' state.
animation.playbackRate = playbackRate;
const prevTimelineTime = document.timeline.currentTime;
animation.play();
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
await waitForDocumentTimelineAdvance();
const currentTime = animation.currentTime;
const timelineTime = document.timeline.currentTime;
assert_times_equal(
currentTime,
(timelineTime - prevTimelineTime) * playbackRate,
'The current time should increase two times faster than timeline.');
}, 'The playback rate set before the animation started playing affects ' +
'the rate of progress of the current time');
promise_test(async t => {
const timing = { duration: 100,
easing: 'linear',
fill: 'none',
iterations: 1
};
// TODO(crbug.com/937382): Currently composited
// workletAnimation.currentTime and the corresponding
// effect.getComputedTiming().localTime are computed by main and
// compositing threads respectively and, as a result, don't match.
// To workaround this limitation we compare the output of two identical
// animations that only differ in playback rate. The expectation is that
// their output matches after taking their playback rates into
// consideration. This works since these two animations start at the same
// time on the same thread.
// Once the issue is fixed, this test needs to change so expected
// effect.getComputedTiming().localTime is compared against
// workletAnimation.currentTime.
const target = createDiv(t);
const targetRef = createDiv(t);
const keyframeEffect = new KeyframeEffect(
target, { opacity: [1, 0] }, timing);
const keyframeEffectRef = new KeyframeEffect(
targetRef, { opacity: [1, 0] }, timing);
const animation = new WorkletAnimation(
'passthrough', keyframeEffect, document.timeline);
const animationRef = new WorkletAnimation(
'passthrough', keyframeEffectRef, document.timeline);
const playbackRate = 2;
animation.playbackRate = playbackRate;
animation.play();
animationRef.play();
// wait until local times are synced back to the main thread.
await waitForAnimationFrameWithCondition(_ => {
return getComputedStyle(target).opacity != '1';
});
assert_times_equal(
keyframeEffect.getComputedTiming().localTime,
keyframeEffectRef.getComputedTiming().localTime * playbackRate,
'When playback rate is set on WorkletAnimation, the underlying ' +
'effect\'s timing should be properly updated.');
assert_approx_equals(
1 - Number(getComputedStyle(target).opacity),
(1 - Number(getComputedStyle(targetRef).opacity)) * playbackRate,
0.001,
'When playback rate is set on WorkletAnimation, the underlying effect' +
' should produce correct visual result.');
}, 'When playback rate is updated, the underlying effect is properly ' +
'updated with the current time of its WorkletAnimation and produces ' +
'correct visual result.');
promise_test(async t => {
const animation = createScrollLinkedWorkletAnimation(t);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.2 * maxScroll;
animation.playbackRate = 0.5;
animation.play();
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
assert_percents_equal(animation.currentTime, 10,
'Initial current time is scaled by playbackRate.');
}, 'Initial current time is scaled by playbackRate set while ' +
'scroll-linked animation is in idle state.');
promise_test(async t => {
const animation = createScrollLinkedWorkletAnimation(t);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.2 * maxScroll;
animation.play();
animation.playbackRate = 0.5;
assert_percents_equal(animation.currentTime, 20,
'Initial current time is not affected by playbackRate.');
}, 'Initial current time is not affected by playbackRate set while '+
'scroll-linked animation is in play-pending state.');
promise_test(async t => {
const animation = createScrollLinkedWorkletAnimation(t);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const playbackRate = 2;
animation.play();
scroller.scrollTop = 0.2 * maxScroll;
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
// 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 ' +
'scroll-linked animation is in play state.');
promise_test(async t => {
const animation = createScrollLinkedWorkletAnimation(t);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const playbackRate = 2;
animation.play();
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
scroller.scrollTop = 0.1 * maxScroll;
// Set playback rate while the animation is playing.
animation.playbackRate = playbackRate;
scroller.scrollTop = 0.2 * maxScroll;
assert_equals(
animation.currentTime.value - 10, 10 * playbackRate,
'The current time should increase twice faster than scroll timeline.');
}, 'Scroll-linked animation playback rate affects the rate of progress ' +
'of the current time.');
promise_test(async t => {
const animation = createScrollLinkedWorkletAnimation(t);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
const playbackRate = 2;
// Set playback rate while the animation is in 'idle' state.
animation.playbackRate = playbackRate;
animation.play();
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
scroller.scrollTop = 0.2 * maxScroll;
assert_percents_equal(animation.currentTime, 20 * playbackRate,
'The current time should increase two times faster than timeline.');
}, 'The playback rate set before scroll-linked animation started playing ' +
'affects the rate of progress of the current time');
promise_test(async t => {
const scroller = createScroller(t);
const timeline = new ScrollTimeline({
scrollSource: scroller
});
const timing = { duration: 1000,
easing: 'linear',
fill: 'none',
iterations: 1
};
const target = createDiv(t);
const keyframeEffect = new KeyframeEffect(
target, { opacity: [1, 0] }, timing);
const animation = new WorkletAnimation(
'passthrough', keyframeEffect, timeline);
const playbackRate = 2;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
animation.play();
animation.playbackRate = playbackRate;
await waitForAnimationFrameWithCondition(_=> {
return animation.playState == "running"
});
scroller.scrollTop = 0.2 * maxScroll;
// wait until local times are synced back to the main thread.
await waitForAnimationFrameWithCondition(_ => {
return getComputedStyle(target).opacity != '1';
});
assert_percents_equal(
keyframeEffect.getComputedTiming().localTime,
20 * playbackRate,
'When playback rate is set on WorkletAnimation, the underlying ' +
'effect\'s timing should be properly updated.');
assert_approx_equals(
Number(getComputedStyle(target).opacity),
1 - 20 * playbackRate / 1000, 0.001,
'When playback rate is set on WorkletAnimation, the underlying ' +
'effect should produce correct visual result.');
}, 'When playback rate is updated, the underlying effect is properly ' +
'updated with the current time of its scroll-linked WorkletAnimation ' +
'and produces correct visual result.');
done();
});
}
</script>
</body>
|