forked from mirrors/gecko-dev
This patch also includes necessary JS changes to support this. Most commonly, the dialog was accessed with document.documentElement, which needed to be changed now that the dialog is not the top level element. Differential Revision: https://phabricator.services.mozilla.com/D52411 --HG-- extra : moz-landing-system : lando
379 lines
12 KiB
JavaScript
379 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 */
|
|
|
|
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
},
|
|
};
|