test_capture_throttled.html (5116B)
1 <!DOCTYPE HTML> 2 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 3 4 <title>Canvas2D test: CaptureStream() with throttled rAF</title> 5 6 <script src="/tests/SimpleTest/SimpleTest.js"></script> 7 <script src="captureStream_common.js"></script> 8 <link rel="stylesheet" href="/tests/SimpleTest/test.css"> 9 <body> 10 <script> 11 SimpleTest.waitForExplicitFinish(); 12 SimpleTest.requestFlakyTimeout("Ensuring nothing happens until timing out with good margin"); 13 14 // CaptureStreamTestHelper holding utility test functions. 15 const h = new CaptureStreamTestHelper2D(); 16 17 async function measureRequestAnimationFrameRate() { 18 const frameRate = await new Promise(resolve => { 19 let start; 20 let count; 21 const tick = time => { 22 if (!start) { 23 start = time; 24 count = 0; 25 } else { 26 count += 1; 27 } 28 if (time - start > 1000) { 29 // One second has passed, break. 30 resolve(count / ((time - start) / 1000)); 31 return; 32 } 33 window.requestAnimationFrame(tick); 34 }; 35 window.requestAnimationFrame(tick); 36 }); 37 return frameRate; 38 } 39 40 async function measureSetTimeoutRate() { 41 const start = performance.now(); 42 const COUNT = 5; 43 for(let i = 0; i < COUNT; ++i) { 44 await new Promise(resolve => setTimeout(resolve, 0)); 45 } 46 return COUNT / ((performance.now() - start) / 1000); 47 } 48 49 async function measureCanvasCaptureFrameRate(captureRate) { 50 // Canvas element captured by streams. 51 const c = h.createAndAppendElement('canvas', 'c'); 52 53 // Since we are in a background tab, the video element won't get composited, 54 // so we cannot look for a frame count there. Instead we use RTCPeerConnection 55 // and RTCOutboundRtpStreamStats.framesEncoded. 56 const pc1 = new RTCPeerConnection(); 57 const pc2 = new RTCPeerConnection(); 58 59 // Add the canvas.captureStream track. 60 const ctx = c.getContext('2d'); 61 const [track] = c.captureStream(captureRate).getTracks(); 62 const sender = pc1.addTrack(track); 63 64 // Ice candidates signaling 65 for (const [local, remote] of [[pc1, pc2], [pc2, pc1]]) { 66 local.addEventListener("icecandidate", ({candidate}) => { 67 if (!candidate || remote.signalingState == "closed") { 68 return; 69 } 70 remote.addIceCandidate(candidate); 71 }); 72 } 73 74 // Offer/Answer exchange 75 await pc1.setLocalDescription(await pc1.createOffer()); 76 await pc2.setRemoteDescription(pc1.localDescription); 77 await pc2.setLocalDescription(await pc2.createAnswer()); 78 await pc1.setRemoteDescription(pc2.localDescription); 79 80 // Wait for connection 81 while ([pc1, pc2].some(pc => pc.iceConnectionState == "new" || 82 pc.iceConnectionState == "checking")) { 83 await Promise.any( 84 [pc1, pc2].map(p => new Promise(r => p.oniceconnectionstatechange = r))); 85 } 86 for (const [pc, name] of [[pc1, "pc1"], [pc2, "pc2"]]) { 87 ok(["connected", "completed"].includes(pc.iceConnectionState), 88 `${name} connection established (${pc.iceConnectionState})`); 89 } 90 91 // Draw to the canvas 92 const intervalMillis = 1000 / 60; 93 const getFrameCount = async () => { 94 const stats = await sender.getStats(); 95 const outbound = 96 [...stats.values()].find(({type}) => type == "outbound-rtp"); 97 return outbound?.framesEncoded ?? 0; 98 }; 99 // Wait for frame count change to ensure sender is working. 100 while (await getFrameCount() == 0) { 101 h.drawColor(c, h.green); 102 await new Promise(resolve => setTimeout(resolve, intervalMillis)); 103 } 104 const startFrameCount = await getFrameCount(); 105 const start = performance.now(); 106 let end = start; 107 while(end - start <= 1000) { 108 h.drawColor(c, h.green); 109 await new Promise(resolve => setTimeout(resolve, intervalMillis)); 110 end = performance.now(); 111 } 112 const framerate = (await getFrameCount() - startFrameCount) / ((end - start) / 1000); 113 pc1.close(); 114 pc2.close(); 115 return framerate; 116 } 117 118 (async () => { 119 // Disable background timer throttling so we can use setTimeout to draw to the 120 // canvas while the refresh driver is throttled. 121 await SpecialPowers.pushPrefEnv({ 122 set: [ 123 ["dom.timeout.enable_budget_timer_throttling", false], 124 ["dom.min_background_timeout_value", 0], 125 ["dom.min_background_timeout_value_without_budget_throttling", 0], 126 ["browser.link.open_newwindow", 3], // window.open in new tab 127 ], 128 }); 129 // Throttle the canvas' refresh driver by opening a new foreground tab 130 const foregroundTab = window.open("about:blank"); 131 132 // Let the throttling take effect 133 await new Promise(r => setTimeout(r, 500)); 134 135 const rAFRate = await measureRequestAnimationFrameRate(); 136 ok(rAFRate < 5, `rAF framerate is at ${rAFRate} fps`); 137 138 const setTimeoutRate = await measureSetTimeoutRate(); 139 ok(setTimeoutRate > 30, `setTimeout rate is at ${setTimeoutRate} tps`); 140 141 const autoRate = await measureCanvasCaptureFrameRate(); 142 ok(autoRate > 5, `captureStream() framerate is at ${autoRate} fps`); 143 144 const cappedRate = await measureCanvasCaptureFrameRate(10); 145 ok(cappedRate > 5, `captureStream(10) framerate is at ${cappedRate} fps`); 146 147 foregroundTab.close(); 148 SimpleTest.finish(); 149 })(); 150 </script>