tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>