test_timeout_clamp.html (4995B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=1378586 5 --> 6 <head> 7 <meta charset="utf-8"> 8 <title>Test for Bug 1378586</title> 9 <script src="/tests/SimpleTest/SimpleTest.js"></script> 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 11 </head> 12 <body> 13 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378586">Mozilla Bug 1378586</a> 14 15 <script> 16 SimpleTest.waitForExplicitFinish(); 17 18 // We need to clear our nesting level periodically. We do this by firing 19 // a postMessage() to get a runnable on the event loop without any setTimeout() 20 // nesting. 21 function clearNestingLevel() { 22 return new Promise(resolve => { 23 window.addEventListener('message', () => { 24 resolve(); 25 }, {once: true}); 26 postMessage('done', '*'); 27 }); 28 } 29 30 function delayByTimeoutChain(iterations) { 31 return new Promise(resolve => { 32 let count = 0; 33 function tick() { 34 count += 1; 35 if (count >= iterations) { 36 resolve(); 37 return; 38 } 39 setTimeout(tick, 0); 40 } 41 setTimeout(tick, 0); 42 }); 43 } 44 45 function delayByInterval(iterations) { 46 return new Promise(resolve => { 47 let count = 0; 48 function tick() { 49 count += 1; 50 if (count >= iterations) { 51 resolve(); 52 } 53 } 54 setInterval(tick, 0); 55 }); 56 } 57 58 function testNestedIntervals() { 59 return new Promise(resolve => { 60 const runCount = 100; 61 let counter = 0; 62 let intervalId = 0; 63 let prevInitTime = performance.now(); 64 let totalTime = 0; 65 function intervalCallback() { 66 let now = performance.now(); 67 let delay = now - prevInitTime; 68 totalTime += delay; 69 prevInitTime = now; 70 clearInterval(intervalId); 71 if (++counter < runCount) { 72 intervalId = setInterval(intervalCallback, 0); 73 } else { 74 // Delays should be clamped to 4ms after the initial calls, but allow 75 // some fuzziness, so divide by 2. 76 let expectedTime = runCount * 4 / 2; 77 ok(totalTime > expectedTime, "Should not run callbacks too fast."); 78 resolve(); 79 } 80 } 81 82 // Use the timeout value defined in the spec, 4ms. 83 SpecialPowers.pushPrefEnv({ 'set': [["dom.min_timeout_value", 4]]}, 84 intervalCallback); 85 }); 86 } 87 88 // Use a very long clamp delay to make it easier to measure the change 89 // in automation. Some of our test servers are very slow and noisy. 90 const clampDelayMS = 10000; 91 92 // We expect that we will clamp on the 5th callback. This should 93 // be the same for both setTimeout() chains and setInterval(). 94 const expectedClampIteration = 5; 95 96 async function runTests() { 97 // Things like pushPrefEnv() can use setTimeout() internally which may give 98 // us a nesting level. Clear the nesting level to start so this doesn't 99 // confuse the test. 100 await clearNestingLevel(); 101 102 // Verify a setTimeout() chain clamps correctly 103 let start = performance.now(); 104 await delayByTimeoutChain(expectedClampIteration); 105 let stop = performance.now(); 106 let delta = stop - start; 107 108 ok(delta >= clampDelayMS, "setTimeout() chain clamped: " + stop + " - " + start + " = " + delta); 109 ok(delta < (2*clampDelayMS), "setTimeout() chain did not clamp twice"); 110 111 await clearNestingLevel(); 112 113 // Verify setInterval() clamps correctly 114 start = performance.now(); 115 await delayByInterval(expectedClampIteration); 116 stop = performance.now(); 117 delta = stop - start; 118 119 ok(delta >= clampDelayMS, "setInterval() clamped: " + stop + " - " + start + " = " + delta); 120 ok(delta < (2*clampDelayMS), "setInterval() did not clamp twice"); 121 122 await clearNestingLevel(); 123 124 // Verify a setTimeout() chain will continue to clamp past the first 125 // expected iteration. 126 const expectedDelay = (1 + expectedClampIteration) * clampDelayMS; 127 128 start = performance.now(); 129 await delayByTimeoutChain(2 * expectedClampIteration); 130 stop = performance.now(); 131 delta = stop - start; 132 133 ok(delta >= expectedDelay, "setTimeout() chain continued to clamp: " + stop + " - " + start + " = " + delta); 134 135 await clearNestingLevel(); 136 137 // Verify setInterval() will continue to clamp past the first expected 138 // iteration. 139 start = performance.now(); 140 await delayByTimeoutChain(2 * expectedClampIteration); 141 stop = performance.now(); 142 delta = stop - start; 143 144 ok(delta >= expectedDelay, "setInterval() continued to clamp: " + stop + " - " + start + " = " + delta); 145 146 await testNestedIntervals(); 147 148 SimpleTest.finish(); 149 } 150 151 // It appears that it's possible to get unlucky with time jittering and fail this test. 152 // If start is jittered upwards, everything executes very quickly, and delta has 153 // a very high midpoint, we may have taken between 10 and 10.002 seconds to execute; but 154 // it will appear to be 9.998. Turn off jitter (and add logging) to test this. 155 SpecialPowers.pushPrefEnv({ 'set': [ 156 ["dom.min_timeout_value", clampDelayMS], 157 ["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false], 158 ]}, runTests); 159 </script> 160 161 </body> 162 </html>