"use strict";

/**
 * This file contains test for 'theme' type WebExtension addons. Tests focus mostly
 * on interoperability between the different theme formats (XUL and LWT) and
 * Addon Manager integration.
 *
 * Coverage may overlap with other tests in this folder.
 */

const THEME_IDS = [
  "theme3@tests.mozilla.org",
  "theme2@personas.mozilla.org", // Unused. Legacy. Evil.
  "default-theme@mozilla.org",
];
const REAL_THEME_IDS = [THEME_IDS[0], THEME_IDS[2]];
const DEFAULT_THEME = THEME_IDS[2];

const profileDir = gProfD.clone();
profileDir.append("extensions");

Services.prefs.setIntPref(
  "extensions.enabledScopes",
  AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION
);

// We remember the last/ currently active theme for tracking events.
var gActiveTheme = null;

add_task(async function setup_to_default_browserish_state() {
  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");

  await promiseWriteWebManifestForExtension(
    {
      author: "Some author",
      manifest_version: 2,
      name: "Web Extension Name",
      version: "1.0",
      theme: { images: { theme_frame: "example.png" } },
      browser_specific_settings: {
        gecko: {
          id: THEME_IDS[0],
        },
      },
    },
    profileDir
  );

  await promiseStartupManager();

  if (AppConstants.MOZ_DEV_EDITION) {
    // Developer Edition selects the wrong theme by default.
    let defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME);
    await defaultTheme.enable();
  }

  let [t1, t2, d] = await promiseAddonsByIDs(THEME_IDS);
  Assert.ok(t1, "Theme addon should exist");
  Assert.equal(t2, null, "Theme addon is not a thing anymore");
  Assert.ok(d, "Theme addon should exist");

  await t1.disable();
  await new Promise(executeSoon);
  Assert.ok(!t1.isActive, "Theme should be disabled");
  Assert.ok(d.isActive, "Default theme should be active");

  await promiseRestartManager();

  [t1, t2, d] = await promiseAddonsByIDs(THEME_IDS);
  Assert.ok(!t1.isActive, "Theme should still be disabled");
  Assert.ok(d.isActive, "Default theme should still be active");

  gActiveTheme = d.id;
});

/**
 * Set the `userDisabled` property of one specific theme and check if the theme
 * switching works as expected by checking the state of all installed themes.
 *
 * @param {String}  which    ID of the addon to set the `userDisabled` property on
 * @param {Boolean} disabled Flag value to switch to
 */
async function setDisabledStateAndCheck(which, disabled = false) {
  if (disabled) {
    Assert.equal(which, gActiveTheme, "Only the active theme can be disabled");
  }

  let themeToDisable = disabled ? which : gActiveTheme;
  let themeToEnable = disabled ? DEFAULT_THEME : which;

  let expectedStates = {
    [themeToDisable]: true,
    [themeToEnable]: false,
  };
  let addonEvents = {
    [themeToDisable]: [{ event: "onDisabling" }, { event: "onDisabled" }],
    [themeToEnable]: [{ event: "onEnabling" }, { event: "onEnabled" }],
  };

  // Set the state of the theme to change.
  let theme = await promiseAddonByID(which);
  await expectEvents({ addonEvents }, () => {
    if (disabled) {
      theme.disable();
    } else {
      theme.enable();
    }
  });

  let isDisabled;
  for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
    isDisabled = theme.id in expectedStates ? expectedStates[theme.id] : true;
    Assert.equal(
      theme.userDisabled,
      isDisabled,
      `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`
    );
    Assert.equal(
      theme.pendingOperations,
      AddonManager.PENDING_NONE,
      "There should be no pending operations when no restart is expected"
    );
    Assert.equal(
      theme.isActive,
      !isDisabled,
      `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active`
    );
  }

  await promiseRestartManager();

  // All should still be good after a restart of the Addon Manager.
  for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) {
    isDisabled = theme.id in expectedStates ? expectedStates[theme.id] : true;
    Assert.equal(
      theme.userDisabled,
      isDisabled,
      `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled`
    );
    Assert.equal(
      theme.isActive,
      !isDisabled,
      `Theme '${theme.id}' should be ${isDisabled ? "in" : ""}active`
    );
    Assert.equal(
      theme.pendingOperations,
      AddonManager.PENDING_NONE,
      "There should be no pending operations left"
    );
    if (!isDisabled) {
      gActiveTheme = theme.id;
    }
  }
}

add_task(async function test_WebExtension_themes() {
  // Enable the WebExtension theme.
  await setDisabledStateAndCheck(THEME_IDS[0]);

  // Disabling WebExtension should revert to the default theme.
  await setDisabledStateAndCheck(THEME_IDS[0], true);

  // Enable it again.
  await setDisabledStateAndCheck(THEME_IDS[0]);
});

add_task(async function test_default_theme() {
  // Explicitly enable the default theme.
  await setDisabledStateAndCheck(DEFAULT_THEME);

  // Swith to the WebExtension theme.
  await setDisabledStateAndCheck(THEME_IDS[0]);

  // Enable it again.
  await setDisabledStateAndCheck(DEFAULT_THEME);
});

add_task(async function uninstall_offers_undo() {
  let defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME);
  const ID = THEME_IDS[0];
  let theme = await promiseAddonByID(ID);

  Assert.ok(theme, "Webextension theme is present");

  async function promiseAddonEvent(event, id) {
    let [addon] = await AddonTestUtils.promiseAddonEvent(event);
    if (id) {
      Assert.equal(addon.id, id, `Got event for expected addon (${event})`);
    }
  }

  async function uninstallTheme() {
    let uninstallingPromise = promiseAddonEvent("onUninstalling", ID);
    await theme.uninstall(true);
    await uninstallingPromise;

    Assert.ok(
      hasFlag(theme.pendingOperations, AddonManager.PENDING_UNINSTALL),
      "Theme being uninstalled has PENDING_UNINSTALL flag"
    );
  }

  async function cancelUninstallTheme() {
    let cancelPromise = promiseAddonEvent("onOperationCancelled", ID);
    theme.cancelUninstall();
    await cancelPromise;

    Assert.equal(
      theme.pendingOperations,
      AddonManager.PENDING_NONE,
      "PENDING_UNINSTALL flag is cleared when uninstall is canceled"
    );
  }

  // A theme should still be disabled if the uninstallation of a disabled theme
  // is undone.
  Assert.ok(!theme.isActive, "Webextension theme is not active");
  Assert.ok(defaultTheme.isActive, "Default theme is active");
  await uninstallTheme();
  await cancelUninstallTheme();
  Assert.ok(!theme.isActive, "Webextension theme is still not active");
  Assert.ok(defaultTheme.isActive, "Default theme is still active");

  // Enable theme, the previously active theme should be disabled.
  await Promise.all([
    promiseAddonEvent("onDisabled", DEFAULT_THEME),
    promiseAddonEvent("onEnabled", ID),
    theme.enable(),
  ]);
  Assert.ok(theme.isActive, "Webextension theme is active after enabling");
  Assert.ok(!defaultTheme.isActive, "Default theme is not active any more");

  // Uninstall active theme, default theme should become active.
  await Promise.all([
    // Note: no listener for onDisabled & ID because the uninstall is pending.
    promiseAddonEvent("onEnabled", DEFAULT_THEME),
    uninstallTheme(),
  ]);
  Assert.ok(!theme.isActive, "Webextension theme is not active upon uninstall");
  Assert.ok(defaultTheme.isActive, "Default theme is active again");

  // Undo uninstall, default theme should be deactivated.
  await Promise.all([
    // Note: no listener for onEnabled & ID because the uninstall was pending.
    promiseAddonEvent("onDisabled", DEFAULT_THEME),
    cancelUninstallTheme(),
  ]);
  Assert.ok(theme.isActive, "Webextension theme is active upon undo uninstall");
  Assert.ok(!defaultTheme.isActive, "Default theme is not active again");

  // Immediately remove the theme. Default theme should be activated.
  await Promise.all([
    promiseAddonEvent("onEnabled", DEFAULT_THEME),
    theme.uninstall(),
  ]);

  await promiseRestartManager();
});

// Test that default_locale works with WE themes
add_task(async function default_locale_themes() {
  let addon = await promiseInstallWebExtension({
    manifest: {
      default_locale: "en",
      name: "__MSG_name__",
      description: "__MSG_description__",
      theme: {
        colors: {
          frame: "black",
          tab_background_text: "white",
        },
      },
    },
    files: {
      "_locales/en/messages.json": `{
        "name": {
          "message": "the name"
        },
        "description": {
          "message": "the description"
        }
      }`,
    },
  });

  addon = await promiseAddonByID(addon.id);
  equal(addon.name, "the name");
  equal(addon.description, "the description");
  equal(addon.type, "theme");
  await addon.uninstall();
});

add_task(async function test_theme_update() {
  let addon = await AddonManager.getAddonByID(DEFAULT_THEME);
  ok(!addon.userDisabled, "default theme is enabled");

  await AddonTestUtils.promiseRestartManager("2");

  addon = await AddonManager.getAddonByID(DEFAULT_THEME);
  ok(!addon.userDisabled, "default theme is enabled after upgrade");
});

add_task(async function test_builtin_theme_permissions() {
  const ADDON_ID = "mytheme@mozilla.org";

  let themeDef = {
    manifest: {
      browser_specific_settings: { gecko: { id: ADDON_ID } },
      version: "1.0",
      theme: {},
    },
  };

  function checkPerms(addon) {
    // builtin themes enable or disable based on disabled state
    Assert.equal(
      addon.userDisabled,
      hasFlag(addon.permissions, AddonManager.PERM_CAN_ENABLE),
      "enable permission is correct"
    );
    Assert.equal(
      !addon.userDisabled,
      hasFlag(addon.permissions, AddonManager.PERM_CAN_DISABLE),
      "disable permission is correct"
    );
    // builtin themes do not get any other permission
    Assert.ok(
      !hasFlag(addon.permissions, AddonManager.PERM_CAN_INSTALL),
      "cannot install by user"
    );
    Assert.ok(
      !hasFlag(addon.permissions, AddonManager.PERM_CAN_UPGRADE),
      "cannot upgrade"
    );
    Assert.ok(
      !hasFlag(addon.permissions, AddonManager.PERM_CAN_UNINSTALL),
      "cannot uninstall"
    );
    Assert.ok(
      !hasFlag(
        addon.permissions,
        AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
      ),
      "can change private browsing access"
    );
    Assert.ok(
      hasFlag(addon.permissions, AddonManager.PERM_API_CAN_UNINSTALL),
      "can uninstall via API"
    );
  }

  await setupBuiltinExtension(themeDef, "first-loc", false);
  await AddonManager.maybeInstallBuiltinAddon(
    ADDON_ID,
    "1.0",
    "resource://first-loc/"
  );

  let addon = await AddonManager.getAddonByID(ADDON_ID);
  checkPerms(addon);
  await addon.enable();
  checkPerms(addon);

  await addon.uninstall();
});
