summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/test_capture_throttled.html
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/test/test_capture_throttled.html')
-rw-r--r--dom/canvas/test/test_capture_throttled.html151
1 files changed, 151 insertions, 0 deletions
diff --git a/dom/canvas/test/test_capture_throttled.html b/dom/canvas/test/test_capture_throttled.html
new file mode 100644
index 0000000000..41cd93a6b9
--- /dev/null
+++ b/dom/canvas/test/test_capture_throttled.html
@@ -0,0 +1,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>
+