<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1737682

This is a test of the macOS video low power telemetry, defined as GFX_MACOS_VIDEO_LOW_POWER
enums in Histograms.json. The test will load some video media files, play them for some
number of frames, then check that appropriate telemetry has been recorded. Since the
telemetry fires based on the number of frames painted, the test is structured around
the `media.video_stats.enabled` pref, which allows us to monitor the number of frames
that have been shown. The telemetry fires every 600 frames. To make sure we have enough
painted frames to trigger telemetry, we run the media for many frames and then check
that the expected telemetry value has been recorded at least once.

The tests are run via the MediaTestManager, which loads and plays the video sequentially.
Since we want to test different telemetry outcomes, we have some special setup and teardown
steps we run before each video. We keep track of which test we are running with a simple
1-based index, `testIndex`. So our test structure is:

1) Set some initial preferences that need to be set for the whole test series.
2) Start MediaTestManager with a set of media to play.
3) When a video arrives, increment testIndex and call preTest.
4) Run the video for PAINTED_FRAMES frames, then call postTest, which checks telemetry
   and cleans up.
5) Tell the MediaTestManager we've finished with that video, which triggers the next.
-->

<head>
  <title>Test of macOS video low power telemetry</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
  <script type="text/javascript" src="manifest.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1737682">Mozilla Bug 1737682</a>
<pre id="test">
<script class="testbody" type="text/javascript">
  // Parallel test must be disabled for media.video_stats.enabled is a global setting
  // to prevent the setting from changing unexpectedly in the middle of the test.
  PARALLEL_TESTS = 1;
  SimpleTest.waitForExplicitFinish();

  // How many frames do we run? Must be more than the value set by the
  // gfx.core-animation.low-power-telemetry-frames pref, because that's how
  // many frames must be shown before telemetry is emitted. We present for
  // longer than that to ensure that the telemetry is emitted at least once.
  // This also has to be enough frames to overcome the animated "<video> is
  // now fullscreen." popup that appears over the video. While that modal
  // is visible and animating away, it will register as an overlay and
  // prevent low power mode. As an estimate, we need at least 200 frames to
  // ensure that animation has finished and there has been time to fire the
  // telemetry at least once after it has finished.
  const TELEMETRY_FRAMES = SpecialPowers.getIntPref("gfx.core-animation.low-power-telemetry-frames");
  const PAINTED_FRAMES = Math.max(Math.floor(TELEMETRY_FRAMES * 1.1), 200 + TELEMETRY_FRAMES);
  info(`Running each video for ${PAINTED_FRAMES} frames.`);

  // Taken from TelemetryHistogramEnums.h
  const GFX_MACOS_VIDEO_LOW_POWER_LowPower = 1;
  const GFX_MACOS_VIDEO_LOW_POWER_FailWindowed = 3;

  var manager = new MediaTestManager;

  // Define some state variables that we'll use to manage multiple setup,
  // check, and teardown steps using the same preTest and postTest functions.
  var testIndex = 0;
  var testsComplete = false;

  async function retrieveSnapshotAndClearTelemetry() {
    return SpecialPowers.spawnChrome([], () => {
      let hist = Services.telemetry.getHistogramById("GFX_MACOS_VIDEO_LOW_POWER");
      let snap = hist.snapshot();
      hist.clear();
      return snap;
    });
  }

  function checkSnapshot(snap, category) {
    if (!snap) {
      return;
    }

    // For debugging purposes, build a string of the snapshot values hashmap.
    let valuesString = '';
    let keys = Object.keys(snap.values);
    keys.forEach(k => {
      valuesString += `[${k}] = ${snap.values[k]}, `;
    });
    info(`Test ${testIndex} telemetry values are ${valuesString}`);

    // Since we've run the media for more frames than the telemetry needs to
    // fire, we should have at least 1 of the expected value.
    let val = snap.values[category];
    if (!val) {
      val = 0;
    }
    ok(val > 0, `Test ${testIndex} enum ${category} should have at least 1 occurrence; found ${val}.`);
  }

  async function doPreTest(v) {
    if (testsComplete) {
      manager.finished(v.token);
      return;
    }

    switch (testIndex) {
      case 1: {
        // Test 1 (FailWindowed): No special setup.
        break;
      }

      case 2: {
        // Test 2 (LowPower): Enter fullscreen.

        // Wait one frame to ensure the old video is no longer in the layer tree, which
        // causes problems with the telemetry.
        await new Promise(resolve => requestAnimationFrame(resolve));

        info("Attempting to enter fullscreen.");
        ok(document.fullscreenEnabled, "Document should permit fullscreen-ing of elements.");
        await SpecialPowers.wrap(v).requestFullscreen();
        ok(document.fullscreenElement, "Document should have one element in fullscreen.");
        break;
      }
    }
  }

  async function doPostTest(v) {
    info(`Test ${testIndex} attempting to retrieve telemetry.`);
    let snap = await retrieveSnapshotAndClearTelemetry();
    ok(snap, `Test ${testIndex} should have telemetry.`);

    switch (testIndex) {
      case 1: {
        // Test 1 (FailWindowed): Just check.
        checkSnapshot(snap, GFX_MACOS_VIDEO_LOW_POWER_FailWindowed);
        break;
      }

      case 2: {
        // Test 2 (LowPower): Check, then exit fullscreen.
        checkSnapshot(snap, GFX_MACOS_VIDEO_LOW_POWER_LowPower);

        info("Attempting to exit fullscreen.");
        await SpecialPowers.wrap(document).exitFullscreen();
        ok(!document.fullscreenElement, "Document should be out of fullscreen.");

        // This is the last test.
        testsComplete = true;
        break;
      }
    }
  }

  function ontimeupdate(event) {
    let v = event.target;
    // Count painted frames to see when we should hit some telemetry threshholds.
    if (v.mozPaintedFrames >= PAINTED_FRAMES) {
      v.pause();
      v.removeEventListener("timeupdate", ontimeupdate);

      doPostTest(v).then(() => {
        let token = v.token;
        removeNodeAndSource(v);
        manager.finished(token);
      });
    }
  }

  function startTest(test, token) {
    manager.started(token);

    testIndex++;
    info(`Starting test ${testIndex} video ${test.name}.`);

    let v = document.createElement('video');
    v.addEventListener("timeupdate", ontimeupdate);
    v.token = token;
    v.src = test.name;
    v.loop = true;
    document.body.appendChild(v);

    doPreTest(v).then(() => {
      info(`Playing test ${testIndex}.`);
      v.play();
    });
  }

  SpecialPowers.pushPrefEnv({"set": [
        ["media.video_stats.enabled", true],
        ["gfx.core-animation.specialize-video", true],
    ]},
    async function() {
      // Clear out existing telemetry in case previous tests were displaying
      // video.
      info("Clearing initial telemetry.");
      await retrieveSnapshotAndClearTelemetry();

      ok(gVideoLowPowerTests.length >= 2, "Should have enough videos to test.");
      manager.runTests(gVideoLowPowerTests, startTest);
  });

</script>
</pre>
</body>
</html>
