Bug 1879774 [wpt PR 44524] - WebKit export: Implement field-sizing support for input...
[gecko.git] / layout / generic / test / test_scroll_behavior.html
blob0a9fefc5595c9216ef59c94cdfe2790890c2559e
1 <!DOCTYPE HTML>
2 <html>
3 <head>
4 <meta charset="utf-8">
5 <title>Tests for CSSOM-View Smooth-Scroll DOM API Methods and MSD Animation</title>
6 <style>
7 #scroll_behavior_test_body {
8 width: 100000px;
9 height: 100000px;
11 .scroll_to_target {
12 position: absolute;
13 left: 20000px;
14 top: 10000px;
15 width: 200px;
16 height: 200px;
17 background-color: rgb(0, 0, 255);
19 </style>
20 <script src="/tests/SimpleTest/SimpleTest.js"></script>
21 <script src="/tests/SimpleTest/paint_listener.js"></script>
22 <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
23 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
24 <script type="application/javascript">
26 SimpleTest.waitForExplicitFinish();
28 function clamp(val, minVal, maxVal) {
29 return Math.max(minVal, Math.min(maxVal, val));
32 window.addEventListener("load", async function(event) {
33 if (event.target != document) {
34 return;
36 await testScrollBehaviorInterruption();
37 await testScrollBehaviorFramerate();
38 window.scrollTo(0,0);
39 SimpleTest.finish();
40 });
42 async function testScrollBehaviorInterruption() {
43 // Take control of refresh driver
44 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
45 await promiseApzFlushedRepaints();
47 window.scrollTo(10, 9);
48 ok(window.scrollX == 10 && window.scrollY == 9,
49 "instant scroll-behavior must be synchronous when setting initial position");
51 window.scrollTo(15, 16);
52 ok(window.scrollX == 15 && window.scrollY == 16,
53 "instant scroll-behavior must be synchronous when setting new position");
55 window.scrollTo({left: 100, top: 200, behavior: 'smooth'});
56 ok(window.scrollX == 15 && window.scrollY == 16,
57 "smooth scroll-behavior must be asynchronous");
59 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100);
60 await promiseApzFlushedRepaints();
62 ok(window.scrollX != 15 && window.scrollY != 16
63 && window.scrollX != 100 && window.scrollY != 200,
64 "smooth scroll-behavior must be triggered by window.scrollTo");
66 window.scrollTo(50, 52);
67 ok(window.scrollX == 50 && window.scrollY == 52,
68 "instant scroll-behavior must interrupt smooth scroll-behavior animation");
70 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100);
71 await promiseApzFlushedRepaints();
73 ok(window.scrollX == 50 && window.scrollY == 52,
74 "smooth scroll-behavior animation must stop after being interrupted");
76 // Release control of refresh driver
77 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
78 await promiseApzFlushedRepaints();
81 async function testScrollBehaviorFramerate() {
82 /**
83 * CSSOM-View scroll-behavior smooth scroll animations must produce the
84 * same results indendently of frame-rate:
86 * - Reference samples of scroll position for each frame are captured from
87 * a smooth scroll at 120fps for variations in X-Distance, Y-Distance.
88 * - Test samples are captured from an animation with the same parameters
89 * at varying framerates.
90 * - Variance in position at each sampled interval is compared to the
91 * 120fps reference. To pass the test, the position of each test
92 * sample must match the reference position with a tolerance of one test
93 * sample frame's range of motion. This range of motion is calculated
94 * by the position delta of the reference samples one test frame duration
95 * before and after.
96 * - The duration of the reference sample animation and the test sample
97 * animation must match within 1 frame to pass the test.
98 * - The simulation driving the animation must converge and stop on the
99 * destination position for the test to pass.
102 // Use 120hz for reference samples
103 var referenceFrameRate = 120;
105 var frameRates = [ 13, 60 ];
106 var deltas = [ {x: 0, y: 0},
107 {x: 1, y: 100},
108 {x: -100, y: 50000} ];
110 for (var deltaIndex = 0; deltaIndex < deltas.length; deltaIndex++) {
111 var deltaX = deltas[deltaIndex].x;
112 var deltaY = deltas[deltaIndex].y;
114 // startX and startY must be at least as big as the greatest negative
115 // number in the deltas array in order to prevent the animation from
116 // being interrupted by scroll range boundaries.
117 var startX = 1000;
118 var startY = 1000;
119 var endX = startX + deltaX;
120 var endY = startY + deltaY;
121 var referenceTimeStep = Math.floor(1000 / referenceFrameRate);
123 let refSamples = await sampleAnimation(startX, startY, endX, endY,
124 referenceTimeStep);
126 var referenceDuration = refSamples.length * referenceTimeStep; // ms
128 for (var frameRateIndex = 0; frameRateIndex < frameRates.length; frameRateIndex++) {
129 var frameRate = frameRates[frameRateIndex];
131 var testTimeStep = Math.floor(1000 / frameRate);
133 let testSamples = await sampleAnimation(startX, startY, endX, endY,
134 testTimeStep);
135 var testDuration = testSamples.length * testTimeStep; // ms
137 // Variance in duration of animation must be accurate to within one
138 // frame interval
139 var durationVariance = Math.max(0, Math.abs(testDuration - referenceDuration) - testTimeStep);
140 is(durationVariance, 0, 'Smooth scroll animation duration must not '
141 + 'be framerate dependent at deltaX: ' + deltaX + ', deltaY: '
142 + deltaY + ', frameRate: ' + frameRate + 'fps');
144 var maxVariance = 0;
145 testSamples.forEach(function(sample, sampleIndex) {
147 var testToRef = refSamples.length / testSamples.length;
148 var refIndexThisFrame = clamp(Math.floor(sampleIndex * testToRef),
149 0, refSamples.length - 1);
150 var refIndexPrevFrame = clamp(Math.floor((sampleIndex - 1) * testToRef),
151 0, refSamples.length - 1);
152 var refIndexNextFrame = clamp(Math.floor((sampleIndex + 1) * testToRef),
153 0, refSamples.length - 1);
155 var refSampleThisFrame = refSamples[refIndexThisFrame];
156 var refSamplePrevFrame = refSamples[refIndexPrevFrame];
157 var refSampleNextFrame = refSamples[refIndexNextFrame];
159 var refXMin = Math.min(refSamplePrevFrame[0],
160 refSampleThisFrame[0],
161 refSampleNextFrame[0]);
163 var refYMin = Math.min(refSamplePrevFrame[1],
164 refSampleThisFrame[1],
165 refSampleNextFrame[1]);
167 var refXMax = Math.max(refSamplePrevFrame[0],
168 refSampleThisFrame[0],
169 refSampleNextFrame[0]);
171 var refYMax = Math.max(refSamplePrevFrame[1],
172 refSampleThisFrame[1],
173 refSampleNextFrame[1]);
175 // Varience is expected to be at most 1 pixel beyond the range,
176 // due to integer rounding of pixel position.
177 var positionTolerance = 1; // 1 pixel
179 maxVariance = Math.max(maxVariance,
180 refXMin - sample[0] - positionTolerance,
181 sample[0] - refXMax - positionTolerance,
182 refYMin - sample[1] - positionTolerance,
183 sample[1] - refYMax - positionTolerance);
186 is(maxVariance, 0, 'Smooth scroll animated position must not be '
187 + 'framerate dependent at deltaX: ' + deltaX + ', deltaY: '
188 + deltaY + ', frameRate: ' + frameRate + 'fps');
191 await promiseApzFlushedRepaints();
195 async function sampleAnimation(startX, startY, endX, endY, timeStep) {
196 // The animation must be stopped at the destination position for
197 // minStoppedFrames consecutive frames to detect that the animation has
198 // completed.
199 var minStoppedFrames = 15; // 15 frames
201 // In case the simulation fails to converge, the test will time out after
202 // processing maxTime milliseconds of animation.
203 var maxTime = 10000; // 10 seconds
205 var positionSamples = [];
207 var frameCountAtDestination = 0;
209 // Take control of refresh driver so we can synthesize
210 // various frame rates
211 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0);
212 await promiseApzFlushedRepaints();
214 window.scrollTo(startX, startY);
215 window.scrollTo({left: endX, top: endY, behavior: 'smooth'});
217 var currentTime = 0; // ms
219 while (currentTime < maxTime && frameCountAtDestination < 15) {
220 positionSamples.push([window.scrollX, window.scrollY]);
222 currentTime += timeStep;
223 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(timeStep);
224 await promiseApzFlushedRepaints();
225 if (window.scrollX == endX && window.scrollY == endY) {
226 frameCountAtDestination++;
227 } else {
228 frameCountAtDestination = 0;
232 isnot(frameCountAtDestination, 0,
233 'Smooth scrolls must always end at their destination '
234 + 'unless they are interrupted, at deltaX: '
235 + (endX - startX) + ', deltaY: ' + (endY - startY));
237 window.scrollTo(0, 0);
239 // Release control of refresh driver
240 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
242 await promiseApzFlushedRepaints();
244 // We must not include the duplicated frames at the animation
245 // destination as the tests are dependant on the total duration of
246 // the animation to be accurate.
247 positionSamples.splice(1 - minStoppedFrames,
248 minStoppedFrames - 1);
250 return positionSamples;
253 </script>
254 </head>
255 <body>
256 <pre id="test">
257 </pre>
259 <div id="scroll_behavior_test_body">
260 <div id="scroll_to_target" class="scroll_to_target"></div>
261 </body>
262 </html>