/* eslint-disable mozilla/no-arbitrary-setTimeout */

/**
 * This test is used to ensure web audio can be allowed to start when we have
 * GUM permission.
 */
add_task(async function startTestingWebAudioWithGUM() {
  info("- setup test preference -");
  await setupTestPreferences();

  info("- test web audio with gUM success -");
  await testWebAudioWithGUM({
    constraints: { audio: true },
    shouldAllowStartingContext: true,
  });
  await testWebAudioWithGUM({
    constraints: { video: true },
    shouldAllowStartingContext: true,
  });
  await testWebAudioWithGUM({
    constraints: { video: true, audio: true },
    shouldAllowStartingContext: true,
  });

  await SpecialPowers.pushPrefEnv({
    set: [["media.navigator.permission.force", true]],
  }).then(async function () {
    info("- test web audio with gUM denied -");
    await testWebAudioWithGUM({
      constraints: { video: true },
      shouldAllowStartingContext: false,
    });
    await testWebAudioWithGUM({
      constraints: { audio: true },
      shouldAllowStartingContext: false,
    });
    await testWebAudioWithGUM({
      constraints: { video: true, audio: true },
      shouldAllowStartingContext: false,
    });
  });
});

/**
 * testing helper functions
 */
function setupTestPreferences() {
  return SpecialPowers.pushPrefEnv({
    set: [
      ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
      ["media.autoplay.blocking_policy", 0],
      ["media.autoplay.block-event.enabled", true],
      ["media.navigator.permission.fake", true],
    ],
  });
}

function createAudioContext() {
  content.ac = new content.AudioContext();
  let ac = content.ac;
  ac.resumePromises = [];
  ac.stateChangePromise = new Promise(resolve => {
    ac.addEventListener(
      "statechange",
      function () {
        resolve();
      },
      { once: true }
    );
  });
  ac.notAllowedToStart = new Promise(resolve => {
    ac.addEventListener(
      "blocked",
      function () {
        resolve();
      },
      { once: true }
    );
  });
}

async function checkingAudioContextRunningState() {
  let ac = content.ac;
  await ac.notAllowedToStart;
  Assert.strictEqual(ac.state, "suspended", `AudioContext is not started yet.`);
}

function resumeWithoutExpectedSuccess() {
  let ac = content.ac;
  let promise = ac.resume();
  ac.resumePromises.push(promise);
  return new Promise((resolve, reject) => {
    content.setTimeout(() => {
      if (ac.state == "suspended") {
        ok(true, "audio context is still suspended");
        resolve();
      } else {
        reject("audio context should not be allowed to start");
      }
    }, 2000);
  });
}

function resumeWithExpectedSuccess() {
  let ac = content.ac;
  ac.resumePromises.push(ac.resume());
  return Promise.all(ac.resumePromises).then(() => {
    Assert.equal(ac.state, "running", "audio context starts running");
  });
}

async function callGUM(testParameters) {
  info("- calling gum with " + JSON.stringify(testParameters.constraints));
  if (testParameters.shouldAllowStartingContext) {
    // Because of the prefs we've set and passed, this is going to allow the
    // window to start an AudioContext synchronously.
    testParameters.constraints.fake = true;
    await content.navigator.mediaDevices.getUserMedia(
      testParameters.constraints
    );
    return;
  }

  // Call gUM, without sucess: we've made it so that only fake requests
  // succeed without permission, and this is requesting non-fake-devices. Return
  // a resolved promise so that the test continues, but the getUserMedia Promise
  // will never be resolved.
  // We do this to check that it's not merely calling gUM that allows starting
  // an AudioContext, it's having the Promise it return resolved successfuly,
  // because of saved permissions for an origin or explicit user consent using
  // the prompt.
  content.navigator.mediaDevices.getUserMedia(testParameters.constraints);
}

async function testWebAudioWithGUM(testParameters) {
  info("- open new tab -");
  let tab = await BrowserTestUtils.openNewForegroundTab(
    window.gBrowser,
    "https://example.com"
  );
  info("- create audio context -");
  await SpecialPowers.spawn(tab.linkedBrowser, [], createAudioContext);

  info("- check whether audio context starts running -");
  try {
    await SpecialPowers.spawn(
      tab.linkedBrowser,
      [],
      checkingAudioContextRunningState
    );
  } catch (error) {
    ok(false, error.toString());
  }

  try {
    await SpecialPowers.spawn(tab.linkedBrowser, [testParameters], callGUM);
  } catch (error) {
    ok(false, error.toString());
  }

  info("- calling resume() again");
  try {
    let resumeFunc = testParameters.shouldAllowStartingContext
      ? resumeWithExpectedSuccess
      : resumeWithoutExpectedSuccess;
    await SpecialPowers.spawn(tab.linkedBrowser, [], resumeFunc);
  } catch (error) {
    ok(false, error.toString());
  }

  info("- remove tab -");
  await BrowserTestUtils.removeTab(tab);
}
