tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_browser_languages_subdialog.js (33693B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 requestLongerTimeout(2);
      5 
      6 const { AddonTestUtils } = ChromeUtils.importESModule(
      7  "resource://testing-common/AddonTestUtils.sys.mjs"
      8 );
      9 
     10 AddonTestUtils.initMochitest(this);
     11 
     12 const BROWSER_LANGUAGES_URL =
     13  "chrome://browser/content/preferences/dialogs/browserLanguages.xhtml";
     14 const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org";
     15 const TELEMETRY_CATEGORY = "intl.ui.browserLanguage";
     16 
     17 function langpackId(locale) {
     18  return `langpack-${locale}@firefox.mozilla.org`;
     19 }
     20 
     21 function getManifestData(locale, version = "2.0") {
     22  return {
     23    langpack_id: locale,
     24    name: `${locale} Language Pack`,
     25    description: `${locale} Language pack`,
     26    languages: {
     27      [locale]: {
     28        chrome_resources: {
     29          branding: `browser/chrome/${locale}/locale/branding/`,
     30        },
     31        version: "1",
     32      },
     33    },
     34    browser_specific_settings: {
     35      gecko: {
     36        id: langpackId(locale),
     37        strict_min_version: AppConstants.MOZ_APP_VERSION,
     38        strict_max_version: AppConstants.MOZ_APP_VERSION,
     39      },
     40    },
     41    version,
     42    manifest_version: 2,
     43    sources: {
     44      browser: {
     45        base_path: "browser/",
     46      },
     47    },
     48    author: "Mozilla",
     49  };
     50 }
     51 
     52 let testLocales = ["fr", "pl", "he"];
     53 let testLangpacks;
     54 
     55 function createLangpack(locale, version) {
     56  return AddonTestUtils.createTempXPIFile({
     57    "manifest.json": getManifestData(locale, version),
     58    [`browser/${locale}/branding/brand.ftl`]: "-brand-short-name = Firefox",
     59  });
     60 }
     61 
     62 function createTestLangpacks() {
     63  if (!testLangpacks) {
     64    testLangpacks = Promise.all(
     65      testLocales.map(async locale => [locale, await createLangpack(locale)])
     66    );
     67  }
     68  return testLangpacks;
     69 }
     70 
     71 function createLocaleResult(target_locale, url) {
     72  return {
     73    guid: langpackId(target_locale),
     74    type: "language",
     75    target_locale,
     76    current_compatible_version: {
     77      files: [
     78        {
     79          platform: "all",
     80          url,
     81        },
     82      ],
     83    },
     84  };
     85 }
     86 
     87 async function createLanguageToolsFile() {
     88  let langpacks = await createTestLangpacks();
     89  let results = langpacks.map(([locale, file]) =>
     90    createLocaleResult(locale, Services.io.newFileURI(file).spec)
     91  );
     92 
     93  let filename = "language-tools.json";
     94  let files = { [filename]: { results } };
     95  let tempdir = AddonTestUtils.tempDir.clone();
     96  let dir = await AddonTestUtils.promiseWriteFilesToDir(tempdir.path, files);
     97  dir.append(filename);
     98 
     99  return dir;
    100 }
    101 
    102 async function createDictionaryBrowseResults() {
    103  let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
    104  let dictionaryPath = testDir + "/addons/pl-dictionary.xpi";
    105  let filename = "dictionaries.json";
    106  let response = {
    107    page_size: 25,
    108    page_count: 1,
    109    count: 1,
    110    results: [
    111      {
    112        current_version: {
    113          id: 1823648,
    114          compatibility: {
    115            firefox: { max: "9999", min: "4.0" },
    116          },
    117          files: [
    118            {
    119              platform: "all",
    120              url: dictionaryPath,
    121            },
    122          ],
    123          version: "1.0.20160228",
    124        },
    125        default_locale: "pl",
    126        description: "Polish spell-check",
    127        guid: DICTIONARY_ID_PL,
    128        name: "Polish Dictionary",
    129        slug: "polish-spellchecker-dictionary",
    130        status: "public",
    131        summary: "Polish dictionary",
    132        type: "dictionary",
    133      },
    134    ],
    135  };
    136 
    137  let files = { [filename]: response };
    138  let dir = await AddonTestUtils.promiseWriteFilesToDir(
    139    AddonTestUtils.tempDir.path,
    140    files
    141  );
    142  dir.append(filename);
    143 
    144  return dir;
    145 }
    146 
    147 function assertLocaleOrder(list, locales, selectedLocale) {
    148  is(
    149    list.itemCount,
    150    locales.split(",").length,
    151    "The right number of locales are in the list"
    152  );
    153  is(
    154    Array.from(list.children)
    155      .map(child => child.value)
    156      .join(","),
    157    locales,
    158    "The listed locales are in order"
    159  );
    160  is(
    161    list.selectedItem.value,
    162    selectedLocale,
    163    "The selected item locale matches"
    164  );
    165 }
    166 
    167 function assertAvailableLocales(list, locales) {
    168  let items = Array.from(list.menupopup.children);
    169  let listLocales = items.filter(item => item.value && item.value != "search");
    170  is(
    171    listLocales.length,
    172    locales.length,
    173    "The right number of locales are available"
    174  );
    175  is(
    176    listLocales
    177      .map(item => item.value)
    178      .sort()
    179      .join(","),
    180    locales.sort().join(","),
    181    "The available locales match"
    182  );
    183  is(items[0].getAttribute("class"), "label-item", "The first row is a label");
    184 }
    185 
    186 function getDialogId(dialogDoc) {
    187  return dialogDoc.ownerGlobal.arguments[0].telemetryId;
    188 }
    189 
    190 function assertTelemetryRecorded(events) {
    191  let snapshot = Services.telemetry.snapshotEvents(
    192    Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
    193    true
    194  );
    195 
    196  // Make sure we got some data.
    197  ok(
    198    snapshot.parent && !!snapshot.parent.length,
    199    "Got parent telemetry events in the snapshot"
    200  );
    201 
    202  // Only look at the related events after stripping the timestamp and category.
    203  let relatedEvents = snapshot.parent
    204    .filter(([, category]) => category == TELEMETRY_CATEGORY)
    205    .map(relatedEvent => relatedEvent.slice(2, 6));
    206 
    207  // Events are now an array of: method, object[, value[, extra]] as expected.
    208  Assert.deepEqual(relatedEvents, events, "The events are recorded correctly");
    209 }
    210 
    211 async function selectLocale(localeCode, available, selected, dialogDoc) {
    212  let [locale] = Array.from(available.menupopup.children).filter(
    213    item => item.value == localeCode
    214  );
    215  available.selectedItem = locale;
    216 
    217  // Get ready for the selected list to change.
    218  let added = waitForMutation(selected, { childList: true }, target =>
    219    Array.from(target.children).some(el => el.value == localeCode)
    220  );
    221 
    222  // Add the locale.
    223  dialogDoc.getElementById("add").doCommand();
    224 
    225  // Wait for the list to update.
    226  await added;
    227 }
    228 
    229 // Select a locale from the list of already added locales.
    230 function selectAddedLocale(localeCode, selected) {
    231  selected.selectedItem = selected.querySelector(`[value="${localeCode}"]`);
    232 }
    233 
    234 async function openDialog(doc, search = false) {
    235  let dialogLoaded = promiseLoadSubDialog(BROWSER_LANGUAGES_URL);
    236  if (search) {
    237    doc.getElementById("primaryBrowserLocaleSearch").doCommand();
    238    doc.getElementById("primaryBrowserLocale").menupopup.hidePopup();
    239  } else {
    240    doc.getElementById("manageBrowserLanguagesButton").doCommand();
    241  }
    242  let dialogWin = await dialogLoaded;
    243  let dialogDoc = dialogWin.document;
    244  return {
    245    dialog: dialogDoc.getElementById("BrowserLanguagesDialog"),
    246    dialogDoc,
    247    available: dialogDoc.getElementById("availableLocales"),
    248    selected: dialogDoc.getElementById("selectedLocales"),
    249  };
    250 }
    251 
    252 add_task(async function testDisabledBrowserLanguages() {
    253  let langpacksFile = await createLanguageToolsFile();
    254  let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
    255 
    256  await SpecialPowers.pushPrefEnv({
    257    set: [
    258      ["intl.multilingual.enabled", true],
    259      ["intl.multilingual.downloadEnabled", true],
    260      ["intl.multilingual.liveReload", false],
    261      ["intl.multilingual.liveReloadBidirectional", false],
    262      ["intl.locale.requested", "en-US,pl,he,de"],
    263      ["extensions.langpacks.signatures.required", false],
    264      ["extensions.getAddons.langpacks.url", langpacksUrl],
    265    ],
    266  });
    267 
    268  // Install an old pl langpack.
    269  let oldLangpack = await createLangpack("pl", "1.0");
    270  await AddonTestUtils.promiseInstallFile(oldLangpack);
    271 
    272  // Install all the other available langpacks.
    273  let pl;
    274  let langpacks = await createTestLangpacks();
    275  let addons = await Promise.all(
    276    langpacks.map(async ([locale, file]) => {
    277      if (locale == "pl") {
    278        pl = await AddonManager.getAddonByID(langpackId("pl"));
    279        // Disable pl so it's removed from selected.
    280        await pl.disable();
    281        return pl;
    282      }
    283      let install = await AddonTestUtils.promiseInstallFile(file);
    284      return install.addon;
    285    })
    286  );
    287 
    288  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    289    leaveOpen: true,
    290  });
    291 
    292  let doc = gBrowser.contentDocument;
    293  let { dialogDoc, available, selected } = await openDialog(doc);
    294 
    295  // pl is not selected since it's disabled.
    296  is(pl.userDisabled, true, "pl is disabled");
    297  is(pl.version, "1.0", "pl is the old 1.0 version");
    298  assertLocaleOrder(selected, "en-US,he", "en-US");
    299 
    300  // Wait for the children menu to be populated.
    301  await BrowserTestUtils.waitForCondition(
    302    () => !!available.children.length,
    303    "Children list populated"
    304  );
    305 
    306  // Only fr is enabled and not selected, so it's the only locale available.
    307  assertAvailableLocales(available, ["fr"]);
    308 
    309  // Search for more languages.
    310  available.menupopup.lastElementChild.doCommand();
    311  available.menupopup.hidePopup();
    312  await waitForMutation(available.menupopup, { childList: true }, () =>
    313    Array.from(available.menupopup.children).some(
    314      locale => locale.value == "pl"
    315    )
    316  );
    317 
    318  // pl is now available since it is available remotely.
    319  assertAvailableLocales(available, ["fr", "pl"]);
    320 
    321  let installId = null;
    322  AddonTestUtils.promiseInstallEvent("onInstallEnded").then(([install]) => {
    323    installId = install.installId;
    324  });
    325 
    326  // Add pl.
    327  await selectLocale("pl", available, selected, dialogDoc);
    328  assertLocaleOrder(selected, "pl,en-US,he", "pl");
    329 
    330  // Find pl again since it's been upgraded.
    331  pl = await AddonManager.getAddonByID(langpackId("pl"));
    332  is(pl.userDisabled, false, "pl is now enabled");
    333  is(pl.version, "2.0", "pl is upgraded to version 2.0");
    334 
    335  let dialogId = getDialogId(dialogDoc);
    336  ok(dialogId, "There's a dialogId");
    337  ok(installId, "There's an installId");
    338 
    339  await Promise.all(addons.map(addon => addon.uninstall()));
    340  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    341 
    342  assertTelemetryRecorded([
    343    ["manage", "main", dialogId],
    344    ["search", "dialog", dialogId],
    345    ["add", "dialog", dialogId, { installId }],
    346 
    347    // Cancel is recorded when the tab is closed.
    348    ["cancel", "dialog", dialogId],
    349  ]);
    350 });
    351 
    352 add_task(async function testReorderingBrowserLanguages() {
    353  await SpecialPowers.pushPrefEnv({
    354    set: [
    355      ["intl.multilingual.enabled", true],
    356      ["intl.multilingual.downloadEnabled", true],
    357      ["intl.multilingual.liveReload", false],
    358      ["intl.multilingual.liveReloadBidirectional", false],
    359      ["intl.locale.requested", "en-US,pl,he,de"],
    360      ["extensions.langpacks.signatures.required", false],
    361    ],
    362  });
    363 
    364  // Install all the available langpacks.
    365  let langpacks = await createTestLangpacks();
    366  let addons = await Promise.all(
    367    langpacks.map(async ([, file]) => {
    368      let install = await AddonTestUtils.promiseInstallFile(file);
    369      return install.addon;
    370    })
    371  );
    372 
    373  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    374    leaveOpen: true,
    375  });
    376 
    377  let doc = gBrowser.contentDocument;
    378  let messageBar = doc.getElementById("confirmBrowserLanguage");
    379  is(messageBar.hidden, true, "The message bar is hidden at first");
    380 
    381  // Open the dialog.
    382  let { dialog, dialogDoc, selected } = await openDialog(doc);
    383  let firstDialogId = getDialogId(dialogDoc);
    384 
    385  // The initial order is set by the pref, filtered by available.
    386  assertLocaleOrder(selected, "en-US,pl,he", "en-US");
    387 
    388  // Moving pl down changes the order.
    389  selectAddedLocale("pl", selected);
    390  dialogDoc.getElementById("down").doCommand();
    391  assertLocaleOrder(selected, "en-US,he,pl", "pl");
    392 
    393  // Accepting the change shows the confirm message bar.
    394  let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
    395  dialog.acceptDialog();
    396  await dialogClosed;
    397 
    398  // The message bar uses async `formatValues` and that may resolve
    399  // after the dialog is closed.
    400  await BrowserTestUtils.waitForMutationCondition(
    401    messageBar,
    402    { attributes: true },
    403    () => !messageBar.hidden
    404  );
    405  is(
    406    messageBar.querySelector("button").getAttribute("locales"),
    407    "en-US,he,pl",
    408    "The locales are set on the message bar button"
    409  );
    410 
    411  // Open the dialog again.
    412  let newDialog = await openDialog(doc);
    413  dialog = newDialog.dialog;
    414  dialogDoc = newDialog.dialogDoc;
    415  let secondDialogId = getDialogId(dialogDoc);
    416  selected = newDialog.selected;
    417 
    418  // The initial order comes from the previous settings.
    419  assertLocaleOrder(selected, "en-US,he,pl", "en-US");
    420 
    421  // Select pl in the list.
    422  selectAddedLocale("pl", selected);
    423  // Move pl back up.
    424  dialogDoc.getElementById("up").doCommand();
    425  assertLocaleOrder(selected, "en-US,pl,he", "pl");
    426 
    427  // Accepting the change hides the confirm message bar.
    428  dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
    429  dialog.acceptDialog();
    430  await dialogClosed;
    431  is(messageBar.hidden, true, "The message bar is hidden again");
    432 
    433  ok(firstDialogId, "There was an id on the first dialog");
    434  ok(secondDialogId, "There was an id on the second dialog");
    435  Assert.notEqual(
    436    firstDialogId,
    437    secondDialogId,
    438    "The dialog ids are different"
    439  );
    440  Assert.less(
    441    parseInt(firstDialogId),
    442    parseInt(secondDialogId),
    443    "The second dialog id is larger than the first"
    444  );
    445 
    446  // Open the dialog yet again.
    447  newDialog = await openDialog(doc);
    448  dialog = newDialog.dialog;
    449  dialogDoc = newDialog.dialogDoc;
    450  let thirdDialogId = getDialogId(dialogDoc);
    451  selected = newDialog.selected;
    452 
    453  // Move pl to the top.
    454  selectAddedLocale("pl", selected);
    455  assertLocaleOrder(selected, "en-US,he,pl", "pl");
    456  dialogDoc.getElementById("up").doCommand();
    457  dialogDoc.getElementById("up").doCommand();
    458  assertLocaleOrder(selected, "pl,en-US,he", "pl");
    459 
    460  dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
    461  dialog.acceptDialog();
    462  await dialogClosed;
    463 
    464  await Promise.all(addons.map(addon => addon.uninstall()));
    465  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    466 
    467  assertTelemetryRecorded([
    468    ["manage", "main", firstDialogId],
    469    ["reorder", "dialog", firstDialogId],
    470    ["accept", "dialog", firstDialogId],
    471    ["set_fallback", "dialog", firstDialogId],
    472    ["manage", "main", secondDialogId],
    473    ["reorder", "dialog", secondDialogId],
    474    ["accept", "dialog", secondDialogId],
    475    ["manage", "main", thirdDialogId],
    476    ["reorder", "dialog", thirdDialogId],
    477    ["reorder", "dialog", thirdDialogId],
    478    ["accept", "dialog", thirdDialogId],
    479  ]);
    480 });
    481 
    482 add_task(async function testAddAndRemoveSelectedLanguages() {
    483  await SpecialPowers.pushPrefEnv({
    484    set: [
    485      ["intl.multilingual.enabled", true],
    486      ["intl.multilingual.downloadEnabled", true],
    487      ["intl.multilingual.liveReload", false],
    488      ["intl.multilingual.liveReloadBidirectional", false],
    489      ["intl.locale.requested", "en-US"],
    490      ["extensions.langpacks.signatures.required", false],
    491    ],
    492  });
    493 
    494  let langpacks = await createTestLangpacks();
    495  let addons = await Promise.all(
    496    langpacks.map(async ([, file]) => {
    497      let install = await AddonTestUtils.promiseInstallFile(file);
    498      return install.addon;
    499    })
    500  );
    501 
    502  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    503    leaveOpen: true,
    504  });
    505 
    506  let doc = gBrowser.contentDocument;
    507  let messageBar = doc.getElementById("confirmBrowserLanguage");
    508  is(messageBar.hidden, true, "The message bar is hidden at first");
    509 
    510  // Open the dialog.
    511  let { dialog, dialogDoc, available, selected } = await openDialog(doc);
    512  let dialogId = getDialogId(dialogDoc);
    513 
    514  // loadLocalesFromAMO is async but `initAvailableLocales` doesn't wait
    515  // for it to be resolved, so we have to wait for the list to be populated
    516  // before we test for its values.
    517  await BrowserTestUtils.waitForMutationCondition(
    518    available.menupopup,
    519    { attributes: true, childList: true },
    520    () => {
    521      let listLocales = Array.from(available.menupopup.children).filter(
    522        item => item.value && item.value != "search"
    523      );
    524      return listLocales.length == 3;
    525    }
    526  );
    527  // The initial order is set by the pref.
    528  assertLocaleOrder(selected, "en-US", "en-US");
    529  assertAvailableLocales(available, ["fr", "pl", "he"]);
    530 
    531  let removeButton = dialogDoc.getElementById("remove");
    532  // Cannot remove the default locale.
    533  is(removeButton.disabled, true, "Remove en-US should be disabled");
    534 
    535  // Add pl and fr to selected.
    536  await selectLocale("pl", available, selected, dialogDoc);
    537  await selectLocale("fr", available, selected, dialogDoc);
    538 
    539  assertLocaleOrder(selected, "fr,pl,en-US", "fr");
    540  assertAvailableLocales(available, ["he"]);
    541 
    542  // Can remove the added locale again.
    543  is(removeButton.disabled, false, "Remove fr should be not be disabled");
    544 
    545  selectAddedLocale("en-US", selected);
    546  // Cannot remove the default locale, even after adding more.
    547  is(removeButton.disabled, true, "Remove en-us should still be disabled");
    548 
    549  // Remove pl and fr from selected.
    550  selectAddedLocale("fr", selected);
    551  is(removeButton.disabled, false, "Remove fr should be not be disabled");
    552  removeButton.doCommand();
    553  // Selection moves to pl.
    554  assertLocaleOrder(selected, "pl,en-US", "pl");
    555  is(removeButton.disabled, false, "Remove pl should be not be disabled");
    556  removeButton.doCommand();
    557  assertLocaleOrder(selected, "en-US", "en-US");
    558  assertAvailableLocales(available, ["fr", "pl", "he"]);
    559  is(removeButton.disabled, true, "Remove en-us should be disabled at end");
    560 
    561  // Add he to selected.
    562  await selectLocale("he", available, selected, dialogDoc);
    563  assertLocaleOrder(selected, "he,en-US", "he");
    564  assertAvailableLocales(available, ["pl", "fr"]);
    565 
    566  // Accepting the change shows the confirm message bar.
    567  let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
    568  dialog.acceptDialog();
    569  await dialogClosed;
    570 
    571  await waitForMutation(
    572    messageBar,
    573    { attributes: true, attributeFilter: ["hidden"] },
    574    target => !target.hidden
    575  );
    576 
    577  is(messageBar.hidden, false, "The message bar is now visible");
    578  is(
    579    messageBar.querySelector("button").getAttribute("locales"),
    580    "he,en-US",
    581    "The locales are set on the message bar button"
    582  );
    583 
    584  await Promise.all(addons.map(addon => addon.uninstall()));
    585  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    586 
    587  assertTelemetryRecorded([
    588    ["manage", "main", dialogId],
    589 
    590    // Install id is not recorded since it was already installed.
    591    ["add", "dialog", dialogId],
    592    ["add", "dialog", dialogId],
    593 
    594    ["remove", "dialog", dialogId],
    595    ["remove", "dialog", dialogId],
    596 
    597    ["add", "dialog", dialogId],
    598    ["accept", "dialog", dialogId],
    599  ]);
    600 });
    601 
    602 add_task(async function testInstallFromAMO() {
    603  let langpacks = await AddonManager.getAddonsByTypes(["locale"]);
    604  is(langpacks.length, 0, "There are no langpacks installed");
    605 
    606  let langpacksFile = await createLanguageToolsFile();
    607  let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
    608  let dictionaryBrowseFile = await createDictionaryBrowseResults();
    609  let browseApiEndpoint = Services.io.newFileURI(dictionaryBrowseFile).spec;
    610 
    611  await SpecialPowers.pushPrefEnv({
    612    set: [
    613      ["intl.multilingual.enabled", true],
    614      ["intl.multilingual.downloadEnabled", true],
    615      ["intl.multilingual.liveReload", false],
    616      ["intl.multilingual.liveReloadBidirectional", false],
    617      ["intl.locale.requested", "en-US"],
    618      ["extensions.getAddons.langpacks.url", langpacksUrl],
    619      ["extensions.langpacks.signatures.required", false],
    620      ["extensions.getAddons.get.url", browseApiEndpoint],
    621    ],
    622  });
    623 
    624  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    625    leaveOpen: true,
    626  });
    627 
    628  let doc = gBrowser.contentDocument;
    629  let messageBar = doc.getElementById("confirmBrowserLanguage");
    630  is(messageBar.hidden, true, "The message bar is hidden at first");
    631 
    632  // Verify only en-US is listed on the main pane.
    633  let getMainPaneLocales = () => {
    634    let available = doc.getElementById("primaryBrowserLocale");
    635    let availableLocales = Array.from(available.menupopup.children);
    636    return availableLocales
    637      .map(item => item.value)
    638      .sort()
    639      .join(",");
    640  };
    641  is(getMainPaneLocales(), "en-US,search", "Only en-US installed to start");
    642 
    643  // Open the dialog.
    644  let { dialog, dialogDoc, available, selected } = await openDialog(doc, true);
    645  let firstDialogId = getDialogId(dialogDoc);
    646 
    647  // Make sure the message bar is still hidden.
    648  is(
    649    messageBar.hidden,
    650    true,
    651    "The message bar is still hidden after searching"
    652  );
    653 
    654  if (available.itemCount == 1) {
    655    await waitForMutation(
    656      available.menupopup,
    657      { childList: true },
    658      () => available.itemCount > 1
    659    );
    660  }
    661 
    662  // The initial order is set by the pref.
    663  assertLocaleOrder(selected, "en-US", "en-US");
    664  assertAvailableLocales(available, ["fr", "he", "pl"]);
    665  is(
    666    Services.locale.availableLocales.join(","),
    667    "en-US",
    668    "There is only one installed locale"
    669  );
    670 
    671  // Verify that there are no extra dictionaries.
    672  let dicts = await AddonManager.getAddonsByTypes(["dictionary"]);
    673  is(dicts.length, 0, "There are no installed dictionaries");
    674 
    675  let installId = null;
    676  AddonTestUtils.promiseInstallEvent("onInstallEnded").then(([install]) => {
    677    installId = install.installId;
    678  });
    679 
    680  // Add Polish, this will install the langpack.
    681  await selectLocale("pl", available, selected, dialogDoc);
    682 
    683  ok(installId, "We got an installId for the langpack installation");
    684 
    685  let langpack = await AddonManager.getAddonByID(langpackId("pl"));
    686  Assert.deepEqual(
    687    langpack.installTelemetryInfo,
    688    { source: "about:preferences" },
    689    "The source is set to preferences"
    690  );
    691 
    692  // Verify the list is correct.
    693  assertLocaleOrder(selected, "pl,en-US", "pl");
    694  assertAvailableLocales(available, ["fr", "he"]);
    695  is(
    696    Services.locale.availableLocales.sort().join(","),
    697    "en-US,pl",
    698    "Polish is now installed"
    699  );
    700 
    701  await BrowserTestUtils.waitForCondition(async () => {
    702    let newDicts = await AddonManager.getAddonsByTypes(["dictionary"]);
    703    let done = !!newDicts.length;
    704 
    705    if (done) {
    706      is(
    707        newDicts[0].id,
    708        DICTIONARY_ID_PL,
    709        "The polish dictionary was installed"
    710      );
    711    }
    712 
    713    return done;
    714  });
    715 
    716  // Move pl down the list, which prevents an error since it isn't valid.
    717  dialogDoc.getElementById("down").doCommand();
    718  assertLocaleOrder(selected, "en-US,pl", "pl");
    719 
    720  // Test that disabling the langpack removes it from the list.
    721  let dialogClosed = BrowserTestUtils.waitForEvent(dialog, "dialogclosing");
    722  dialog.acceptDialog();
    723  await dialogClosed;
    724 
    725  // Verify pl is now available to select.
    726  is(getMainPaneLocales(), "en-US,pl,search", "en-US and pl now available");
    727 
    728  // Disable the Polish langpack.
    729  langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
    730  await langpack.disable();
    731 
    732  ({ dialogDoc, available, selected } = await openDialog(doc, true));
    733  let secondDialogId = getDialogId(dialogDoc);
    734 
    735  // Wait for the available langpacks to load.
    736  if (available.itemCount == 1) {
    737    await waitForMutation(
    738      available.menupopup,
    739      { childList: true },
    740      () => available.itemCount > 1
    741    );
    742  }
    743  assertLocaleOrder(selected, "en-US", "en-US");
    744  assertAvailableLocales(available, ["fr", "he", "pl"]);
    745 
    746  // Uninstall the langpack and dictionary.
    747  let installs = await AddonManager.getAddonsByTypes(["locale", "dictionary"]);
    748  is(installs.length, 2, "There is one langpack and one dictionary installed");
    749  await Promise.all(installs.map(item => item.uninstall()));
    750 
    751  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    752 
    753  assertTelemetryRecorded([
    754    // First dialog installs a locale and accepts.
    755    ["search", "main", firstDialogId],
    756    // It has an installId since it was downloaded.
    757    ["add", "dialog", firstDialogId, { installId }],
    758    // It got moved down to avoid errors with finding translations.
    759    ["reorder", "dialog", firstDialogId],
    760    ["accept", "dialog", firstDialogId],
    761 
    762    // The second dialog just checks the state and is closed with the tab.
    763    ["search", "main", secondDialogId],
    764    ["cancel", "dialog", secondDialogId],
    765  ]);
    766 });
    767 
    768 let hasSearchOption = popup =>
    769  Array.from(popup.children).some(el => el.value == "search");
    770 
    771 add_task(async function testDownloadEnabled() {
    772  await SpecialPowers.pushPrefEnv({
    773    set: [
    774      ["intl.multilingual.enabled", true],
    775      ["intl.multilingual.downloadEnabled", true],
    776      ["intl.multilingual.liveReload", false],
    777      ["intl.multilingual.liveReloadBidirectional", false],
    778    ],
    779  });
    780 
    781  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    782    leaveOpen: true,
    783  });
    784  let doc = gBrowser.contentDocument;
    785 
    786  let defaultMenulist = doc.getElementById("primaryBrowserLocale");
    787  ok(
    788    hasSearchOption(defaultMenulist.menupopup),
    789    "There's a search option in the General pane"
    790  );
    791 
    792  let { available } = await openDialog(doc, false);
    793  ok(
    794    hasSearchOption(available.menupopup),
    795    "There's a search option in the dialog"
    796  );
    797 
    798  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    799 });
    800 
    801 add_task(async function testDownloadDisabled() {
    802  await SpecialPowers.pushPrefEnv({
    803    set: [
    804      ["intl.multilingual.enabled", true],
    805      ["intl.multilingual.downloadEnabled", false],
    806      ["intl.multilingual.liveReload", false],
    807      ["intl.multilingual.liveReloadBidirectional", false],
    808    ],
    809  });
    810 
    811  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    812    leaveOpen: true,
    813  });
    814  let doc = gBrowser.contentDocument;
    815 
    816  let defaultMenulist = doc.getElementById("primaryBrowserLocale");
    817  ok(
    818    !hasSearchOption(defaultMenulist.menupopup),
    819    "There's no search option in the General pane"
    820  );
    821 
    822  let { available } = await openDialog(doc, false);
    823  ok(
    824    !hasSearchOption(available.menupopup),
    825    "There's no search option in the dialog"
    826  );
    827 
    828  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    829 });
    830 
    831 add_task(async function testReorderMainPane() {
    832  await SpecialPowers.pushPrefEnv({
    833    set: [
    834      ["intl.multilingual.enabled", true],
    835      ["intl.multilingual.downloadEnabled", false],
    836      ["intl.multilingual.liveReload", false],
    837      ["intl.multilingual.liveReloadBidirectional", false],
    838      ["intl.locale.requested", "en-US"],
    839      ["extensions.langpacks.signatures.required", false],
    840    ],
    841  });
    842 
    843  // Clear the telemetry from other tests.
    844  Services.telemetry.clearEvents();
    845 
    846  let langpacks = await createTestLangpacks();
    847  let addons = await Promise.all(
    848    langpacks.map(async ([, file]) => {
    849      let install = await AddonTestUtils.promiseInstallFile(file);
    850      return install.addon;
    851    })
    852  );
    853 
    854  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    855    leaveOpen: true,
    856  });
    857  let doc = gBrowser.contentDocument;
    858 
    859  let messageBar = doc.getElementById("confirmBrowserLanguage");
    860  is(messageBar.hidden, true, "The message bar is hidden at first");
    861 
    862  let available = doc.getElementById("primaryBrowserLocale");
    863  let availableLocales = Array.from(available.menupopup.children);
    864  let availableCodes = availableLocales
    865    .map(item => item.value)
    866    .sort()
    867    .join(",");
    868  is(
    869    availableCodes,
    870    "en-US,fr,he,pl",
    871    "All of the available locales are listed"
    872  );
    873 
    874  is(available.selectedItem.value, "en-US", "English is selected");
    875 
    876  let hebrew = availableLocales.find(item => item.value == "he");
    877  hebrew.click();
    878  available.menupopup.hidePopup();
    879 
    880  await BrowserTestUtils.waitForCondition(
    881    () => !messageBar.hidden,
    882    "Wait for message bar to show"
    883  );
    884 
    885  is(messageBar.hidden, false, "The message bar is now shown");
    886  is(
    887    messageBar.querySelector("button").getAttribute("locales"),
    888    "he,en-US",
    889    "The locales are set on the message bar button"
    890  );
    891 
    892  await Promise.all(addons.map(addon => addon.uninstall()));
    893  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    894 
    895  assertTelemetryRecorded([["reorder", "main"]]);
    896 });
    897 
    898 add_task(async function testLiveLanguageReloading() {
    899  await SpecialPowers.pushPrefEnv({
    900    set: [
    901      ["intl.multilingual.enabled", true],
    902      ["intl.multilingual.downloadEnabled", true],
    903      ["intl.multilingual.liveReload", true],
    904      ["intl.multilingual.liveReloadBidirectional", false],
    905      ["intl.locale.requested", "en-US,fr,he,de"],
    906      ["extensions.langpacks.signatures.required", false],
    907    ],
    908  });
    909 
    910  // Clear the telemetry from other tests.
    911  Services.telemetry.clearEvents();
    912 
    913  let langpacks = await createTestLangpacks();
    914  let addons = await Promise.all(
    915    langpacks.map(async ([, file]) => {
    916      let install = await AddonTestUtils.promiseInstallFile(file);
    917      return install.addon;
    918    })
    919  );
    920 
    921  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    922    leaveOpen: true,
    923  });
    924 
    925  let doc = gBrowser.contentDocument;
    926 
    927  let available = doc.getElementById("primaryBrowserLocale");
    928  let availableLocales = Array.from(available.menupopup.children);
    929 
    930  is(
    931    Services.locale.appLocaleAsBCP47,
    932    "en-US",
    933    "The app locale starts as English."
    934  );
    935 
    936  Assert.deepEqual(
    937    Services.locale.requestedLocales,
    938    ["en-US", "fr", "he", "de"],
    939    "The locale order starts as what was initially requested."
    940  );
    941 
    942  // French and English are both LTR languages.
    943  let french = availableLocales.find(item => item.value == "fr");
    944 
    945  french.click();
    946  available.menupopup.hidePopup();
    947 
    948  is(
    949    Services.locale.appLocaleAsBCP47,
    950    "fr",
    951    "The app locale was changed to French"
    952  );
    953 
    954  Assert.deepEqual(
    955    Services.locale.requestedLocales,
    956    ["fr", "en-US", "he", "de"],
    957    "The locale order is switched to french first."
    958  );
    959 
    960  await Promise.all(addons.map(addon => addon.uninstall()));
    961  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    962 
    963  assertTelemetryRecorded([["reorder", "main"]]);
    964 });
    965 
    966 add_task(async function testLiveLanguageReloadingBidiOff() {
    967  await SpecialPowers.pushPrefEnv({
    968    set: [
    969      ["intl.multilingual.enabled", true],
    970      ["intl.multilingual.downloadEnabled", true],
    971      ["intl.multilingual.liveReload", true],
    972      ["intl.multilingual.liveReloadBidirectional", false],
    973      ["intl.locale.requested", "en-US,fr,he,de"],
    974      ["extensions.langpacks.signatures.required", false],
    975    ],
    976  });
    977 
    978  // Clear the telemetry from other tests.
    979  Services.telemetry.clearEvents();
    980 
    981  let langpacks = await createTestLangpacks();
    982  let addons = await Promise.all(
    983    langpacks.map(async ([, file]) => {
    984      let install = await AddonTestUtils.promiseInstallFile(file);
    985      return install.addon;
    986    })
    987  );
    988 
    989  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
    990    leaveOpen: true,
    991  });
    992 
    993  let doc = gBrowser.contentDocument;
    994 
    995  let available = doc.getElementById("primaryBrowserLocale");
    996  let availableLocales = Array.from(available.menupopup.children);
    997 
    998  is(
    999    Services.locale.appLocaleAsBCP47,
   1000    "en-US",
   1001    "The app locale starts as English."
   1002  );
   1003 
   1004  Assert.deepEqual(
   1005    Services.locale.requestedLocales,
   1006    ["en-US", "fr", "he", "de"],
   1007    "The locale order starts as what was initially requested."
   1008  );
   1009 
   1010  let messageBar = doc.getElementById("confirmBrowserLanguage");
   1011  is(messageBar.hidden, true, "The message bar is hidden at first");
   1012 
   1013  // English is LTR and Hebrew is RTL.
   1014  let hebrew = availableLocales.find(item => item.value == "he");
   1015 
   1016  hebrew.click();
   1017  available.menupopup.hidePopup();
   1018 
   1019  await BrowserTestUtils.waitForCondition(
   1020    () => !messageBar.hidden,
   1021    "Wait for message bar to show"
   1022  );
   1023 
   1024  is(messageBar.hidden, false, "The message bar is now shown");
   1025 
   1026  is(
   1027    Services.locale.appLocaleAsBCP47,
   1028    "en-US",
   1029    "The app locale remains in English"
   1030  );
   1031 
   1032  Assert.deepEqual(
   1033    Services.locale.requestedLocales,
   1034    ["en-US", "fr", "he", "de"],
   1035    "The locale order did not change."
   1036  );
   1037 
   1038  await Promise.all(addons.map(addon => addon.uninstall()));
   1039 
   1040  assertTelemetryRecorded([["reorder", "main"]]);
   1041 
   1042  BrowserTestUtils.removeTab(gBrowser.selectedTab);
   1043 });
   1044 
   1045 add_task(async function testLiveLanguageReloadingBidiOn() {
   1046  await SpecialPowers.pushPrefEnv({
   1047    set: [
   1048      ["intl.multilingual.enabled", true],
   1049      ["intl.multilingual.downloadEnabled", true],
   1050      ["intl.multilingual.liveReload", true],
   1051      ["intl.multilingual.liveReloadBidirectional", true],
   1052      ["intl.locale.requested", "en-US,fr,he,de"],
   1053      ["extensions.langpacks.signatures.required", false],
   1054    ],
   1055  });
   1056 
   1057  // Clear the telemetry from other tests.
   1058  Services.telemetry.clearEvents();
   1059 
   1060  let langpacks = await createTestLangpacks();
   1061  let addons = await Promise.all(
   1062    langpacks.map(async ([, file]) => {
   1063      let install = await AddonTestUtils.promiseInstallFile(file);
   1064      return install.addon;
   1065    })
   1066  );
   1067 
   1068  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {
   1069    leaveOpen: true,
   1070  });
   1071 
   1072  let doc = gBrowser.contentDocument;
   1073 
   1074  let available = doc.getElementById("primaryBrowserLocale");
   1075  let availableLocales = Array.from(available.menupopup.children);
   1076 
   1077  is(
   1078    Services.locale.appLocaleAsBCP47,
   1079    "en-US",
   1080    "The app locale starts as English."
   1081  );
   1082 
   1083  Assert.deepEqual(
   1084    Services.locale.requestedLocales,
   1085    ["en-US", "fr", "he", "de"],
   1086    "The locale order starts as what was initially requested."
   1087  );
   1088 
   1089  let messageBar = doc.getElementById("confirmBrowserLanguage");
   1090  is(messageBar.hidden, true, "The message bar is hidden at first");
   1091 
   1092  // English is LTR and Hebrew is RTL.
   1093  let hebrew = availableLocales.find(item => item.value == "he");
   1094 
   1095  hebrew.click();
   1096  available.menupopup.hidePopup();
   1097 
   1098  is(messageBar.hidden, true, "The message bar is still hidden");
   1099 
   1100  is(
   1101    Services.locale.appLocaleAsBCP47,
   1102    "he",
   1103    "The app locale was changed to Hebrew."
   1104  );
   1105 
   1106  Assert.deepEqual(
   1107    Services.locale.requestedLocales,
   1108    ["he", "en-US", "fr", "de"],
   1109    "The locale changed with Hebrew first."
   1110  );
   1111 
   1112  await Promise.all(addons.map(addon => addon.uninstall()));
   1113 
   1114  assertTelemetryRecorded([["reorder", "main"]]);
   1115 
   1116  BrowserTestUtils.removeTab(gBrowser.selectedTab);
   1117 });