<!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>

