forked from mirrors/gecko-dev
		
	If a locale code with region (e.g. it-IT) is not defined in language.properties, we display an empty language name. In this dialog, we can still show a language name if it matches a known locale code without the region (e.g. it). Differential Revision: https://phabricator.services.mozilla.com/D157113
		
			
				
	
	
		
			382 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			382 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/* import-globals-from /toolkit/content/preferencesBindings.js */
 | 
						|
 | 
						|
document
 | 
						|
  .getElementById("LanguagesDialog")
 | 
						|
  .addEventListener("dialoghelp", window.top.openPrefsHelp);
 | 
						|
 | 
						|
Preferences.addAll([
 | 
						|
  { id: "intl.accept_languages", type: "wstring" },
 | 
						|
  { id: "pref.browser.language.disable_button.up", type: "bool" },
 | 
						|
  { id: "pref.browser.language.disable_button.down", type: "bool" },
 | 
						|
  { id: "pref.browser.language.disable_button.remove", type: "bool" },
 | 
						|
  { id: "privacy.spoof_english", type: "int" },
 | 
						|
]);
 | 
						|
 | 
						|
var gLanguagesDialog = {
 | 
						|
  _availableLanguagesList: [],
 | 
						|
  _acceptLanguages: {},
 | 
						|
 | 
						|
  _selectedItemID: null,
 | 
						|
 | 
						|
  onLoad() {
 | 
						|
    let spoofEnglishElement = document.getElementById("spoofEnglish");
 | 
						|
    Preferences.addSyncFromPrefListener(spoofEnglishElement, () =>
 | 
						|
      gLanguagesDialog.readSpoofEnglish()
 | 
						|
    );
 | 
						|
    Preferences.addSyncToPrefListener(spoofEnglishElement, () =>
 | 
						|
      gLanguagesDialog.writeSpoofEnglish()
 | 
						|
    );
 | 
						|
 | 
						|
    Preferences.get("intl.accept_languages").on("change", () =>
 | 
						|
      this._readAcceptLanguages().catch(Cu.reportError)
 | 
						|
    );
 | 
						|
 | 
						|
    if (!this._availableLanguagesList.length) {
 | 
						|
      document.mozSubdialogReady = this._loadAvailableLanguages();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  get _activeLanguages() {
 | 
						|
    return document.getElementById("activeLanguages");
 | 
						|
  },
 | 
						|
 | 
						|
  get _availableLanguages() {
 | 
						|
    return document.getElementById("availableLanguages");
 | 
						|
  },
 | 
						|
 | 
						|
  async _loadAvailableLanguages() {
 | 
						|
    // This is a parser for: resource://gre/res/language.properties
 | 
						|
    // The file is formatted like so:
 | 
						|
    // ab[-cd].accept=true|false
 | 
						|
    //  ab = language
 | 
						|
    //  cd = region
 | 
						|
    var bundleAccepted = document.getElementById("bundleAccepted");
 | 
						|
 | 
						|
    function LocaleInfo(aLocaleName, aLocaleCode, aIsVisible) {
 | 
						|
      this.name = aLocaleName;
 | 
						|
      this.code = aLocaleCode;
 | 
						|
      this.isVisible = aIsVisible;
 | 
						|
    }
 | 
						|
 | 
						|
    // 1) Read the available languages out of language.properties
 | 
						|
 | 
						|
    let localeCodes = [];
 | 
						|
    let localeValues = [];
 | 
						|
    for (let currString of bundleAccepted.strings) {
 | 
						|
      var property = currString.key.split("."); // ab[-cd].accept
 | 
						|
      if (property[1] == "accept") {
 | 
						|
        localeCodes.push(property[0]);
 | 
						|
        localeValues.push(currString.value);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    let localeNames = Services.intl.getLocaleDisplayNames(
 | 
						|
      undefined,
 | 
						|
      localeCodes
 | 
						|
    );
 | 
						|
 | 
						|
    for (let i in localeCodes) {
 | 
						|
      let isVisible =
 | 
						|
        localeValues[i] == "true" &&
 | 
						|
        (!(localeCodes[i] in this._acceptLanguages) ||
 | 
						|
          !this._acceptLanguages[localeCodes[i]]);
 | 
						|
 | 
						|
      let li = new LocaleInfo(localeNames[i], localeCodes[i], isVisible);
 | 
						|
      this._availableLanguagesList.push(li);
 | 
						|
    }
 | 
						|
 | 
						|
    await this._buildAvailableLanguageList();
 | 
						|
    await this._readAcceptLanguages();
 | 
						|
  },
 | 
						|
 | 
						|
  async _buildAvailableLanguageList() {
 | 
						|
    var availableLanguagesPopup = document.getElementById(
 | 
						|
      "availableLanguagesPopup"
 | 
						|
    );
 | 
						|
    while (availableLanguagesPopup.hasChildNodes()) {
 | 
						|
      availableLanguagesPopup.firstChild.remove();
 | 
						|
    }
 | 
						|
 | 
						|
    let frag = document.createDocumentFragment();
 | 
						|
 | 
						|
    // Load the UI with the data
 | 
						|
    for (var i = 0; i < this._availableLanguagesList.length; ++i) {
 | 
						|
      let locale = this._availableLanguagesList[i];
 | 
						|
      let localeCode = locale.code;
 | 
						|
      if (
 | 
						|
        locale.isVisible &&
 | 
						|
        (!(localeCode in this._acceptLanguages) ||
 | 
						|
          !this._acceptLanguages[localeCode])
 | 
						|
      ) {
 | 
						|
        var menuitem = document.createXULElement("menuitem");
 | 
						|
        menuitem.id = localeCode;
 | 
						|
        document.l10n.setAttributes(menuitem, "languages-code-format", {
 | 
						|
          locale: locale.name,
 | 
						|
          code: localeCode,
 | 
						|
        });
 | 
						|
        frag.appendChild(menuitem);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    await document.l10n.translateFragment(frag);
 | 
						|
 | 
						|
    // Sort the list of languages by name
 | 
						|
    let comp = new Services.intl.Collator(undefined, {
 | 
						|
      usage: "sort",
 | 
						|
    });
 | 
						|
 | 
						|
    let items = Array.from(frag.children);
 | 
						|
 | 
						|
    items.sort((a, b) => {
 | 
						|
      return comp.compare(a.getAttribute("label"), b.getAttribute("label"));
 | 
						|
    });
 | 
						|
 | 
						|
    // Re-append items in the correct order:
 | 
						|
    items.forEach(item => frag.appendChild(item));
 | 
						|
 | 
						|
    availableLanguagesPopup.appendChild(frag);
 | 
						|
 | 
						|
    this._availableLanguages.setAttribute(
 | 
						|
      "label",
 | 
						|
      this._availableLanguages.getAttribute("placeholder")
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  async _readAcceptLanguages() {
 | 
						|
    while (this._activeLanguages.hasChildNodes()) {
 | 
						|
      this._activeLanguages.firstChild.remove();
 | 
						|
    }
 | 
						|
 | 
						|
    var selectedIndex = 0;
 | 
						|
    var preference = Preferences.get("intl.accept_languages");
 | 
						|
    if (preference.value == "") {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    var languages = preference.value.toLowerCase().split(/\s*,\s*/);
 | 
						|
    for (var i = 0; i < languages.length; ++i) {
 | 
						|
      var listitem = document.createXULElement("richlistitem");
 | 
						|
      var label = document.createXULElement("label");
 | 
						|
      listitem.appendChild(label);
 | 
						|
      listitem.id = languages[i];
 | 
						|
      if (languages[i] == this._selectedItemID) {
 | 
						|
        selectedIndex = i;
 | 
						|
      }
 | 
						|
      this._activeLanguages.appendChild(listitem);
 | 
						|
      var localeName = this._getLocaleName(languages[i]);
 | 
						|
      document.l10n.setAttributes(label, "languages-active-code-format", {
 | 
						|
        locale: localeName,
 | 
						|
        code: languages[i],
 | 
						|
      });
 | 
						|
 | 
						|
      // Hash this language as an "Active" language so we don't
 | 
						|
      // show it in the list that can be added.
 | 
						|
      this._acceptLanguages[languages[i]] = true;
 | 
						|
    }
 | 
						|
 | 
						|
    // We're forcing an early localization here because otherwise
 | 
						|
    // the initial sizing of the dialog will happen before it and
 | 
						|
    // result in overflow.
 | 
						|
    await document.l10n.translateFragment(this._activeLanguages);
 | 
						|
 | 
						|
    if (this._activeLanguages.childNodes.length) {
 | 
						|
      this._activeLanguages.ensureIndexIsVisible(selectedIndex);
 | 
						|
      this._activeLanguages.selectedIndex = selectedIndex;
 | 
						|
    }
 | 
						|
 | 
						|
    // Update states of accept-language list and buttons according to
 | 
						|
    // privacy.resistFingerprinting and privacy.spoof_english.
 | 
						|
    this.readSpoofEnglish();
 | 
						|
  },
 | 
						|
 | 
						|
  onAvailableLanguageSelect() {
 | 
						|
    var availableLanguages = this._availableLanguages;
 | 
						|
    var addButton = document.getElementById("addButton");
 | 
						|
    addButton.disabled =
 | 
						|
      availableLanguages.disabled || availableLanguages.selectedIndex < 0;
 | 
						|
 | 
						|
    this._availableLanguages.removeAttribute("accesskey");
 | 
						|
  },
 | 
						|
 | 
						|
  addLanguage() {
 | 
						|
    var selectedID = this._availableLanguages.selectedItem.id;
 | 
						|
    var preference = Preferences.get("intl.accept_languages");
 | 
						|
    var arrayOfPrefs = preference.value.toLowerCase().split(/\s*,\s*/);
 | 
						|
    for (var i = 0; i < arrayOfPrefs.length; ++i) {
 | 
						|
      if (arrayOfPrefs[i] == selectedID) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this._selectedItemID = selectedID;
 | 
						|
 | 
						|
    if (preference.value == "") {
 | 
						|
      preference.value = selectedID;
 | 
						|
    } else {
 | 
						|
      arrayOfPrefs.unshift(selectedID);
 | 
						|
      preference.value = arrayOfPrefs.join(",");
 | 
						|
    }
 | 
						|
 | 
						|
    this._acceptLanguages[selectedID] = true;
 | 
						|
    this._availableLanguages.selectedItem = null;
 | 
						|
 | 
						|
    // Rebuild the available list with the added item removed...
 | 
						|
    this._buildAvailableLanguageList().catch(Cu.reportError);
 | 
						|
  },
 | 
						|
 | 
						|
  removeLanguage() {
 | 
						|
    // Build the new preference value string.
 | 
						|
    var languagesArray = [];
 | 
						|
    for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
 | 
						|
      var item = this._activeLanguages.childNodes[i];
 | 
						|
      if (!item.selected) {
 | 
						|
        languagesArray.push(item.id);
 | 
						|
      } else {
 | 
						|
        this._acceptLanguages[item.id] = false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    var string = languagesArray.join(",");
 | 
						|
 | 
						|
    // Get the item to select after the remove operation completes.
 | 
						|
    var selection = this._activeLanguages.selectedItems;
 | 
						|
    var lastSelected = selection[selection.length - 1];
 | 
						|
    var selectItem = lastSelected.nextSibling || lastSelected.previousSibling;
 | 
						|
    selectItem = selectItem ? selectItem.id : null;
 | 
						|
 | 
						|
    this._selectedItemID = selectItem;
 | 
						|
 | 
						|
    // Update the preference and force a UI rebuild
 | 
						|
    var preference = Preferences.get("intl.accept_languages");
 | 
						|
    preference.value = string;
 | 
						|
 | 
						|
    this._buildAvailableLanguageList().catch(Cu.reportError);
 | 
						|
  },
 | 
						|
 | 
						|
  _getLocaleName(localeCode) {
 | 
						|
    if (!this._availableLanguagesList.length) {
 | 
						|
      this._loadAvailableLanguages();
 | 
						|
    }
 | 
						|
    for (var i = 0; i < this._availableLanguagesList.length; ++i) {
 | 
						|
      if (localeCode == this._availableLanguagesList[i].code) {
 | 
						|
        return this._availableLanguagesList[i].name;
 | 
						|
      }
 | 
						|
      // Try resolving the locale code without region code
 | 
						|
      if (localeCode.split("-")[0] == this._availableLanguagesList[i].code) {
 | 
						|
        return this._availableLanguagesList[i].name;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return "";
 | 
						|
  },
 | 
						|
 | 
						|
  moveUp() {
 | 
						|
    var selectedItem = this._activeLanguages.selectedItems[0];
 | 
						|
    var previousItem = selectedItem.previousSibling;
 | 
						|
 | 
						|
    var string = "";
 | 
						|
    for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
 | 
						|
      var item = this._activeLanguages.childNodes[i];
 | 
						|
      string += i == 0 ? "" : ",";
 | 
						|
      if (item.id == previousItem.id) {
 | 
						|
        string += selectedItem.id;
 | 
						|
      } else if (item.id == selectedItem.id) {
 | 
						|
        string += previousItem.id;
 | 
						|
      } else {
 | 
						|
        string += item.id;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this._selectedItemID = selectedItem.id;
 | 
						|
 | 
						|
    // Update the preference and force a UI rebuild
 | 
						|
    var preference = Preferences.get("intl.accept_languages");
 | 
						|
    preference.value = string;
 | 
						|
  },
 | 
						|
 | 
						|
  moveDown() {
 | 
						|
    var selectedItem = this._activeLanguages.selectedItems[0];
 | 
						|
    var nextItem = selectedItem.nextSibling;
 | 
						|
 | 
						|
    var string = "";
 | 
						|
    for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
 | 
						|
      var item = this._activeLanguages.childNodes[i];
 | 
						|
      string += i == 0 ? "" : ",";
 | 
						|
      if (item.id == nextItem.id) {
 | 
						|
        string += selectedItem.id;
 | 
						|
      } else if (item.id == selectedItem.id) {
 | 
						|
        string += nextItem.id;
 | 
						|
      } else {
 | 
						|
        string += item.id;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    this._selectedItemID = selectedItem.id;
 | 
						|
 | 
						|
    // Update the preference and force a UI rebuild
 | 
						|
    var preference = Preferences.get("intl.accept_languages");
 | 
						|
    preference.value = string;
 | 
						|
  },
 | 
						|
 | 
						|
  onLanguageSelect() {
 | 
						|
    var upButton = document.getElementById("up");
 | 
						|
    var downButton = document.getElementById("down");
 | 
						|
    var removeButton = document.getElementById("remove");
 | 
						|
    switch (this._activeLanguages.selectedCount) {
 | 
						|
      case 0:
 | 
						|
        upButton.disabled = downButton.disabled = removeButton.disabled = true;
 | 
						|
        break;
 | 
						|
      case 1:
 | 
						|
        upButton.disabled = this._activeLanguages.selectedIndex == 0;
 | 
						|
        downButton.disabled =
 | 
						|
          this._activeLanguages.selectedIndex ==
 | 
						|
          this._activeLanguages.childNodes.length - 1;
 | 
						|
        removeButton.disabled = false;
 | 
						|
        break;
 | 
						|
      default:
 | 
						|
        upButton.disabled = true;
 | 
						|
        downButton.disabled = true;
 | 
						|
        removeButton.disabled = false;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  readSpoofEnglish() {
 | 
						|
    var checkbox = document.getElementById("spoofEnglish");
 | 
						|
    var resistFingerprinting = Services.prefs.getBoolPref(
 | 
						|
      "privacy.resistFingerprinting"
 | 
						|
    );
 | 
						|
    if (!resistFingerprinting) {
 | 
						|
      checkbox.hidden = true;
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    var spoofEnglish = Preferences.get("privacy.spoof_english").value;
 | 
						|
    var activeLanguages = this._activeLanguages;
 | 
						|
    var availableLanguages = this._availableLanguages;
 | 
						|
    checkbox.hidden = false;
 | 
						|
    switch (spoofEnglish) {
 | 
						|
      case 1: // don't spoof intl.accept_languages
 | 
						|
        activeLanguages.disabled = false;
 | 
						|
        activeLanguages.selectItem(activeLanguages.firstChild);
 | 
						|
        availableLanguages.disabled = false;
 | 
						|
        this.onAvailableLanguageSelect();
 | 
						|
        return false;
 | 
						|
      case 2: // spoof intl.accept_languages
 | 
						|
        activeLanguages.clearSelection();
 | 
						|
        activeLanguages.disabled = true;
 | 
						|
        availableLanguages.disabled = true;
 | 
						|
        this.onAvailableLanguageSelect();
 | 
						|
        return true;
 | 
						|
      default:
 | 
						|
        // will prompt for spoofing intl.accept_languages if resisting fingerprinting
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  writeSpoofEnglish() {
 | 
						|
    return document.getElementById("spoofEnglish").checked ? 2 : 1;
 | 
						|
  },
 | 
						|
};
 |