Bug 1488442 - Part 1: Only list available locales in the requested set r=zbraniecki
authorMark Striemer <mstriemer@mozilla.com>
Fri, 23 Nov 2018 19:59:59 +0000
changeset 504321 796ce1c75aa4f16bcb657f5957e04542e3f6c6f2
parent 504320 73385b8318802d1d7769bada221d96d17eea0ead
child 504322 7bc33731a895a6d634bef92a9da8c4148eb269a9
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerszbraniecki
bugs1488442
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1488442 - Part 1: Only list available locales in the requested set r=zbraniecki Generally, this switches Services.locale.requestedLocales calls to use Services.locale.appLocalesAsBCP47. Differential Revision: https://phabricator.services.mozilla.com/D10980
browser/components/preferences/browserLanguages.js
browser/components/preferences/browserLanguages.xul
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
--- a/browser/components/preferences/browserLanguages.js
+++ b/browser/components/preferences/browserLanguages.js
@@ -10,16 +10,28 @@ ChromeUtils.defineModuleGetter(this, "Ad
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonRepository",
                                "resource://gre/modules/addons/AddonRepository.jsm");
 ChromeUtils.defineModuleGetter(this, "RemoteSettings",
                                "resource://services-settings/remote-settings.js");
 ChromeUtils.defineModuleGetter(this, "SelectionChangedMenulist",
                                "resource:///modules/SelectionChangedMenulist.jsm");
 
+/* This dialog provides an interface for managing what language the browser is
+ * displayed in.
+ *
+ * There is a list of "requested" locales and a list of "available" locales. The
+ * requested locales must be installed and enabled. Available locales could be
+ * installed and enabled, or fetched from the AMO language tools API.
+ *
+ * If a langpack is disabled, there is no way to determine what locale it is for and
+ * it will only be listed as available if that locale is also available on AMO and
+ * the user has opted to search for more languages.
+ */
+
 async function installFromUrl(url, hash) {
   let install = await AddonManager.getInstallForURL(
     url, "application/x-xpinstall", hash);
   return install.install();
 }
 
 async function dictionaryIdsForLocale(locale) {
   let entries = await RemoteSettings("language-dictionaries").get({
@@ -288,53 +300,64 @@ function compareItems(a, b) {
   } else if (a.value) {
     return 1;
   }
   return -1;
 }
 
 var gBrowserLanguagesDialog = {
   _availableLocales: null,
-  _requestedLocales: null,
-  requestedLocales: null,
+  _selectedLocales: null,
+  selectedLocales: null,
 
   get downloadEnabled() {
     // Downloading langpacks isn't always supported, check the pref.
     return Services.prefs.getBoolPref("intl.multilingual.downloadEnabled");
   },
 
   beforeAccept() {
-    this.requestedLocales = this.getRequestedLocales();
+    this.selected = this.getSelectedLocales();
     return true;
   },
 
   async onLoad() {
-    // Maintain the previously requested locales even if we cancel out.
-    let {requesting, search} = window.arguments[0] || {};
-    this.requestedLocales = requesting;
+    // Maintain the previously selected locales even if we cancel out.
+    let {selected, search} = window.arguments[0] || {};
+    this.selectedLocales = selected;
 
-    let requested = this.requestedLocales || Services.locale.requestedLocales;
-    let requestedSet = new Set(requested);
-    let available = Services.locale.availableLocales
-      .filter(locale => !requestedSet.has(locale));
+    // This is a list of available locales that the user selected. It's more
+    // restricted than the Intl notion of `requested` as it only contains
+    // locale codes for which we have matching locales available.
+    // The first time this dialog is opened, populate with appLocalesAsBCP47.
+    let selectedLocales = this.selectedLocales || Services.locale.appLocalesAsBCP47;
+    let selectedLocaleSet = new Set(selectedLocales);
+    let available = Services.locale.availableLocales;
+    let availableSet = new Set(available);
 
-    this.initRequestedLocales(requested);
+    // Filter selectedLocales since the user may select a locale when it is
+    // available and then disable it.
+    selectedLocales = selectedLocales.filter(locale => availableSet.has(locale));
+    // Nothing in available should be in selectedSet.
+    available = available.filter(locale => !selectedLocaleSet.has(locale));
+
+    this.initSelectedLocales(selectedLocales);
     await this.initAvailableLocales(available, search);
+
     this.initialized = true;
   },
 
-  initRequestedLocales(requested) {
-    this._requestedLocales = new OrderedListBox({
-      richlistbox: document.getElementById("requestedLocales"),
+  initSelectedLocales(selectedLocales) {
+    this._selectedLocales = new OrderedListBox({
+      richlistbox: document.getElementById("selectedLocales"),
       upButton: document.getElementById("up"),
       downButton: document.getElementById("down"),
       removeButton: document.getElementById("remove"),
-      onRemove: (item) => this.requestedLocaleRemoved(item),
+      onRemove: (item) => this.selectedLocaleRemoved(item),
     });
-    this._requestedLocales.setItems(getLocaleDisplayInfo(requested));
+    this._selectedLocales.setItems(getLocaleDisplayInfo(selectedLocales));
   },
 
   async initAvailableLocales(available, search) {
     this._availableLocales = new SortedItemSelectList({
       menulist: document.getElementById("availableLocales"),
       button: document.getElementById("add"),
       compareFn: compareItems,
       onSelect: (item) => this.availableLanguageSelected(item),
@@ -377,34 +400,33 @@ var gBrowserLanguagesDialog = {
     }
 
     // Store the available langpack info for later use.
     this.availableLangpacks = new Map();
     for (let {target_locale, url, hash} of availableLangpacks) {
       this.availableLangpacks.set(target_locale, {url, hash});
     }
 
-    // Create a list of installed locales to hide.
-    let installedLocales = new Set([
-      ...Services.locale.requestedLocales,
-      ...Services.locale.availableLocales,
-    ]);
-
-    let availableLocales = availableLangpacks
+    // Remove the installed locales from the available ones.
+    let installedLocales = new Set(Services.locale.availableLocales);
+    let notInstalledLocales = availableLangpacks
       .filter(({target_locale}) => !installedLocales.has(target_locale))
       .map(lang => lang.target_locale);
-    let availableItems = getLocaleDisplayInfo(availableLocales);
+
+    // Create the rows for the remote locales.
+    let availableItems = getLocaleDisplayInfo(notInstalledLocales);
     availableItems.push({
       label: await document.l10n.formatValue("browser-languages-available-label"),
       className: "label-item",
       disabled: true,
       installed: false,
     });
+
+    // Remove the search option and add the remote locales.
     let items = this._availableLocales.items;
-    // Drop the search item.
     items.pop();
     items = items.concat(availableItems);
 
     // Update the dropdown and enable it again.
     this._availableLocales.setItems(items);
     this._availableLocales.enableWithMessageId("browser-languages-select-language");
   },
 
@@ -431,20 +453,20 @@ var gBrowserLanguagesDialog = {
     } else if (this.availableLangpacks.has(item.value)) {
       await this.requestRemoteLanguage(item);
     } else {
       this.showError();
     }
   },
 
   requestLocalLanguage(item, available) {
-    this._requestedLocales.addItem(item);
-    let requestedCount = this._requestedLocales.items.length;
+    this._selectedLocales.addItem(item);
+    let selectedCount = this._selectedLocales.items.length;
     let availableCount = Services.locale.availableLocales.length;
-    if (requestedCount == availableCount) {
+    if (selectedCount == availableCount) {
       // Remove the installed label, they're all installed.
       this._availableLocales.items.shift();
       this._availableLocales.setItems(this._availableLocales.items);
     }
   },
 
   async requestRemoteLanguage(item) {
     this._availableLocales.disableWithMessageId(
@@ -455,17 +477,17 @@ var gBrowserLanguagesDialog = {
     try {
       await installFromUrl(url, hash);
     } catch (e) {
       this.showError();
       return;
     }
 
     item.installed = true;
-    this._requestedLocales.addItem(item);
+    this._selectedLocales.addItem(item);
     this._availableLocales.enableWithMessageId(
       "browser-languages-select-language");
 
     // This is an async task that will install the recommended dictionaries for
     // this locale. This will fail silently at least until a management UI is
     // added in bug 1493705.
     this.installDictionariesForLanguage(item.value);
   },
@@ -489,21 +511,21 @@ var gBrowserLanguagesDialog = {
   },
 
   hideError() {
     document.querySelectorAll(".warning-message-separator")
       .forEach(separator => separator.classList.remove("thin"));
     document.getElementById("warning-message").hidden = true;
   },
 
-  getRequestedLocales() {
-    return this._requestedLocales.items.map(item => item.value);
+  getSelectedLocales() {
+    return this._selectedLocales.items.map(item => item.value);
   },
 
-  async requestedLocaleRemoved(item) {
+  async selectedLocaleRemoved(item) {
     this._availableLocales.addItem(item);
 
     // If the item we added is at the top of the list, it needs the label.
     if (this._availableLocales.items[0] == item) {
       this._availableLocales.addItem(await this.createInstalledLabel());
     }
   },
 
--- a/browser/components/preferences/browserLanguages.xul
+++ b/browser/components/preferences/browserLanguages.xul
@@ -32,17 +32,17 @@
 
   <grid flex="1">
     <columns>
       <column flex="1"/>
       <column/>
     </columns>
     <rows>
       <row>
-        <richlistbox id="requestedLocales"/>
+        <richlistbox id="selectedLocales"/>
         <vbox>
           <button id="up" class="action-button" disabled="true" data-l10n-id="languages-customize-moveup"/>
           <button id="down" class="action-button" disabled="true" data-l10n-id="languages-customize-movedown"/>
           <button id="remove" class="action-button" disabled="true" data-l10n-id="languages-customize-remove"/>
         </vbox>
       </row>
       <row>
         <menulist id="availableLocales"
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -741,25 +741,25 @@ var gMainPane = {
     }
     newValue = pbAutoStartPref.value ? false : startupPref.value === this.STARTUP_PREF_RESTORE_SESSION;
     if (checkbox.checked !== newValue) {
       checkbox.checked = newValue;
     }
   },
 
   initBrowserLocale() {
-    gMainPane.setBrowserLocales(Services.locale.requestedLocale);
+    gMainPane.setBrowserLocales(Services.locale.appLocaleAsBCP47);
   },
 
   /**
    * Update the available list of locales and select the locale that the user
-   * is "requesting". This could be the currently requested locale or a locale
+   * is "selecting". This could be the currently requested locale or a locale
    * that the user would like to switch to after confirmation.
    */
-  async setBrowserLocales(requesting) {
+  async setBrowserLocales(selected) {
     let available = Services.locale.availableLocales;
     let localeNames = Services.intl.getLocaleDisplayNames(undefined, available);
     let locales = available.map((code, i) => ({code, name: localeNames[i]}));
     locales.sort((a, b) => a.name > b.name);
 
     let fragment = document.createDocumentFragment();
     for (let {code, name} of locales) {
       let menuitem = document.createXULElement("menuitem");
@@ -777,17 +777,17 @@ var gMainPane = {
       menuitem.setAttribute("value", "search");
       fragment.appendChild(menuitem);
     }
 
     let menulist = document.getElementById("defaultBrowserLanguage");
     let menupopup = menulist.querySelector("menupopup");
     menupopup.textContent = "";
     menupopup.appendChild(fragment);
-    menulist.value = requesting;
+    menulist.value = selected;
 
     // This will register the "command" listener.
     new SelectionChangedMenulist(menulist, event => {
       gMainPane.onBrowserLanguageChange(event);
     });
 
     document.getElementById("browserLanguagesBox").hidden = false;
   },
@@ -832,17 +832,17 @@ var gMainPane = {
       button.setAttribute("locales", locales.join(","));
       button.setAttribute("label", buttonLabels[i]);
       messageContainer.appendChild(button);
 
       contentContainer.appendChild(messageContainer);
     }
 
     messageBar.hidden = false;
-    gMainPane.requestingLocales = locales;
+    gMainPane.selectedLocales = locales;
   },
 
   hideConfirmLanguageChangeMessageBar() {
     let messageBar = document.getElementById("confirmBrowserLanguage");
     messageBar.hidden = true;
     let contentContainer = messageBar.querySelector(".message-bar-content-container");
     contentContainer.textContent = "";
     gMainPane.requestingLocales = null;
@@ -867,17 +867,17 @@ var gMainPane = {
 
   /* Show or hide the confirm change message bar based on the new locale. */
   onBrowserLanguageChange(event) {
     let locale = event.target.value;
 
     if (locale == "search") {
       gMainPane.showBrowserLanguages({search: true});
       return;
-    } else if (locale == Services.locale.requestedLocale) {
+    } else if (locale == Services.locale.appLocaleAsBCP47) {
       this.hideConfirmLanguageChangeMessageBar();
       return;
     }
 
     let locales = Array.from(new Set([
       locale,
       ...Services.locale.requestedLocales,
     ]).values());
@@ -1004,32 +1004,36 @@ var gMainPane = {
   /**
    * Shows a dialog in which the preferred language for web content may be set.
    */
   showLanguages() {
     gSubDialog.open("chrome://browser/content/preferences/languages.xul");
   },
 
   showBrowserLanguages({search}) {
-    let opts = {requesting: gMainPane.requestingLocales, search};
+    let opts = {selected: gMainPane.selectedLocales, search};
     gSubDialog.open(
       "chrome://browser/content/preferences/browserLanguages.xul",
       null, opts, this.browserLanguagesClosed);
   },
 
   /* Show or hide the confirm change message bar based on the updated ordering. */
   browserLanguagesClosed() {
-    let requesting = this.gBrowserLanguagesDialog.requestedLocales;
-    let requested = Services.locale.requestedLocales;
-    if (requesting && requesting.join(",") != requested.join(",")) {
-      gMainPane.showConfirmLanguageChangeMessageBar(requesting);
-      gMainPane.setBrowserLocales(requesting[0]);
+    let selected = this.gBrowserLanguagesDialog.selected;
+    let active = Services.locale.appLocalesAsBCP47;
+
+    // Prepare for changing the locales if they are different than the current locales.
+    if (selected && selected.join(",") != active.join(",")) {
+      gMainPane.showConfirmLanguageChangeMessageBar(selected);
+      gMainPane.setBrowserLocales(selected[0]);
       return;
     }
-    gMainPane.setBrowserLocales(Services.locale.requestedLocale);
+
+    // They matched, so we can reset the UI.
+    gMainPane.setBrowserLocales(Services.locale.appLocaleAsBCP47);
     gMainPane.hideConfirmLanguageChangeMessageBar();
   },
 
   /**
    * Displays the translation exceptions dialog where specific site and language
    * translation preferences can be set.
    */
   showTranslationExceptions() {
--- a/browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
+++ b/browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
@@ -12,17 +12,19 @@ const DICTIONARY_ID_PL = "pl@dictionarie
 
 function getManifestData(locale) {
   return {
     langpack_id: locale,
     name: `${locale} Language Pack`,
     description: `${locale} Language pack`,
     languages: {
       [locale]: {
-        chrome_resources: {},
+        chrome_resources: {
+          "branding": `browser/chrome/${locale}/locale/branding/`,
+        },
         version: "1",
       },
     },
     applications: {
       gecko: {
         strict_min_version: AppConstants.MOZ_APP_VERSION,
         id: `langpack-${locale}@firefox.mozilla.org`,
         strict_max_version: AppConstants.MOZ_APP_VERSION,
@@ -43,16 +45,17 @@ let testLocales = ["fr", "pl", "he"];
 let testLangpacks;
 
 function createTestLangpacks() {
   if (!testLangpacks) {
     testLangpacks = Promise.all(testLocales.map(async locale => [
       locale,
       await AddonTestUtils.createTempXPIFile({
         "manifest.json": getManifestData(locale),
+        [`browser/${locale}/branding/brand.ftl`]: "-brand-short-name = Firefox",
       }),
     ]));
   }
   return testLangpacks;
 }
 
 function createLocaleResult(target_locale, url) {
   return {
@@ -117,32 +120,32 @@ async function createDictionaryBrowseRes
     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 requested");
+     "The right number of locales are selected");
   is(Array.from(list.children).map(child => child.value).join(","),
-     locales, "The requested locales are in order");
+     locales, "The selected locales are in order");
 }
 
 function assertAvailableLocales(list, locales) {
   let items = Array.from(list.firstElementChild.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(),
      locales.sort().join(","), "The available locales match");
   is(items[0].getAttribute("class"), "label-item", "The first row is a label");
 }
 
-function requestLocale(localeCode, available, dialogDoc) {
+function selectLocale(localeCode, available, dialogDoc) {
   let [locale] = Array.from(available.firstElementChild.children)
     .filter(item => item.value == localeCode);
   available.selectedItem = locale;
   dialogDoc.getElementById("add").doCommand();
 }
 
 async function openDialog(doc, search = false) {
   let dialogLoaded = promiseLoadSubDialog(BROWSER_LANGUAGES_URL);
@@ -153,78 +156,89 @@ async function openDialog(doc, search = 
     doc.getElementById("manageBrowserLanguagesButton").doCommand();
   }
   let dialogWin = await dialogLoaded;
   let dialogDoc = dialogWin.document;
   return {
     dialog: dialogDoc.getElementById("BrowserLanguagesDialog"),
     dialogDoc,
     available: dialogDoc.getElementById("availableLocales"),
-    requested: dialogDoc.getElementById("requestedLocales"),
+    selected: dialogDoc.getElementById("selectedLocales"),
   };
 }
 
 add_task(async function testReorderingBrowserLanguages() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["intl.multilingual.enabled", true],
       ["intl.multilingual.downloadEnabled", true],
-      ["intl.locale.requested", "pl,en-US"],
+      ["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, requested} = await openDialog(doc);
+  let {dialog, dialogDoc, selected} = await openDialog(doc);
 
-  // The initial order is set by the pref.
-  assertLocaleOrder(requested, "pl,en-US");
+  // 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(requested, "en-US,pl");
+  assertLocaleOrder(selected, "en-US,he,pl");
 
   // Accepting the change shows the confirm message bar.
   let dialogClosed = BrowserTestUtils.waitForEvent(dialogDoc.documentElement, "dialogclosing");
   dialog.acceptDialog();
   await dialogClosed;
   is(messageBar.hidden, false, "The message bar is now visible");
-  is(messageBar.querySelector("button").getAttribute("locales"), "en-US,pl",
+  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;
-  requested = newDialog.requested;
+  selected = newDialog.selected;
 
   // The initial order comes from the previous settings.
-  assertLocaleOrder(requested, "en-US,pl");
+  assertLocaleOrder(selected, "en-US,he,pl");
 
   // Select pl in the list.
-  requested.selectedItem = requested.querySelector("[value='pl']");
+  selected.selectedItem = selected.querySelector("[value='pl']");
   // Move pl back up.
   dialogDoc.getElementById("up").doCommand();
-  assertLocaleOrder(requested, "pl,en-US");
+  assertLocaleOrder(selected, "en-US,pl,he");
 
   // Accepting the change hides the confirm message bar.
   dialogClosed = BrowserTestUtils.waitForEvent(dialogDoc.documentElement, "dialogclosing");
   dialog.acceptDialog();
   await dialogClosed;
   is(messageBar.hidden, true, "The message bar is hidden again");
 
+  await Promise.all(addons.map(addon => addon.uninstall()));
+
   BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
-add_task(async function testAddAndRemoveRequestedLanguages() {
+add_task(async function testAddAndRemoveSelectedLanguages() {
   await SpecialPowers.pushPrefEnv({
     set: [
       ["intl.multilingual.enabled", true],
       ["intl.multilingual.downloadEnabled", true],
       ["intl.locale.requested", "en-US"],
       ["extensions.langpacks.signatures.required", false],
     ],
   });
@@ -237,38 +251,38 @@ add_task(async function testAddAndRemove
 
   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, requested} = await openDialog(doc);
+  let {dialog, dialogDoc, available, selected} = await openDialog(doc);
 
   // The initial order is set by the pref.
-  assertLocaleOrder(requested, "en-US");
+  assertLocaleOrder(selected, "en-US");
   assertAvailableLocales(available, ["fr", "pl", "he"]);
 
-  // Add pl and fr to requested.
-  requestLocale("pl", available, dialogDoc);
-  requestLocale("fr", available, dialogDoc);
+  // Add pl and fr to selected.
+  selectLocale("pl", available, dialogDoc);
+  selectLocale("fr", available, dialogDoc);
 
-  assertLocaleOrder(requested, "fr,pl,en-US");
+  assertLocaleOrder(selected, "fr,pl,en-US");
   assertAvailableLocales(available, ["he"]);
 
-  // Remove pl and fr from requested.
+  // Remove pl and fr from selected.
   dialogDoc.getElementById("remove").doCommand();
   dialogDoc.getElementById("remove").doCommand();
-  assertLocaleOrder(requested, "en-US");
+  assertLocaleOrder(selected, "en-US");
   assertAvailableLocales(available, ["fr", "pl", "he"]);
 
-  // Add he to requested.
-  requestLocale("he", available, dialogDoc);
-  assertLocaleOrder(requested, "he,en-US");
+  // Add he to selected.
+  selectLocale("he", available, dialogDoc);
+  assertLocaleOrder(selected, "he,en-US");
   assertAvailableLocales(available, ["pl", "fr"]);
 
   // Accepting the change shows the confirm message bar.
   let dialogClosed = BrowserTestUtils.waitForEvent(dialogDoc.documentElement, "dialogclosing");
   dialog.acceptDialog();
   await dialogClosed;
 
   await waitForMutation(
@@ -307,66 +321,90 @@ add_task(async function testInstallFromA
 
   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 {dialogDoc, available, requested} = await openDialog(doc, true);
+  let {dialog, dialogDoc, available, selected} = await openDialog(doc, true);
 
   // Make sure the message bar is still hidden.
   is(messageBar.hidden, true, "The message bar is still hidden after searching");
 
-  let dropdown = dialogDoc.getElementById("availableLocales");
-  if (dropdown.itemCount == 1) {
+  if (available.itemCount == 1) {
     await waitForMutation(
-      dropdown.firstElementChild,
+      available.firstElementChild,
       {childList: true},
-      target => dropdown.itemCount > 1);
+      target => available.itemCount > 1);
   }
 
   // The initial order is set by the pref.
-  assertLocaleOrder(requested, "en-US");
+  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");
 
   // Add Polish, this will install the langpack.
-  requestLocale("pl", available, dialogDoc);
+  selectLocale("pl", available, dialogDoc);
 
   // Wait for the langpack to install and be added to the list.
-  let requestedLocales = dialogDoc.getElementById("requestedLocales");
+  let selectedLocales = dialogDoc.getElementById("selectedLocales");
   await waitForMutation(
-    requestedLocales,
+    selectedLocales,
     {childList: true},
-    target => requestedLocales.itemCount == 2);
+    target => selectedLocales.itemCount == 2);
 
   // Verify the list is correct.
-  assertLocaleOrder(requested, "pl,en-US");
+  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 != 0;
 
     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(dialogDoc.documentElement, "dialogclosing");
+  dialog.acceptDialog();
+  await dialogClosed;
+
+  // Disable the Polish langpack.
+  let langpack = await AddonManager.getAddonByID("langpack-pl@firefox.mozilla.org");
+  await langpack.disable();
+
+  ({dialogDoc, available, selected} = await openDialog(doc, true));
+
+  // Wait for the available langpacks to load.
+  if (available.itemCount == 1) {
+    await waitForMutation(
+      available.firstElementChild,
+      {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);
 });