summaryrefslogtreecommitdiffstats
path: root/dom/base/test/test_timeout_clamp.html
blob: bb740b86939bf0e5cc761df92ebb32e356751633 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1378586
-->
<head>
  <meta charset="utf-8">
  <title>Test for Bug 1378586</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1378586">Mozilla Bug 1378586</a>

<script>
SimpleTest.waitForExplicitFinish();

// We need to clear our nesting level periodically.  We do this by firing
// a postMessage() to get a runnable on the event loop without any setTimeout()
// nesting.
function clearNestingLevel() {
  return new Promise(resolve => {
    window.addEventListener('message', () => {
      resolve();
    }, {once: true});
    postMessage('done', '*');
  });
}

function delayByTimeoutChain(iterations) {
  return new Promise(resolve => {
    let count = 0;
    function tick() {
      count += 1;
      if (count >= iterations) {
        resolve();
        return;
      }
      setTimeout(tick, 0);
    }
    setTimeout(tick, 0);
  });
}

function delayByInterval(iterations) {
  return new Promise(resolve => {
    let count = 0;
    function tick() {
      count += 1;
      if (count >= iterations) {
        resolve();
      }
    }
    setInterval(tick, 0);
  });
}

function testNestedIntervals() {
  return new Promise(resolve => {
    const runCount = 100;
    let counter = 0;
    let intervalId = 0;
    let prevInitTime = performance.now();
    let totalTime = 0;
    function intervalCallback() {
      let now = performance.now();
      let delay = now - prevInitTime;
      totalTime += delay;
      prevInitTime = now;
      clearInterval(intervalId);
      if (++counter < runCount) {
        intervalId = setInterval(intervalCallback, 0);
      } else {
        // Delays should be clamped to 4ms after the initial calls, but allow
        // some fuzziness, so divide by 2.
        let expectedTime = runCount * 4 / 2;
        ok(totalTime > expectedTime, "Should not run callbacks too fast.");
        resolve();
      }
    }

    // Use the timeout value defined in the spec, 4ms.
    SpecialPowers.pushPrefEnv({ 'set': [["dom.min_timeout_value", 4]]},
                              intervalCallback);
  });
}

// Use a very long clamp delay to make it easier to measure the change
// in automation.  Some of our test servers are very slow and noisy.
const clampDelayMS = 10000;

// We expect that we will clamp on the 5th callback.  This should
// be the same for both setTimeout() chains and setInterval().
const expectedClampIteration = 5;

async function runTests() {
  // Things like pushPrefEnv() can use setTimeout() internally which may give
  // us a nesting level.  Clear the nesting level to start so this doesn't
  // confuse the test.
  await clearNestingLevel();

  // Verify a setTimeout() chain clamps correctly
  let start = performance.now();
  await delayByTimeoutChain(expectedClampIteration);
  let stop = performance.now();
  let delta = stop - start;

  ok(delta >= clampDelayMS, "setTimeout() chain clamped: " + stop + " - " + start + " = " + delta);
  ok(delta < (2*clampDelayMS), "setTimeout() chain did not clamp twice");

  await clearNestingLevel();

  // Verify setInterval() clamps correctly
  start = performance.now();
  await delayByInterval(expectedClampIteration);
  stop = performance.now();
  delta = stop - start;

  ok(delta >= clampDelayMS, "setInterval() clamped: " + stop + " - " + start + " = " + delta);
  ok(delta < (2*clampDelayMS), "setInterval() did not clamp twice");

  await clearNestingLevel();

  // Verify a setTimeout() chain will continue to clamp past the first
  // expected iteration.
  const expectedDelay = (1 + expectedClampIteration) * clampDelayMS;

  start = performance.now();
  await delayByTimeoutChain(2 * expectedClampIteration);
  stop = performance.now();
  delta = stop - start;

  ok(delta >= expectedDelay, "setTimeout() chain continued to clamp: " + stop + " - " + start + " = " + delta);

  await clearNestingLevel();

  // Verify setInterval() will continue to clamp past the first expected
  // iteration.
  start = performance.now();
  await delayByTimeoutChain(2 * expectedClampIteration);
  stop = performance.now();
  delta = stop - start;

  ok(delta >= expectedDelay, "setInterval() continued to clamp: " + stop + " - " + start + " = " + delta);

  await testNestedIntervals();

  SimpleTest.finish();
}

// It appears that it's possible to get unlucky with time jittering and fail this test.
// If start is jittered upwards, everything executes very quickly, and delta has
// a very high midpoint, we may have taken between 10 and 10.002 seconds to execute; but
// it will appear to be 9.998. Turn off jitter (and add logging) to test this.
SpecialPowers.pushPrefEnv({ 'set': [
  ["dom.min_timeout_value", clampDelayMS],
  ["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false],
  ]}, runTests);
</script>

</body>
</html>