blob: 14b35b3f6033f0577999de110c1df72905ade475 [file] [log] [blame]
<!DOCTYPE html>
<title>ScrollTimelines may trigger multiple style/layout passes</title>
<link rel="help" src="https://github.com/w3c/csswg-drafts/issues/5261">
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<style>
@keyframes expand_width {
from { width: 100px; }
to { width: 100px; }
}
@keyframes expand_height {
from { height: 100px; }
to { height: 100px; }
}
@scroll-timeline timeline1 {
source: selector(#scroller1);
time-range: 10s;
}
@scroll-timeline timeline2 {
source: selector(#scroller2);
time-range: 10s;
}
main {
height: 0px;
overflow: hidden;
}
.scroller {
height: 100px;
overflow: scroll;
}
.scroller > div {
height: 200px;
}
#element1 {
width: 1px;
animation: expand_width 10s timeline1;
}
#element2 {
height: 1px;
animation: expand_height 10s timeline2;
}
</style>
<main id=main></main>
<div id=element1></div>
<div>
<div id=element2></div>
</div>
<script>
function insertScroller(id) {
let scroller = document.createElement('div');
scroller.setAttribute('id', id);
scroller.setAttribute('class', 'scroller');
scroller.append(document.createElement('div'));
main.append(scroller);
}
promise_test(async () => {
await waitForNextFrame();
let events1 = [];
let events2 = [];
insertScroller('scroller1');
// Even though #scroller1 was just inserted into the DOM, |timeline1|
// remains inactive until the next frame.
//
// https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles
assert_equals(getComputedStyle(element1).width, '1px');
(new ResizeObserver(entries => {
events1.push(entries);
insertScroller('scroller2');
assert_equals(getComputedStyle(element2).height, '1px');
})).observe(element1);
(new ResizeObserver(entries => {
events2.push(entries);
})).observe(element2);
await waitForNextFrame();
// According to the basic rules of the spec [1], the timeline is
// inactive at the time the resize observer event was delivered, because
// #scroller1 did not have a layout box at the time style recalc for
// #element1 happened.
//
// However, an additional style/layout pass should take place
// (before resize observer deliveries) if we detect new ScrollTimelines
// in this situation, hence we ultimately do expect the animation to
// apply [2].
//
// [1] https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles
// [2] https://github.com/w3c/csswg-drafts/issues/5261
assert_equals(events1.length, 1);
assert_equals(events1[0].length, 1);
assert_equals(events1[0][0].contentBoxSize.length, 1);
assert_equals(events1[0][0].contentBoxSize[0].inlineSize, 100);
// ScrollTimelines created during the ResizeObserver should remain
// inactive during the frame they're created, so the ResizeObserver
// event should not reflect the animated value.
assert_equals(events2.length, 1);
assert_equals(events2[0].length, 1);
assert_equals(events2[0][0].contentBoxSize.length, 1);
assert_equals(events2[0][0].contentBoxSize[0].blockSize, 1);
assert_equals(getComputedStyle(element1).width, '100px');
assert_equals(getComputedStyle(element2).height, '100px');
}, 'Multiple style/layout passes occur when necessary');
</script>