"use strict";

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

AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
AddonTestUtils.createAppInfo(
  "xpcshell@tests.mozilla.org",
  "XPCShell",
  "1",
  "42"
);

add_task(async function setup() {
  // Bug 1646182: Force ExtensionPermissions to run in rkv mode, the legacy
  // storage mode will run in xpcshell-legacy-ep.ini
  await ExtensionPermissions._uninit();

  Services.prefs.setBoolPref(
    "extensions.webextOptionalPermissionPrompts",
    false
  );
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("extensions.webextOptionalPermissionPrompts");
  });
  await AddonTestUtils.promiseStartupManager();
  AddonTestUtils.usePrivilegedSignatures = false;
});

async function test_migrated_permission_to_optional({ manifest_version }) {
  let id = "permission-upgrade@test";
  let extensionData = {
    manifest: {
      version: "1.0",
      browser_specific_settings: { gecko: { id } },
      permissions: [
        "webRequest",
        "tabs",
        "http://example.net/*",
        "http://example.com/*",
      ],
    },
    useAddonManager: "permanent",
  };

  function checkPermissions() {
    let policy = WebExtensionPolicy.getByID(id);
    ok(policy.hasPermission("webRequest"), "addon has webRequest permission");
    ok(policy.hasPermission("tabs"), "addon has tabs permission");
    ok(
      policy.canAccessURI(Services.io.newURI("http://example.net/")),
      "addon has example.net host permission"
    );
    ok(
      policy.canAccessURI(Services.io.newURI("http://example.com/")),
      "addon has example.com host permission"
    );
    ok(
      !policy.canAccessURI(Services.io.newURI("http://other.com/")),
      "addon does not have other.com host permission"
    );
  }

  let extension = ExtensionTestUtils.loadExtension(extensionData);
  await extension.startup();
  checkPermissions();

  extensionData.manifest.manifest_version = manifest_version;

  // Move to using optional permission
  extensionData.manifest.version = "2.0";
  extensionData.manifest.permissions = ["tabs"];

  // The ExtensionTestCommon.generateFiles() test helper will normalize (move)
  // host permissions into the `permissions` key for the MV2 test.
  extensionData.manifest.host_permissions = ["http://example.net/*"];

  extensionData.manifest.optional_permissions = [
    "webRequest",
    "http://example.com/*",
    "http://other.com/*",
  ];

  // Restart the addon manager to flush the AddonInternal instance created
  // when installing the addon above.  See bug 1622117.
  await AddonTestUtils.promiseRestartManager();
  await extension.upgrade(extensionData);

  equal(extension.version, "2.0", "Expected extension version");
  checkPermissions();

  await extension.unload();
}

add_task(function test_migrated_permission_to_optional_mv2() {
  return test_migrated_permission_to_optional({ manifest_version: 2 });
});

// Test migration of mv2 (required) to mv3 (optional) host permissions.
add_task(function test_migrated_permission_to_optional_mv3() {
  return test_migrated_permission_to_optional({ manifest_version: 3 });
});

// This tests that settings are removed if a required permission is removed.
// We use two settings APIs to make sure the one we keep permission to is not
// removed inadvertantly.
add_task(async function test_required_permissions_removed() {
  function cacheIsEnabled() {
    return (
      Services.prefs.getBoolPref("browser.cache.disk.enable") &&
      Services.prefs.getBoolPref("browser.cache.memory.enable")
    );
  }

  let extData = {
    background() {
      if (browser.browserSettings) {
        browser.browserSettings.cacheEnabled.set({ value: false });
      }
      browser.privacy.services.passwordSavingEnabled.set({ value: false });
    },
    manifest: {
      browser_specific_settings: { gecko: { id: "pref-test@test" } },
      permissions: ["tabs", "browserSettings", "privacy", "http://test.com/*"],
    },
    useAddonManager: "permanent",
  };
  let extension = ExtensionTestUtils.loadExtension(extData);
  ok(
    Services.prefs.getBoolPref("signon.rememberSignons"),
    "privacy setting intial value as expected"
  );
  await extension.startup();
  ok(!cacheIsEnabled(), "setting is set after startup");

  extData.manifest.permissions = ["tabs"];
  extData.manifest.optional_permissions = ["privacy"];
  await extension.upgrade(extData);
  ok(cacheIsEnabled(), "setting is reset after upgrade");
  ok(
    !Services.prefs.getBoolPref("signon.rememberSignons"),
    "privacy setting is still set after upgrade"
  );

  await extension.unload();
});

// This tests that settings are removed if a granted permission is removed.
// We use two settings APIs to make sure the one we keep permission to is not
// removed inadvertantly.
add_task(async function test_granted_permissions_removed() {
  function cacheIsEnabled() {
    return (
      Services.prefs.getBoolPref("browser.cache.disk.enable") &&
      Services.prefs.getBoolPref("browser.cache.memory.enable")
    );
  }

  let extData = {
    async background() {
      browser.test.onMessage.addListener(async msg => {
        await browser.permissions.request({ permissions: msg.permissions });
        if (browser.browserSettings) {
          browser.browserSettings.cacheEnabled.set({ value: false });
        }
        browser.privacy.services.passwordSavingEnabled.set({ value: false });
        browser.test.sendMessage("done");
      });
    },
    // "tabs" is never granted, it is included to exercise the removal code
    // that called during the upgrade.
    manifest: {
      browser_specific_settings: { gecko: { id: "pref-test@test" } },
      optional_permissions: [
        "tabs",
        "browserSettings",
        "privacy",
        "http://test.com/*",
      ],
    },
    useAddonManager: "permanent",
  };
  let extension = ExtensionTestUtils.loadExtension(extData);
  ok(
    Services.prefs.getBoolPref("signon.rememberSignons"),
    "privacy setting intial value as expected"
  );
  await extension.startup();
  await withHandlingUserInput(extension, async () => {
    extension.sendMessage({ permissions: ["browserSettings", "privacy"] });
    await extension.awaitMessage("done");
  });
  ok(!cacheIsEnabled(), "setting is set after startup");

  extData.manifest.permissions = ["privacy"];
  delete extData.manifest.optional_permissions;
  await extension.upgrade(extData);
  ok(cacheIsEnabled(), "setting is reset after upgrade");
  ok(
    !Services.prefs.getBoolPref("signon.rememberSignons"),
    "privacy setting is still set after upgrade"
  );

  await extension.unload();
});

// Test an update where an add-on becomes a theme.
add_task(async function test_addon_to_theme_update() {
  let id = "theme-test@test";
  let extData = {
    manifest: {
      browser_specific_settings: { gecko: { id } },
      version: "1.0",
      optional_permissions: ["tabs"],
    },
    async background() {
      browser.test.onMessage.addListener(async msg => {
        await browser.permissions.request({ permissions: msg.permissions });
        browser.test.sendMessage("done");
      });
    },
    useAddonManager: "permanent",
  };
  let extension = ExtensionTestUtils.loadExtension(extData);
  await extension.startup();

  await withHandlingUserInput(extension, async () => {
    extension.sendMessage({ permissions: ["tabs"] });
    await extension.awaitMessage("done");
  });

  let policy = WebExtensionPolicy.getByID(id);
  ok(policy.hasPermission("tabs"), "addon has tabs permission");

  await extension.upgrade({
    manifest: {
      browser_specific_settings: { gecko: { id } },
      version: "2.0",
      theme: {
        images: {
          theme_frame: "image1.png",
        },
      },
    },
    useAddonManager: "permanent",
  });
  // When a theme is installed, it starts off in disabled mode, as seen in
  //  toolkit/mozapps/extensions/test/xpcshell/test_update_theme.js .
  // But if we upgrade from an enabled extension, the theme is enabled.
  equal(extension.addon.userDisabled, false, "Theme is enabled");

  policy = WebExtensionPolicy.getByID(id);
  ok(!policy.hasPermission("tabs"), "addon tabs permission was removed");
  let perms = await ExtensionPermissions._get(id);
  ok(!perms?.permissions?.length, "no retained permissions");

  extData.manifest.version = "3.0";
  extData.manifest.permissions = ["privacy"];
  await extension.upgrade(extData);

  policy = WebExtensionPolicy.getByID(id);
  ok(!policy.hasPermission("tabs"), "addon tabs permission not added");
  ok(policy.hasPermission("privacy"), "addon privacy permission added");

  await extension.unload();
});
