/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

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

AddonTestUtils.initMochitest(this);

const BROWSER_LANGUAGES_URL =
  "chrome://browser/content/preferences/dialogs/browserLanguages.xhtml";
const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org";
const TELEMETRY_CATEGORY = "intl.ui.browserLanguage";

function langpackId(locale) {
  return `langpack-${locale}@firefox.mozilla.org`;
}

function getManifestData(locale, version = "2.0") {
  return {
    langpack_id: locale,
    name: `${locale} Language Pack`,
    description: `${locale} Language pack`,
    languages: {
      [locale]: {
        chrome_resources: {
          branding: `browser/chrome/${locale}/locale/branding/`,
        },
        version: "1",
      },
    },
    browser_specific_settings: {
      gecko: {
        id: langpackId(locale),
        strict_min_version: AppConstants.MOZ_APP_VERSION,
        strict_max_version: AppConstants.MOZ_APP_VERSION,
      },
    },
    version,
    manifest_version: 2,
    sources: {
      browser: {
        base_path: "browser/",
      },
    },
    author: "Mozilla",
  };
}

let testLocales = ["fr", "pl", "he"];
let testLangpacks;

function createLangpack(locale, version) {
  return AddonTestUtils.createTempXPIFile({
    "manifest.json": getManifestData(locale, version),
    [`browser/${locale}/branding/brand.ftl`]: "-brand-short-name = Firefox",
  });
}

function createTestLangpacks() {
  if (!testLangpacks) {
    testLangpacks = Promise.all(
      testLocales.map(async locale => [locale, await createLangpack(locale)])
    );
  }
  return testLangpacks;
}

function createLocaleResult(target_locale, url) {
  return {
    guid: langpackId(target_locale),
    type: "language",
    target_locale,
    current_compatible_version: {
      files: [
        {
          platform: "all",
          url,
        },
      ],
    },
  };
}

async function createLanguageToolsFile() {
  let langpacks = await createTestLangpacks();
  let results = langpacks.map(([locale, file]) =>
    createLocaleResult(locale, Services.io.newFileURI(file).spec)
  );

  let filename = "language-tools.json";
  let files = { [filename]: { results } };
  let tempdir = AddonTestUtils.tempDir.clone();
  let dir = await AddonTestUtils.promiseWriteFilesToDir(tempdir.path, files);
  dir.append(filename);

  return dir;
}

async function createDictionaryBrowseResults() {
  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
  let dictionaryPath = testDir + "/addons/pl-dictionary.xpi";
  let filename = "dictionaries.json";
  let response = {
    page_size: 25,
    page_count: 1,
    count: 1,
    results: [
      {
        current_version: {
          id: 1823648,
          compatibility: {
            firefox: { max: "9999", min: "4.0" },
          },
          files: [
            {
              platform: "all",
              url: dictionaryPath,
            },
          ],
          version: "1.0.20160228",
        },
        default_locale: "pl",
        description: "Polish spell-check",
        guid: DICTIONARY_ID_PL,
        name: "Polish Dictionary",
        slug: "polish-spellchecker-dictionary",
        status: "public",
        summary: "Polish dictionary",
        type: "dictionary",
      },
    ],
  };

  let files = { [filename]: response };
  let dir = await AddonTestUtils.promiseWriteFilesToDir(
    AddonTestUtils.tempDir.path,
    files
  );
  dir.append(filename);

  return dir;
}

function assertLocaleOrder(list, locales) {
  is(
    list.itemCount,
    locales.split(",").length,
    "The right number of locales are selected"
  );
  is(
    Array.from(list.children)
      .map(child => child.value)
      .join(","),
    locales,
    "The selected locales are in order"
  );
}

function assertAvailableLocales(list, locales) {
  let items = Array.from(list.menupopup.children);
  let listLocales = items.filter(item => item.value && item.value != "search");
  is(
    listLocales.length,
    locales.length,
    "The right number of locales are available"
  );
  is(
    listLocales
      .map(item => item.value)
      .sort()
      .join(","),
    locales.sort().join(","),
    "The available locales match"
  );
  is(items[0].getAttribute("class"), "label-item", "The first row is a label");
}

function getDialogId(dialogDoc) {
  return dialogDoc.ownerGlobal.arguments[0].telemetryId;
}

function assertTelemetryRecorded(events) {
  let snapshot = Services.telemetry.snapshotEvents(
    Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
    true
  );

  // Make sure we got some data.
  ok(
    snapshot.parent && !!snapshot.parent.length,
    "Got parent telemetry events in the snapshot"
  );

  // Only look at the related events after stripping the timestamp and category.
  let relatedEvents = snapshot.parent
    .filter(([timestamp, category]) => category == TELEMETRY_CATEGORY)
    .map(relatedEvent => relatedEvent.slice(2, 6));

  // Events are now an array of: method, object[, value[, extra]] as expected.
  Assert.deepEqual(relatedEvents, events, "The events are recorded correctly");
}

async function selectLocale(localeCode, available, selected, dialogDoc) {
  let [locale] = Array.from(available.menupopup.children).filter(
    item => item.value == localeCode
  );
  available.selectedItem = locale;

  // Get ready for the selected list to change.
  let added = waitForMutation(selected, { childList: true }, target =>
    Array.from(target.children).some(el => el.value == localeCode)
  );

  // Add the locale.
  dialogDoc.getElementById("add").doCommand();

  // Wait for the list to update.
  await added;
}

async function openDialog(doc, search = false) {
  let dialogLoaded = promiseLoadSubDialog(BROWSER_LANGUAGES_URL);
  if (search) {
    doc.getElementById("primaryBrowserLocaleSearch").doCommand();
    doc.getElementById("primaryBrowserLocale").menupopup.hidePopup();
  } else {
    doc.getElementById("manageBrowserLanguagesButton").doCommand();
  }
  let dialogWin = await dialogLoaded;
  let dialogDoc = dialogWin.document;
  return {
    dialog: dialogDoc.getElementById("BrowserLanguagesDialog"),
    dialogDoc,
    available: dialogDoc.getElementById("availableLocales"),
    selected: dialogDoc.getElementById("selectedLocales"),
  };
}

add_task(async function testDisabledBrowserLanguages() {
  let langpacksFile = await createLanguageToolsFile();
  let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;

  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US,pl,he,de"],
      ["extensions.langpacks.signatures.required", false],
      ["extensions.getAddons.langpacks.url", langpacksUrl],
    ],
  });

  // Install an old pl langpack.
  let oldLangpack = await createLangpack("pl", "1.0");
  await AddonTestUtils.promiseInstallFile(oldLangpack);

  // Install all the other available langpacks.
  let pl;
  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      if (locale == "pl") {
        pl = await AddonManager.getAddonByID(langpackId("pl"));
        // Disable pl so it's removed from selected.
        await pl.disable();
        return pl;
      }
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;
  let { dialogDoc, available, selected } = await openDialog(doc);

  // pl is not selected since it's disabled.
  is(pl.userDisabled, true, "pl is disabled");
  is(pl.version, "1.0", "pl is the old 1.0 version");
  assertLocaleOrder(selected, "en-US,he");

  // Wait for the children menu to be populated.
  await BrowserTestUtils.waitForCondition(
    () => !!available.children.length,
    "Children list populated"
  );

  // Only fr is enabled and not selected, so it's the only locale available.
  assertAvailableLocales(available, ["fr"]);

  // Search for more languages.
  available.menupopup.lastElementChild.doCommand();
  available.menupopup.hidePopup();
  await waitForMutation(available.menupopup, { childList: true }, target =>
    Array.from(available.menupopup.children).some(
      locale => locale.value == "pl"
    )
  );

  // pl is now available since it is available remotely.
  assertAvailableLocales(available, ["fr", "pl"]);

  let installId = null;
  AddonTestUtils.promiseInstallEvent("onInstallEnded").then(([install]) => {
    installId = install.installId;
  });

  // Add pl.
  await selectLocale("pl", available, selected, dialogDoc);
  assertLocaleOrder(selected, "pl,en-US,he");

  // Find pl again since it's been upgraded.
  pl = await AddonManager.getAddonByID(langpackId("pl"));
  is(pl.userDisabled, false, "pl is now enabled");
  is(pl.version, "2.0", "pl is upgraded to version 2.0");

  let dialogId = getDialogId(dialogDoc);
  ok(dialogId, "There's a dialogId");
  ok(installId, "There's an installId");

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([
    ["manage", "main", dialogId],
    ["search", "dialog", dialogId],
    ["add", "dialog", dialogId, { installId }],

    // Cancel is recorded when the tab is closed.
    ["cancel", "dialog", dialogId],
  ]);
});

add_task(async function testReorderingBrowserLanguages() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US,pl,he,de"],
      ["extensions.langpacks.signatures.required", false],
    ],
  });

  // Install all the available langpacks.
  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;
  let messageBar = doc.getElementById("confirmBrowserLanguage");
  is(messageBar.hidden, true, "The message bar is hidden at first");

  // Open the dialog.
  let { dialog, dialogDoc, selected } = await openDialog(doc);
  let firstDialogId = getDialogId(dialogDoc);

  // The initial order is set by the pref, filtered by available.
  assertLocaleOrder(selected, "en-US,pl,he");

  // Moving pl down changes the order.
  selected.selectedItem = selected.querySelector("[value='pl']");
  dialogDoc.getElementById("down").doCommand();
  assertLocaleOrder(selected, "en-US,he,pl");

  // Accepting the change shows the confirm message bar.
  let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
  dialog.acceptDialog();
  await dialogClosed;

  // The message bar uses async `formatValues` and that may resolve
  // after the dialog is closed.
  await BrowserTestUtils.waitForMutationCondition(
    messageBar,
    { attributes: true },
    () => !messageBar.hidden
  );
  is(
    messageBar.querySelector("button").getAttribute("locales"),
    "en-US,he,pl",
    "The locales are set on the message bar button"
  );

  // Open the dialog again.
  let newDialog = await openDialog(doc);
  dialog = newDialog.dialog;
  dialogDoc = newDialog.dialogDoc;
  let secondDialogId = getDialogId(dialogDoc);
  selected = newDialog.selected;

  // The initial order comes from the previous settings.
  assertLocaleOrder(selected, "en-US,he,pl");

  // Select pl in the list.
  selected.selectedItem = selected.querySelector("[value='pl']");
  // Move pl back up.
  dialogDoc.getElementById("up").doCommand();
  assertLocaleOrder(selected, "en-US,pl,he");

  // Accepting the change hides the confirm message bar.
  dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
  dialog.acceptDialog();
  await dialogClosed;
  is(messageBar.hidden, true, "The message bar is hidden again");

  ok(firstDialogId, "There was an id on the first dialog");
  ok(secondDialogId, "There was an id on the second dialog");
  ok(firstDialogId != secondDialogId, "The dialog ids are different");
  ok(
    parseInt(firstDialogId) < parseInt(secondDialogId),
    "The second dialog id is larger than the first"
  );

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([
    ["manage", "main", firstDialogId],
    ["reorder", "dialog", firstDialogId],
    ["accept", "dialog", firstDialogId],
    ["manage", "main", secondDialogId],
    ["reorder", "dialog", secondDialogId],
    ["accept", "dialog", secondDialogId],
  ]);
});

add_task(async function testAddAndRemoveSelectedLanguages() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US"],
      ["extensions.langpacks.signatures.required", false],
    ],
  });

  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;
  let messageBar = doc.getElementById("confirmBrowserLanguage");
  is(messageBar.hidden, true, "The message bar is hidden at first");

  // Open the dialog.
  let { dialog, dialogDoc, available, selected } = await openDialog(doc);
  let dialogId = getDialogId(dialogDoc);

  // loadLocalesFromAMO is async but `initAvailableLocales` doesn't wait
  // for it to be resolved, so we have to wait for the list to be populated
  // before we test for its values.
  await BrowserTestUtils.waitForMutationCondition(
    available.menupopup,
    { attributes: true, childList: true },
    () => {
      let listLocales = Array.from(available.menupopup.children).filter(
        item => item.value && item.value != "search"
      );
      return listLocales.length == 3;
    }
  );
  // The initial order is set by the pref.
  assertLocaleOrder(selected, "en-US");
  assertAvailableLocales(available, ["fr", "pl", "he"]);

  // Add pl and fr to selected.
  await selectLocale("pl", available, selected, dialogDoc);
  await selectLocale("fr", available, selected, dialogDoc);

  assertLocaleOrder(selected, "fr,pl,en-US");
  assertAvailableLocales(available, ["he"]);

  // Remove pl and fr from selected.
  dialogDoc.getElementById("remove").doCommand();
  dialogDoc.getElementById("remove").doCommand();
  assertLocaleOrder(selected, "en-US");
  assertAvailableLocales(available, ["fr", "pl", "he"]);

  // Add he to selected.
  await selectLocale("he", available, selected, dialogDoc);
  assertLocaleOrder(selected, "he,en-US");
  assertAvailableLocales(available, ["pl", "fr"]);

  // Accepting the change shows the confirm message bar.
  let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
  dialog.acceptDialog();
  await dialogClosed;

  await waitForMutation(
    messageBar,
    { attributes: true, attributeFilter: ["hidden"] },
    target => !target.hidden
  );

  is(messageBar.hidden, false, "The message bar is now visible");
  is(
    messageBar.querySelector("button").getAttribute("locales"),
    "he,en-US",
    "The locales are set on the message bar button"
  );

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([
    ["manage", "main", dialogId],

    // Install id is not recorded since it was already installed.
    ["add", "dialog", dialogId],
    ["add", "dialog", dialogId],

    ["remove", "dialog", dialogId],
    ["remove", "dialog", dialogId],

    ["add", "dialog", dialogId],
    ["accept", "dialog", dialogId],
  ]);
});

add_task(async function testInstallFromAMO() {
  let langpacks = await AddonManager.getAddonsByTypes(["locale"]);
  is(langpacks.length, 0, "There are no langpacks installed");

  let langpacksFile = await createLanguageToolsFile();
  let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
  let dictionaryBrowseFile = await createDictionaryBrowseResults();
  let browseApiEndpoint = Services.io.newFileURI(dictionaryBrowseFile).spec;

  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US"],
      ["extensions.getAddons.langpacks.url", langpacksUrl],
      ["extensions.langpacks.signatures.required", false],
      ["extensions.getAddons.get.url", browseApiEndpoint],
    ],
  });

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;
  let messageBar = doc.getElementById("confirmBrowserLanguage");
  is(messageBar.hidden, true, "The message bar is hidden at first");

  // Verify only en-US is listed on the main pane.
  let getMainPaneLocales = () => {
    let available = doc.getElementById("primaryBrowserLocale");
    let availableLocales = Array.from(available.menupopup.children);
    return availableLocales
      .map(item => item.value)
      .sort()
      .join(",");
  };
  is(getMainPaneLocales(), "en-US,search", "Only en-US installed to start");

  // Open the dialog.
  let { dialog, dialogDoc, available, selected } = await openDialog(doc, true);
  let firstDialogId = getDialogId(dialogDoc);

  // Make sure the message bar is still hidden.
  is(
    messageBar.hidden,
    true,
    "The message bar is still hidden after searching"
  );

  if (available.itemCount == 1) {
    await waitForMutation(
      available.menupopup,
      { childList: true },
      target => available.itemCount > 1
    );
  }

  // The initial order is set by the pref.
  assertLocaleOrder(selected, "en-US");
  assertAvailableLocales(available, ["fr", "he", "pl"]);
  is(
    Services.locale.availableLocales.join(","),
    "en-US",
    "There is only one installed locale"
  );

  // Verify that there are no extra dictionaries.
  let dicts = await AddonManager.getAddonsByTypes(["dictionary"]);
  is(dicts.length, 0, "There are no installed dictionaries");

  let installId = null;
  AddonTestUtils.promiseInstallEvent("onInstallEnded").then(([install]) => {
    installId = install.installId;
  });

  // Add Polish, this will install the langpack.
  await selectLocale("pl", available, selected, dialogDoc);

  ok(installId, "We got an installId for the langpack installation");

  let langpack = await AddonManager.getAddonByID(langpackId("pl"));
  Assert.deepEqual(
    langpack.installTelemetryInfo,
    { source: "about:preferences" },
    "The source is set to preferences"
  );

  // Verify the list is correct.
  assertLocaleOrder(selected, "pl,en-US");
  assertAvailableLocales(available, ["fr", "he"]);
  is(
    Services.locale.availableLocales.sort().join(","),
    "en-US,pl",
    "Polish is now installed"
  );

  await BrowserTestUtils.waitForCondition(async () => {
    let newDicts = await AddonManager.getAddonsByTypes(["dictionary"]);
    let done = !!newDicts.length;

    if (done) {
      is(
        newDicts[0].id,
        DICTIONARY_ID_PL,
        "The polish dictionary was installed"
      );
    }

    return done;
  });

  // Move pl down the list, which prevents an error since it isn't valid.
  dialogDoc.getElementById("down").doCommand();
  assertLocaleOrder(selected, "en-US,pl");

  // Test that disabling the langpack removes it from the list.
  let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
  dialog.acceptDialog();
  await dialogClosed;

  // Verify pl is now available to select.
  is(getMainPaneLocales(), "en-US,pl,search", "en-US and pl now available");

  // Disable the Polish langpack.
  langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
  await langpack.disable();

  ({ dialogDoc, available, selected } = await openDialog(doc, true));
  let secondDialogId = getDialogId(dialogDoc);

  // Wait for the available langpacks to load.
  if (available.itemCount == 1) {
    await waitForMutation(
      available.menupopup,
      { childList: true },
      target => available.itemCount > 1
    );
  }
  assertLocaleOrder(selected, "en-US");
  assertAvailableLocales(available, ["fr", "he", "pl"]);

  // Uninstall the langpack and dictionary.
  let installs = await AddonManager.getAddonsByTypes(["locale", "dictionary"]);
  is(installs.length, 2, "There is one langpack and one dictionary installed");
  await Promise.all(installs.map(item => item.uninstall()));

  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([
    // First dialog installs a locale and accepts.
    ["search", "main", firstDialogId],
    // It has an installId since it was downloaded.
    ["add", "dialog", firstDialogId, { installId }],
    // It got moved down to avoid errors with finding translations.
    ["reorder", "dialog", firstDialogId],
    ["accept", "dialog", firstDialogId],

    // The second dialog just checks the state and is closed with the tab.
    ["search", "main", secondDialogId],
    ["cancel", "dialog", secondDialogId],
  ]);
});

let hasSearchOption = popup =>
  Array.from(popup.children).some(el => el.value == "search");

add_task(async function testDownloadEnabled() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
    ],
  });

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });
  let doc = gBrowser.contentDocument;

  let defaultMenulist = doc.getElementById("primaryBrowserLocale");
  ok(
    hasSearchOption(defaultMenulist.menupopup),
    "There's a search option in the General pane"
  );

  let { available } = await openDialog(doc, false);
  ok(
    hasSearchOption(available.menupopup),
    "There's a search option in the dialog"
  );

  BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

add_task(async function testDownloadDisabled() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", false],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
    ],
  });

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });
  let doc = gBrowser.contentDocument;

  let defaultMenulist = doc.getElementById("primaryBrowserLocale");
  ok(
    !hasSearchOption(defaultMenulist.menupopup),
    "There's no search option in the General pane"
  );

  let { available } = await openDialog(doc, false);
  ok(
    !hasSearchOption(available.menupopup),
    "There's no search option in the dialog"
  );

  BrowserTestUtils.removeTab(gBrowser.selectedTab);
});

add_task(async function testReorderMainPane() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", false],
      ["intl.multilingual.liveReload", false],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US"],
      ["extensions.langpacks.signatures.required", false],
    ],
  });

  // Clear the telemetry from other tests.
  Services.telemetry.clearEvents();

  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });
  let doc = gBrowser.contentDocument;

  let messageBar = doc.getElementById("confirmBrowserLanguage");
  is(messageBar.hidden, true, "The message bar is hidden at first");

  let available = doc.getElementById("primaryBrowserLocale");
  let availableLocales = Array.from(available.menupopup.children);
  let availableCodes = availableLocales
    .map(item => item.value)
    .sort()
    .join(",");
  is(
    availableCodes,
    "en-US,fr,he,pl",
    "All of the available locales are listed"
  );

  is(available.selectedItem.value, "en-US", "English is selected");

  let hebrew = availableLocales.find(item => item.value == "he");
  hebrew.click();
  available.menupopup.hidePopup();

  await BrowserTestUtils.waitForCondition(
    () => !messageBar.hidden,
    "Wait for message bar to show"
  );

  is(messageBar.hidden, false, "The message bar is now shown");
  is(
    messageBar.querySelector("button").getAttribute("locales"),
    "he,en-US",
    "The locales are set on the message bar button"
  );

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([["reorder", "main"]]);
});

add_task(async function testLiveLanguageReloading() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", true],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US,fr,he,de"],
      ["extensions.langpacks.signatures.required", false],
    ],
  });

  // Clear the telemetry from other tests.
  Services.telemetry.clearEvents();

  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;

  let available = doc.getElementById("primaryBrowserLocale");
  let availableLocales = Array.from(available.menupopup.children);

  is(
    Services.locale.appLocaleAsBCP47,
    "en-US",
    "The app locale starts as English."
  );

  Assert.deepEqual(
    Services.locale.requestedLocales,
    ["en-US", "fr", "he", "de"],
    "The locale order starts as what was initially requested."
  );

  // French and English are both LTR languages.
  let french = availableLocales.find(item => item.value == "fr");

  french.click();
  available.menupopup.hidePopup();

  is(
    Services.locale.appLocaleAsBCP47,
    "fr",
    "The app locale was changed to French"
  );

  Assert.deepEqual(
    Services.locale.requestedLocales,
    ["fr", "en-US", "he", "de"],
    "The locale order is switched to french first."
  );

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([["reorder", "main"]]);
});

add_task(async function testLiveLanguageReloadingBidiOff() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", true],
      ["intl.multilingual.liveReloadBidirectional", false],
      ["intl.locale.requested", "en-US,fr,he,de"],
      ["extensions.langpacks.signatures.required", false],
    ],
  });

  // Clear the telemetry from other tests.
  Services.telemetry.clearEvents();

  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;

  let available = doc.getElementById("primaryBrowserLocale");
  let availableLocales = Array.from(available.menupopup.children);

  is(
    Services.locale.appLocaleAsBCP47,
    "en-US",
    "The app locale starts as English."
  );

  Assert.deepEqual(
    Services.locale.requestedLocales,
    ["en-US", "fr", "he", "de"],
    "The locale order starts as what was initially requested."
  );

  let messageBar = doc.getElementById("confirmBrowserLanguage");
  is(messageBar.hidden, true, "The message bar is hidden at first");

  // English is LTR and Hebrew is RTL.
  let hebrew = availableLocales.find(item => item.value == "he");

  hebrew.click();
  available.menupopup.hidePopup();

  await BrowserTestUtils.waitForCondition(
    () => !messageBar.hidden,
    "Wait for message bar to show"
  );

  is(messageBar.hidden, false, "The message bar is now shown");

  is(
    Services.locale.appLocaleAsBCP47,
    "en-US",
    "The app locale remains in English"
  );

  Assert.deepEqual(
    Services.locale.requestedLocales,
    ["en-US", "fr", "he", "de"],
    "The locale order did not change."
  );

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([["reorder", "main"]]);
});

add_task(async function testLiveLanguageReloadingBidiOn() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["intl.multilingual.enabled", true],
      ["intl.multilingual.downloadEnabled", true],
      ["intl.multilingual.liveReload", true],
      ["intl.multilingual.liveReloadBidirectional", true],
      ["intl.locale.requested", "en-US,fr,he,de"],
      ["extensions.langpacks.signatures.required", false],
    ],
  });

  // Clear the telemetry from other tests.
  Services.telemetry.clearEvents();

  let langpacks = await createTestLangpacks();
  let addons = await Promise.all(
    langpacks.map(async ([locale, file]) => {
      let install = await AddonTestUtils.promiseInstallFile(file);
      return install.addon;
    })
  );

  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    leaveOpen: true,
  });

  let doc = gBrowser.contentDocument;

  let available = doc.getElementById("primaryBrowserLocale");
  let availableLocales = Array.from(available.menupopup.children);

  is(
    Services.locale.appLocaleAsBCP47,
    "en-US",
    "The app locale starts as English."
  );

  Assert.deepEqual(
    Services.locale.requestedLocales,
    ["en-US", "fr", "he", "de"],
    "The locale order starts as what was initially requested."
  );

  let messageBar = doc.getElementById("confirmBrowserLanguage");
  is(messageBar.hidden, true, "The message bar is hidden at first");

  // English is LTR and Hebrew is RTL.
  let hebrew = availableLocales.find(item => item.value == "he");

  hebrew.click();
  available.menupopup.hidePopup();

  is(messageBar.hidden, true, "The message bar is still hidden");

  is(
    Services.locale.appLocaleAsBCP47,
    "he",
    "The app locale was changed to Hebrew."
  );

  Assert.deepEqual(
    Services.locale.requestedLocales,
    ["he", "en-US", "fr", "de"],
    "The locale changed with Hebrew first."
  );

  await Promise.all(addons.map(addon => addon.uninstall()));
  BrowserTestUtils.removeTab(gBrowser.selectedTab);

  assertTelemetryRecorded([["reorder", "main"]]);
});
