/**
 * This test is used to ensure that invisible play time would be accumulated
 * when tab is in background. It also checks the HDR video accumulation time.
 * However, this test won't directly check the reported telemetry result,
 * because we can't check the snapshot histogram in the content process.
 * The actual probe checking happens in `test_accumulated_play_time.html`.
 */
"use strict";

const PAGE_URL =
  "https://example.com/browser/dom/media/test/browser/file_media.html";

// This HDR tests will only pass on platforms that accurately report color
// depth in their VideoInfo structures. Presently, that is only true for
// macOS.

const reportsColorDepthFromVideoData = AppConstants.platform == "macosx";

add_task(async function testChangingTabVisibilityAffectsInvisiblePlayTime() {
  const originalTab = gBrowser.selectedTab;
  const mediaTab = await openMediaTab(PAGE_URL);

  info(`measuring play time when tab is in foreground`);
  await startMedia({
    mediaTab,
    shouldAccumulateTime: true,
    shouldAccumulateInvisibleTime: false,
    shouldAccumulateHDRTime: reportsColorDepthFromVideoData,
  });
  await pauseMedia(mediaTab);

  info(`measuring play time when tab is in background`);
  await BrowserTestUtils.switchTab(window.gBrowser, originalTab);
  await startMedia({
    mediaTab,
    shouldAccumulateTime: true,
    shouldAccumulateInvisibleTime: true,
    shouldAccumulateHDRTime: reportsColorDepthFromVideoData,
  });
  await pauseMedia(mediaTab);

  BrowserTestUtils.removeTab(mediaTab);
});

/**
 * Following are helper functions.
 */
async function openMediaTab(url) {
  info(`open tab for media playback`);
  const tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url);
  info(`add content helper functions and variables`);
  await SpecialPowers.spawn(tab.linkedBrowser, [], _ => {
    content.waitForOnTimeUpdate = element => {
      return new Promise(resolve => {
        element.addEventListener(
          "timeupdate",
          e => {
            resolve();
          },
          { once: true }
        );
      });
    };

    content.sleep = ms => {
      return new Promise(resolve => content.setTimeout(resolve, ms));
    };

    content.assertAttributeDefined = (videoChrome, checkType) => {
      ok(videoChrome[checkType] != undefined, `${checkType} exists`);
    };
    content.assertValueEqualTo = (videoChrome, checkType, expectedValue) => {
      content.assertAttributeDefined(videoChrome, checkType);
      is(
        videoChrome[checkType],
        expectedValue,
        `${checkType} equals to ${expectedValue}`
      );
    };
    content.assertValueConstantlyIncreases = async (videoChrome, checkType) => {
      content.assertAttributeDefined(videoChrome, checkType);
      const valueSnapshot = videoChrome[checkType];
      await content.waitForOnTimeUpdate(videoChrome);
      ok(
        videoChrome[checkType] > valueSnapshot,
        `${checkType} keeps increasing`
      );
    };
    content.assertValueKeptUnchanged = async (videoChrome, checkType) => {
      content.assertAttributeDefined(videoChrome, checkType);
      const valueSnapshot = videoChrome[checkType];
      await content.sleep(1000);
      ok(
        videoChrome[checkType] == valueSnapshot,
        `${checkType} keeps unchanged`
      );
    };
  });
  return tab;
}

function startMedia({
  mediaTab,
  shouldAccumulateTime,
  shouldAccumulateInvisibleTime,
  shouldAccumulateHDRTime,
}) {
  return SpecialPowers.spawn(
    mediaTab.linkedBrowser,
    [
      shouldAccumulateTime,
      shouldAccumulateInvisibleTime,
      shouldAccumulateHDRTime,
    ],
    async (accumulateTime, accumulateInvisibleTime, accumulateHDRTime) => {
      const video = content.document.getElementById("video");
      ok(
        await video.play().then(
          () => true,
          () => false
        ),
        "video started playing"
      );
      const videoChrome = SpecialPowers.wrap(video);
      if (accumulateTime) {
        await content.assertValueConstantlyIncreases(
          videoChrome,
          "totalVideoPlayTime"
        );
      } else {
        await content.assertValueKeptUnchanged(
          videoChrome,
          "totalVideoPlayTime"
        );
      }
      if (accumulateInvisibleTime) {
        await content.assertValueConstantlyIncreases(
          videoChrome,
          "invisiblePlayTime"
        );
      } else {
        await content.assertValueKeptUnchanged(
          videoChrome,
          "invisiblePlayTime"
        );
      }

      const videoHDR = content.document.getElementById("videoHDR");

      // HDR test video might not decode on all platforms, so catch
      // the play() command and exit early in such a case. Failure to
      // decode might manifest as a timeout, so add a rejection race
      // to catch that.
      let didDecode = true;
      const playPromise = videoHDR.play().then(
        () => true,
        () => false
      );
      /* eslint-disable mozilla/no-arbitrary-setTimeout */
      const tooSlowPromise = new Promise(resolve =>
        setTimeout(() => {
          info("videoHDR timed out.");
          didDecode = false;
          resolve(false);
        }, 1000)
      );
      /* eslint-enable mozilla/no-arbitrary-setTimeout */

      let didPlay = await Promise.race(playPromise, tooSlowPromise).catch(
        err => {
          info("videoHDR failed to decode with error: " + err.message);
          didDecode = false;
          return false;
        }
      );

      if (!didDecode) {
        return;
      }

      ok(didPlay, "videoHDR started playing");
      const videoHDRChrome = SpecialPowers.wrap(videoHDR);
      if (accumulateHDRTime) {
        await content.assertValueConstantlyIncreases(
          videoHDRChrome,
          "totalVideoHDRPlayTime"
        );
      } else {
        await content.assertValueKeptUnchanged(
          videoHDRChrome,
          "totalVideoHDRPlayTime"
        );
      }
    }
  );
}

function pauseMedia(tab) {
  return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => {
    const video = content.document.getElementById("video");
    video.pause();
    ok(true, "video paused");
    const videoChrome = SpecialPowers.wrap(video);
    await content.assertValueKeptUnchanged(videoChrome, "totalVideoPlayTime");
    await content.assertValueKeptUnchanged(videoChrome, "invisiblePlayTime");

    const videoHDR = content.document.getElementById("videoHDR");
    videoHDR.pause();
    ok(true, "videoHDR paused");
    const videoHDRChrome = SpecialPowers.wrap(videoHDR);
    await content.assertValueKeptUnchanged(
      videoHDRChrome,
      "totalVideoHDRPlayTime"
    );
  });
}
