test_scroll_behavior.html (9995B)
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; 10 } 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); 18 } 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"> 25 26 SimpleTest.waitForExplicitFinish(); 27 28 function clamp(val, minVal, maxVal) { 29 return Math.max(minVal, Math.min(maxVal, val)); 30 } 31 32 window.addEventListener("load", async function(event) { 33 if (event.target != document) { 34 return; 35 } 36 await testScrollBehaviorInterruption(); 37 await testScrollBehaviorFramerate(); 38 window.scrollTo(0,0); 39 SimpleTest.finish(); 40 }); 41 42 async function testScrollBehaviorInterruption() { 43 // Take control of refresh driver 44 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); 45 await promiseApzFlushedRepaints(); 46 47 window.scrollTo(10, 9); 48 ok(window.scrollX == 10 && window.scrollY == 9, 49 "instant scroll-behavior must be synchronous when setting initial position"); 50 51 window.scrollTo(15, 16); 52 ok(window.scrollX == 15 && window.scrollY == 16, 53 "instant scroll-behavior must be synchronous when setting new position"); 54 55 window.scrollTo({left: 100, top: 200, behavior: 'smooth'}); 56 ok(window.scrollX == 15 && window.scrollY == 16, 57 "smooth scroll-behavior must be asynchronous"); 58 59 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100); 60 await promiseApzFlushedRepaints(); 61 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"); 65 66 window.scrollTo(50, 52); 67 ok(window.scrollX == 50 && window.scrollY == 52, 68 "instant scroll-behavior must interrupt smooth scroll-behavior animation"); 69 70 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(100); 71 await promiseApzFlushedRepaints(); 72 73 ok(window.scrollX == 50 && window.scrollY == 52, 74 "smooth scroll-behavior animation must stop after being interrupted"); 75 76 // Release control of refresh driver 77 SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); 78 await promiseApzFlushedRepaints(); 79 } 80 81 async function testScrollBehaviorFramerate() { 82 /** 83 * CSSOM-View scroll-behavior smooth scroll animations must produce the 84 * same results indendently of frame-rate: 85 * 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. 100 */ 101 102 // Use 120hz for reference samples 103 var referenceFrameRate = 120; 104 105 var frameRates = [ 13, 60 ]; 106 var deltas = [ {x: 0, y: 0}, 107 {x: 1, y: 100}, 108 {x: -100, y: 50000} ]; 109 110 for (var deltaIndex = 0; deltaIndex < deltas.length; deltaIndex++) { 111 var deltaX = deltas[deltaIndex].x; 112 var deltaY = deltas[deltaIndex].y; 113 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); 122 123 let refSamples = await sampleAnimation(startX, startY, endX, endY, 124 referenceTimeStep); 125 126 var referenceDuration = refSamples.length * referenceTimeStep; // ms 127 128 for (var frameRateIndex = 0; frameRateIndex < frameRates.length; frameRateIndex++) { 129 var frameRate = frameRates[frameRateIndex]; 130 131 var testTimeStep = Math.floor(1000 / frameRate); 132 133 let testSamples = await sampleAnimation(startX, startY, endX, endY, 134 testTimeStep); 135 var testDuration = testSamples.length * testTimeStep; // ms 136 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'); 143 144 var maxVariance = 0; 145 testSamples.forEach(function(sample, sampleIndex) { 146 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); 154 155 var refSampleThisFrame = refSamples[refIndexThisFrame]; 156 var refSamplePrevFrame = refSamples[refIndexPrevFrame]; 157 var refSampleNextFrame = refSamples[refIndexNextFrame]; 158 159 var refXMin = Math.min(refSamplePrevFrame[0], 160 refSampleThisFrame[0], 161 refSampleNextFrame[0]); 162 163 var refYMin = Math.min(refSamplePrevFrame[1], 164 refSampleThisFrame[1], 165 refSampleNextFrame[1]); 166 167 var refXMax = Math.max(refSamplePrevFrame[0], 168 refSampleThisFrame[0], 169 refSampleNextFrame[0]); 170 171 var refYMax = Math.max(refSamplePrevFrame[1], 172 refSampleThisFrame[1], 173 refSampleNextFrame[1]); 174 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 178 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); 184 }); 185 186 is(maxVariance, 0, 'Smooth scroll animated position must not be ' 187 + 'framerate dependent at deltaX: ' + deltaX + ', deltaY: ' 188 + deltaY + ', frameRate: ' + frameRate + 'fps'); 189 } 190 191 await promiseApzFlushedRepaints(); 192 } 193 } 194 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 200 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 204 205 var positionSamples = []; 206 207 var frameCountAtDestination = 0; 208 209 // Take control of refresh driver so we can synthesize 210 // various frame rates 211 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(0); 212 await promiseApzFlushedRepaints(); 213 214 window.scrollTo(startX, startY); 215 window.scrollTo({left: endX, top: endY, behavior: 'smooth'}); 216 217 var currentTime = 0; // ms 218 219 while (currentTime < maxTime && frameCountAtDestination < 15) { 220 positionSamples.push([window.scrollX, window.scrollY]); 221 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; 229 } 230 } 231 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)); 236 237 window.scrollTo(0, 0); 238 239 // Release control of refresh driver 240 SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); 241 242 await promiseApzFlushedRepaints(); 243 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); 249 250 return positionSamples; 251 } 252 253 </script> 254 </head> 255 <body> 256 <pre id="test"> 257 </pre> 258 259 <div id="scroll_behavior_test_body"> 260 <div id="scroll_to_target" class="scroll_to_target"></div> 261 </body> 262 </html>