Skip to content

Commit b242238

Browse files
andruudsylvestre
authored andcommitted
Bug 1676858 [wpt PR 26502] - [scroll-animations] Run style/layout twice if necessary, a=testonly
Automatic update from web-platform-tests [scroll-animations] Run style/layout twice if necessary This CL solves the "first frame problem" [1], where ScrollTimelines created by style recalc end up as inactive, because layout hasn't run yet at the time that style recalc takes place. When a new timeline is created, it goes into a list of "unvalidated" timelines. During the lifecycle update, right after the style and layout steps, we go through all such timelines and check if the current timeline state (snapshot) is "stale" compared to a freshly computed snapshot. If it is, it means that layout had some effect on the timeline(s) that we would like to include in the next frame. We then mark the associated effect targets for style recalc, which in turn triggers a re-run of the style/layout steps in the lifecycle. After all timelines have been validated, the list of unvalidated timelines is cleared. This guarantees that we don't accidentally trigger style/layout more than twice. [1] w3c/csswg-drafts#5261 Fixed: 1145933 Bug: 1074052 Change-Id: Ic915bb71b204c93cbde1c5f485d26a4b1c2bd09f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2517448 Commit-Queue: Anders Hartvoll Ruud <[email protected]> Reviewed-by: Robert Flack <[email protected]> Reviewed-by: Stefan Zager <[email protected]> Reviewed-by: Kevin Ellis <[email protected]> Cr-Commit-Position: refs/heads/master@{#829624} -- wpt-commits: ccb29a2eaf3a7ff05e2cb47e76cf1cfcb59ebc0c wpt-pr: 26502
1 parent e704a87 commit b242238

File tree

1 file changed

+111
-0
lines changed

1 file changed

+111
-0
lines changed
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<!DOCTYPE html>
2+
<title>ScrollTimelines may trigger multiple style/layout passes</title>
3+
<link rel="help" src="https://github.com/w3c/csswg-drafts/issues/5261">
4+
<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles">
5+
<script src="/resources/testharness.js"></script>
6+
<script src="/resources/testharnessreport.js"></script>
7+
<script src="/web-animations/testcommon.js"></script>
8+
<style>
9+
@keyframes expand_width {
10+
from { width: 100px; }
11+
to { width: 100px; }
12+
}
13+
@keyframes expand_height {
14+
from { height: 100px; }
15+
to { height: 100px; }
16+
}
17+
@scroll-timeline timeline1 {
18+
source: selector(#scroller1);
19+
time-range: 10s;
20+
}
21+
@scroll-timeline timeline2 {
22+
source: selector(#scroller2);
23+
time-range: 10s;
24+
}
25+
main {
26+
height: 0px;
27+
overflow: hidden;
28+
}
29+
.scroller {
30+
height: 100px;
31+
overflow: scroll;
32+
}
33+
.scroller > div {
34+
height: 200px;
35+
}
36+
#element1 {
37+
width: 1px;
38+
animation: expand_width 10s timeline1;
39+
}
40+
#element2 {
41+
height: 1px;
42+
animation: expand_height 10s timeline2;
43+
}
44+
</style>
45+
<main id=main></main>
46+
<div id=element1></div>
47+
<div>
48+
<div id=element2></div>
49+
</div>
50+
<script>
51+
function insertScroller(id) {
52+
let scroller = document.createElement('div');
53+
scroller.setAttribute('id', id);
54+
scroller.setAttribute('class', 'scroller');
55+
scroller.append(document.createElement('div'));
56+
main.append(scroller);
57+
}
58+
59+
promise_test(async () => {
60+
await waitForNextFrame();
61+
62+
let events1 = [];
63+
let events2 = [];
64+
65+
insertScroller('scroller1');
66+
// Even though #scroller1 was just inserted into the DOM, |timeline1|
67+
// remains inactive until the next frame.
68+
//
69+
// https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles
70+
assert_equals(getComputedStyle(element1).width, '1px');
71+
(new ResizeObserver(entries => {
72+
events1.push(entries);
73+
insertScroller('scroller2');
74+
assert_equals(getComputedStyle(element2).height, '1px');
75+
})).observe(element1);
76+
77+
(new ResizeObserver(entries => {
78+
events2.push(entries);
79+
})).observe(element2);
80+
81+
await waitForNextFrame();
82+
83+
// According to the basic rules of the spec [1], the timeline is
84+
// inactive at the time the resize observer event was delivered, because
85+
// #scroller1 did not have a layout box at the time style recalc for
86+
// #element1 happened.
87+
//
88+
// However, an additional style/layout pass should take place
89+
// (before resize observer deliveries) if we detect new ScrollTimelines
90+
// in this situation, hence we ultimately do expect the animation to
91+
// apply [2].
92+
//
93+
// [1] https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles
94+
// [2] https://github.com/w3c/csswg-drafts/issues/5261
95+
assert_equals(events1.length, 1);
96+
assert_equals(events1[0].length, 1);
97+
assert_equals(events1[0][0].contentBoxSize.length, 1);
98+
assert_equals(events1[0][0].contentBoxSize[0].inlineSize, 100);
99+
100+
// ScrollTimelines created during the ResizeObserver should remain
101+
// inactive during the frame they're created, so the ResizeObserver
102+
// event should not reflect the animated value.
103+
assert_equals(events2.length, 1);
104+
assert_equals(events2[0].length, 1);
105+
assert_equals(events2[0][0].contentBoxSize.length, 1);
106+
assert_equals(events2[0][0].contentBoxSize[0].blockSize, 1);
107+
108+
assert_equals(getComputedStyle(element1).width, '100px');
109+
assert_equals(getComputedStyle(element2).height, '100px');
110+
}, 'Multiple style/layout passes occur when necessary');
111+
</script>

0 commit comments

Comments
 (0)