From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../components/preferences/dialogs/translations.js | 465 +++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 browser/components/preferences/dialogs/translations.js (limited to 'browser/components/preferences/dialogs/translations.js') diff --git a/browser/components/preferences/dialogs/translations.js b/browser/components/preferences/dialogs/translations.js new file mode 100644 index 0000000000..30d2b22f17 --- /dev/null +++ b/browser/components/preferences/dialogs/translations.js @@ -0,0 +1,465 @@ +/* -*- 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/. */ + +"use strict"; + +/** + * The permission type to give to Services.perms for Translations. + */ +const TRANSLATIONS_PERMISSION = "translations"; +/** + * The list of BCP-47 language tags that will trigger auto-translate. + */ +const ALWAYS_TRANSLATE_LANGS_PREF = + "browser.translations.alwaysTranslateLanguages"; +/** + * The list of BCP-47 language tags that will prevent showing Translations UI. + */ +const NEVER_TRANSLATE_LANGS_PREF = + "browser.translations.neverTranslateLanguages"; + +function Tree(aId, aData) { + this._data = aData; + this._tree = document.getElementById(aId); + this._tree.view = this; +} + +Tree.prototype = { + get tree() { + return this._tree; + }, + get isEmpty() { + return !this._data.length; + }, + get hasSelection() { + return this.selection.count > 0; + }, + getSelectedItems() { + let result = []; + + let rc = this.selection.getRangeCount(); + for (let i = 0; i < rc; ++i) { + let min = {}, + max = {}; + this.selection.getRangeAt(i, min, max); + for (let j = min.value; j <= max.value; ++j) { + result.push(this._data[j]); + } + } + + return result; + }, + + // nsITreeView implementation + get rowCount() { + return this._data.length; + }, + getCellText(aRow, aColumn) { + return this._data[aRow]; + }, + isSeparator(aIndex) { + return false; + }, + isSorted() { + return false; + }, + isContainer(aIndex) { + return false; + }, + setTree(aTree) {}, + getImageSrc(aRow, aColumn) {}, + getCellValue(aRow, aColumn) {}, + cycleHeader(column) {}, + getRowProperties(row) { + return ""; + }, + getColumnProperties(column) { + return ""; + }, + getCellProperties(row, column) { + return ""; + }, + QueryInterface: ChromeUtils.generateQI(["nsITreeView"]), +}; + +function Lang(aCode, label) { + this.langCode = aCode; + this._label = label; +} + +Lang.prototype = { + toString() { + return this._label; + }, +}; + +var gTranslationsSettings = { + onLoad() { + if (this._neverTranslateSiteTree) { + // Re-using an open dialog, clear the old observers. + this.removeObservers(); + } + + // Load site permissions into an array. + this._neverTranslateSites = []; + for (const perm of Services.perms.getAllByTypes([ + TRANSLATIONS_PERMISSION, + ])) { + if (perm.capability === Services.perms.DENY_ACTION) { + this._neverTranslateSites.push(perm.principal.origin); + } + } + let stripProtocol = s => s?.replace(/^\w+:/, "") || ""; + this._neverTranslateSites.sort((a, b) => { + return stripProtocol(a).localeCompare(stripProtocol(b)); + }); + + // Load language tags into arrays. + this._alwaysTranslateLangs = this.getAlwaysTranslateLanguages(); + this._neverTranslateLangs = this.getNeverTranslateLanguages(); + + // Add observers for relevant prefs and permissions. + Services.obs.addObserver(this, "perm-changed"); + Services.prefs.addObserver(ALWAYS_TRANSLATE_LANGS_PREF, this); + Services.prefs.addObserver(NEVER_TRANSLATE_LANGS_PREF, this); + + // Build trees from the arrays. + this._alwaysTranslateLangsTree = new Tree( + "alwaysTranslateLanguagesTree", + this._alwaysTranslateLangs + ); + this._neverTranslateLangsTree = new Tree( + "neverTranslateLanguagesTree", + this._neverTranslateLangs + ); + this._neverTranslateSiteTree = new Tree( + "neverTranslateSitesTree", + this._neverTranslateSites + ); + + // Ensure the UI for each group is in the correct state. + this.onSelectAlwaysTranslateLanguage(); + this.onSelectNeverTranslateLanguage(); + this.onSelectNeverTranslateSite(); + }, + + /** + * Retrieves the value of a char-pref splits its value into an + * array delimited by commas. + * + * This is used for the translations preferences which are comma- + * separated lists of BCP-47 language tags. + * + * @param {string} pref + * @returns {Array} + */ + getLangsFromPref(pref) { + let rawLangs = Services.prefs.getCharPref(pref); + if (!rawLangs) { + return []; + } + + let langArr = rawLangs.split(","); + let displayNames = Services.intl.getLanguageDisplayNames( + undefined, + langArr + ); + let langs = langArr.map((lang, i) => new Lang(lang, displayNames[i])); + langs.sort(); + + return langs; + }, + + /** + * Retrieves the always-translate language tags as an array. + * @returns {Array} + */ + getAlwaysTranslateLanguages() { + return this.getLangsFromPref(ALWAYS_TRANSLATE_LANGS_PREF); + }, + + /** + * Retrieves the never-translate language tags as an array. + * @returns {Array} + */ + getNeverTranslateLanguages() { + return this.getLangsFromPref(NEVER_TRANSLATE_LANGS_PREF); + }, + + /** + * Handles updating the UI components on pref or permission changes. + */ + observe(aSubject, aTopic, aData) { + if (aTopic === "perm-changed") { + if (aData === "cleared") { + // Permissions have been cleared + if (!this._neverTranslateSites.length) { + // There were no sites with permissions set, nothing to do. + return; + } + // Update the tree based on the amount of permissions removed. + let removed = this._neverTranslateSites.splice( + 0, + this._neverTranslateSites.length + ); + this._neverTranslateSiteTree.tree.rowCountChanged(0, -removed.length); + } else { + let perm = aSubject.QueryInterface(Ci.nsIPermission); + if (perm.type != TRANSLATIONS_PERMISSION) { + // The updated permission was not for Translations, nothing to do. + return; + } + if (aData === "added") { + if (perm.capability != Services.perms.DENY_ACTION) { + // We are only showing data for sites we should never translate. + // If the permission is not DENY_ACTION, we don't care about it here. + return; + } + this._neverTranslateSites.push(perm.principal.origin); + this._neverTranslateSites.sort(); + let tree = this._neverTranslateSiteTree.tree; + tree.rowCountChanged(0, 1); + tree.invalidate(); + } else if (aData == "deleted") { + let index = this._neverTranslateSites.indexOf(perm.principal.origin); + if (index == -1) { + // The deleted permission was not in the tree, nothing to do. + return; + } + this._neverTranslateSites.splice(index, 1); + this._neverTranslateSiteTree.tree.rowCountChanged(index, -1); + } + } + // Ensure the UI updates to the changes. + this.onSelectNeverTranslateSite(); + } else if (aTopic === "nsPref:changed") { + switch (aData) { + case ALWAYS_TRANSLATE_LANGS_PREF: { + this._alwaysTranslateLangs = this.getAlwaysTranslateLanguages(); + + let alwaysTranslateLangsChange = + this._alwaysTranslateLangs.length - + this._alwaysTranslateLangsTree.rowCount; + + this._alwaysTranslateLangsTree._data = this._alwaysTranslateLangs; + let alwaysTranslateLangsTree = this._alwaysTranslateLangsTree.tree; + + if (alwaysTranslateLangsChange) { + alwaysTranslateLangsTree.rowCountChanged( + 0, + alwaysTranslateLangsChange + ); + } + + alwaysTranslateLangsTree.invalidate(); + + // Ensure the UI updates to the changes. + this.onSelectAlwaysTranslateLanguage(); + break; + } + case NEVER_TRANSLATE_LANGS_PREF: { + this._neverTranslateLangs = this.getNeverTranslateLanguages(); + + let neverTranslateLangsChange = + this._neverTranslateLangs.length - + this._neverTranslateLangsTree.rowCount; + + this._neverTranslateLangsTree._data = this._neverTranslateLangs; + let neverTranslateLangsTree = this._neverTranslateLangsTree.tree; + + if (neverTranslateLangsChange) { + neverTranslateLangsTree.rowCountChanged( + 0, + neverTranslateLangsChange + ); + } + + neverTranslateLangsTree.invalidate(); + + // Ensure the UI updates to the changes. + this.onSelectNeverTranslateLanguage(); + break; + } + } + } + }, + + /** + * Ensures that buttons states are enabled/disabled accordingly based on the + * content of the trees. + * + * The remove button should be enabled only if an item is selected. + * The removeAll button should be enabled any time the tree has content. + * + * @param {Tree} aTree + * @param {string} aIdPart + */ + _handleButtonDisabling(aTree, aIdPart) { + let empty = aTree.isEmpty; + document.getElementById("removeAll" + aIdPart + "s").disabled = empty; + document.getElementById("remove" + aIdPart).disabled = + empty || !aTree.hasSelection; + }, + + /** + * Updates the UI state for the always-translate languages section. + */ + onSelectAlwaysTranslateLanguage() { + this._handleButtonDisabling( + this._alwaysTranslateLangsTree, + "AlwaysTranslateLanguage" + ); + }, + + /** + * Updates the UI state for the never-translate languages section. + */ + onSelectNeverTranslateLanguage() { + this._handleButtonDisabling( + this._neverTranslateLangsTree, + "NeverTranslateLanguage" + ); + }, + + /** + * Updates the UI state for the never-translate sites section. + */ + onSelectNeverTranslateSite() { + this._handleButtonDisabling( + this._neverTranslateSiteTree, + "NeverTranslateSite" + ); + }, + + /** + * Updates the value of a language pref to match when a language is removed + * through the UI. + * + * @param {string} pref + * @param {Tree} tree + */ + _onRemoveLanguage(pref, tree) { + let langs = Services.prefs.getCharPref(pref); + if (!langs) { + return; + } + + let removed = tree.getSelectedItems().map(l => l.langCode); + + langs = langs.split(",").filter(l => !removed.includes(l)); + Services.prefs.setCharPref(pref, langs.join(",")); + }, + + /** + * Updates the never-translate language pref when a never-translate language + * is removed via the UI. + */ + onRemoveAlwaysTranslateLanguage() { + this._onRemoveLanguage( + ALWAYS_TRANSLATE_LANGS_PREF, + this._alwaysTranslateLangsTree + ); + }, + + /** + * Updates the always-translate language pref when a always-translate language + * is removed via the UI. + */ + onRemoveNeverTranslateLanguage() { + this._onRemoveLanguage( + NEVER_TRANSLATE_LANGS_PREF, + this._neverTranslateLangsTree + ); + }, + + /** + * Updates the permissions for a never-translate site when it is removed via the UI. + */ + onRemoveNeverTranslateSite() { + let removedNeverTranslateSites = + this._neverTranslateSiteTree.getSelectedItems(); + for (let origin of removedNeverTranslateSites) { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin); + Services.perms.removeFromPrincipal(principal, TRANSLATIONS_PERMISSION); + } + }, + + /** + * Clears the always-translate languages pref when the list is cleared in the UI. + */ + onRemoveAllAlwaysTranslateLanguages() { + Services.prefs.setCharPref(ALWAYS_TRANSLATE_LANGS_PREF, ""); + }, + + /** + * Clears the never-translate languages pref when the list is cleared in the UI. + */ + onRemoveAllNeverTranslateLanguages() { + Services.prefs.setCharPref(NEVER_TRANSLATE_LANGS_PREF, ""); + }, + + /** + * Clears the never-translate sites pref when the list is cleared in the UI. + */ + onRemoveAllNeverTranslateSites() { + if (this._neverTranslateSiteTree.isEmpty) { + return; + } + + let removedNeverTranslateSites = this._neverTranslateSites.splice( + 0, + this._neverTranslateSites.length + ); + this._neverTranslateSiteTree.tree.rowCountChanged( + 0, + -removedNeverTranslateSites.length + ); + + for (let origin of removedNeverTranslateSites) { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin); + Services.perms.removeFromPrincipal(principal, TRANSLATIONS_PERMISSION); + } + + this.onSelectNeverTranslateSite(); + }, + + /** + * Handles removing a selected always-translate language via the keyboard. + */ + onAlwaysTranslateLanguageKeyPress(aEvent) { + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) { + this.onRemoveAlwaysTranslateLanguage(); + } + }, + + /** + * Handles removing a selected never-translate language via the keyboard. + */ + onNeverTranslateLanguageKeyPress(aEvent) { + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) { + this.onRemoveNeverTranslateLanguage(); + } + }, + + /** + * Handles removing a selected never-translate site via the keyboard. + */ + onNeverTranslateSiteKeyPress(aEvent) { + if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE) { + this.onRemoveNeverTranslateSite(); + } + }, + + /** + * Removes any active preference and permissions observers. + */ + removeObservers() { + Services.obs.removeObserver(this, "perm-changed"); + Services.prefs.removeObserver(ALWAYS_TRANSLATE_LANGS_PREF, this); + Services.prefs.removeObserver(NEVER_TRANSLATE_LANGS_PREF, this); + }, +}; -- cgit v1.2.3