<html>
  <head>
    <title>WebMIDI Listener Test</title>
    <script src="/tests/SimpleTest/SimpleTest.js"></script>
    <script type="application/javascript" src="MIDITestUtils.js"></script>
  </head>

  <body onload="runTests()">
    <iframe id="subdomain"></iframe>
    <iframe id="localhost"></iframe>
    <script class="testbody" type="application/javascript">
    SimpleTest.waitForExplicitFinish();
    const filePath = "/tests/dom/midi/tests/file_midi_permission_gated.html";
    // Generally this runs on example.com but with --enable-xorigin-tests it runs
    // on example.org.
    let subdomainURL = "https://test1." + location.host + filePath;
    $("subdomain").src = subdomainURL;
    // For some reason the mochitest server returns "Bad request" with localhost,
    // but permits the loopback address. That's good enough for testing purposes.
    $("localhost").src = "http://127.0.0.1:8888" + filePath;

    function waitForMessage() {
      return new Promise((resolve) => {
        window.addEventListener("message", (e) => resolve(e.data), {once: true});
      });
    }

    async function runTests() {
      await SpecialPowers.pushPrefEnv({
        set: [
          ["dom.webmidi.enabled", true],
          ["midi.testing", true],
        ],
      });
      ok(
        await SpecialPowers.testPermission(
          "midi-sysex",
          SpecialPowers.Services.perms.UNKNOWN_ACTION,
          document
        ),
        "midi-sysex value should have UNKNOWN permission"
      );
      ok(
        await SpecialPowers.testPermission(
          "midi-sysex",
          SpecialPowers.Services.perms.UNKNOWN_ACTION,
          subdomainURL
        ),
        "permission should also not be set for subdomain"
      );

      let onChangeCalled = 0;
      let onChangeCalledWithSysex = 0;
      // We expect the same states with and without sysex support.
      const expectedChangedStates = ["denied", "granted", "prompt"];

      const results = [];
      for (let sysex of [false, true]) {
        let result = await navigator.permissions.query({ name: "midi", sysex });
        is(result?.state, "prompt", "expected 'prompt' permission status");
        // Register two unique listeners that should be invoked every time we
        // change permissions in the rest of this test case: one with sysex
        // support, and the other one without.
        if (sysex) {
          result.onchange = () => {
            is(
              result.state,
              expectedChangedStates[onChangeCalledWithSysex++],
              "expected change event with sysex support"
            );
          };
          results.push(result);
        } else {
          result.onchange = () => {
            is(
              result.state,
              expectedChangedStates[onChangeCalled++],
              "expected change event"
            );
          };
          results.push(result);
        }
      }

      // Explicitly set the permission as blocked, and expect the
      // `requestMIDIAccess` call to be automatically rejected (not having any
      // permission set would trigger the synthetic addon install provided by
      // AddonManager and SitePermsAddonProvider).
      await SpecialPowers.addPermission(
        "midi-sysex",
        SpecialPowers.Services.perms.DENY_ACTION,
        document
      );
      await SpecialPowers.addPermission(
        "midi",
        SpecialPowers.Services.perms.DENY_ACTION,
        document
      );
      for (let sysex of [false, true]) {
        try {
          await navigator.requestMIDIAccess({ sysex });
          ok(false, "MIDI Access Request gate allowed but expected to be denied");
        } catch (ex) {
          ok(true, "MIDI Access Request denied by default");
        }

        let result = await navigator.permissions.query({ name: "midi", sysex });
        // We expect "denied" because that's what has been set above (with
        // `SpecialPowers.addPermission()`). In practice, this state should
        // never be returned since explicit rejection is handled at the add-on
        // installation level.
        is(result?.state, "denied", "expected 'denied' permission status");
      }

      // Gated permission should prompt for localhost.
      //
      // Note: We don't appear to have good test machinery anymore for
      // navigating prompts from a plain mochitest. If you uncomment the lines
      // below and run the test interactively, it should pass. Given that this
      // is a niche feature that's unlikely to break, it doesn't seem worth
      // investing in complicated test infrastructure to check it in automation.
      // for (let sysex of [false, true]) {
      //   $("localhost").contentWindow.postMessage(sysex, "*");
      //   let response = await waitForMessage();
      //   is(response, "succeeded", "MIDI Access Request allowed for localhost");
      // }

      // When an addon is installed, the permission is inserted.  Test
      // that the request succeeds after we insert the permission.
      await SpecialPowers.addPermission(
        "midi-sysex",
        SpecialPowers.Services.perms.ALLOW_ACTION,
        document
      );
      await SpecialPowers.addPermission(
        "midi",
        SpecialPowers.Services.perms.ALLOW_ACTION,
        document
      );
      // Gated permission should allow access after addon inserted permission.
      for (let sysex of [false, true]) {
        try {
          await navigator.requestMIDIAccess({ sysex });
          ok(true, "MIDI Access Request allowed");
        } catch (ex) {
          ok(false, "MIDI Access Request failed");
        }

        let result = await navigator.permissions.query({ name: "midi", sysex });
        is(result?.state, "granted", "expected 'granted' permission status");
      }

      // Gated permission should also apply to subdomains.
      for (let sysex of [false, true]) {
        $("subdomain").contentWindow.postMessage(sysex, "*");
        let response = await waitForMessage();
        is(response, "succeeded", "MIDI Access Request allowed for subdomain");
      }

      is(
        onChangeCalled,
        expectedChangedStates.length - 1,
        `expected onchange listener to have been called ${expectedChangedStates.length - 1} times`
      );
      is(
        onChangeCalledWithSysex,
        expectedChangedStates.length - 1,
        `expected onchange listener to have been called ${expectedChangedStates.length - 1} times (sysex)`
      );

      // Remove the permission.
      await SpecialPowers.removePermission("midi-sysex", document);
      await SpecialPowers.removePermission("midi", document);

      results.forEach(result => result.onchange = null);

      SimpleTest.finish();
    }
    </script>
  </body>
</html>
