/**
 * These tests test the ability for the PermissionUI module to open
 * permission prompts to the user. It also tests to ensure that
 * add-ons can introduce their own permission prompts.
 */

"use strict";

const { PermissionUI } = ChromeUtils.importESModule(
  "resource:///modules/PermissionUI.sys.mjs"
);

const { PermissionTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/PermissionTestUtils.sys.mjs"
);

/**
 * Tests the PermissionPromptForRequest prototype to ensure that a prompt
 * can be displayed. Does not test permission handling.
 */
add_task(async function test_permission_prompt_for_request() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "http://example.com/",
    },
    async function (browser) {
      const kTestNotificationID = "test-notification";
      const kTestMessage = "Test message";
      let mainAction = {
        label: "Main",
        accessKey: "M",
      };
      let secondaryAction = {
        label: "Secondary",
        accessKey: "S",
      };

      let mockRequest = makeMockPermissionRequest(browser);
      class TestPrompt extends PermissionUI.PermissionPromptForRequest {
        get request() {
          return mockRequest;
        }
        get notificationID() {
          return kTestNotificationID;
        }
        get message() {
          return kTestMessage;
        }
        get promptActions() {
          return [mainAction, secondaryAction];
        }
      }
      let shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;
      let notification = PopupNotifications.getNotification(
        kTestNotificationID,
        browser
      );
      Assert.ok(notification, "Should have gotten the notification");

      Assert.equal(
        notification.message,
        kTestMessage,
        "Should be showing the right message"
      );
      Assert.equal(
        notification.mainAction.label,
        mainAction.label,
        "The main action should have the right label"
      );
      Assert.equal(
        notification.mainAction.accessKey,
        mainAction.accessKey,
        "The main action should have the right access key"
      );
      Assert.equal(
        notification.secondaryActions.length,
        1,
        "There should only be 1 secondary action"
      );
      Assert.equal(
        notification.secondaryActions[0].label,
        secondaryAction.label,
        "The secondary action should have the right label"
      );
      Assert.equal(
        notification.secondaryActions[0].accessKey,
        secondaryAction.accessKey,
        "The secondary action should have the right access key"
      );
      Assert.ok(
        notification.options.displayURI.equals(mockRequest.principal.URI),
        "Should be showing the URI of the requesting page"
      );

      let removePromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popuphidden"
      );
      notification.remove();
      await removePromise;
    }
  );
});

/**
 * Tests that if the PermissionPrompt sets displayURI to false in popupOptions,
 * then there is no URI shown on the popupnotification.
 */
add_task(async function test_permission_prompt_for_popupOptions() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "http://example.com/",
    },
    async function (browser) {
      const kTestNotificationID = "test-notification";
      const kTestMessage = "Test message";
      let mainAction = {
        label: "Main",
        accessKey: "M",
      };
      let secondaryAction = {
        label: "Secondary",
        accessKey: "S",
      };

      let mockRequest = makeMockPermissionRequest(browser);
      class TestPrompt extends PermissionUI.PermissionPromptForRequest {
        get request() {
          return mockRequest;
        }
        get notificationID() {
          return kTestNotificationID;
        }
        get message() {
          return kTestMessage;
        }
        get promptActions() {
          return [mainAction, secondaryAction];
        }
        get popupOptions() {
          return {
            displayURI: false,
          };
        }
      }
      let shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;
      let notification = PopupNotifications.getNotification(
        kTestNotificationID,
        browser
      );

      Assert.ok(
        !notification.options.displayURI,
        "Should not show the URI of the requesting page"
      );

      let removePromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popuphidden"
      );
      notification.remove();
      await removePromise;
    }
  );
});

/**
 * Tests that if the PermissionPrompt has the permissionKey
 * set that permissions can be set properly by the user. Also
 * ensures that callbacks for promptActions are properly fired.
 */
add_task(async function test_with_permission_key() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "http://example.com",
    },
    async function (browser) {
      const kTestNotificationID = "test-notification";
      const kTestMessage = "Test message";
      const kTestPermissionKey = "test-permission-key";

      let allowed = false;
      let mainAction = {
        label: "Allow",
        accessKey: "M",
        action: SitePermissions.ALLOW,
        callback() {
          allowed = true;
        },
      };

      let denied = false;
      let secondaryAction = {
        label: "Deny",
        accessKey: "D",
        action: SitePermissions.BLOCK,
        callback() {
          denied = true;
        },
      };

      let mockRequest = makeMockPermissionRequest(browser);
      let principal = mockRequest.principal;
      registerCleanupFunction(function () {
        PermissionTestUtils.remove(principal.URI, kTestPermissionKey);
      });
      class TestPrompt extends PermissionUI.PermissionPromptForRequest {
        get request() {
          return mockRequest;
        }
        get notificationID() {
          return kTestNotificationID;
        }
        get permissionKey() {
          return kTestPermissionKey;
        }
        get message() {
          return kTestMessage;
        }
        get promptActions() {
          return [mainAction, secondaryAction];
        }
        get popupOptions() {
          return {
            checkbox: {
              label: "Remember this decision",
              show: true,
              checked: true,
            },
          };
        }
      }
      let shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;
      let notification = PopupNotifications.getNotification(
        kTestNotificationID,
        browser
      );
      Assert.ok(notification, "Should have gotten the notification");

      let curPerm = SitePermissions.getForPrincipal(
        principal,
        kTestPermissionKey,
        browser
      );
      Assert.equal(
        curPerm.state,
        SitePermissions.UNKNOWN,
        "Should be no permission set to begin with."
      );

      // First test denying the permission request without the checkbox checked.
      let popupNotification = getPopupNotificationNode();
      popupNotification.checkbox.checked = false;

      Assert.equal(
        notification.secondaryActions.length,
        1,
        "There should only be 1 secondary action"
      );
      await clickSecondaryAction();
      curPerm = SitePermissions.getForPrincipal(
        principal,
        kTestPermissionKey,
        browser
      );
      Assert.deepEqual(
        curPerm,
        {
          state: SitePermissions.BLOCK,
          scope: SitePermissions.SCOPE_TEMPORARY,
        },
        "Should have denied the action temporarily"
      );
      // Try getting the permission without passing the browser object (should fail).
      curPerm = PermissionTestUtils.getPermissionObject(
        principal.URI,
        kTestPermissionKey
      );
      Assert.equal(
        curPerm,
        null,
        "Should have made no permanent permission entry"
      );
      Assert.ok(denied, "The secondaryAction callback should have fired");
      Assert.ok(!allowed, "The mainAction callback should not have fired");
      Assert.ok(
        mockRequest._cancelled,
        "The request should have been cancelled"
      );
      Assert.ok(
        !mockRequest._allowed,
        "The request should not have been allowed"
      );

      // Clear the permission and pretend we never denied
      SitePermissions.removeFromPrincipal(
        principal,
        kTestPermissionKey,
        browser
      );
      denied = false;
      mockRequest._cancelled = false;

      // Bring the PopupNotification back up now...
      shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;

      // Test denying the permission request.
      Assert.equal(
        notification.secondaryActions.length,
        1,
        "There should only be 1 secondary action"
      );
      await clickSecondaryAction();
      curPerm = PermissionTestUtils.getPermissionObject(
        principal.URI,
        kTestPermissionKey
      );
      Assert.equal(
        curPerm.capability,
        Services.perms.DENY_ACTION,
        "Should have denied the action"
      );
      Assert.equal(curPerm.expireTime, 0, "Deny should be permanent");
      Assert.ok(denied, "The secondaryAction callback should have fired");
      Assert.ok(!allowed, "The mainAction callback should not have fired");
      Assert.ok(
        mockRequest._cancelled,
        "The request should have been cancelled"
      );
      Assert.ok(
        !mockRequest._allowed,
        "The request should not have been allowed"
      );

      // Clear the permission and pretend we never denied
      PermissionTestUtils.remove(principal.URI, kTestPermissionKey);
      denied = false;
      mockRequest._cancelled = false;

      // Bring the PopupNotification back up now...
      shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;

      // Test allowing the permission request.
      await clickMainAction();
      curPerm = PermissionTestUtils.getPermissionObject(
        principal.URI,
        kTestPermissionKey
      );
      Assert.equal(
        curPerm.capability,
        Services.perms.ALLOW_ACTION,
        "Should have allowed the action"
      );
      Assert.equal(curPerm.expireTime, 0, "Allow should be permanent");
      Assert.ok(!denied, "The secondaryAction callback should not have fired");
      Assert.ok(allowed, "The mainAction callback should have fired");
      Assert.ok(
        !mockRequest._cancelled,
        "The request should not have been cancelled"
      );
      Assert.ok(mockRequest._allowed, "The request should have been allowed");
    }
  );
});

/**
 * Tests that the onBeforeShow method will be called before
 * the popup appears.
 */
add_task(async function test_on_before_show() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "http://example.com",
    },
    async function (browser) {
      const kTestNotificationID = "test-notification";
      const kTestMessage = "Test message";

      let mainAction = {
        label: "Test action",
        accessKey: "T",
      };

      let mockRequest = makeMockPermissionRequest(browser);
      let beforeShown = false;
      class TestPrompt extends PermissionUI.PermissionPromptForRequest {
        get request() {
          return mockRequest;
        }
        get notificationID() {
          return kTestNotificationID;
        }
        get message() {
          return kTestMessage;
        }
        get promptActions() {
          return [mainAction];
        }
        get popupOptions() {
          return {
            checkbox: {
              label: "Remember this decision",
              show: true,
              checked: true,
            },
          };
        }
        onBeforeShow() {
          beforeShown = true;
          return true;
        }
      }
      let shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      Assert.ok(beforeShown, "Should have called onBeforeShown");
      await shownPromise;
      let notification = PopupNotifications.getNotification(
        kTestNotificationID,
        browser
      );

      let removePromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popuphidden"
      );
      notification.remove();
      await removePromise;
    }
  );
});

/**
 * Tests that we can open a PermissionPrompt without wrapping a
 * nsIContentPermissionRequest.
 */
add_task(async function test_no_request() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "http://example.com",
    },
    async function (browser) {
      const kTestNotificationID = "test-notification";
      let allowed = false;
      let mainAction = {
        label: "Allow",
        accessKey: "M",
        callback() {
          allowed = true;
        },
      };

      let denied = false;
      let secondaryAction = {
        label: "Deny",
        accessKey: "D",
        callback() {
          denied = true;
        },
      };

      const kTestMessage = "Test message with no request";
      let principal = browser.contentPrincipal;
      let beforeShown = false;
      class TestPrompt extends PermissionUI.PermissionPromptForRequest {
        get notificationID() {
          return kTestNotificationID;
        }
        get principal() {
          return principal;
        }
        get browser() {
          return browser;
        }
        get message() {
          return kTestMessage;
        }
        get promptActions() {
          return [mainAction, secondaryAction];
        }
        onBeforeShow() {
          beforeShown = true;
          return true;
        }
      }

      let shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      Assert.ok(beforeShown, "Should have called onBeforeShown");
      await shownPromise;
      let notification = PopupNotifications.getNotification(
        kTestNotificationID,
        browser
      );

      Assert.equal(
        notification.message,
        kTestMessage,
        "Should be showing the right message"
      );
      Assert.equal(
        notification.mainAction.label,
        mainAction.label,
        "The main action should have the right label"
      );
      Assert.equal(
        notification.mainAction.accessKey,
        mainAction.accessKey,
        "The main action should have the right access key"
      );
      Assert.equal(
        notification.secondaryActions.length,
        1,
        "There should only be 1 secondary action"
      );
      Assert.equal(
        notification.secondaryActions[0].label,
        secondaryAction.label,
        "The secondary action should have the right label"
      );
      Assert.equal(
        notification.secondaryActions[0].accessKey,
        secondaryAction.accessKey,
        "The secondary action should have the right access key"
      );
      Assert.ok(
        notification.options.displayURI.equals(principal.URI),
        "Should be showing the URI of the requesting page"
      );

      // First test denying the permission request.
      Assert.equal(
        notification.secondaryActions.length,
        1,
        "There should only be 1 secondary action"
      );
      await clickSecondaryAction();
      Assert.ok(denied, "The secondaryAction callback should have fired");
      Assert.ok(!allowed, "The mainAction callback should not have fired");

      shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;

      // Next test allowing the permission request.
      await clickMainAction();
      Assert.ok(allowed, "The mainAction callback should have fired");
    }
  );
});

/**
 * Tests that when the tab is moved to a different window, the notification
 * is transferred to the new window.
 */
add_task(async function test_window_swap() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: "http://example.com",
    },
    async function (browser) {
      const kTestNotificationID = "test-notification";
      const kTestMessage = "Test message";

      let mainAction = {
        label: "Test action",
        accessKey: "T",
      };
      let secondaryAction = {
        label: "Secondary",
        accessKey: "S",
      };

      let mockRequest = makeMockPermissionRequest(browser);
      class TestPrompt extends PermissionUI.PermissionPromptForRequest {
        get request() {
          return mockRequest;
        }
        get notificationID() {
          return kTestNotificationID;
        }
        get message() {
          return kTestMessage;
        }
        get promptActions() {
          return [mainAction, secondaryAction];
        }
      }
      let shownPromise = BrowserTestUtils.waitForEvent(
        PopupNotifications.panel,
        "popupshown"
      );
      new TestPrompt().prompt();
      await shownPromise;

      let newWindowOpened = BrowserTestUtils.waitForNewWindow();
      gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
      let newWindow = await newWindowOpened;
      // We may have already opened the panel, because it was open before we moved the tab.
      if (newWindow.PopupNotifications.panel.state != "open") {
        shownPromise = BrowserTestUtils.waitForEvent(
          newWindow.PopupNotifications.panel,
          "popupshown"
        );
        new TestPrompt().prompt();
        await shownPromise;
      }

      let notification = newWindow.PopupNotifications.getNotification(
        kTestNotificationID,
        newWindow.gBrowser.selectedBrowser
      );
      Assert.ok(notification, "Should have gotten the notification");

      Assert.equal(
        notification.message,
        kTestMessage,
        "Should be showing the right message"
      );
      Assert.equal(
        notification.mainAction.label,
        mainAction.label,
        "The main action should have the right label"
      );
      Assert.equal(
        notification.mainAction.accessKey,
        mainAction.accessKey,
        "The main action should have the right access key"
      );
      Assert.equal(
        notification.secondaryActions.length,
        1,
        "There should only be 1 secondary action"
      );
      Assert.equal(
        notification.secondaryActions[0].label,
        secondaryAction.label,
        "The secondary action should have the right label"
      );
      Assert.equal(
        notification.secondaryActions[0].accessKey,
        secondaryAction.accessKey,
        "The secondary action should have the right access key"
      );
      Assert.ok(
        notification.options.displayURI.equals(mockRequest.principal.URI),
        "Should be showing the URI of the requesting page"
      );

      await BrowserTestUtils.closeWindow(newWindow);
    }
  );
});
