summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/test_capture_throttled.html
blob: 41cd93a6b96406cc8c1c1b0b1d3bb39450c36128 (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
<!DOCTYPE HTML>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />

<title>Canvas2D test: CaptureStream() with throttled rAF</title>

<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="captureStream_common.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<body>
<script>
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin");

// CaptureStreamTestHelper holding utility test functions.
const h = new CaptureStreamTestHelper2D();

async function measureRequestAnimationFrameRate() {
  const frameRate = await new Promise(resolve => {
    let start;
    let count;
    const tick = time => {
      if (!start) {
        start = time;
        count = 0;
      } else {
        count += 1;
      }
      if (time - start > 1000) {
        // One second has passed, break.
        resolve(count / ((time - start) / 1000));
        return;
      }
      window.requestAnimationFrame(tick);
    };
    window.requestAnimationFrame(tick);
  });
  return frameRate;
}

async function measureSetTimeoutRate() {
  const start = performance.now();
  const COUNT = 5;
  for(let i = 0; i < COUNT; ++i) {
    await new Promise(resolve => setTimeout(resolve, 0));
  }
  return COUNT / ((performance.now() - start) / 1000);
}

async function measureCanvasCaptureFrameRate(captureRate) {
  // Canvas element captured by streams.
  const c = h.createAndAppendElement('canvas', 'c');

  // Since we are in a background tab, the video element won't get composited,
  // so we cannot look for a frame count there. Instead we use RTCPeerConnection
  // and RTCOutboundRtpStreamStats.framesEncoded.
  const pc1 = new RTCPeerConnection();
  const pc2 = new RTCPeerConnection();

  // Add the canvas.captureStream track.
  const ctx = c.getContext('2d');
  const [track] = c.captureStream(captureRate).getTracks();
  const sender = pc1.addTrack(track);

  // Ice candidates signaling
  for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) {
    local.addEventListener("icecandidate", ({candidate}) => {
      if (!candidate || remote.signalingState == "closed") {
        return;
      }
      remote.addIceCandidate(candidate);
    });
  }

  // Offer/Answer exchange
  await pc1.setLocalDescription(await pc1.createOffer());
  await pc2.setRemoteDescription(pc1.localDescription);
  await pc2.setLocalDescription(await pc2.createAnswer());
  await pc1.setRemoteDescription(pc2.localDescription);

  // Wait for connection
  while ([pc1, pc2].some(pc => pc.iceConnectionState == "new" ||
                               pc.iceConnectionState == "checking")) {
    await Promise.any(
      [pc1, pc2].map(p => new Promise(r => p.oniceconnectionstatechange = r)));
  }
  for (const [pc, name] of [[pc1, "pc1"], [pc2, "pc2"]]) {
    ok(["connected", "completed"].includes(pc.iceConnectionState),
      `${name} connection established (${pc.iceConnectionState})`);
  }

  // Draw to the canvas
  const intervalMillis = 1000 / 60;
  const getFrameCount = async () => {
    const stats = await sender.getStats();
    const outbound =
      [...stats.values()].find(({type}) => type == "outbound-rtp");
    return outbound?.framesEncoded ?? 0;
  };
  // Wait for frame count change to ensure sender is working.
  while (await getFrameCount() == 0) {
    h.drawColor(c, h.green);
    await new Promise(resolve => setTimeout(resolve, intervalMillis));
  }
  const startFrameCount = await getFrameCount();
  const start = performance.now();
  let end = start;
  while(end - start <= 1000) {
    h.drawColor(c, h.green);
    await new Promise(resolve => setTimeout(resolve, intervalMillis));
    end = performance.now();
  }
  const framerate = (await getFrameCount() - startFrameCount) / ((end - start) / 1000);
  pc1.close();
  pc2.close();
  return framerate;
}

(async () => {
  // Disable background timer throttling so we can use setTimeout to draw to the
  // canvas while the refresh driver is throttled.
  await SpecialPowers.pushPrefEnv({
    set: [
      ["dom.timeout.enable_budget_timer_throttling", false],
      ["dom.min_background_timeout_value", 0],
      ["dom.min_background_timeout_value_without_budget_throttling", 0],
      ["browser.link.open_newwindow", 3], // window.open in new tab
    ],
  });
  // Throttle the canvas' refresh driver by opening a new foreground tab
  const foregroundTab = window.open("about:blank");

  // Let the throttling take effect
  await new Promise(r => setTimeout(r, 500));

  const rAFRate = await measureRequestAnimationFrameRate();
  ok(rAFRate < 5, `rAF framerate is at ${rAFRate} fps`);

  const setTimeoutRate = await measureSetTimeoutRate();
  ok(setTimeoutRate > 30, `setTimeout rate is at ${setTimeoutRate} tps`);

  const autoRate = await measureCanvasCaptureFrameRate();
  ok(autoRate > 5, `captureStream() framerate is at ${autoRate} fps`);

  const cappedRate = await measureCanvasCaptureFrameRate(10);
  ok(cappedRate > 5, `captureStream(10) framerate is at ${cappedRate} fps`);

  foregroundTab.close();
  SimpleTest.finish();
})();
</script>