/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
/* eslint-disable mozilla/no-arbitrary-setTimeout */

"use strict";

const ORIGIN = "https://example.com";
const PERMISSIONS_PAGE =
  getRootDirectory(gTestPath).replace("chrome://mochitests/content", ORIGIN) +
  "permissions.html";

// Ignore promise rejection caused by clicking Deny button.
const { PromiseTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/The request is not allowed/);

const EXPIRE_TIME_MS = 100;
const TIMEOUT_MS = 500;

const EXPIRE_TIME_CUSTOM_MS = 1000;
const TIMEOUT_CUSTOM_MS = 1500;

const kVREnabled = SpecialPowers.getBoolPref("dom.vr.enabled");

// Test that temporary permissions can be re-requested after they expired
// and that the identity block is updated accordingly.
add_task(async function testTempPermissionRequestAfterExpiry() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["privacy.temporary_permission_expire_time_ms", EXPIRE_TIME_MS],
      ["media.navigator.permission.fake", true],
      ["dom.vr.always_support_vr", true],
    ],
  });

  let principal =
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(ORIGIN);
  let ids = ["geo", "camera"];

  if (kVREnabled) {
    ids.push("xr");
  }

  for (let id of ids) {
    await BrowserTestUtils.withNewTab(
      PERMISSIONS_PAGE,
      async function (browser) {
        let blockedIcon = gPermissionPanel._identityPermissionBox.querySelector(
          `.blocked-permission-icon[data-permission-id='${id}']`
        );

        SitePermissions.setForPrincipal(
          principal,
          id,
          SitePermissions.BLOCK,
          SitePermissions.SCOPE_TEMPORARY,
          browser
        );

        Assert.deepEqual(
          SitePermissions.getForPrincipal(principal, id, browser),
          {
            state: SitePermissions.BLOCK,
            scope: SitePermissions.SCOPE_TEMPORARY,
          }
        );

        ok(
          blockedIcon.hasAttribute("showing"),
          "blocked permission icon is shown"
        );

        await new Promise(c => setTimeout(c, TIMEOUT_MS));

        Assert.deepEqual(
          SitePermissions.getForPrincipal(principal, id, browser),
          {
            state: SitePermissions.UNKNOWN,
            scope: SitePermissions.SCOPE_PERSISTENT,
          }
        );

        let popupshown = BrowserTestUtils.waitForEvent(
          PopupNotifications.panel,
          "popupshown"
        );

        // Request a permission;
        await BrowserTestUtils.synthesizeMouseAtCenter(`#${id}`, {}, browser);

        await popupshown;

        ok(
          !blockedIcon.hasAttribute("showing"),
          "blocked permission icon is not shown"
        );

        let popuphidden = BrowserTestUtils.waitForEvent(
          PopupNotifications.panel,
          "popuphidden"
        );

        let notification = PopupNotifications.panel.firstElementChild;
        EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});

        await popuphidden;

        SitePermissions.removeFromPrincipal(principal, id, browser);
      }
    );
  }
});

/**
 * Test whether the identity UI shows the permission granted state.
 *
 * @param {boolean} state - true = Shows permission granted, false otherwise.
 */
async function testIdentityPermissionGrantedState(state) {
  let hasAttribute;
  let msg = `Identity permission box ${
    state ? "shows" : "does not show"
  } granted permissions.`;
  await TestUtils.waitForCondition(() => {
    hasAttribute =
      gPermissionPanel._identityPermissionBox.hasAttribute("hasPermissions");
    return hasAttribute == state;
  }, msg);
  is(hasAttribute, state, msg);
}

// Test that temporary permissions can have custom expiry time and the identity
// block is updated correctly on expiry.
add_task(async function testTempPermissionCustomExpiry() {
  const TEST_ID = "geo";
  // Set a default expiry time which is lower than the custom one we'll set.
  await SpecialPowers.pushPrefEnv({
    set: [["privacy.temporary_permission_expire_time_ms", EXPIRE_TIME_MS]],
  });

  await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async browser => {
    Assert.deepEqual(
      SitePermissions.getForPrincipal(null, TEST_ID, browser),
      {
        state: SitePermissions.UNKNOWN,
        scope: SitePermissions.SCOPE_PERSISTENT,
      },
      "Permission not set initially"
    );

    await testIdentityPermissionGrantedState(false);

    // Set permission with custom expiry time.
    SitePermissions.setForPrincipal(
      null,
      "geo",
      SitePermissions.ALLOW,
      SitePermissions.SCOPE_TEMPORARY,
      browser,
      EXPIRE_TIME_CUSTOM_MS
    );

    await testIdentityPermissionGrantedState(true);

    // We've set the permission, start the timer promise.
    let timeout = new Promise(resolve =>
      setTimeout(resolve, TIMEOUT_CUSTOM_MS)
    );

    Assert.deepEqual(
      SitePermissions.getForPrincipal(null, TEST_ID, browser),
      {
        state: SitePermissions.ALLOW,
        scope: SitePermissions.SCOPE_TEMPORARY,
      },
      "We should see the temporary permission we just set."
    );

    // Wait for half of the expiry time.
    await new Promise(resolve =>
      setTimeout(resolve, EXPIRE_TIME_CUSTOM_MS / 2)
    );
    Assert.deepEqual(
      SitePermissions.getForPrincipal(null, TEST_ID, browser),
      {
        state: SitePermissions.ALLOW,
        scope: SitePermissions.SCOPE_TEMPORARY,
      },
      "Temporary permission should not have expired yet."
    );

    // Wait until permission expiry.
    await timeout;

    // Identity permission section should have updated by now. It should do this
    // without relying on side-effects of the SitePermissions getter.
    await testIdentityPermissionGrantedState(false);

    Assert.deepEqual(
      SitePermissions.getForPrincipal(null, TEST_ID, browser),
      {
        state: SitePermissions.UNKNOWN,
        scope: SitePermissions.SCOPE_PERSISTENT,
      },
      "Permission should have expired"
    );
  });
});
