summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/prefs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/prefs
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--comm/mailnews/base/prefs/.eslintrc.js5
-rw-r--r--comm/mailnews/base/prefs/content/AccountManager.js1949
-rw-r--r--comm/mailnews/base/prefs/content/AccountManager.xhtml171
-rw-r--r--comm/mailnews/base/prefs/content/AccountWizard.js605
-rw-r--r--comm/mailnews/base/prefs/content/AccountWizard.xhtml161
-rw-r--r--comm/mailnews/base/prefs/content/SmtpServerEdit.js241
-rw-r--r--comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml208
-rw-r--r--comm/mailnews/base/prefs/content/accountUtils.js369
-rw-r--r--comm/mailnews/base/prefs/content/am-addressing.inc.xhtml125
-rw-r--r--comm/mailnews/base/prefs/content/am-addressing.js68
-rw-r--r--comm/mailnews/base/prefs/content/am-addressing.xhtml32
-rw-r--r--comm/mailnews/base/prefs/content/am-archiveoptions.js74
-rw-r--r--comm/mailnews/base/prefs/content/am-archiveoptions.xhtml136
-rw-r--r--comm/mailnews/base/prefs/content/am-copies.inc.xhtml308
-rw-r--r--comm/mailnews/base/prefs/content/am-copies.js555
-rw-r--r--comm/mailnews/base/prefs/content/am-copies.xhtml35
-rw-r--r--comm/mailnews/base/prefs/content/am-identities-list.js206
-rw-r--r--comm/mailnews/base/prefs/content/am-identities-list.xhtml101
-rw-r--r--comm/mailnews/base/prefs/content/am-identity-edit.js577
-rw-r--r--comm/mailnews/base/prefs/content/am-identity-edit.xhtml239
-rw-r--r--comm/mailnews/base/prefs/content/am-junk.js335
-rw-r--r--comm/mailnews/base/prefs/content/am-junk.xhtml293
-rw-r--r--comm/mailnews/base/prefs/content/am-main.js110
-rw-r--r--comm/mailnews/base/prefs/content/am-main.xhtml344
-rw-r--r--comm/mailnews/base/prefs/content/am-offline.js435
-rw-r--r--comm/mailnews/base/prefs/content/am-offline.xhtml350
-rw-r--r--comm/mailnews/base/prefs/content/am-prefs.js142
-rw-r--r--comm/mailnews/base/prefs/content/am-server-advanced.js151
-rw-r--r--comm/mailnews/base/prefs/content/am-server-advanced.xhtml222
-rw-r--r--comm/mailnews/base/prefs/content/am-server.js615
-rw-r--r--comm/mailnews/base/prefs/content/am-server.xhtml674
-rw-r--r--comm/mailnews/base/prefs/content/am-serverwithnoidentities.js94
-rw-r--r--comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml152
-rw-r--r--comm/mailnews/base/prefs/content/am-smtp.js277
-rw-r--r--comm/mailnews/base/prefs/content/am-smtp.xhtml121
-rw-r--r--comm/mailnews/base/prefs/content/amUtils.js275
-rw-r--r--comm/mailnews/base/prefs/content/aw-accname.js28
-rw-r--r--comm/mailnews/base/prefs/content/aw-done.js39
-rw-r--r--comm/mailnews/base/prefs/content/aw-identity.js77
-rw-r--r--comm/mailnews/base/prefs/content/aw-incoming.js42
-rw-r--r--comm/mailnews/base/prefs/content/converterDialog.js385
-rw-r--r--comm/mailnews/base/prefs/content/converterDialog.xhtml53
-rw-r--r--comm/mailnews/base/prefs/content/removeAccount.js168
-rw-r--r--comm/mailnews/base/prefs/content/removeAccount.xhtml84
44 files changed, 11631 insertions, 0 deletions
diff --git a/comm/mailnews/base/prefs/.eslintrc.js b/comm/mailnews/base/prefs/.eslintrc.js
new file mode 100644
index 0000000000..5816519fbb
--- /dev/null
+++ b/comm/mailnews/base/prefs/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/valid-jsdoc"],
+};
diff --git a/comm/mailnews/base/prefs/content/AccountManager.js b/comm/mailnews/base/prefs/content/AccountManager.js
new file mode 100644
index 0000000000..8b319d51d4
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/AccountManager.js
@@ -0,0 +1,1949 @@
+/* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+/**
+ * Here's how this dialog works:
+ * The main dialog contains a tree on the left (id="accounttree") and an
+ * iframe which loads a particular preference document (such as am-main.xhtml)
+ * on the right.
+ *
+ * When the user clicks on items in the tree on the left, two things have
+ * to be determined before the UI can be updated:
+ * - the relevant account
+ * - the relevant page
+ *
+ * When both of these are known, this is what happens:
+ * - every form element of the previous page is saved in the account value
+ * hashtable for the previous account
+ * - the relevant page is loaded into the iframe
+ * - each form element in the page is filled in with an appropriate value
+ * from the current account's hashtable
+ * - in the iframe inside the page, if there is an onInit() method,
+ * it is called. The onInit method can further update this page based
+ * on values set in the previous step.
+ */
+
+/* import-globals-from accountUtils.js */
+/* import-globals-from am-prefs.js */
+/* import-globals-from amUtils.js */
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { Gloda } = ChromeUtils.import("resource:///modules/gloda/Gloda.jsm");
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { UIDensity } = ChromeUtils.import("resource:///modules/UIDensity.jsm");
+var { UIFontSize } = ChromeUtils.import("resource:///modules/UIFontSize.jsm");
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "FolderUtils",
+ "resource:///modules/FolderUtils.jsm"
+);
+var { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import(
+ "resource:///modules/hostnameUtils.jsm"
+);
+var { ChatIcons } = ChromeUtils.importESModule(
+ "resource:///modules/chatIcons.sys.mjs"
+);
+
+XPCOMUtils.defineLazyGetter(this, "gSubDialog", function () {
+ const { SubDialogManager } = ChromeUtils.importESModule(
+ "resource://gre/modules/SubDialog.sys.mjs"
+ );
+ return new SubDialogManager({
+ dialogStack: document.getElementById("dialogStack"),
+ dialogTemplate: document.getElementById("dialogTemplate"),
+ dialogOptions: {
+ styleSheets: [
+ "chrome://messenger/skin/preferences/dialog.css",
+ "chrome://messenger/skin/preferences/preferences.css",
+ ],
+ resizeCallback: ({ title, frame }) => {
+ UIFontSize.registerWindow(frame.contentWindow);
+
+ // Resize the dialog to fit the content with edited font size.
+ requestAnimationFrame(() => {
+ let dialogs = frame.ownerGlobal.gSubDialog._dialogs;
+ let dialog = dialogs.find(
+ d => d._frame.contentDocument == frame.contentDocument
+ );
+ if (dialog) {
+ UIFontSize.resizeSubDialog(dialog);
+ }
+ });
+ },
+ },
+ });
+});
+
+// If Local directory has changed the app needs to restart. Once this is set
+// a restart will be attempted at each attempt to close the Account manager with OK.
+var gRestartNeeded = false;
+
+// This is a hash-map for every account we've touched in the pane. Each entry
+// has additional maps of attribute-value pairs that we're going to want to save
+// when the user hits OK.
+var accountArray;
+var gGenericAttributeTypes;
+
+var currentAccount;
+var currentPageId;
+
+var pendingAccount;
+var pendingPageId;
+
+/**
+ * This array contains filesystem folders that are deemed inappropriate
+ * for use as the local directory pref for message storage.
+ * It is global to allow extensions to add to/remove from it if needed.
+ * Extensions adding new server types should first consider setting
+ * nsIMsgProtocolInfo(of the server type).defaultLocalPath properly
+ * so that the test will allow that directory automatically.
+ * See the checkLocalDirectoryIsSafe function for description of the members.
+ */
+var gDangerousLocalStorageDirs = [
+ // profile folder
+ { dirsvc: "ProfD", OS: null },
+ // GRE install folder
+ { dirsvc: "GreD", OS: null },
+ // Application install folder
+ { dirsvc: "CurProcD", OS: null },
+ // system temporary folder
+ { dirsvc: "TmpD", OS: null },
+ // Windows system folder
+ { dirsvc: "SysD", OS: "WINNT" },
+ // Windows folder
+ { dirsvc: "WinD", OS: "WINNT" },
+ // Program Files folder
+ { dirsvc: "ProgF", OS: "WINNT" },
+ // trash folder
+ { dirsvc: "Trsh", OS: "Darwin" },
+ // Mac OS system folder
+ { dir: "/System", OS: "Darwin" },
+ // devices folder
+ { dir: "/dev", OS: "Darwin,Linux" },
+ // process info folder
+ { dir: "/proc", OS: "Linux" },
+ // system state folder
+ { dir: "/sys", OS: "Linux" },
+];
+
+// This sets an attribute in a xul element so that we can later
+// know what value to substitute in a prefstring. Different
+// preference types set different attributes. We get the value
+// in the same way as the function getAccountValue() determines it.
+function updateElementWithKeys(account, element, type) {
+ switch (type) {
+ case "identity":
+ element.identitykey = account.defaultIdentity.key;
+ break;
+ case "pop3":
+ case "imap":
+ case "nntp":
+ case "server":
+ element.serverkey = account.incomingServer.key;
+ break;
+ case "smtp":
+ if (MailServices.smtp.defaultServer) {
+ element.serverkey = MailServices.smtp.defaultServer.key;
+ }
+ break;
+ default:
+ // dump("unknown element type! "+type+"\n");
+ }
+}
+
+// called when the whole document loads
+// perform initialization here
+function onLoad() {
+ let selectedServer = document.documentElement.server;
+ let selectPage = document.documentElement.selectPage || null;
+
+ // Arguments can have two properties: (1) "server," the nsIMsgIncomingServer
+ // to select initially and (2) "selectPage," the page for that server to that
+ // should be selected.
+
+ accountArray = {};
+ gGenericAttributeTypes = {};
+
+ gAccountTree.load();
+
+ setTimeout(selectServer, 0, selectedServer, selectPage);
+
+ let contentFrame = document.getElementById("contentFrame");
+ contentFrame.addEventListener("load", event => {
+ let inputElements = contentFrame.contentDocument.querySelectorAll(
+ "checkbox, input, menulist, textarea, radiogroup, richlistbox"
+ );
+ contentFrame.contentDocument.addEventListener("prefchange", event => {
+ onAccept(true);
+ });
+ for (let input of inputElements) {
+ if (input.localName == "input" || input.localName == "textarea") {
+ input.addEventListener("change", event => {
+ onAccept(true);
+ });
+ } else {
+ input.addEventListener("command", event => {
+ onAccept(true);
+ });
+ }
+ }
+ UIFontSize.registerWindow(contentFrame.contentWindow);
+ });
+
+ UIDensity.registerWindow(window);
+ UIFontSize.registerWindow(window);
+}
+
+function onUnload() {
+ gAccountTree.unload();
+}
+
+function selectServer(server, selectPageId) {
+ let accountTree = document.getElementById("accounttree");
+
+ // Default to showing the first account.
+ let accountRow = accountTree.rows[0];
+
+ // Find the tree-node for the account we want to select.
+ if (server) {
+ for (let row of accountTree.children) {
+ let account = row._account;
+ if (account && server == account.incomingServer) {
+ accountRow = row;
+ // Make sure all the panes of the account to be selected are shown.
+ accountTree.expandRow(accountRow);
+ break;
+ }
+ }
+ }
+
+ let pageToSelect = accountRow;
+
+ if (selectPageId) {
+ // Find the page that also corresponds to this server.
+ // It either is the accountRow itself...
+ let pageId = accountRow.getAttribute("PageTag");
+ if (pageId != selectPageId) {
+ // ... or one of its children.
+ pageToSelect = accountRow.querySelector(
+ '[PageTag="' + selectPageId + '"]'
+ );
+ }
+ }
+
+ accountTree.selectedIndex = accountTree.rows.indexOf(pageToSelect);
+}
+
+function replaceWithDefaultSmtpServer(deletedSmtpServerKey) {
+ // First we replace the smtpserverkey in every identity.
+ for (let identity of MailServices.accounts.allIdentities) {
+ if (identity.smtpServerKey == deletedSmtpServerKey) {
+ identity.smtpServerKey = "";
+ }
+ }
+
+ // When accounts have already been loaded in the panel then the first
+ // replacement will be overwritten when the accountvalues are written out
+ // from the pagedata. We get the loaded accounts and check to make sure
+ // that the account exists for the accountid and that it has a default
+ // identity associated with it (to exclude smtpservers and local folders)
+ // Then we check only for the identity[type] and smtpServerKey[slot] and
+ // replace that with the default smtpserverkey if necessary.
+
+ for (var accountid in accountArray) {
+ var account = accountArray[accountid]._account;
+ if (account && account.defaultIdentity) {
+ var accountValues = accountArray[accountid];
+ var smtpServerKey = getAccountValue(
+ account,
+ accountValues,
+ "identity",
+ "smtpServerKey",
+ null,
+ false
+ );
+ if (smtpServerKey == deletedSmtpServerKey) {
+ setAccountValue(accountValues, "identity", "smtpServerKey", "");
+ }
+ }
+ }
+}
+
+/**
+ * Called when OK is clicked on the dialog.
+ *
+ * @param {boolean} aDoChecks - If true, execute checks on data, otherwise hope
+ * they were already done elsewhere and proceed directly to saving the data.
+ */
+function onAccept(aDoChecks) {
+ if (aDoChecks) {
+ // Check if user/host have been modified correctly.
+ if (!checkUserServerChanges(true)) {
+ return false;
+ }
+
+ if (!checkAccountNameIsValid()) {
+ return false;
+ }
+ }
+
+ // Run checks as if the page was being left.
+ if ("onLeave" in top.frames.contentFrame) {
+ if (!top.frames.contentFrame.onLeave()) {
+ // Prevent closing Account manager if user declined the changes.
+ return false;
+ }
+ }
+
+ if (!onSave()) {
+ return false;
+ }
+
+ // hack hack - save the prefs file NOW in case we crash
+ Services.prefs.savePrefFile(null);
+
+ if (gRestartNeeded) {
+ MailUtils.restartApplication();
+ // Prevent closing Account manager in case restart failed. If restart did not fail,
+ // return value does not matter, as we are restarting.
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * See if the given path to a directory is usable on the current OS.
+ *
+ * aLocalPath the nsIFile of a directory to check.
+ */
+function checkDirectoryIsValid(aLocalPath) {
+ // Any directory selected in the file picker already exists.
+ // Any directory specified in prefs.js will be created at start if it does
+ // not exist yet.
+ // If at the time of entering Account Manager the directory does not exist,
+ // it must be invalid in the current OS or not creatable due to permissions.
+ // Even then, the backend sometimes tries to create a new one
+ // under the current profile.
+ if (!aLocalPath.exists() || !aLocalPath.isDirectory()) {
+ return false;
+ }
+
+ if (Services.appinfo.OS == "WINNT") {
+ // Do not allow some special filenames on Windows.
+ // Taken from mozilla/widget/windows/nsDataObj.cpp::MangleTextToValidFilename()
+ let dirLeafName = aLocalPath.leafName;
+ const kForbiddenNames = [
+ "COM1",
+ "COM2",
+ "COM3",
+ "COM4",
+ "COM5",
+ "COM6",
+ "COM7",
+ "COM8",
+ "COM9",
+ "LPT1",
+ "LPT2",
+ "LPT3",
+ "LPT4",
+ "LPT5",
+ "LPT6",
+ "LPT7",
+ "LPT8",
+ "LPT9",
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "CLOCK$",
+ ];
+ if (kForbiddenNames.includes(dirLeafName)) {
+ return false;
+ }
+ }
+
+ // The directory must be readable and writable to work as a mail store.
+ if (!(aLocalPath.isReadable() && aLocalPath.isWritable())) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Even if the local path is usable, there are some special folders we do not
+ * want to allow for message storage as they cause problems (see e.g. bug 750781).
+ *
+ * aLocalPath The nsIFile of a directory to check.
+ */
+function checkDirectoryIsAllowed(aLocalPath) {
+ /**
+ * Check if the local path (aLocalPath) is 'safe' i.e. NOT a parent
+ * or subdirectory of the given special system/app directory (aDirToCheck).
+ *
+ * @param {object} aDirToCheck - An object describing the special directory.
+ * @param {string} aDirToCheck.dirsvc - A path keyword to retrieve from the
+ * Directory service.
+ * @param {string} aDirToCheck.dir - An absolute filesystem path.
+ * @param {string} aDirToCheck.OS - A string of comma separated values defining on which.
+ * Operating systems the folder is unusable:
+ * - null = all
+ * - WINNT = Windows
+ * - Darwin = OS X
+ * - Linux = Linux
+ * @param {string} aDirToCheck.safeSubdirs - An array of directory names that
+ * are allowed to be used under the tested directory.
+ * @param {nsIFile} aLocalPath - An nsIFile of the directory to check,
+ * intended for message storage.
+ */
+ function checkLocalDirectoryIsSafe(aDirToCheck, aLocalPath) {
+ if (aDirToCheck.OS) {
+ if (!aDirToCheck.OS.split(",").includes(Services.appinfo.OS)) {
+ return true;
+ }
+ }
+
+ let testDir = null;
+ if ("dirsvc" in aDirToCheck) {
+ try {
+ testDir = Services.dirsvc.get(aDirToCheck.dirsvc, Ci.nsIFile);
+ } catch (e) {
+ console.error(
+ "The special folder " +
+ aDirToCheck.dirsvc +
+ " cannot be retrieved on this platform: " +
+ e
+ );
+ }
+
+ if (!testDir) {
+ return true;
+ }
+ } else if ("dir" in aDirToCheck) {
+ testDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ testDir.initWithPath(aDirToCheck.dir);
+ if (!testDir.exists()) {
+ return true;
+ }
+ } else {
+ console.error("No directory to check?");
+ return true;
+ }
+
+ testDir.normalize();
+
+ if (testDir.equals(aLocalPath) || aLocalPath.contains(testDir)) {
+ return false;
+ }
+
+ if (testDir.contains(aLocalPath)) {
+ if (!("safeSubdirs" in aDirToCheck)) {
+ return false;
+ }
+
+ // While the tested directory may not be safe,
+ // a subdirectory of some safe subdirectories may be fine.
+ let isInSubdir = false;
+ for (let subDir of aDirToCheck.safeSubdirs) {
+ let checkDir = testDir.clone();
+ checkDir.append(subDir);
+ if (checkDir.contains(aLocalPath)) {
+ isInSubdir = true;
+ break;
+ }
+ }
+ return isInSubdir;
+ }
+
+ return true;
+ } // end of checkDirectoryIsNotSpecial
+
+ // If the server type has a nsIMsgProtocolInfo.defaultLocalPath set,
+ // allow that directory.
+ if (currentAccount.incomingServer) {
+ try {
+ let defaultPath =
+ currentAccount.incomingServer.protocolInfo.defaultLocalPath;
+ if (defaultPath) {
+ defaultPath.normalize();
+ if (defaultPath.contains(aLocalPath)) {
+ return true;
+ }
+ }
+ } catch (e) {
+ /* No problem if this fails. */
+ }
+ }
+
+ for (let tryDir of gDangerousLocalStorageDirs) {
+ if (!checkLocalDirectoryIsSafe(tryDir, aLocalPath)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Check if the specified directory does meet all the requirements
+ * for safe mail storage.
+ *
+ * aLocalPath the nsIFile of a directory to check.
+ */
+function checkDirectoryIsUsable(aLocalPath) {
+ const kAlertTitle = document
+ .getElementById("bundle_prefs")
+ .getString("prefPanel-server");
+ const originalPath = aLocalPath;
+
+ let invalidPath = false;
+ try {
+ aLocalPath.normalize();
+ } catch (e) {
+ invalidPath = true;
+ }
+
+ if (invalidPath || !checkDirectoryIsValid(aLocalPath)) {
+ let alertString = document
+ .getElementById("bundle_prefs")
+ .getFormattedString("localDirectoryInvalid", [originalPath.path]);
+ Services.prompt.alert(window, kAlertTitle, alertString);
+ return false;
+ }
+
+ if (!checkDirectoryIsAllowed(aLocalPath)) {
+ let alertNotAllowed = document
+ .getElementById("bundle_prefs")
+ .getFormattedString("localDirectoryNotAllowed", [originalPath.path]);
+ Services.prompt.alert(window, kAlertTitle, alertNotAllowed);
+ return false;
+ }
+
+ // Check that no other account has this same or dependent local directory.
+ for (let server of MailServices.accounts.allServers) {
+ if (server.key == currentAccount.incomingServer.key) {
+ continue;
+ }
+
+ let serverPath = server.localPath;
+ try {
+ serverPath.normalize();
+ let alertStringID = null;
+ if (serverPath.equals(aLocalPath)) {
+ alertStringID = "directoryAlreadyUsedByOtherAccount";
+ } else if (serverPath.contains(aLocalPath)) {
+ alertStringID = "directoryParentUsedByOtherAccount";
+ } else if (aLocalPath.contains(serverPath)) {
+ alertStringID = "directoryChildUsedByOtherAccount";
+ }
+
+ if (alertStringID) {
+ let alertString = document
+ .getElementById("bundle_prefs")
+ .getFormattedString(alertStringID, [server.prettyName]);
+
+ Services.prompt.alert(window, kAlertTitle, alertString);
+ return false;
+ }
+ } catch (e) {
+ // The other account's path is seriously broken, so we can't compare it.
+ console.error(
+ "The Local Directory path of the account " +
+ server.prettyName +
+ " seems invalid."
+ );
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Check if the user and/or host names have been changed and if so check
+ * if the new names already exists for an account or are empty.
+ * Also check if the Local Directory path was changed.
+ *
+ * @param {boolean} showAlert - Show and alert if a problem with the host / user
+ * name is found.
+ */
+function checkUserServerChanges(showAlert) {
+ const prefBundle = document.getElementById("bundle_prefs");
+ const alertTitle = prefBundle.getString("prefPanel-server");
+ var alertText = null;
+
+ var accountValues = getValueArrayFor(currentAccount);
+ if (!accountValues) {
+ return true;
+ }
+
+ let currentServer = currentAccount ? currentAccount.incomingServer : null;
+
+ // If this type doesn't exist (just removed) then return.
+ if (!("server" in accountValues) || !accountValues.server) {
+ return true;
+ }
+
+ // Get the new username, hostname and type from the page.
+ var typeElem = getPageFormElement("server.type");
+ var hostElem = getPageFormElement("server.hostName");
+ var userElem = getPageFormElement("server.username");
+ if (typeElem && userElem && hostElem) {
+ var newType = getFormElementValue(typeElem);
+ var oldHost = getAccountValue(
+ currentAccount,
+ accountValues,
+ "server",
+ "hostName",
+ null,
+ false
+ );
+ var newHost = getFormElementValue(hostElem);
+ var oldUser = getAccountValue(
+ currentAccount,
+ accountValues,
+ "server",
+ "username",
+ null,
+ false
+ );
+
+ var newUser = getFormElementValue(userElem);
+ var checkUser = true;
+ // There is no username needed for e.g. news so reset it.
+ if (currentServer && !currentServer.protocolInfo.requiresUsername) {
+ oldUser = newUser = "";
+ checkUser = false;
+ }
+ alertText = null;
+ // If something is changed then check if the new user/host already exists.
+ if (oldUser != newUser || oldHost != newHost) {
+ newUser = newUser.trim();
+ newHost = cleanUpHostName(newHost);
+ if (checkUser && newUser == "") {
+ alertText = prefBundle.getString("userNameEmpty");
+ } else if (!isLegalHostNameOrIP(newHost)) {
+ alertText = prefBundle.getString("enterValidServerName");
+ } else {
+ let sameServer = MailServices.accounts.findServer(
+ newUser,
+ newHost,
+ newType
+ );
+ if (sameServer && sameServer != currentServer) {
+ alertText = prefBundle.getString("modifiedAccountExists");
+ } else {
+ // New hostname passed all checks. We may have cleaned it up so set
+ // the new value back into the input element.
+ setFormElementValue(hostElem, newHost);
+ }
+ }
+
+ if (alertText) {
+ if (showAlert) {
+ Services.prompt.alert(window, alertTitle, alertText);
+ }
+ // Restore the old values before return
+ if (checkUser) {
+ setFormElementValue(userElem, oldUser);
+ }
+ setFormElementValue(hostElem, oldHost);
+ // If no message is shown to the user, silently revert the values
+ // and consider the check a success.
+ return !showAlert;
+ }
+
+ // If username is changed remind users to change Your Name and Email Address.
+ // If server name is changed and has defined filters then remind users
+ // to edit rules.
+ if (showAlert) {
+ let filterList;
+ if (currentServer && checkUser) {
+ filterList = currentServer.getEditableFilterList(null);
+ }
+ let changeText = "";
+ if (
+ oldHost != newHost &&
+ filterList != undefined &&
+ filterList.filterCount
+ ) {
+ changeText = prefBundle.getString("serverNameChanged");
+ }
+ // In the event that oldHost == newHost or oldUser == newUser,
+ // the \n\n will be trimmed off before the message is shown.
+ if (oldUser != newUser) {
+ changeText =
+ changeText + "\n\n" + prefBundle.getString("userNameChanged");
+ }
+
+ if (changeText != "") {
+ Services.prompt.alert(window, alertTitle, changeText.trim());
+ }
+ }
+
+ let l10n = new Localization(["messenger/accountManager.ftl"], true);
+ let cancel = Services.prompt.confirmEx(
+ window,
+ alertTitle,
+ l10n.formatValueSync("server-change-restart-required"),
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL,
+ prefBundle.getString("localDirectoryRestart"),
+ null,
+ null,
+ null,
+ {}
+ );
+ if (cancel) {
+ setFormElementValue(hostElem, oldHost);
+ setFormElementValue(userElem, oldUser);
+ return false;
+ }
+ gRestartNeeded = true;
+ }
+ }
+
+ // Check the new value of the server.localPath field for validity.
+ var pathElem = getPageFormElement("server.localPath");
+ if (!pathElem) {
+ return true;
+ }
+ let dir = getFormElementValue(pathElem);
+ if (!checkDirectoryIsUsable(dir)) {
+ // return false; // Temporarily disable this. Just show warning but do not block. See bug 921371.
+ console.error(
+ `Local directory ${dir.path} of account ${currentAccount.key} is not safe to use. Consider changing it.`
+ );
+ }
+
+ // Warn if the Local directory path was changed.
+ // This can be removed once bug 2654 is fixed.
+ let oldLocalDir = getAccountValue(
+ currentAccount,
+ accountValues,
+ "server",
+ "localPath",
+ null,
+ false
+ ); // both return nsIFile
+ let newLocalDir = getFormElementValue(pathElem);
+ if (oldLocalDir && newLocalDir && oldLocalDir.path != newLocalDir.path) {
+ let brandName = document
+ .getElementById("bundle_brand")
+ .getString("brandShortName");
+ alertText = prefBundle.getFormattedString("localDirectoryChanged", [
+ brandName,
+ ]);
+
+ let cancel = Services.prompt.confirmEx(
+ window,
+ alertTitle,
+ alertText,
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL,
+ prefBundle.getString("localDirectoryRestart"),
+ null,
+ null,
+ null,
+ {}
+ );
+ if (cancel) {
+ setFormElementValue(pathElem, oldLocalDir);
+ return false;
+ }
+ gRestartNeeded = true;
+ }
+
+ return true;
+}
+
+/**
+ * If account name is not valid, alert the user.
+ */
+function checkAccountNameIsValid() {
+ if (!currentAccount) {
+ return true;
+ }
+
+ const prefBundle = document.getElementById("bundle_prefs");
+ let alertText = null;
+
+ let serverNameElem = getPageFormElement("server.prettyName");
+ if (serverNameElem) {
+ let accountName = getFormElementValue(serverNameElem);
+
+ if (!accountName) {
+ alertText = prefBundle.getString("accountNameEmpty");
+ } else if (accountNameExists(accountName, currentAccount.key)) {
+ alertText = prefBundle.getString("accountNameExists");
+ // Change the account name to prevent UI freeze.
+ let counter = 2;
+ while (
+ accountNameExists(`${accountName}_${counter}`, currentAccount.key)
+ ) {
+ counter++;
+ }
+ serverNameElem.value = `${accountName}_${counter}`;
+ }
+
+ if (alertText) {
+ const alertTitle = prefBundle.getString("accountWizard");
+ Services.prompt.alert(window, alertTitle, alertText);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function onSave() {
+ if (pendingPageId) {
+ dump("ERROR: " + pendingPageId + " hasn't loaded yet! Not saving.\n");
+ return false;
+ }
+
+ // make sure the current visible page is saved
+ savePage(currentAccount);
+
+ for (var accountid in accountArray) {
+ var accountValues = accountArray[accountid];
+ var account = accountArray[accountid]._account;
+ if (!saveAccount(accountValues, account)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Highlight the default account row in the account tree,
+ * optionally un-highlight the previous one.
+ *
+ * @param {?nsIMsgAccount} newDefault - The account that has become the new
+ * default. Can be given as null if there is none.
+ * @param {?nsIMsgAccount} oldDefault - The account that has stopped being the
+ * default. Can be given as null if there was none.
+ */
+function markDefaultServer(newDefault, oldDefault) {
+ if (oldDefault == newDefault) {
+ return;
+ }
+
+ let accountTree = document.getElementById("accounttree");
+ for (let accountRow of accountTree.children) {
+ if (newDefault && newDefault == accountRow._account) {
+ accountRow.classList.add("isDefaultServer");
+ }
+ if (oldDefault && oldDefault == accountRow._account) {
+ accountRow.classList.remove("isDefaultServer");
+ }
+ }
+}
+
+/**
+ * Notify the UI to rebuild the account tree.
+ */
+function rebuildAccountTree() {
+ // TODO: Reimplement or replace.
+}
+
+/**
+ * Make currentAccount (currently selected in the account tree) the default one.
+ */
+function onSetDefault(event) {
+ // Make sure this function was not called while the control item is disabled
+ if (event.target.getAttribute("disabled") == "true") {
+ return;
+ }
+
+ let previousDefault = MailServices.accounts.defaultAccount;
+ MailServices.accounts.defaultAccount = currentAccount;
+ markDefaultServer(currentAccount, previousDefault);
+
+ // Update gloda's myContact with the new default account's default identity.
+ Gloda._initMyIdentities();
+
+ gAccountTree.load();
+}
+
+function onRemoveAccount(event) {
+ if (event.target.getAttribute("disabled") == "true" || !currentAccount) {
+ return;
+ }
+
+ let server = currentAccount.incomingServer;
+
+ let canDelete = server.protocolInfo.canDelete || server.canDelete;
+ if (!canDelete) {
+ return;
+ }
+
+ let serverList = [];
+ let accountTree = document.getElementById("accounttree");
+ // build the list of servers in the account tree (order is important)
+ for (let row of accountTree.children) {
+ if ("_account" in row) {
+ let curServer = row._account.incomingServer;
+ if (!serverList.includes(curServer)) {
+ serverList.push(curServer);
+ }
+ }
+ }
+
+ // get position of the current server in the server list
+ let serverIndex = serverList.indexOf(server);
+
+ // After the current server is deleted, choose the next server/account,
+ // or the previous one if the last one was deleted.
+ if (serverIndex == serverList.length - 1) {
+ serverIndex--;
+ } else {
+ serverIndex++;
+ }
+
+ // Need to save these before the account and its server is removed.
+ let serverId = server.serverURI;
+
+ // Confirm account deletion.
+ let removeArgs = {
+ server,
+ account: currentAccount,
+ result: false,
+ };
+
+ let onCloseDialog = function () {
+ // If result is true, the account was removed.
+ if (!removeArgs.result) {
+ return;
+ }
+
+ // clear cached data out of the account array
+ currentAccount = currentPageId = null;
+ if (serverId in accountArray) {
+ delete accountArray[serverId];
+ }
+
+ if (serverIndex >= 0 && serverIndex < serverList.length) {
+ selectServer(serverList[serverIndex], null);
+ }
+
+ // Either the default account was deleted so there is a new one
+ // or the default account was not changed. Either way, there is
+ // no need to unmark the old one.
+ markDefaultServer(MailServices.accounts.defaultAccount, null);
+ };
+
+ gSubDialog.open(
+ "chrome://messenger/content/removeAccount.xhtml",
+ {
+ features: "resizable=no",
+ closingCallback: onCloseDialog,
+ },
+ removeArgs
+ );
+}
+
+function saveAccount(accountValues, account) {
+ var identity = null;
+ var server = null;
+
+ if (account) {
+ identity = account.defaultIdentity;
+ server = account.incomingServer;
+ }
+
+ for (var type in accountValues) {
+ var dest;
+ try {
+ if (type == "identity") {
+ dest = identity;
+ } else if (type == "server") {
+ dest = server;
+ } else if (type == "pop3") {
+ dest = server.QueryInterface(Ci.nsIPop3IncomingServer);
+ } else if (type == "imap") {
+ dest = server.QueryInterface(Ci.nsIImapIncomingServer);
+ } else if (type == "none") {
+ dest = server.QueryInterface(Ci.nsINoIncomingServer);
+ } else if (type == "nntp") {
+ dest = server.QueryInterface(Ci.nsINntpIncomingServer);
+ } else if (type == "smtp") {
+ dest = MailServices.smtp.defaultServer;
+ }
+ } catch (ex) {
+ // don't do anything, just means we don't support that
+ }
+ if (dest == undefined) {
+ continue;
+ }
+ var typeArray = accountValues[type];
+
+ for (var slot in typeArray) {
+ if (
+ type in gGenericAttributeTypes &&
+ slot in gGenericAttributeTypes[type]
+ ) {
+ var methodName = "get";
+ switch (gGenericAttributeTypes[type][slot]) {
+ case "int":
+ methodName += "Int";
+ break;
+ case "wstring":
+ methodName += "Unichar";
+ break;
+ case "string":
+ methodName += "Char";
+ break;
+ case "bool":
+ // in some cases
+ // like for radiogroups of type boolean
+ // the value will be "false" instead of false
+ // we need to convert it.
+ if (typeArray[slot] == "false") {
+ typeArray[slot] = false;
+ } else if (typeArray[slot] == "true") {
+ typeArray[slot] = true;
+ }
+
+ methodName += "Bool";
+ break;
+ default:
+ dump(
+ "unexpected preftype: " +
+ gGenericAttributeTypes[type][slot] +
+ "\n"
+ );
+ break;
+ }
+ methodName += methodName + "Value" in dest ? "Value" : "Attribute";
+ if (dest[methodName](slot) != typeArray[slot]) {
+ methodName = methodName.replace("get", "set");
+ dest[methodName](slot, typeArray[slot]);
+ }
+ } else if (
+ slot in dest &&
+ typeArray[slot] != undefined &&
+ dest[slot] != typeArray[slot]
+ ) {
+ try {
+ dest[slot] = typeArray[slot];
+ } catch (ex) {
+ // hrm... need to handle special types here
+ }
+ }
+ }
+ }
+
+ // if we made account changes to the spam settings, we'll need to re-initialize
+ // our settings object
+ if (server && server.spamSettings) {
+ try {
+ server.spamSettings.initialize(server);
+ } catch (e) {
+ let accountName = getAccountValue(
+ account,
+ getValueArrayFor(account),
+ "server",
+ "prettyName",
+ null,
+ false
+ );
+ let alertText = document
+ .getElementById("bundle_prefs")
+ .getFormattedString("junkSettingsBroken", [accountName]);
+ let review = Services.prompt.confirmEx(
+ window,
+ null,
+ alertText,
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO,
+ null,
+ null,
+ null,
+ null,
+ {}
+ );
+ if (!review) {
+ onAccountTreeSelect("am-junk.xhtml", account);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Set enabled/disabled state for the actions in the Account Actions menu.
+ * Called only by Thunderbird.
+ */
+function initAccountActionsButtons(menupopup) {
+ if (!Services.prefs.getBoolPref("mail.chat.enabled")) {
+ document.getElementById("accountActionsAddIMAccount").hidden = true;
+ }
+
+ updateItems(
+ document.getElementById("accounttree"),
+ getCurrentAccount(),
+ document.getElementById("accountActionsAddMailAccount"),
+ document.getElementById("accountActionsDropdownSetDefault"),
+ document.getElementById("accountActionsDropdownRemove")
+ );
+
+ updateBlockedItems(menupopup.children, true);
+}
+
+/**
+ * Determine enabled/disabled state for the passed in elements
+ * representing account actions.
+ */
+function updateItems(
+ tree,
+ account,
+ addAccountItem,
+ setDefaultItem,
+ removeItem
+) {
+ // Start with items disabled and then find out what can be enabled.
+ let canSetDefault = false;
+ let canDelete = false;
+
+ if (account && tree.selectedIndex >= 0) {
+ // Only try to check properties if there was anything selected in the tree
+ // and it belongs to an account.
+ // Otherwise we have either selected a SMTP server, or there is some
+ // problem. Either way, we don't want the user to act on it.
+ let server = account.incomingServer;
+
+ if (
+ account != MailServices.accounts.defaultAccount &&
+ server.canBeDefaultServer &&
+ account.identities.length > 0
+ ) {
+ canSetDefault = true;
+ }
+
+ canDelete = server.protocolInfo.canDelete || server.canDelete;
+ }
+
+ setEnabled(addAccountItem, true);
+ setEnabled(setDefaultItem, canSetDefault);
+ setEnabled(removeItem, canDelete);
+}
+
+/**
+ * Disable buttons/menu items if their control preference is locked.
+ *
+ * @param {Node[]|NodeList} aItems - Elements to be checked.
+ * @param {boolean} aMustBeTrue - If true then the pref must be boolean and set
+ * to true to trigger the disabling.
+ */
+function updateBlockedItems(aItems, aMustBeTrue) {
+ for (let item of aItems) {
+ let prefstring = item.getAttribute("prefstring");
+ if (!prefstring) {
+ continue;
+ }
+
+ if (
+ Services.prefs.prefIsLocked(prefstring) &&
+ (!aMustBeTrue || Services.prefs.getBoolPref(prefstring))
+ ) {
+ item.setAttribute("disabled", true);
+ }
+ }
+}
+
+/**
+ * Set enabled/disabled state for the control.
+ */
+function setEnabled(control, enabled) {
+ if (!control) {
+ return;
+ }
+
+ if (enabled) {
+ control.removeAttribute("disabled");
+ } else {
+ control.setAttribute("disabled", true);
+ }
+}
+
+// Called when someone clicks on an account. Figure out context by what they
+// clicked on. This is also called when an account is removed. In this case,
+// nothing is selected.
+function onAccountTreeSelect(pageId, account) {
+ let tree = document.getElementById("accounttree");
+
+ let changeView = pageId && account;
+ if (!changeView) {
+ if (tree.selectedIndex < 0) {
+ return false;
+ }
+
+ let node = tree.rows[tree.selectedIndex];
+ account = "_account" in node ? node._account : null;
+
+ pageId = node.getAttribute("PageTag");
+ }
+
+ if (pageId == currentPageId && account == currentAccount) {
+ return true;
+ }
+
+ if (
+ document
+ .getElementById("contentFrame")
+ .contentDocument.getElementById("server.localPath")
+ ) {
+ // Check if user/host names have been changed or the Local Directory is invalid.
+ if (!checkUserServerChanges(false)) {
+ changeView = true;
+ account = currentAccount;
+ pageId = currentPageId;
+ }
+
+ if (gRestartNeeded) {
+ onAccept(false);
+ }
+ }
+
+ if (
+ document
+ .getElementById("contentFrame")
+ .contentDocument.getElementById("server.prettyName")
+ ) {
+ // Check if account name is valid.
+ if (!checkAccountNameIsValid()) {
+ changeView = true;
+ account = currentAccount;
+ pageId = currentPageId;
+ }
+ }
+
+ if (currentPageId) {
+ // Change focus to the account tree first so that any 'onchange' handlers
+ // on elements in the current page have a chance to run before the page
+ // is saved and replaced by the new one.
+ tree.focus();
+ }
+
+ // Provide opportunity to do cleanups or checks when the current page is being left.
+ if ("onLeave" in top.frames.contentFrame) {
+ top.frames.contentFrame.onLeave();
+ }
+
+ // save the previous page
+ savePage(currentAccount);
+
+ let changeAccount = account != currentAccount;
+
+ if (changeView) {
+ selectServer(account.incomingServer, pageId);
+ }
+
+ if (pageId != currentPageId) {
+ // loading a complete different page
+
+ // prevent overwriting with bad stuff
+ currentAccount = currentPageId = null;
+
+ pendingAccount = account;
+ pendingPageId = pageId;
+ loadPage(pageId);
+ } else if (changeAccount) {
+ // same page, different server
+ restorePage(pageId, account);
+ }
+
+ return true;
+}
+
+// page has loaded
+function onPanelLoaded(pageId) {
+ if (pageId != pendingPageId) {
+ // if we're reloading the current page, we'll assume the
+ // page has asked itself to be completely reloaded from
+ // the prefs. to do this, clear out the the old entry in
+ // the account data, and then restore theh page
+ if (pageId == currentPageId) {
+ var serverId = currentAccount
+ ? currentAccount.incomingServer.serverURI
+ : "global";
+ delete accountArray[serverId];
+ restorePage(currentPageId, currentAccount);
+ }
+ } else {
+ restorePage(pendingPageId, pendingAccount);
+ }
+
+ // probably unnecessary, but useful for debugging
+ pendingAccount = null;
+ pendingPageId = null;
+}
+
+function pageURL(pageId) {
+ // If we have a special non account manager pane (e.g. about:blank),
+ // do not translate it into ChromePackageName URL.
+ if (!pageId.startsWith("am-")) {
+ return pageId;
+ }
+
+ let chromePackageName;
+ try {
+ // we could compare against "main","server","copies","offline","addressing",
+ // "smtp" and "advanced" first to save the work, but don't,
+ // as some of these might be turned into extensions (for thunderbird)
+ let packageName = pageId.split("am-")[1].split(".xhtml")[0];
+ chromePackageName = MailServices.accounts.getChromePackageName(packageName);
+ } catch (ex) {
+ chromePackageName = "messenger";
+ }
+ return "chrome://" + chromePackageName + "/content/" + pageId;
+}
+
+function loadPage(pageId) {
+ const loadURIOptions = {
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ };
+ document
+ .getElementById("contentFrame")
+ .webNavigation.fixupAndLoadURIString(pageURL(pageId), loadURIOptions);
+}
+
+// save the values of the widgets to the given server
+function savePage(account) {
+ if (!account) {
+ return;
+ }
+
+ // tell the page that it's about to save
+ if ("onSave" in top.frames.contentFrame) {
+ top.frames.contentFrame.onSave();
+ }
+
+ var accountValues = getValueArrayFor(account);
+ if (!accountValues) {
+ return;
+ }
+ // Reset accountArray so that only the current page will be saved. This is
+ // needed to prevent resetting prefs unintentionally. An example is when
+ // changing username/hostname, MsgIncomingServer.jsm will modify identities,
+ // without this, identities changes may be reverted to old values in
+ // accountArray.
+ accountArray = {};
+ accountValues = {};
+ let serverId = account.incomingServer.serverURI;
+ accountArray[serverId] = accountValues;
+ accountArray[serverId]._account = account;
+
+ var pageElements = getPageFormElements();
+ if (!pageElements) {
+ return;
+ }
+
+ // store the value in the account
+ for (let i = 0; i < pageElements.length; i++) {
+ if (pageElements[i].id) {
+ let vals = pageElements[i].id.split(".");
+ if (vals.length >= 2) {
+ let type = vals[0];
+ let slot = pageElements[i].id.slice(type.length + 1);
+
+ setAccountValue(
+ accountValues,
+ type,
+ slot,
+ getFormElementValue(pageElements[i])
+ );
+ }
+ }
+ }
+}
+
+function setAccountValue(accountValues, type, slot, value) {
+ if (!(type in accountValues)) {
+ accountValues[type] = {};
+ }
+
+ accountValues[type][slot] = value;
+}
+
+function getAccountValue(
+ account,
+ accountValues,
+ type,
+ slot,
+ preftype,
+ isGeneric
+) {
+ if (!(type in accountValues)) {
+ accountValues[type] = {};
+ }
+
+ // fill in the slot from the account if necessary
+ if (
+ !(slot in accountValues[type]) ||
+ accountValues[type][slot] == undefined
+ ) {
+ var server;
+ if (account) {
+ server = account.incomingServer;
+ }
+ var source = null;
+ try {
+ if (type == "identity") {
+ source = account.defaultIdentity;
+ } else if (type == "server") {
+ source = account.incomingServer;
+ } else if (type == "pop3") {
+ source = server.QueryInterface(Ci.nsIPop3IncomingServer);
+ } else if (type == "imap") {
+ source = server.QueryInterface(Ci.nsIImapIncomingServer);
+ } else if (type == "none") {
+ source = server.QueryInterface(Ci.nsINoIncomingServer);
+ } else if (type == "nntp") {
+ source = server.QueryInterface(Ci.nsINntpIncomingServer);
+ } else if (type == "smtp") {
+ source = MailServices.smtp.defaultServer;
+ }
+ } catch (ex) {}
+
+ if (source) {
+ if (isGeneric) {
+ if (!(type in gGenericAttributeTypes)) {
+ gGenericAttributeTypes[type] = {};
+ }
+
+ // we need the preftype later, for setting when we save.
+ gGenericAttributeTypes[type][slot] = preftype;
+ var methodName = "get";
+ switch (preftype) {
+ case "int":
+ methodName += "Int";
+ break;
+ case "wstring":
+ methodName += "Unichar";
+ break;
+ case "string":
+ methodName += "Char";
+ break;
+ case "bool":
+ methodName += "Bool";
+ break;
+ default:
+ dump("unexpected preftype: " + preftype + "\n");
+ break;
+ }
+ methodName += methodName + "Value" in source ? "Value" : "Attribute";
+ accountValues[type][slot] = source[methodName](slot);
+ } else if (slot in source) {
+ accountValues[type][slot] = source[slot];
+ } else {
+ accountValues[type][slot] = null;
+ }
+ } else {
+ accountValues[type][slot] = null;
+ }
+ }
+ return accountValues[type][slot];
+}
+
+// restore the values of the widgets from the given server
+function restorePage(pageId, account) {
+ if (!account) {
+ return;
+ }
+
+ var accountValues = getValueArrayFor(account);
+ if (!accountValues) {
+ return;
+ }
+
+ if ("onPreInit" in top.frames.contentFrame) {
+ top.frames.contentFrame.onPreInit(account, accountValues);
+ }
+
+ var pageElements = getPageFormElements();
+ if (!pageElements) {
+ return;
+ }
+
+ // restore the value from the account
+ for (let i = 0; i < pageElements.length; i++) {
+ if (pageElements[i].id) {
+ let vals = pageElements[i].id.split(".");
+ if (vals.length >= 2) {
+ let type = vals[0];
+ let slot = pageElements[i].id.slice(type.length + 1);
+
+ // buttons are lockable, but don't have any data so we skip that part.
+ // elements that do have data, we get the values at poke them in.
+ if (pageElements[i].localName != "button") {
+ var value = getAccountValue(
+ account,
+ accountValues,
+ type,
+ slot,
+ pageElements[i].getAttribute("preftype"),
+ pageElements[i].getAttribute("genericattr") == "true"
+ );
+ setFormElementValue(pageElements[i], value);
+ }
+ var element = pageElements[i];
+ switch (type) {
+ case "identity":
+ element.identitykey = account.defaultIdentity.key;
+ break;
+ case "pop3":
+ case "imap":
+ case "nntp":
+ case "server":
+ element.serverkey = account.incomingServer.key;
+ break;
+ case "smtp":
+ if (MailServices.smtp.defaultServer) {
+ element.serverkey = MailServices.smtp.defaultServer.key;
+ }
+ break;
+ }
+ var isLocked = getAccountValueIsLocked(pageElements[i]);
+ setEnabled(pageElements[i], !isLocked);
+ }
+ }
+ }
+
+ // tell the page that new values have been loaded
+ if ("onInit" in top.frames.contentFrame) {
+ top.frames.contentFrame.onInit(pageId, account.incomingServer.serverURI);
+ }
+
+ // everything has succeeded, vervied by setting currentPageId
+ currentPageId = pageId;
+ currentAccount = account;
+}
+
+/**
+ * Gets the value of a widget in current the account settings page,
+ * automatically setting the right property of it depending on element type.
+ *
+ * @param {HTMLInputElement} formElement - An input element.
+ */
+function getFormElementValue(formElement) {
+ try {
+ var type = formElement.localName;
+ if (type == "checkbox") {
+ if (formElement.getAttribute("reversed")) {
+ return !formElement.checked;
+ }
+ return formElement.checked;
+ }
+ if (type == "input" && formElement.getAttribute("datatype") == "nsIFile") {
+ if (formElement.value) {
+ let localfile = Cc["@mozilla.org/file/local;1"].createInstance(
+ Ci.nsIFile
+ );
+
+ localfile.initWithPath(formElement.value);
+ return localfile;
+ }
+ return null;
+ }
+ if (type == "input" || "value" in formElement) {
+ return formElement.value.trim();
+ }
+ return null;
+ } catch (ex) {
+ console.error("getFormElementValue failed, ex=" + ex + "\n");
+ }
+ return null;
+}
+
+/**
+ * Sets the value of a widget in current the account settings page,
+ * automatically setting the right property of it depending on element type.
+ *
+ * @param {HTMLInputElement} formElement - An input element.
+ * @param {string|nsIFile} value - The value to store in the element.
+ */
+function setFormElementValue(formElement, value) {
+ var type = formElement.localName;
+ if (type == "checkbox") {
+ if (value == null) {
+ formElement.checked = false;
+ } else if (formElement.getAttribute("reversed")) {
+ formElement.checked = !value;
+ } else {
+ formElement.checked = value;
+ }
+ } else if (type == "radiogroup" || type == "menulist") {
+ if (value == null) {
+ formElement.selectedIndex = 0;
+ } else {
+ formElement.value = value;
+ }
+ } else if (
+ type == "input" &&
+ formElement.getAttribute("datatype") == "nsIFile"
+ ) {
+ // handle nsIFile
+ if (value) {
+ let localfile = value.QueryInterface(Ci.nsIFile);
+ try {
+ formElement.value = localfile.path;
+ } catch (ex) {
+ dump("Still need to fix uninitialized nsIFile problem!\n");
+ }
+ } else {
+ formElement.value = "";
+ }
+ } else if (type == "input") {
+ if (value == null) {
+ formElement.value = null;
+ } else {
+ formElement.value = value;
+ }
+ } else if (type == "label") {
+ formElement.value = value || "";
+ } else if (value == null) {
+ // let the form figure out what to do with it
+ formElement.value = null;
+ } else {
+ formElement.value = value;
+ }
+}
+
+//
+// conversion routines - get data associated
+// with a given pageId, serverId, etc
+//
+
+// helper routine for account manager panels to get the current account for the selected server
+function getCurrentAccount() {
+ return currentAccount;
+}
+
+/**
+ * Get the array of persisted form elements for the given page.
+ */
+function getPageFormElements() {
+ // Uses getElementsByAttribute() which returns a live NodeList which is usually
+ // faster than e.g. querySelector().
+ if ("getElementsByAttribute" in top.frames.contentFrame.document) {
+ return top.frames.contentFrame.document.getElementsByAttribute(
+ "wsm_persist",
+ "true"
+ );
+ }
+
+ return null;
+}
+
+/**
+ * Get a single persisted form element in the current page.
+ *
+ * @param {srtring} aId - ID of the element requested.
+ */
+function getPageFormElement(aId) {
+ let elem = top.frames.contentFrame.document.getElementById(aId);
+ if (elem && elem.getAttribute("wsm_persist") == "true") {
+ return elem;
+ }
+
+ return null;
+}
+
+// get the value array for the given account
+function getValueArrayFor(account) {
+ var serverId = account ? account.incomingServer.serverURI : "global";
+
+ if (!(serverId in accountArray)) {
+ accountArray[serverId] = {};
+ accountArray[serverId]._account = account;
+ }
+
+ return accountArray[serverId];
+}
+
+/**
+ * Sets the name of the account rowitem in the tree pane.
+ *
+ * @param {string} aAccountKey - The key of the account to change.
+ * @param {string} aLabel - The value of the label to set.
+ */
+function setAccountLabel(aAccountKey, aLabel) {
+ let row = document.getElementById(aAccountKey);
+ if (row) {
+ row.setAttribute("aria-label", aLabel);
+ row.title = aLabel;
+ row.querySelector(".name").textContent = aLabel;
+ }
+ rebuildAccountTree(false);
+}
+
+var gAccountTree = {
+ load() {
+ this._build();
+
+ let mainTree = document.getElementById("accounttree");
+ mainTree.__defineGetter__("_orderableChildren", function () {
+ let rows = [...this.children];
+ rows.pop();
+ return rows;
+ });
+ mainTree.addEventListener("ordering", event => {
+ if (!event.detail || event.detail.id == "smtp") {
+ event.preventDefault();
+ }
+ });
+ mainTree.addEventListener("ordered", event => {
+ let accountKeyList = Array.from(mainTree.children, row => row.id);
+ accountKeyList.pop(); // Remove SMTP.
+ MailServices.accounts.reorderAccounts(accountKeyList);
+ rebuildAccountTree();
+ });
+ mainTree.addEventListener("expanded", event => {
+ this._dataStore.setValue(
+ document.documentURI,
+ event.target.id,
+ "open",
+ "true"
+ );
+ });
+ mainTree.addEventListener("collapsed", event => {
+ this._dataStore.setValue(
+ document.documentURI,
+ event.target.id,
+ "open",
+ "false"
+ );
+ });
+
+ MailServices.accounts.addIncomingServerListener(this);
+ },
+ unload() {
+ MailServices.accounts.removeIncomingServerListener(this);
+ },
+ onServerLoaded(server) {
+ // We assume the newly appeared server was created by the user so we select
+ // it in the tree.
+ this._build(server);
+ },
+ onServerUnloaded(aServer) {
+ this._build();
+ },
+ onServerChanged(aServer) {},
+
+ _dataStore: Services.xulStore,
+
+ /**
+ * Retrieve from XULStore.json whether the account should be expanded (open)
+ * in the account tree.
+ *
+ * @param {string} aAccountKey - Key of the account to check.
+ */
+ _getAccountOpenState(aAccountKey) {
+ if (!this._dataStore.hasValue(document.documentURI, aAccountKey, "open")) {
+ // If there was no value stored, use opened state.
+ return "true";
+ }
+ // Retrieve the persisted value from XULStore.json.
+ // It is stored under the URI of the current document and ID of the XUL element.
+ return this._dataStore.getValue(document.documentURI, aAccountKey, "open");
+ },
+
+ _build(newServer) {
+ var bundle = document.getElementById("bundle_prefs");
+ function getString(aString) {
+ return bundle.getString(aString);
+ }
+ var panels = [
+ { string: getString("prefPanel-server"), src: "am-server.xhtml" },
+ { string: getString("prefPanel-copies"), src: "am-copies.xhtml" },
+ {
+ string: getString("prefPanel-synchronization"),
+ src: "am-offline.xhtml",
+ },
+ { string: getString("prefPanel-diskspace"), src: "am-offline.xhtml" },
+ { string: getString("prefPanel-addressing"), src: "am-addressing.xhtml" },
+ { string: getString("prefPanel-junk"), src: "am-junk.xhtml" },
+ ];
+
+ let accounts = FolderUtils.allAccountsSorted(false);
+
+ let mainTree = document.getElementById("accounttree");
+ // Clear off all children...
+ while (mainTree.hasChildNodes()) {
+ mainTree.lastChild.remove();
+ }
+
+ for (let account of accounts) {
+ let accountName = null;
+ let accountKey = account.key;
+ let amChrome = "about:blank";
+ let panelsToKeep = [];
+ let server = null;
+
+ // This "try {} catch {}" block is intentionally very long to catch
+ // unknown exceptions and confine them to this single account.
+ // This may happen from broken accounts. See e.g. bug 813929.
+ // Other accounts can still be shown properly if they are valid.
+ try {
+ server = account.incomingServer;
+
+ if (
+ server.type == "im" &&
+ !Services.prefs.getBoolPref("mail.chat.enabled")
+ ) {
+ continue;
+ }
+
+ accountName = server.prettyName;
+
+ // Now add our panels.
+ let idents = MailServices.accounts.getIdentitiesForServer(server);
+ if (idents.length) {
+ panelsToKeep.push(panels[0]); // The server panel is valid
+ panelsToKeep.push(panels[1]); // also the copies panel
+ panelsToKeep.push(panels[4]); // and addressing
+ }
+
+ // Everyone except News, RSS and IM has a junk panel
+ // XXX: unextensible!
+ // The existence of server.spamSettings can't currently be used for this.
+ if (
+ server.type != "nntp" &&
+ server.type != "rss" &&
+ server.type != "im"
+ ) {
+ panelsToKeep.push(panels[5]);
+ }
+
+ // Check offline/diskspace support level.
+ let diskspace = server.supportsDiskSpace;
+ if (server.offlineSupportLevel >= 10 && diskspace) {
+ panelsToKeep.push(panels[2]);
+ } else if (diskspace) {
+ panelsToKeep.push(panels[3]);
+ }
+
+ // extensions
+ const CATEGORY = "mailnews-accountmanager-extensions";
+ for (let { data } of Services.catMan.enumerateCategory(CATEGORY)) {
+ try {
+ let svc = Cc[
+ Services.catMan.getCategoryEntry(CATEGORY, data)
+ ].getService(Ci.nsIMsgAccountManagerExtension);
+ if (svc.showPanel(server)) {
+ let bundleName =
+ "chrome://" +
+ svc.chromePackageName +
+ "/locale/am-" +
+ svc.name +
+ ".properties";
+ let bundle = Services.strings.createBundle(bundleName);
+ let title = bundle.GetStringFromName("prefPanel-" + svc.name);
+ panelsToKeep.push({
+ string: title,
+ src: "am-" + svc.name + ".xhtml",
+ });
+ }
+ } catch (e) {
+ // Fetching of this extension panel failed so do not show it,
+ // just log error.
+ let extName = data || "(unknown)";
+ console.error(
+ "Error accessing panel from extension '" + extName + "': " + e
+ );
+ }
+ }
+ amChrome = server.accountManagerChrome;
+ } catch (e) {
+ // Show only a placeholder in the account list saying this account
+ // is broken, with no child panels.
+ let accountID = accountName || accountKey;
+ console.error("Error accessing account " + accountID + ": " + e);
+ accountName = "Invalid account " + accountID;
+ panelsToKeep.length = 0;
+ }
+
+ // Create the top level tree-item.
+ let treeitem = document
+ .getElementById("accountTreeItem")
+ .content.firstElementChild.cloneNode(true);
+ mainTree.appendChild(treeitem);
+ treeitem.setAttribute("aria-label", accountName);
+ treeitem.title = accountName;
+ treeitem.querySelector(".name").textContent = accountName;
+ treeitem.setAttribute("PageTag", amChrome);
+ // Add icons based on account type.
+ if (server) {
+ treeitem.classList.add("serverType-" + server.type);
+ if (server.isSecure) {
+ treeitem.classList.add("isSecure");
+ }
+ // For IM accounts, we can try to fetch a protocol specific icon.
+ if (server.type == "im") {
+ treeitem.querySelector(".icon").style.backgroundImage =
+ "url(" +
+ ChatIcons.getProtocolIconURI(
+ server.wrappedJSObject.imAccount.protocol
+ ) +
+ ")";
+ treeitem.id = accountKey;
+ }
+ }
+
+ if (panelsToKeep.length > 0) {
+ let treekids = treeitem.querySelector("ul");
+ for (let panel of panelsToKeep) {
+ let kidtreeitem = document.createElement("li");
+ kidtreeitem.title = panel.string;
+ treekids.appendChild(kidtreeitem);
+ let kidtreerow = document.createElement("div");
+ kidtreeitem.appendChild(kidtreerow);
+ let kidtreecell = document.createElement("span");
+ kidtreecell.classList.add("name");
+ kidtreecell.tabIndex = -1;
+ kidtreerow.appendChild(kidtreecell);
+ kidtreecell.textContent = panel.string;
+ kidtreeitem.setAttribute("PageTag", panel.src);
+ kidtreeitem._account = account;
+ kidtreeitem.id = `${accountKey}/${panel.src}`;
+ }
+ treeitem.id = accountKey;
+ // Load the 'open' state of the account from XULStore.json.
+ if (this._getAccountOpenState(accountKey) != "true") {
+ treeitem.classList.add("collapsed");
+ }
+ }
+ treeitem._account = account;
+ }
+
+ markDefaultServer(MailServices.accounts.defaultAccount, null);
+
+ // Now add the outgoing server node.
+ let treeitem = document
+ .getElementById("accountTreeItem")
+ .content.firstElementChild.cloneNode(true);
+ mainTree.appendChild(treeitem);
+ treeitem.id = "smtp";
+ treeitem.querySelector(".name").textContent = getString("prefPanel-smtp");
+ treeitem.setAttribute("PageTag", "am-smtp.xhtml");
+ treeitem.classList.add("serverType-smtp");
+
+ // If a new server was created, select the server after rebuild of the tree.
+ if (newServer) {
+ setTimeout(selectServer, 0, newServer);
+ }
+ },
+};
diff --git a/comm/mailnews/base/prefs/content/AccountManager.xhtml b/comm/mailnews/base/prefs/content/AccountManager.xhtml
new file mode 100644
index 0000000000..7769ff730b
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/AccountManager.xhtml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % accountManagerDTD SYSTEM "chrome://messenger/locale/AccountManager.dtd">
+ %accountManagerDTD;
+ <!ENTITY % utilityDTD SYSTEM "chrome://communicator/locale/utilityOverlay.dtd">
+ %utilityDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false">
+<head>
+ <title>&accountManagerTitle.label;</title>
+
+ <meta http-equiv="Content-Security-Policy" content="default-src chrome:; script-src chrome: 'unsafe-inline'; img-src chrome: moz-icon: https: data:; style-src chrome: data: 'unsafe-inline'; object-src 'none'" />
+ <meta name="color-scheme" content="light dark" />
+
+ <link rel="icon" href="chrome://messenger/skin/icons/new/compact/account-settings.svg" />
+
+ <link rel="stylesheet" href="chrome://messenger/skin/inContentDialog.css" />
+ <link rel="stylesheet" href="chrome://messenger/skin/icons.css" />
+ <link rel="stylesheet" href="chrome://messenger/skin/folderPane.css" />
+ <link rel="stylesheet" href="chrome://messenger/skin/shared/tree-listbox.css" />
+ <link rel="stylesheet" href="chrome://messenger/skin/shared/accountManager.css" />
+
+ <link rel="localization" href="branding/brand.ftl" />
+ <link rel="localization" href="messenger/accountManager.ftl" />
+ <link rel="localization" href="messenger/addressbook/aboutAddressBook.ftl" />
+
+ <script defer="defer" src="chrome://messenger/content/mailCore.js"></script>
+ <script defer="defer" src="chrome://communicator/content/utilityOverlay.js"></script>
+ <script defer="defer" src="chrome://messenger/content/accountUtils.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script defer="defer" src="chrome://messenger/content/AccountManager.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script defer="defer" src="chrome://messenger/content/tree-listbox.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => { onLoad(event); });
+ window.addEventListener("unload", event => { onUnload(); });
+ </script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+ <html:aside id="accountTreeBox">
+ <html:ol is="orderable-tree-listbox" id="accounttree" class="no-overscroll"
+ role="tree"
+ flex="1"
+ onselect="onAccountTreeSelect(null, null);">
+ </html:ol>
+ <template id="accountTreeItem" xmlns="http://www.w3.org/1999/xhtml">
+ <li>
+ <div draggable="true">
+ <div class="twisty">
+ <img class="twisty-icon" alt=""
+ src="chrome://global/skin/icons/arrow-down-12.svg" />
+ </div>
+ <div class="icon"></div>
+ <span class="name" tabindex="-1"></span>
+ </div>
+ <ul></ul>
+ </li>
+ </template>
+
+ <button id="accountActionsButton" type="menu"
+ label="&accountActionsButton.label;"
+ accesskey="&accountActionsButton.accesskey;">
+ <menupopup id="accountActionsDropdown"
+ onpopupshowing="initAccountActionsButtons(this);">
+ <menuitem id="accountActionsAddMailAccount"
+ label="&addMailAccountButton.label;"
+ accesskey="&addMailAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="openAccountSetupTab();"/>
+ <menuitem id="accountActionsAddIMAccount"
+ label="&addIMAccountButton.label;"
+ accesskey="&addIMAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="AddIMAccount(event); event.stopPropagation();"/>
+ <menuitem id="accountActionsAddFeedAccount"
+ label="&addFeedAccountButton.label;"
+ accesskey="&addFeedAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="AddFeedAccount(event); event.stopPropagation();"/>
+ <menuitem id="accountActionsAddOtherAccount"
+ data-l10n-id="account-action-add-newsgroup-account"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="openNewsgroupAccountWizard(); event.stopPropagation();"/>
+ <menuseparator id="accountActionsDropdownSep1"/>
+ <menuitem id="accountActionsDropdownSetDefault"
+ label="&setDefaultButton.label;"
+ accesskey="&setDefaultButton.accesskey;"
+ prefstring="mail.disable_button.set_default_account"
+ oncommand="onSetDefault(event); event.stopPropagation();"/>
+ <menuitem id="accountActionsDropdownRemove"
+ label="&removeButton.label;"
+ accesskey="&removeButton.accesskey;"
+ prefstring="mail.disable_button.delete_account"
+ oncommand="onRemoveAccount(event); event.stopPropagation();"/>
+ </menupopup>
+ </button>
+
+ <vbox class="sidebar-footer-list">
+ <html:a id="prefsButton" class="sidebar-footer-link"
+ onclick="openOptionsDialog();">
+ <html:img class="sidebar-footer-icon"
+ src="chrome://messenger/skin/icons/new/compact/settings.svg" alt="" />
+ <label class="sidebar-footer-label"
+ data-l10n-id="open-preferences-sidebar-button2"
+ flex="1"/>
+ </html:a>
+
+ <html:a id="addonsButton" class="sidebar-footer-link"
+ onclick="window.browsingContext.topChromeWindow.openAddonsMgr();">
+ <html:img class="sidebar-footer-icon"
+ src="chrome://messenger/skin/icons/new/compact/extension.svg"
+ alt="" />
+ <label class="sidebar-footer-label"
+ data-l10n-id="open-addons-sidebar-button"
+ flex="1"/>
+ </html:a>
+ </vbox>
+ </html:aside>
+ <html:aside>
+ <iframe id="contentFrame" name="contentFrame" />
+ </html:aside>
+
+ <stack id="dialogStack" hidden="true"/>
+ <vbox id="dialogTemplate"
+ class="dialogOverlay"
+ align="center"
+ pack="center"
+ topmost="true"
+ hidden="true">
+ <vbox class="dialogBox"
+ pack="end"
+ role="dialog"
+ aria-labelledby="dialogTitle">
+ <hbox class="dialogTitleBar" align="center">
+ <label class="dialogTitle" flex="1"/>
+ <button class="dialogClose close-icon"
+ aria-label="&accountManagerCloseButton.label;"/>
+ </hbox>
+ <browser class="dialogFrame"
+ autoscroll="false"
+ disablehistory="true"/>
+ </vbox>
+ </vbox>
+
+ <dialog xmlns="http://www.w3.org/1999/xhtml" id="editVCardDialog">
+ <form autocomplete="off">
+#include ../../../../mail/components/addrbook/content/vcard-edit/vCardTemplates.inc.xhtml
+ <vcard-edit class="edit"/>
+ <menu class="dialog-menu-container">
+ <button type="reset" class="cancel"
+ data-l10n-id="edit-vcard-dialog-cancel-button"
+ data-l10n-attrs="accesskey"></button>
+ <button type="submit" class="accept primary"
+ data-l10n-id="edit-vcard-dialog-accept-button"
+ data-l10n-attrs="accesskey"></button>
+ </menu>
+ </form>
+ </dialog>
+</html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/AccountWizard.js b/comm/mailnews/base/prefs/content/AccountWizard.js
new file mode 100644
index 0000000000..69047c3f9e
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/AccountWizard.js
@@ -0,0 +1,605 @@
+/* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 accountUtils.js */
+/* import-globals-from amUtils.js */
+/* import-globals-from aw-identity.js */
+/* import-globals-from aw-incoming.js */
+/* import-globals-from aw-accname.js */
+/* import-globals-from aw-done.js */
+
+/* NOTE: This Account Wizard is *only* for Newsgroup accounts.
+ * Historically, it was a generic Account Wizard, hence the generic naming.
+ */
+
+/*
+ data flow into the account wizard like this:
+
+ For new accounts:
+ * pageData -> accountData -> createAccount -> finishAccount
+
+ for "unfinished accounts"
+ * account -> accountData -> pageData -> accountData -> finishAccount
+*/
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { NntpUtils } = ChromeUtils.import("resource:///modules/NntpUtils.jsm");
+
+var contentWindow;
+
+var gPageData;
+
+var nsIMsgIdentity = Ci.nsIMsgIdentity;
+var nsIMsgIncomingServer = Ci.nsIMsgIncomingServer;
+var gPrefsBundle, gMessengerBundle;
+
+// the current nsIMsgAccount
+var gCurrentAccount;
+
+// The default account before we create a new account.
+// We need to store this as just asking for the default account may switch
+// it to the newly created one if there was none before.
+var gDefaultAccount;
+
+// the current associative array that
+// will eventually be dumped into the account
+var gCurrentAccountData = null;
+
+// default picker mode for copies and folders
+var gDefaultSpecialFolderPickerMode = "0";
+
+// event handlers
+function onAccountWizardLoad() {
+ document.querySelector("wizard").addEventListener("wizardcancel", onCancel);
+ document
+ .querySelector("wizard")
+ .addEventListener("wizardfinish", FinishAccount);
+ let identityPage = document.getElementById("identitypage");
+ identityPage.addEventListener("pageshow", identityPageInit);
+ identityPage.addEventListener("pageadvanced", identityPageUnload);
+ identityPage.next = "newsserver";
+ let newsserverPage = document.getElementById("newsserver");
+ newsserverPage.addEventListener("pageshow", incomingPageInit);
+ newsserverPage.addEventListener("pageadvanced", incomingPageUnload);
+ newsserverPage.next = "accnamepage";
+ let accnamePage = document.getElementById("accnamepage");
+ accnamePage.addEventListener("pageshow", acctNamePageInit);
+ accnamePage.addEventListener("pageadvanced", acctNamePageUnload);
+ accnamePage.next = "done";
+ let donePage = document.getElementById("done");
+ donePage.addEventListener("pageshow", donePageInit);
+
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ gMessengerBundle = document.getElementById("bundle_messenger");
+
+ checkForInvalidAccounts();
+
+ // It is fine if there is no default account, this is expected the first
+ // time you launch mail on a new profile.
+ gDefaultAccount = MailServices.accounts.defaultAccount;
+
+ identityPageInit();
+}
+
+function onCancel() {
+ if ("ActivationOnCancel" in this && this.ActivationOnCancel()) {
+ return false;
+ }
+ var firstInvalidAccount = getInvalidAccounts(
+ MailServices.accounts.accounts
+ ).find(account => account.incomingServer.type == "nntp");
+ var closeWizard = true;
+
+ // if the user cancels the the wizard when it pops up because of
+ // an invalid account (example, a webmail account that activation started)
+ // we just force create it by setting some values and calling the FinishAccount()
+ // see bug #47521 for the full discussion
+ if (firstInvalidAccount) {
+ var pageData = GetPageData();
+ // set the fullName if it doesn't exist
+ if (!pageData.fullName) {
+ pageData.fullName = "";
+ }
+
+ // set the email if it doesn't exist
+ if (!pageData.email) {
+ pageData.email = "user@domain.invalid";
+ }
+
+ // call FinishAccount() and not onFinish(), since the "finish"
+ // button may be disabled
+ FinishAccount();
+ } else if (!MailServices.accounts.accounts.length) {
+ // since this is not an invalid account
+ // really cancel if the user hits the "cancel" button
+ // if the length of the account list is less than 1, there are no accounts
+ let confirmMsg = gPrefsBundle.getString("cancelWizard");
+ let confirmTitle = gPrefsBundle.getString("accountWizard");
+ let result = Services.prompt.confirmEx(
+ window,
+ confirmTitle,
+ confirmMsg,
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1,
+ gPrefsBundle.getString("WizardExit"),
+ gPrefsBundle.getString("WizardContinue"),
+ null,
+ null,
+ { value: 0 }
+ );
+
+ if (result == 1) {
+ closeWizard = false;
+ }
+ }
+
+ return closeWizard;
+}
+
+function FinishAccount() {
+ try {
+ var pageData = GetPageData();
+
+ var accountData = gCurrentAccountData;
+
+ if (!accountData) {
+ accountData = {};
+ }
+
+ // we may need local folders before account is "Finished"
+ // if it's a pop3 account which defers to Local Folders.
+ verifyLocalFoldersAccount();
+
+ PageDataToAccountData(pageData, accountData);
+
+ // we might be simply finishing another account
+ if (!gCurrentAccount) {
+ gCurrentAccount = createAccount(accountData);
+ }
+
+ // transfer all attributes from the accountdata
+ finishAccount(gCurrentAccount, accountData);
+
+ setupCopiesAndFoldersServer(gCurrentAccount, accountData);
+
+ if (gCurrentAccount.incomingServer.canBeDefaultServer) {
+ EnableCheckMailAtStartUpIfNeeded(gCurrentAccount);
+ }
+
+ // in case we crash, force us a save of the prefs file NOW
+ try {
+ MailServices.accounts.saveAccountInfo();
+ } catch (ex) {
+ dump("Error saving account info: " + ex + "\n");
+ }
+ let openerWindow = window.opener.top;
+ // The following block is the same as in feedAccountWizard.js.
+ if ("selectServer" in openerWindow) {
+ // Opened from Account Settings.
+ openerWindow.selectServer(gCurrentAccount.incomingServer);
+ }
+
+ // Post a message to the main window on successful account setup.
+ openerWindow.postMessage("account-created", "*");
+
+ window.close();
+ } catch (ex) {
+ dump("FinishAccount failed, " + ex + "\n");
+ }
+}
+
+// prepopulate pageData with stuff from accountData
+// use: to prepopulate the wizard with account information
+function AccountDataToPageData(accountData, pageData) {
+ var server = accountData.incomingServer;
+ pageData.hostname = server.hostName;
+ pageData.prettyName = server.prettyName || "";
+
+ var identity;
+
+ if (accountData.identity) {
+ dump("This is an accountdata\n");
+ identity = accountData.identity;
+ } else if (accountData.identities) {
+ identity = accountData.identities[0];
+ dump("this is an account, id= " + identity + "\n");
+ }
+
+ pageData.email = identity.email || "";
+ pageData.fullName = identity.fullName || "";
+}
+
+// take data from each page of pageData and dump it into accountData
+// use: to put results of wizard into a account-oriented object
+function PageDataToAccountData(pageData, accountData) {
+ if (!accountData.identity) {
+ accountData.identity = {};
+ }
+ if (!accountData.incomingServer) {
+ accountData.incomingServer = {};
+ }
+
+ var identity = accountData.identity;
+ var server = accountData.incomingServer;
+
+ if (pageData.email) {
+ identity.email = pageData.email;
+ }
+ if (pageData.fullName) {
+ identity.fullName = pageData.fullName;
+ }
+
+ server.hostName = pageData.hostname;
+ if (pageData.prettyName) {
+ server.prettyName = pageData.prettyName;
+ }
+}
+
+// given an accountData structure, create an account
+// (but don't fill in any fields, that's for finishAccount()
+function createAccount(accountData) {
+ let hostName = accountData.incomingServer.hostName;
+ // If we're here, the server must not be associated with any account, so reuse
+ // it.
+ let server = NntpUtils.findServer(hostName);
+
+ if (!server) {
+ dump(`MailServices.accounts.createIncomingServer(${hostName})\n`);
+ // Create a (actual) server.
+ server = MailServices.accounts.createIncomingServer(null, hostName, "nntp");
+ }
+
+ dump("MailServices.accounts.createAccount()\n");
+ // Create an account.
+ let account = MailServices.accounts.createAccount();
+
+ // only create an identity for this account if we really have one
+ // (use the email address as a check)
+ if (accountData.identity && accountData.identity.email) {
+ dump("MailServices.accounts.createIdentity()\n");
+ // Create an identity.
+ let identity = MailServices.accounts.createIdentity();
+
+ // New nntp identities should use plain text by default;
+ // we want that GNKSA (The Good Net-Keeping Seal of Approval).
+ identity.composeHtml = false;
+
+ account.addIdentity(identity);
+ }
+
+ // we mark the server as invalid so that the account manager won't
+ // tell RDF about the new server - it's not quite finished getting
+ // set up yet, in particular, the deferred storage pref hasn't been set.
+ server.valid = false;
+ // Set the new account to use the new server.
+ account.incomingServer = server;
+ server.valid = true;
+ return account;
+}
+
+// given an accountData structure, copy the data into the
+// given account, incoming server, and so forth
+function finishAccount(account, accountData) {
+ if (accountData.incomingServer) {
+ var destServer = account.incomingServer;
+ var srcServer = accountData.incomingServer;
+ copyObjectToInterface(destServer, srcServer, true);
+
+ // See if there are any protocol-specific attributes.
+ // If so, we use the type to get the IID, QueryInterface
+ // as appropriate, then copy the data over.
+ const typeProperty = "ServerType-" + srcServer.type;
+ let serverAttrs =
+ typeProperty in srcServer ? srcServer[typeProperty] : null;
+ dump(`srcServer.${typeProperty} = ${serverAttrs}\n`);
+ if (serverAttrs) {
+ // handle server-specific stuff
+ var IID;
+ try {
+ IID = destServer.protocolInfo.serverIID;
+ } catch (ex) {
+ console.error("Could not get IID for " + srcServer.type + ": " + ex);
+ }
+
+ if (IID) {
+ let destProtocolServer = destServer.QueryInterface(IID);
+ let srcProtocolServer = srcServer["ServerType-" + srcServer.type];
+
+ dump("Copying over " + srcServer.type + "-specific data\n");
+ copyObjectToInterface(destProtocolServer, srcProtocolServer, false);
+ }
+ }
+
+ account.incomingServer.valid = true;
+ // hack to cause an account loaded notification now the server is valid
+ account.incomingServer = account.incomingServer; // eslint-disable-line no-self-assign
+ }
+
+ // copy identity info
+ var destIdentity = account.identities.length ? account.identities[0] : null;
+
+ if (destIdentity) {
+ // does this account have an identity?
+ if (accountData.identity && accountData.identity.email) {
+ // fixup the email address if we have a default domain
+ let emailArray = accountData.identity.email.split("@");
+ if (emailArray.length < 2 && accountData.domain) {
+ accountData.identity.email += "@" + accountData.domain;
+ }
+
+ copyObjectToInterface(destIdentity, accountData.identity, true);
+ destIdentity.valid = true;
+ }
+
+ /**
+ * If signature file need to be set, get the path to the signature file.
+ * Signature files, if exist, are placed under default location. Get
+ * default files location for messenger using directory service. Signature
+ * file name should be extracted from the account data to build the complete
+ * path for signature file. Once the path is built, set the identity's signature pref.
+ */
+ if (destIdentity.attachSignature) {
+ var sigFileName = accountData.signatureFileName;
+ let sigFile = MailServices.mailSession.getDataFilesDir("messenger");
+ sigFile.append(sigFileName);
+ destIdentity.signature = sigFile;
+ }
+ } // if the account has an identity...
+
+ if (this.FinishAccountHook != undefined) {
+ this.FinishAccountHook(accountData.domain);
+ }
+}
+
+// Helper method used by copyObjectToInterface which attempts to set dest[attribute] as a generic
+// attribute on the xpconnect object, src.
+// This routine skips any attribute that begins with ServerType-
+function setGenericAttribute(dest, src, attribute) {
+ if (!attribute.toLowerCase().startsWith("servertype-") && src[attribute]) {
+ switch (typeof src[attribute]) {
+ case "string":
+ dest.setUnicharAttribute(attribute, src[attribute]);
+ break;
+ case "boolean":
+ dest.setBoolAttribute(attribute, src[attribute]);
+ break;
+ case "number":
+ dest.setIntAttribute(attribute, src[attribute]);
+ break;
+ default:
+ dump(
+ "Error: No Generic attribute " +
+ attribute +
+ " found for: " +
+ dest +
+ "\n"
+ );
+ break;
+ }
+ }
+}
+
+// copy over all attributes from dest into src that already exist in src
+// the assumption is that src is an XPConnect interface full of attributes
+// @param useGenericFallback if we can't set an attribute directly on src, then fall back
+// and try setting it generically. This assumes that src supports setIntAttribute, setUnicharAttribute
+// and setBoolAttribute.
+function copyObjectToInterface(dest, src, useGenericFallback) {
+ if (!dest) {
+ return;
+ }
+ if (!src) {
+ return;
+ }
+
+ var attribute;
+ for (attribute in src) {
+ if (dest.__lookupSetter__(attribute)) {
+ if (dest[attribute] != src[attribute]) {
+ dest[attribute] = src[attribute];
+ }
+ } else if (useGenericFallback) {
+ // fall back to setting the attribute generically
+ setGenericAttribute(dest, src, attribute);
+ }
+ } // for each attribute in src we want to copy
+}
+
+// check if there already is a "Local Folders"
+// if not, create it.
+function verifyLocalFoldersAccount() {
+ var localMailServer = null;
+ try {
+ localMailServer = MailServices.accounts.localFoldersServer;
+ } catch (ex) {
+ // dump("exception in findserver: " + ex + "\n");
+ localMailServer = null;
+ }
+
+ try {
+ if (!localMailServer) {
+ // dump("Creating local mail account\n");
+ // creates a copy of the identity you pass in
+ MailServices.accounts.createLocalMailAccount();
+ try {
+ localMailServer = MailServices.accounts.localFoldersServer;
+ } catch (ex) {
+ dump(
+ "error! we should have found the local mail server after we created it.\n"
+ );
+ localMailServer = null;
+ }
+ }
+ } catch (ex) {
+ dump("Error in verifyLocalFoldersAccount" + ex + "\n");
+ }
+}
+
+function setupCopiesAndFoldersServer(account, accountData) {
+ try {
+ var server = account.incomingServer;
+
+ if (!account.identities.length) {
+ return false;
+ }
+
+ let identity = account.identities[0];
+ // For this server, do we default the folder prefs to this server, or to the "Local Folders" server
+ // If it's deferred, we use the local folders account.
+ var defaultCopiesAndFoldersPrefsToServer =
+ server.defaultCopiesAndFoldersPrefsToServer;
+
+ var copiesAndFoldersServer = null;
+ if (defaultCopiesAndFoldersPrefsToServer) {
+ copiesAndFoldersServer = server;
+ } else {
+ if (!MailServices.accounts.localFoldersServer) {
+ dump("error! we should have a local mail server at this point\n");
+ return false;
+ }
+ copiesAndFoldersServer = MailServices.accounts.localFoldersServer;
+ }
+
+ setDefaultCopiesAndFoldersPrefs(
+ identity,
+ copiesAndFoldersServer,
+ accountData
+ );
+ } catch (ex) {
+ // return false (meaning we did not setupCopiesAndFoldersServer)
+ // on any error
+ dump("Error in setupCopiesAndFoldersServer: " + ex + "\n");
+ return false;
+ }
+ return true;
+}
+
+function setDefaultCopiesAndFoldersPrefs(identity, server, accountData) {
+ var rootFolder = server.rootFolder;
+
+ // we need to do this or it is possible that the server's draft,
+ // stationery fcc folder will not be in rdf
+ //
+ // this can happen in a couple cases
+ // 1) the first account we create, creates the local mail. since
+ // local mail was just created, it obviously hasn't been opened,
+ // or in rdf..
+ // 2) the account we created is of a type where
+ // defaultCopiesAndFoldersPrefsToServer is true
+ // this since we are creating the server, it obviously hasn't been
+ // opened, or in rdf.
+ //
+ // this makes the assumption that the server's draft, stationery fcc folder
+ // are at the top level (ie subfolders of the root folder.) this works
+ // because we happen to be doing things that way, and if the user changes
+ // that, it will work because to change the folder, it must be in rdf,
+ // coming from the folder cache, in the worst case.
+ var msgFolder = rootFolder.QueryInterface(Ci.nsIMsgFolder);
+
+ /**
+ * When a new account is created, folders 'Sent', 'Drafts'
+ * and 'Templates' are not created then, but created on demand at runtime.
+ * But we do need to present them as possible choices in the Copies and Folders
+ * UI. To do that, folder URIs have to be created and stored in the prefs file.
+ * So, if there is a need to build special folders, append the special folder
+ * names and create right URIs.
+ */
+ var folderDelim = "/";
+
+ /* we use internal names known to everyone like Sent, Templates and Drafts */
+ /* if folder names were already given in isp rdf, we use them,
+ otherwise we use internal names known to everyone like Sent, Templates and Drafts */
+
+ // Note the capital F, D and S!
+ var draftFolder =
+ accountData.identity && accountData.identity.DraftFolder
+ ? accountData.identity.DraftFolder
+ : "Drafts";
+ var stationeryFolder =
+ accountData.identity && accountData.identity.StationeryFolder
+ ? accountData.identity.StationeryFolder
+ : "Templates";
+ var fccFolder =
+ accountData.identity && accountData.identity.FccFolder
+ ? accountData.identity.FccFolder
+ : "Sent";
+
+ identity.draftFolder = msgFolder.server.serverURI + folderDelim + draftFolder;
+ identity.stationeryFolder =
+ msgFolder.server.serverURI + folderDelim + stationeryFolder;
+ identity.fccFolder = msgFolder.server.serverURI + folderDelim + fccFolder;
+
+ // Note the capital F, D and S!
+ identity.fccFolderPickerMode =
+ accountData.identity && accountData.identity.FccFolder
+ ? 1
+ : gDefaultSpecialFolderPickerMode;
+ identity.draftsFolderPickerMode =
+ accountData.identity && accountData.identity.DraftFolder
+ ? 1
+ : gDefaultSpecialFolderPickerMode;
+ identity.tmplFolderPickerMode =
+ accountData.identity && accountData.identity.StationeryFolder
+ ? 1
+ : gDefaultSpecialFolderPickerMode;
+}
+
+function checkForInvalidAccounts() {
+ var firstInvalidAccount = getInvalidAccounts(
+ MailServices.accounts.accounts
+ ).find(account => account.incomingServer.type == "nntp");
+
+ if (firstInvalidAccount) {
+ var pageData = GetPageData();
+ dump(
+ "We have an invalid account, " +
+ firstInvalidAccount +
+ ", let's use that!\n"
+ );
+ gCurrentAccount = firstInvalidAccount;
+
+ var accountData = {};
+ accountData.incomingServer = firstInvalidAccount.incomingServer;
+ accountData.identity = firstInvalidAccount.identities[0];
+ AccountDataToPageData(accountData, pageData);
+
+ gCurrentAccountData = accountData;
+ }
+}
+
+function getUsernameFromEmail(email) {
+ return email && email.substr(0, email.indexOf("@"));
+}
+
+function GetPageData() {
+ if (!gPageData) {
+ gPageData = {};
+ }
+
+ return gPageData;
+}
+
+// flush the XUL cache - just for debugging purposes - not called
+function onFlush() {
+ Services.prefs.setBoolPref("nglayout.debug.disable_xul_cache", true);
+ Services.prefs.setBoolPref("nglayout.debug.disable_xul_cache", false);
+}
+
+/** If there are no default accounts..
+ * this is will be the new default, so enable
+ * check for mail at startup
+ */
+function EnableCheckMailAtStartUpIfNeeded(newAccount) {
+ // Check if default account existed.
+ // If no such account, make this one the default account
+ // and turn on the new mail check at startup for the current account
+ if (!gDefaultAccount) {
+ MailServices.accounts.defaultAccount = newAccount;
+ newAccount.incomingServer.loginAtStartUp = true;
+ newAccount.incomingServer.downloadOnBiff = true;
+ }
+}
diff --git a/comm/mailnews/base/prefs/content/AccountWizard.xhtml b/comm/mailnews/base/prefs/content/AccountWizard.xhtml
new file mode 100644
index 0000000000..b58db60f60
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/AccountWizard.xhtml
@@ -0,0 +1,161 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<!-- NOTE: This Account Wizard is *only* for Newsgroup accounts. Historically,
+ - it was a generic Account Wizard, hence the generic naming. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountWizard.css" type="text/css"?>
+<?xml-stylesheet type="text/css" href="chrome://messenger/skin/input-fields.css"?>
+<?xml-stylesheet href="chrome://messenger/skin/shared/grid-layout.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % AccountWizardDTD SYSTEM "chrome://messenger/locale/AccountWizard.dtd">
+%AccountWizardDTD;
+<!ENTITY % newsblogDTD SYSTEM "chrome://messenger-newsblog/locale/am-newsblog.dtd">
+%newsblogDTD;
+]>
+
+<window id="AccountWizard" title="&windowTitle.label;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onAccountWizardLoad();"
+ style="width: 40em; height: 38em;"
+ lightweightthemes="true">
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <linkset>
+ <html:link rel="localization" href="toolkit/global/wizard.ftl"/>
+ </linkset>
+
+ <script src="chrome://messenger/content/globalOverlay.js"/>
+ <script src="chrome://global/content/editMenuOverlay.js"/>
+ <script src="chrome://messenger/content/accountUtils.js"/>
+ <script src="chrome://messenger/content/amUtils.js"/>
+ <script src="chrome://messenger/content/AccountWizard.js"/>
+ <script src="chrome://messenger/content/aw-identity.js"/>
+ <script src="chrome://messenger/content/aw-incoming.js"/>
+ <script src="chrome://messenger/content/aw-accname.js"/>
+ <script src="chrome://messenger/content/aw-done.js"/>
+
+ <wizard>
+ <!-- Identity page : Collects user's full name and email address -->
+ <wizardpage id="identitypage" pageid="identitypage"
+ label="&identityTitle.label;">
+ <vbox>
+ <description flex="1">&identityDesc.label;</description>
+ <separator/>
+ <description flex="1">&fullnameDesc.label; &fullnameExample.label;</description>
+ <separator class="thin"/>
+ <hbox align="center" class="input-container">
+ <label id="fullnameLabel" class="awIdentityLabel" value="&fullnameLabel.label;"
+ accesskey="&fullnameLabel.accesskey;" control="fullName"/>
+ <html:input id="fullName"
+ type="text"
+ required="required"
+ wsm_persist="true"
+ name="fullName"
+ class="input-inline"
+ aria-labelledby="fullnameLabel"
+ oninput="identityPageValidate();"/>
+ </hbox>
+ <separator/>
+ <vbox>
+ <hbox>
+ <description id="emailDescText" flex="1"/>
+ </hbox>
+ <hbox align="center">
+ <label id="emailFieldLabel"
+ class="awIdentityLabel"
+ value="&emailLabel.label;"
+ accesskey="&emailLabel.accesskey;"
+ control="email"/>
+ <hbox class="uri-element input-container" align="center" flex="1">
+ <html:input id="email"
+ type="email"
+ required="required"
+ wsm_persist="true"
+ name="email"
+ aria-labelledby="emailFieldLabel"
+ oninput="identityPageValidate();"
+ class="uri-element input-inline"/>
+ </hbox>
+ </hbox>
+ </vbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- News Server page : Collects the News groups server name -->
+ <wizardpage id="newsserver" pageid="newsserver"
+ label="&incomingTitle.label;">
+ <vbox flex="1">
+ <description>&newsServerNameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center" class="input-container">
+ <label id="newsServerLabel" control="newsServer"
+ value="&newsServerLabel.label;"
+ accesskey="&newsServerLabel.accesskey;"
+ style="width: 8em;"/>
+ <html:input id="newsServer"
+ type="text"
+ wsm_persist="true"
+ class="uri-element input-inline"
+ aria-labelledby="newsServerLabel"
+ oninput="incomingPageValidate();"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Account name page : User gets a choice to enter a pretty name for the account -->
+ <!-- Defaults : Mail account -> Email address, Newsgroup account -> Newsgroup server name -->
+ <wizardpage id="accnamepage" pageid="accnamepage"
+ label="&accnameTitle.label;">
+ <vbox flex="1">
+ <description>&accnameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center" class="input-container">
+ <label id="prettyNameLabel" class="label"
+ value="&accnameLabel.label;"
+ style="width: 8em;"
+ accesskey="&accnameLabel.accesskey;"
+ control="prettyName"/>
+ <html:input id="prettyName"
+ type="text"
+ size="40"
+ wsm_persist="true"
+ class="input-inline"
+ aria-labelledby="prettyNameLabel"
+ oninput="acctNamePageValidate();"/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Done page : this page summarizes information collected to create a mail/news account -->
+ <wizardpage id="done" pageid="done"
+ label="&completionTitle.label;">
+ <vbox flex="1">
+ <description>&completionText.label;</description>
+ <separator class="thin"/>
+ <html:div class="grid-two-column-fr grid-items-baseline">
+ <label id="account.name.label" flex="1" class="label" value="&accnameLabel.label;"/>
+ <label id="account.name.text" flex="1" class="label"/>
+ <label id="identity.email.label" flex="1" class="label" value="&emailLabel.label;"/>
+ <label id="identity.email.text" flex="1" class="label"/>
+ <label id="server.username.label" flex="1" class="label" value="&incomingUsername.label;"/>
+ <label id="server.username.text" flex="1" class="label"/>
+ <label id="newsServer.name.label" flex="1" class="label" value="&newsServerNamePrefix.label;"/>
+ <label id="newsServer.name.text" flex="1" class="label"/>
+ </html:div>
+ <separator/>
+ <spacer flex="1"/>
+#ifndef XP_MACOSX
+ <description>&clickFinish.label;</description>
+#else
+ <description>&clickFinish.labelMac;</description>
+#endif
+ </vbox>
+ </wizardpage>
+ </wizard>
+</window>
diff --git a/comm/mailnews/base/prefs/content/SmtpServerEdit.js b/comm/mailnews/base/prefs/content/SmtpServerEdit.js
new file mode 100644
index 0000000000..56d6076c5f
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/SmtpServerEdit.js
@@ -0,0 +1,241 @@
+/* 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/. */
+
+var { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import(
+ "resource:///modules/hostnameUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { OAuth2Providers } = ChromeUtils.import(
+ "resource:///modules/OAuth2Providers.jsm"
+);
+
+var gSmtpServer;
+var gSmtpUsername;
+var gSmtpDescription;
+var gSmtpUsernameLabel;
+var gSmtpHostname;
+var gSmtpPort;
+var gSmtpAuthMethod;
+var gSmtpSocketType;
+var gPort;
+var gDefaultPort;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+document.addEventListener("dialogaccept", onAccept);
+
+function onLoad() {
+ gSmtpServer = window.arguments[0].server;
+ initSmtpSettings(gSmtpServer);
+}
+
+function onAccept(event) {
+ if (!isLegalHostNameOrIP(cleanUpHostName(gSmtpHostname.value))) {
+ let prefsBundle = document.getElementById("bundle_prefs");
+ let brandBundle = document.getElementById("bundle_brand");
+ let alertTitle = brandBundle.getString("brandShortName");
+ let alertMsg = prefsBundle.getString("enterValidServerName");
+ Services.prompt.alert(window, alertTitle, alertMsg);
+
+ window.arguments[0].result = false;
+ event.preventDefault();
+ return;
+ }
+
+ // If we didn't have an SMTP server to initialize with,
+ // we must be creating one.
+ try {
+ if (!gSmtpServer) {
+ gSmtpServer = MailServices.smtp.createServer();
+ window.arguments[0].addSmtpServer = gSmtpServer.key;
+ }
+
+ saveSmtpSettings(gSmtpServer);
+ } catch (ex) {
+ console.error("Error saving smtp server: " + ex);
+ }
+
+ window.arguments[0].result = true;
+}
+
+function initSmtpSettings(server) {
+ gSmtpUsername = document.getElementById("smtpUsername");
+ gSmtpDescription = document.getElementById("smtp.description");
+ gSmtpUsernameLabel = document.getElementById("smtpUsernameLabel");
+ gSmtpHostname = document.getElementById("smtp.hostname");
+ gSmtpPort = document.getElementById("smtp.port");
+ gSmtpAuthMethod = document.getElementById("smtp.authMethod");
+ gSmtpSocketType = document.getElementById("smtp.socketType");
+ gDefaultPort = document.getElementById("smtp.defaultPort");
+ gPort = document.getElementById("smtp.port");
+
+ if (server) {
+ gSmtpHostname.value = server.hostname;
+ gSmtpDescription.value = server.description;
+ gSmtpPort.value = server.port;
+ gSmtpUsername.value = server.username;
+ gSmtpAuthMethod.value = server.authMethod;
+ gSmtpSocketType.value = server.socketType < 4 ? server.socketType : 1;
+ } else {
+ // New server, load default values.
+ gSmtpAuthMethod.value = Services.prefs.getIntPref(
+ "mail.smtpserver.default.authMethod"
+ );
+ gSmtpSocketType.value = Services.prefs.getIntPref(
+ "mail.smtpserver.default.try_ssl"
+ );
+ }
+
+ // Although sslChanged will set a label for cleartext password,
+ // we need to use the long label so that we can size the dialog.
+ setLabelFromStringBundle("authMethod-no", "authNo");
+ setLabelFromStringBundle(
+ "authMethod-password-encrypted",
+ "authPasswordEncrypted"
+ );
+ setLabelFromStringBundle(
+ "authMethod-password-cleartext",
+ "authPasswordCleartextInsecurely"
+ );
+ setLabelFromStringBundle("authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("authMethod-ntlm", "authNTLM");
+ setLabelFromStringBundle("authMethod-oauth2", "authOAuth2");
+ setLabelFromStringBundle("authMethod-anysecure", "authAnySecure");
+ setLabelFromStringBundle("authMethod-any", "authAny");
+
+ window.sizeToContent();
+
+ sslChanged(false);
+ authMethodChanged(false);
+
+ if (MailServices.smtp.defaultServer) {
+ onLockPreference();
+ }
+
+ // Hide OAuth2 option if we can't use it.
+ let details = server
+ ? OAuth2Providers.getHostnameDetails(server.hostname)
+ : null;
+ document.getElementById("authMethod-oauth2").hidden = !details;
+
+ // Hide deprecated/hidden auth options, unless selected
+ hideUnlessSelected(document.getElementById("authMethod-anysecure"));
+ hideUnlessSelected(document.getElementById("authMethod-any"));
+
+ // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't
+ // allow users to choose it anymore. Hide the option unless the user already
+ // has it set.
+ hideUnlessSelected(document.getElementById("connectionSecurityType-1"));
+}
+
+function hideUnlessSelected(element) {
+ element.hidden = !element.selected;
+}
+
+function setLabelFromStringBundle(elementID, stringName) {
+ document.getElementById(elementID).label = document
+ .getElementById("bundle_messenger")
+ .getString(stringName);
+}
+
+// Disables xul elements that have associated preferences locked.
+function onLockPreference() {
+ try {
+ let allPrefElements = {
+ hostname: gSmtpHostname,
+ description: gSmtpDescription,
+ port: gSmtpPort,
+ authMethod: gSmtpAuthMethod,
+ try_ssl: gSmtpSocketType,
+ };
+ disableIfLocked(allPrefElements);
+ } catch (e) {
+ // non-fatal
+ console.error("Error while getting locked prefs: " + e);
+ }
+}
+
+/**
+ * Does the work of disabling an element given the array which contains
+ * id/prefstring pairs.
+ *
+ * @param {Element[]} prefstrArray - Elements to check.
+ *
+ * TODO: try to merge this with disableIfLocked function in am-offline.js (bug 755885)
+ */
+function disableIfLocked(prefstrArray) {
+ let smtpPrefBranch = Services.prefs.getBranch(
+ "mail.smtpserver." + MailServices.smtp.defaultServer.key + "."
+ );
+
+ for (let prefstring in prefstrArray) {
+ if (smtpPrefBranch.prefIsLocked(prefstring)) {
+ prefstrArray[prefstring].disabled = true;
+ }
+ }
+}
+
+function saveSmtpSettings(server) {
+ if (server) {
+ server.hostname = cleanUpHostName(gSmtpHostname.value);
+ server.description = gSmtpDescription.value;
+ server.port = gSmtpPort.value;
+ server.authMethod = gSmtpAuthMethod.value;
+ server.username = gSmtpUsername.value;
+ server.socketType = gSmtpSocketType.value;
+ }
+}
+
+function authMethodChanged(userAction) {
+ var noUsername = gSmtpAuthMethod.value == Ci.nsMsgAuthMethod.none;
+ gSmtpUsername.disabled = noUsername;
+ gSmtpUsernameLabel.disabled = noUsername;
+}
+
+/**
+ * Resets the default port to SMTP or SMTPS, dependending on
+ * the |gSmtpSocketType| value, and sets the port to use to this default,
+ * if that's appropriate.
+ *
+ * @param {boolean} userAction - false for dialog initialization,
+ * true for user action.
+ */
+function sslChanged(userAction) {
+ const DEFAULT_SMTP_PORT = "587";
+ const DEFAULT_SMTPS_PORT = "465";
+ var socketType = gSmtpSocketType.value;
+ var otherDefaultPort;
+ var prevDefaultPort = gDefaultPort.value;
+
+ if (socketType == Ci.nsMsgSocketType.SSL) {
+ gDefaultPort.value = DEFAULT_SMTPS_PORT;
+ otherDefaultPort = DEFAULT_SMTP_PORT;
+ } else {
+ gDefaultPort.value = DEFAULT_SMTP_PORT;
+ otherDefaultPort = DEFAULT_SMTPS_PORT;
+ }
+
+ // If the port is not set,
+ // or the user is causing the default port to change,
+ // and the port is set to the default for the other protocol,
+ // then set the port to the default for the new protocol.
+ if (
+ gPort.value == 0 ||
+ (userAction &&
+ gDefaultPort.value != prevDefaultPort &&
+ gPort.value == otherDefaultPort)
+ ) {
+ gPort.value = gDefaultPort.value;
+ }
+
+ // switch "insecure password" label
+ setLabelFromStringBundle(
+ "authMethod-password-cleartext",
+ socketType == Ci.nsMsgSocketType.SSL ||
+ socketType == Ci.nsMsgSocketType.alwaysSTARTTLS
+ ? "authPasswordCleartextViaSSL"
+ : "authPasswordCleartextInsecurely"
+ );
+}
diff --git a/comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml b/comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml
new file mode 100644
index 0000000000..d0bbda97c6
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/SmtpServerEdit.xhtml
@@ -0,0 +1,208 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/smtpEditOverlay.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false"
+>
+ <head>
+ <title>&smtpEditTitle.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/SmtpServerEdit.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog style="width: 100vw; height: 100vh">
+ <stringbundle
+ id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"
+ />
+ <stringbundle
+ id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"
+ />
+ <stringbundle
+ id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"
+ />
+
+ <vbox id="smtpServerEditor">
+ <html:div>
+ <html:fieldset>
+ <html:legend>&settings.caption;</html:legend>
+ <hbox flex="1">
+ <vbox>
+ <hbox flex="1" align="center">
+ <label
+ id="smtp.description.label"
+ value="&serverDescription.label;"
+ accesskey="&serverDescription.accesskey;"
+ control="smtp.description"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label
+ id="smtp.hostname.label"
+ value="&serverName.label;"
+ accesskey="&serverName.accesskey;"
+ control="smtp.hostname"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label
+ value="&serverPort.label;"
+ accesskey="&serverPort.accesskey;"
+ control="smtp.port"
+ />
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <html:input
+ id="smtp.description"
+ type="text"
+ preftype="string"
+ class="input-inline"
+ prefstring="mail.smtpserver.%serverkey%.description"
+ aria-labelledby="smtp.description.label"
+ />
+ <html:input
+ id="smtp.hostname"
+ type="text"
+ preftype="string"
+ class="uri-element input-inline"
+ prefstring="mail.smtpserver.%serverkey%.hostname"
+ aria-labelledby="smtp.hostname.label"
+ />
+ <hbox align="center">
+ <html:input
+ id="smtp.port"
+ type="number"
+ class="size5 input-inline"
+ min="0"
+ max="65535"
+ preftype="int"
+ prefstring="mail.smtpserver.%serverkey%.port"
+ />
+ <label value="&serverPortDefault.label;" />
+ <label id="smtp.defaultPort" />
+ </hbox>
+ </vbox>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <separator class="thin" />
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&security.caption;</html:legend>
+
+ <hbox flex="1">
+ <vbox>
+ <hbox flex="1" align="center">
+ <label
+ value="&connectionSecurity.label;"
+ accesskey="&connectionSecurity.accesskey;"
+ control="smtp.socketType"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label
+ value="&authMethod.label;"
+ accesskey="&authMethod.accesskey;"
+ control="server.authMethod"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label
+ id="smtpUsernameLabel"
+ value="&userName.label;"
+ accesskey="&userName.accesskey;"
+ control="smtpUsername"
+ />
+ </hbox>
+ </vbox>
+ <vbox>
+ <menulist
+ id="smtp.socketType"
+ oncommand="sslChanged(true);"
+ prefstring="mail.smtpserver.%serverkey%.try_ssl"
+ >
+ <menupopup id="smtp.socketTypePopup">
+ <menuitem
+ value="0"
+ label="&connectionSecurityType-0.label;"
+ />
+ <menuitem
+ id="connectionSecurityType-1"
+ value="1"
+ label="&connectionSecurityType-1.label;"
+ disabled="true"
+ hidden="true"
+ />
+ <menuitem
+ value="2"
+ label="&connectionSecurityType-2.label;"
+ />
+ <menuitem
+ value="3"
+ label="&connectionSecurityType-3.label;"
+ />
+ </menupopup>
+ </menulist>
+ <menulist
+ id="smtp.authMethod"
+ oncommand="authMethodChanged(true);"
+ wsm_persist="true"
+ preftype="int"
+ prefstring="mail.smtpserver.%serverkey%.authMethod"
+ >
+ <menupopup id="smtp.authMethodPopup">
+ <menuitem id="authMethod-no" value="1" />
+ <menuitem id="authMethod-password-cleartext" value="3" />
+ <menuitem id="authMethod-password-encrypted" value="4" />
+ <menuitem id="authMethod-kerberos" value="5" />
+ <menuitem id="authMethod-ntlm" value="6" />
+ <menuitem id="authMethod-oauth2" value="10" />
+ <menuitem id="authMethod-anysecure" value="8" />
+ <menuitem id="authMethod-any" value="9" />
+ </menupopup>
+ </menulist>
+ <hbox class="input-container">
+ <html:input
+ id="smtpUsername"
+ type="text"
+ class="input-inline"
+ preftype="string"
+ prefstring="mail.smtpserver.%serverkey%.username"
+ aria-labelledby="smtpUsernameLabel"
+ />
+ </hbox>
+ </vbox>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+ </vbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/accountUtils.js b/comm/mailnews/base/prefs/content/accountUtils.js
new file mode 100644
index 0000000000..8af2e72cf3
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/accountUtils.js
@@ -0,0 +1,369 @@
+/* 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 AccountManager.js */
+/* globals openTab */ // From utilityOverlay.js
+/* globals SelectFolder */ // From messageWindow.js or messenger.js.
+/* globals MsgGetMessage */ // From mailWindowOverlay.js.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gAnyValidIdentity = false; // If there are no valid identities for any account
+// returns the first account with an invalid server or identity
+
+var gNewAccountToLoad = null; // used to load new messages if we come from the mail3pane
+
+function getInvalidAccounts(accounts) {
+ let invalidAccounts = [];
+ for (let account of accounts) {
+ try {
+ if (!account.incomingServer.valid) {
+ invalidAccounts.push(account);
+ // skip to the next account
+ continue;
+ }
+ } catch (ex) {
+ // this account is busted, just keep going
+ continue;
+ }
+
+ for (let identity of account.identities) {
+ if (identity.valid) {
+ gAnyValidIdentity = true;
+ } else {
+ invalidAccounts.push(account);
+ }
+ }
+ }
+ return invalidAccounts;
+}
+
+function showMailIntegrationDialog() {
+ const nsIShellService = Ci.nsIShellService;
+
+ try {
+ var shellService =
+ Cc["@mozilla.org/suite/shell-service;1"].getService(nsIShellService);
+ var appTypesCheck =
+ shellService.shouldBeDefaultClientFor &
+ (nsIShellService.MAIL | nsIShellService.NEWS);
+
+ // show the default client dialog only if we have at least one account,
+ // if we should check for the default client, and we want to check if we are
+ // the default for mail/news and are not the default client for mail/news
+ if (
+ appTypesCheck &&
+ shellService.shouldCheckDefaultClient &&
+ !shellService.isDefaultClient(true, appTypesCheck)
+ ) {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://communicator/content/defaultClientDialog.xhtml",
+ "DefaultClient",
+ "modal,centerscreen,chrome,resizable=no"
+ );
+ }
+ } catch (ex) {}
+}
+
+/**
+ * Check that an account exists which requires Local Folders.
+ *
+ * @returns {boolean} - true if at least 1 account exists that requires
+ * Local Folders, else false.
+ */
+function requireLocalFoldersAccount() {
+ return MailServices.accounts.accounts.some(account =>
+ ["imap", "pop3", "nntp"].includes(account.incomingServer?.type)
+ );
+}
+
+/**
+ * Open the Nntp Account Wizard, or focus it if it's already open.
+ */
+function openNewsgroupAccountWizard() {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://messenger/content/AccountWizard.xhtml",
+ "AccountWizard",
+ "chrome,modal,titlebar,centerscreen"
+ );
+}
+
+function AddIMAccount() {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://messenger/content/chat/imAccountWizard.xhtml",
+ "",
+ "chrome,modal,titlebar,centerscreen"
+ );
+}
+
+function AddFeedAccount() {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://messenger-newsblog/content/feedAccountWizard.xhtml",
+ "",
+ "chrome,modal,titlebar,centerscreen"
+ );
+}
+
+/**
+ * Opens Address Book tab and triggers the address book creation dialog based on
+ * the passed type.
+ *
+ * @param {string} type - The address book type needing creation. Accepted types
+ * are "JS", "LDAP", and "CARDDAV".
+ */
+function addNewAddressBook(type) {
+ window.browsingContext.topChromeWindow.toAddressBook({
+ action: `create_ab_${type}`,
+ });
+}
+
+function showCalendarWizard() {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://calendar/content/calendar-creation.xhtml",
+ "caEditServer",
+ "chrome,titlebar,resizable,centerscreen",
+ {}
+ );
+}
+
+/**
+ * Opens the account settings window on the specified account
+ * and page of settings. If the window is already open it is only focused.
+ *
+ * @param {?string} selectPage - The file name for the viewing page, or null for
+ * the account main page. Other pages are 'am-server.xhtml',
+ * 'am-copies.xhtml', 'am-offline.xhtml', 'am-addressing.xhtml',
+ * 'am-smtp.xhtml'
+ * @param {nsIMsgIncomingServer} [server] - The server of the account to select.
+ */
+async function MsgAccountManager(selectPage, server) {
+ let win = Services.wm.getMostRecentWindow("mail:3pane");
+ if (!win) {
+ // No window available, so force open a new one.
+ openTab(
+ "contentTab",
+ {
+ url: "about:accountsettings",
+ onLoad(event, browser) {
+ browser.contentDocument.documentElement.server = server;
+ browser.contentDocument.documentElement.selectPage = selectPage;
+ browser.contentDocument.getElementById("accounttree").focus();
+ },
+ },
+ "window"
+ );
+ return;
+ }
+
+ let tabmail = win.document.getElementById("tabmail");
+ // If the server wasn't specified, and we have the window open, try
+ // and use the currently selected folder to work out the server to select.
+ if (!server) {
+ server = tabmail.currentAbout3Pane?.gFolder ?? null;
+ }
+ // If the server is still not found, account settings will default to
+ // the first account.
+
+ // If Account settings tab is already open, change the server
+ // and the selected page, reload the tab and switch to the tab.
+ for (let tabInfo of tabmail.tabInfo) {
+ let tab = tabmail.getTabForBrowser(tabInfo.browser);
+ if (tab?.urlbar?.value == "about:accountsettings") {
+ tab.browser.contentDocument.documentElement.server = server;
+ tab.browser.contentDocument.documentElement.selectPage = selectPage;
+ tab.browser.contentWindow.onLoad();
+ tabmail.switchToTab(tabInfo);
+ return;
+ }
+ }
+
+ // Else fallback to opening a new tab in the window.
+ tabmail.openTab("contentTab", {
+ url: "about:accountsettings",
+ onLoad(event, browser) {
+ browser.contentDocument.documentElement.server = server;
+ browser.contentDocument.documentElement.selectPage = selectPage;
+ browser.contentDocument.getElementById("accounttree").focus();
+ },
+ });
+}
+
+function loadInboxForNewAccount() {
+ // gNewAccountToLoad is set in the final screen of the Account Wizard if a POP account
+ // was created, the download messages box is checked, and the wizard was opened from the 3pane
+ if (gNewAccountToLoad) {
+ var rootMsgFolder = gNewAccountToLoad.incomingServer.rootMsgFolder;
+ const kInboxFlag = Ci.nsMsgFolderFlags.Inbox;
+ var inboxFolder = rootMsgFolder.getFolderWithFlags(kInboxFlag);
+ SelectFolder(inboxFolder.URI);
+ window.focus();
+ setTimeout(MsgGetMessage, 0);
+ gNewAccountToLoad = null;
+ }
+}
+
+// returns true if we migrated - it knows this because 4.x did not have the
+// pref mailnews.quotingPrefs.version, so if it's not set, we're either
+// migrating from 4.x, or a much older version of Mozilla.
+function migrateGlobalQuotingPrefs(allIdentities) {
+ // if reply_on_top and auto_quote exist then, if non-default
+ // migrate and delete, if default just delete.
+ var reply_on_top = 0;
+ var auto_quote = true;
+ var quotingPrefs = Services.prefs.getIntPref(
+ "mailnews.quotingPrefs.version",
+ 0
+ );
+ var migrated = false;
+
+ // If the quotingPrefs version is 0 then we need to migrate our preferences
+ if (quotingPrefs == 0) {
+ migrated = true;
+ try {
+ reply_on_top = Services.prefs.getIntPref("mailnews.reply_on_top");
+ auto_quote = Services.prefs.getBoolPref("mail.auto_quote");
+ } catch (ex) {}
+
+ if (!auto_quote || reply_on_top) {
+ for (let identity of allIdentities) {
+ if (identity.valid) {
+ identity.autoQuote = auto_quote;
+ identity.replyOnTop = reply_on_top;
+ }
+ }
+ }
+ Services.prefs.setIntPref("mailnews.quotingPrefs.version", 1);
+ }
+ return migrated;
+}
+
+/**
+ * Open the Account Setup Tab or focus it if it's already open.
+ */
+function openAccountSetupTab() {
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+ let tabmail = mail3Pane.document.getElementById("tabmail");
+
+ // Switch to the account setup tab if it's already open.
+ for (let tabInfo of tabmail.tabInfo) {
+ let tab = tabmail.getTabForBrowser(tabInfo.browser);
+ if (tab?.urlbar?.value == "about:accountsetup") {
+ let accountSetup = tabInfo.browser.contentWindow.gAccountSetup;
+ // Reset the entire UI only if the previously opened setup was completed.
+ if (accountSetup._currentModename == "success") {
+ accountSetup.resetSetup();
+ }
+ tabmail.switchToTab(tabInfo);
+ return;
+ }
+ }
+
+ tabmail.openTab("contentTab", { url: "about:accountsetup" });
+}
+
+/**
+ * Open the account setup tab and switch to the success view to show the newly
+ * created account, or show an error if the account wasn't created.
+ *
+ * @param {object} account - A newly created account.
+ * @param {string} name - The account name defined in the provider's website.
+ * @param {string} email - The newly created email address.
+ */
+function openAccountSetupTabWithAccount(account, name, email) {
+ // Define which actions we need to take after the account setup tab has been
+ // loaded and we have access to its objects.
+ let onTabLoaded = function (event, browser, account) {
+ let accountSetup = browser.contentWindow.gAccountSetup;
+
+ if (account) {
+ // Update the account setup variables before kicking off the success view
+ // which will start fetching linked services with these values.
+ accountSetup._realname = name;
+ accountSetup._email = email;
+ accountSetup._password = account.incomingServer.password;
+ accountSetup.showSuccessView(account);
+ return;
+ }
+
+ accountSetup.showErrorNotification("account-setup-provisioner-error");
+ };
+
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+ let tabmail = mail3Pane.document.getElementById("tabmail");
+
+ // Switch to the account setup tab if it's already open.
+ for (let tabInfo of tabmail.tabInfo) {
+ let tab = tabmail.getTabForBrowser(tabInfo.browser);
+ if (tab?.urlbar?.value == "about:accountsetup") {
+ let accountSetup = tabInfo.browser.contentWindow.gAccountSetup;
+ // Reset the entire UI only if the previously opened setup was completed.
+ if (accountSetup._currentModename == "success") {
+ accountSetup.resetSetup();
+ }
+ tabmail.switchToTab(tabInfo);
+ onTabLoaded(null, tabInfo.browser, account);
+ return;
+ }
+ }
+
+ // Open the account setup tab.
+ tabmail.openTab("contentTab", {
+ url: "about:accountsetup",
+ onLoad(event, browser) {
+ onTabLoaded(event, browser, account);
+ },
+ });
+}
+
+/**
+ * Open the Account Provisioner Tab or focus it if it's already open.
+ */
+function openAccountProvisionerTab() {
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+ let tabmail = mail3Pane.document.getElementById("tabmail");
+
+ // Switch to the account setup tab if it's already open.
+ for (let tabInfo of tabmail.tabInfo) {
+ let tab = tabmail.getTabForBrowser(tabInfo.browser);
+ if (tab?.urlbar?.value == "about:accountprovisioner") {
+ tabmail.switchToTab(tabInfo);
+ return;
+ }
+ }
+
+ tabmail.openTab("contentTab", { url: "about:accountprovisioner" });
+}
+
+/**
+ * Reveal the Folder Pane after an account creation callback.
+ */
+function updateMailPaneUI() {
+ // Nothing to update since no account has been created.
+ if (MailServices.accounts.accounts.length == 0) {
+ return;
+ }
+
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+ // Set the folderPaneVisible to true in the tabmail to prevent collapsing
+ // on tab switch.
+ let tabmail = mail3Pane.document.getElementById("tabmail");
+ tabmail.tabInfo[0].folderPaneVisible = true;
+}
+
+/**
+ * Open the OpenPGP Key Manager from outside the Account Settings.
+ */
+function openKeyManager() {
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://openpgp/content/ui/enigmailKeyManager.xhtml",
+ "enigmail:KeyManager",
+ "dialog,centerscreen,resizable",
+ {
+ cancelCallback: null,
+ okCallback: null,
+ }
+ );
+}
diff --git a/comm/mailnews/base/prefs/content/am-addressing.inc.xhtml b/comm/mailnews/base/prefs/content/am-addressing.inc.xhtml
new file mode 100644
index 0000000000..44d393f4ed
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-addressing.inc.xhtml
@@ -0,0 +1,125 @@
+# 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/.
+
+ <vbox flex="1" id="compositionAndAddressing">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&compositionGroupTitle.label;</html:legend>
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.composeHtml" label="&useHtml.label;"
+ accesskey="&useHtml.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.compose_html"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.autoQuote" label="&autoQuote.label;"
+ accesskey="&autoQuote.accesskey;"
+ pref="true" preftype="bool" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.auto_quote"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&quoting.label;" accesskey="&quoting.accesskey;" control="identity.replyOnTop"/>
+ <menulist wsm_persist="true" id="identity.replyOnTop" oncommand="quoteEnabling();"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.reply_on_top">
+ <menupopup>
+ <menuitem value="1" label="&aboveQuote.label;"/>
+ <menuitem value="0" label="&belowQuote.label;"/>
+ <menuitem value="2" label="&selectAndQuote.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <hbox class="indent" align="center" id="placeBox">
+ <label value="&place.label;" accesskey="&place.accesskey;" control="identity.sig_bottom"/>
+ <menulist wsm_persist="true" id="identity.sig_bottom" genericattr="true"
+ pref="true" preftype="bool" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.sig_bottom">
+ <menupopup>
+ <menuitem value="true" label="&belowText.label;"/>
+ <menuitem value="false" label="&aboveText.label;"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <checkbox id="identity.sig_on_reply" wsm_persist="true"
+ label="&includeSigOnReply.label;"
+ accesskey="&includeSigOnReply.accesskey;"
+ preftype="bool" genericattr="true"
+ prefstring="mail.identity.%identitykey%.sig_on_reply"/>
+
+ <checkbox id="identity.sig_on_fwd" wsm_persist="true"
+ label="&includeSigOnForward.label;"
+ accesskey="&includeSigOnForward.accesskey;"
+ preftype="bool" genericattr="true"
+ prefstring="mail.identity.%identitykey%.sig_on_fwd"/>
+
+ <separator class="thin"/>
+
+ <hbox pack="end">
+ <button id="globalComposingPrefsLink"
+ label="&globalComposingPrefs.label;"
+ accesskey="&globalComposingPrefs.accesskey;"
+ oncommand="showGlobalComposingPrefs();"/>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <separator class="thin"/>
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&addressingGroupTitle.label;</html:legend>
+#ifndef MOZ_THUNDERBIRD
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.autocompleteToMyDomain"
+ label="&autocompleteToMyDomain.label;"
+ accesskey="&autocompleteToMyDomain.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.autocompleteToMyDomain"/>
+ </hbox>
+
+ <separator class="thin"/>
+#endif
+
+ <label control="identity.overrideGlobal_Pref">&addressingText.label;</label>
+ <radiogroup id="identity.overrideGlobal_Pref" class="indent"
+ oncommand="LDAPenabling();" wsm_persist="true"
+ genericattr="true" preftype="bool"
+ prefstring="mail.identity.%identitykey%.overrideGlobal_Pref">
+ <radio value="false" label="&useGlobal.label;"
+ accesskey="&useGlobal.accesskey;"/>
+ <radio value="true" id="directories" label="&directories.label;"
+ accesskey="&directories.accesskey;"/>
+ <hbox class="indent">
+ <menulist is="menulist-addrbooks" id="identity.directoryServer"
+ none="&directoriesNone.label;"
+ remoteonly="true"
+ wsm_persist="true"
+ preftype="string"
+ prefstring="mail.identity.%identitykey%.directoryServer"
+ style="min-width: 16em;"
+ aria-labelledby="directories"
+ flex="1"/>
+ <button id="editButton" label="&editDirectories.label;"
+ accesskey="&editDirectories.accesskey;"
+ oncommand="onEditDirectories();"/>
+ </hbox>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <hbox pack="end">
+ <button id="globalAddressingPrefsLink"
+ label="&globalAddressingPrefs.label;"
+ accesskey="&globalAddressingPrefs.accesskey;"
+ oncommand="showGlobalAddressingPrefs();"/>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+ </vbox>
diff --git a/comm/mailnews/base/prefs/content/am-addressing.js b/comm/mailnews/base/prefs/content/am-addressing.js
new file mode 100644
index 0000000000..24a3bf0004
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-addressing.js
@@ -0,0 +1,68 @@
+/* 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 am-prefs.js */
+/* import-globals-from amUtils.js */
+
+function onLoad() {
+ parent.onPanelLoaded("am-addressing.xhtml");
+}
+
+function onInit(aPageId, aServerId) {
+ onInitCompositionAndAddressing();
+}
+
+function onInitCompositionAndAddressing() {
+ LDAPenabling();
+ quoteEnabling();
+}
+
+function onEditDirectories() {
+ parent.gSubDialog.open(
+ "chrome://messenger/content/addressbook/pref-editdirectories.xhtml"
+ );
+}
+
+function onPreInit(account, accountValues) {}
+
+function LDAPenabling() {
+ onCheckItem("identity.directoryServer", ["directories"]);
+ onCheckItem("editButton", ["directories"]);
+}
+
+function quoteEnabling() {
+ var placebox = document.getElementById("placeBox");
+
+ if (document.getElementById("identity.replyOnTop").value == "1") {
+ placebox.firstElementChild.removeAttribute("disabled");
+ placebox.lastElementChild.removeAttribute("disabled");
+ } else {
+ placebox.firstElementChild.setAttribute("disabled", "true");
+ placebox.lastElementChild.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Open the Preferences dialog on the tab with Addressing options.
+ */
+function showGlobalAddressingPrefs() {
+ openPrefsFromAccountManager(
+ "paneCompose",
+ "compositionAddressingCategory",
+ null,
+ "addressing_pane"
+ );
+}
+
+/**
+ * Open the Preferences dialog on the tab with Composing options.
+ */
+function showGlobalComposingPrefs() {
+ openPrefsFromAccountManager(
+ "paneCompose",
+ null,
+ null,
+ "composing_messages_pane"
+ );
+}
diff --git a/comm/mailnews/base/prefs/content/am-addressing.xhtml b/comm/mailnews/base/prefs/content/am-addressing.xhtml
new file mode 100644
index 0000000000..554ee398c1
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-addressing.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-addressing.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&addressing.label;</title>
+ <script defer="defer" src="chrome://messenger/content/am-addressing.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => { onLoad(); });
+ </script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox id="containerBox" flex="1">
+ <hbox class="dialogheader">
+ <label class="dialogheader-title" value="&addressing.label;"/>
+ </hbox>
+#include am-addressing.inc.xhtml
+ </vbox>
+</html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-archiveoptions.js b/comm/mailnews/base/prefs/content/am-archiveoptions.js
new file mode 100644
index 0000000000..44c0817149
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-archiveoptions.js
@@ -0,0 +1,74 @@
+/* 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/. */
+
+var gIdentity = null;
+
+window.addEventListener("load", onLoadArchiveOptions);
+document.addEventListener("dialogaccept", onAcceptArchiveOptions);
+
+/**
+ * Load the archive options dialog, set the radio/checkbox items to the
+ * appropriate values, and update the archive hierarchy example.
+ */
+function onLoadArchiveOptions() {
+ // extract the account
+ gIdentity = window.arguments[0].identity;
+
+ let granularity = document.getElementById("archiveGranularity");
+ granularity.selectedIndex = gIdentity.archiveGranularity;
+ granularity.addEventListener("command", updateArchiveExample);
+
+ let kfs = document.getElementById("archiveKeepFolderStructure");
+ kfs.checked = gIdentity.archiveKeepFolderStructure;
+ kfs.addEventListener("command", updateArchiveExample);
+
+ updateArchiveExample();
+}
+
+/**
+ * Save the archive settings to the current identity.
+ */
+function onAcceptArchiveOptions() {
+ gIdentity.archiveGranularity =
+ document.getElementById("archiveGranularity").selectedIndex;
+ gIdentity.archiveKeepFolderStructure = document.getElementById(
+ "archiveKeepFolderStructure"
+ ).checked;
+}
+
+/**
+ * Update the example tree to show what the current options would look like.
+ */
+function updateArchiveExample() {
+ let granularity = document.getElementById("archiveGranularity").selectedIndex;
+ let kfs = document.getElementById("archiveKeepFolderStructure").checked;
+ let hierarchy = [
+ document.getElementsByClassName("root"),
+ document.getElementsByClassName("year"),
+ document.getElementsByClassName("month"),
+ ];
+
+ // First, show/hide the appropriate levels in the hierarchy and turn the
+ // necessary items into containers.
+ for (let i = 0; i < hierarchy.length; i++) {
+ for (let j = 0; j < hierarchy[i].length; j++) {
+ hierarchy[i][j].setAttribute("container", granularity > i);
+ hierarchy[i][j].setAttribute("open", granularity > i);
+ hierarchy[i][j].hidden = granularity < i;
+ }
+ }
+
+ // Next, handle the "keep folder structures" case by moving a tree item around
+ // and making sure its parent is a container.
+ let folders = document.getElementById("folders");
+ folders.hidden = !kfs;
+ if (kfs) {
+ let parent = hierarchy[granularity][0];
+ parent.setAttribute("container", true);
+ parent.setAttribute("open", true);
+
+ let treechildren = parent.children[1];
+ treechildren.appendChild(folders);
+ }
+}
diff --git a/comm/mailnews/base/prefs/content/am-archiveoptions.xhtml b/comm/mailnews/base/prefs/content/am-archiveoptions.xhtml
new file mode 100644
index 0000000000..e34de7a420
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-archiveoptions.xhtml
@@ -0,0 +1,136 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-archiveoptions.dtd">
+<html
+ id="archiveOptions"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ style="width: 500px"
+ scrolling="false"
+>
+ <head>
+ <title>&dialogTitle.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-archiveoptions.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog style="width: 100vw; height: 100vh">
+ <vbox flex="1">
+ <label>&archiveGranularityPrefix.label;</label>
+ <radiogroup id="archiveGranularity">
+ <radio
+ label="&archiveFlat.label;"
+ accesskey="&archiveFlat.accesskey;"
+ class="indent"
+ />
+ <radio
+ label="&archiveYearly.label;"
+ accesskey="&archiveYearly.accesskey;"
+ class="indent"
+ />
+ <radio
+ label="&archiveMonthly.label;"
+ accesskey="&archiveMonthly.accesskey;"
+ class="indent"
+ />
+ </radiogroup>
+ <checkbox
+ id="archiveKeepFolderStructure"
+ label="&keepFolderStructure.label;"
+ accesskey="&keepFolderStructure.accesskey;"
+ />
+
+ <html:div>
+ <html:fieldset flex="1" style="margin-block-end: 5px">
+ <html:legend>&archiveExample.label;</html:legend>
+ <hbox flex="1">
+ <tree
+ id="archiveTree"
+ hidecolumnpicker="true"
+ disabled="true"
+ flex="1"
+ style="min-height: 8em"
+ >
+ <treecols>
+ <treecol
+ id="folderNameCol"
+ primary="true"
+ hideheader="true"
+ style="flex: 1 auto"
+ />
+ </treecols>
+ <treechildren>
+ <treeitem class="root">
+ <treerow>
+ <treecell
+ properties="specialFolder-Archive"
+ label="&archiveFolderName.label;"
+ />
+ </treerow>
+ <treechildren>
+ <treeitem id="folders">
+ <treerow>
+ <treecell label="&inboxFolderName.label;" />
+ </treerow>
+ </treeitem>
+ <treeitem class="year">
+ <treerow>
+ <treecell label="2020" />
+ </treerow>
+ <treechildren>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2020-11" />
+ </treerow>
+ <treechildren />
+ </treeitem>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2020-12" />
+ </treerow>
+ <treechildren />
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem class="year">
+ <treerow>
+ <treecell label="2021" />
+ </treerow>
+ <treechildren>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2021-01" />
+ </treerow>
+ <treechildren />
+ </treeitem>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2021-02" />
+ </treerow>
+ <treechildren />
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </tree>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+ </vbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-copies.inc.xhtml b/comm/mailnews/base/prefs/content/am-copies.inc.xhtml
new file mode 100644
index 0000000000..75834fd60a
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-copies.inc.xhtml
@@ -0,0 +1,308 @@
+# 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/.
+
+ <vbox id="copiesAndFolders" flex="1">
+
+ <linkset>
+ <html:link rel="localization" href="messenger/preferences/am-copies.ftl"/>
+ </linkset>
+
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+
+ <label id="identity.fccFolder" hidden="true" wsm_persist="true"
+ pref="true" preftype="wstring" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_folder"/>
+ <label id="identity.draftFolder" hidden="true" wsm_persist="true"
+ pref="true" preftype="wstring" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.draft_folder"/>
+ <label id="identity.archiveFolder" hidden="true" wsm_persist="true"
+ pref="true" preftype="wstring" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archive_folder"/>
+ <label id="identity.stationeryFolder" hidden="true" wsm_persist="true"
+ pref="true" preftype="wstring" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.stationary_folder"/>
+ <label id="identity.email" hidden="true" wsm_persist="true"/>
+ <label id="identity.fccFolderPickerMode" hidden="true" wsm_persist="true"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_folder_picker_mode"/>
+ <label id="identity.draftsFolderPickerMode" hidden="true" wsm_persist="true"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.drafts_folder_picker_mode"/>
+ <label id="identity.archivesFolderPickerMode" hidden="true" wsm_persist="true"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archives_folder_picker_mode"/>
+ <label id="identity.tmplFolderPickerMode" hidden="true" wsm_persist="true"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.tmpl_folder_picker_mode"/>
+ <html:div>
+ <html:fieldset id="copiesGroup">
+ <html:legend>&sendingPrefix.label;</html:legend>
+
+ <hbox align="center">
+ <checkbox id="identity.doFcc" wsm_persist="true" label="&fccMailFolder.label;"
+ accesskey="&fccMailFolder.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc"
+ oncommand="setupFccItems();"/>
+ </hbox>
+ <radiogroup id="doFcc" aria-labelledby="identity.doFcc">
+ <hbox class="specialFolderPickerGrid">
+ <vbox>
+ <hbox flex="1" align="center">
+ <radio id="fcc_selectAccount"
+ class="depends-on-do-fcc"
+ value="0" label="&sentFolderOn.label;"
+ accesskey="&sentFolderOn.accesskey;"
+ oncommand="setPickersState('msgFccAccountPicker', 'msgFccFolderPicker', event)"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio id="fcc_selectFolder"
+ class="depends-on-do-fcc"
+ value="1" label="&sentInOtherFolder.label;"
+ accesskey="&sentInOtherFolder.accesskey;"
+ oncommand="setPickersState('msgFccFolderPicker', 'msgFccAccountPicker', event)"/>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <menulist id="msgFccAccountPicker"
+ class="folderMenuItem depends-on-do-fcc"
+ aria-labelledby="fcc_selectAccount">
+ <menupopup is="folder-menupopup" id="msgFccAccountPopup"
+ mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('fcc', 'Account', event)"/>
+ </menulist>
+ <menulist id="msgFccFolderPicker"
+ class="folderMenuItem depends-on-do-fcc"
+ aria-labelledby="fcc_selectFolder"
+ displayformat="verbose">
+ <menupopup is="folder-menupopup" id="msgFccFolderPopup"
+ mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('fcc', 'Folder', event)"/>
+ </menulist>
+ </vbox>
+ </hbox>
+ </radiogroup>
+
+ <hbox align="center" class="fccReplyFollowsParent" hidable="true" hidefor="nntp,rss">
+ <checkbox id="identity.fccReplyFollowsParent" wsm_persist="true"
+ class="depends-on-do-fcc"
+ label="&fccReplyFollowsParent.label;"
+ accesskey="&fccReplyFollowsParent.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_reply_follows_parent"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox>
+ <vbox>
+ <hbox flex="1" align="center">
+ <checkbox id="identity.doCc" wsm_persist="true" label="&ccAddress.label;"
+ accesskey="&ccAddress.accesskey;"
+ control="identity.doCcList"
+ oncommand="identityDoCcBccOnCommand(event);"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.doCc"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <checkbox id="identity.doBcc" wsm_persist="true" label="&bccAddress.label;"
+ accesskey="&bccAddress.accesskey;"
+ control="identity.doBccList"
+ oncommand="identityDoCcBccOnCommand(event);"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.doBcc"/>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <hbox flex="1" class="input-container" align="center">
+ <html:input id="identity.doCcList" wsm_persist="true"
+ type="text"
+ aria-labelledby="identity.doCc"
+ prefstring="mail.identity.%identitykey%.doCcList"
+ class="uri-element input-inline"
+ placeholder="&ccAddressList.placeholder;"
+ onblur="identityDoCcBccOnBlur(event);"/>
+ </hbox>
+ <hbox flex="1" class="input-container" align="center">
+ <html:input id="identity.doBccList" wsm_persist="true"
+ type="text"
+ aria-labelledby="identity.doBcc"
+ prefstring="mail.identity.%identitykey%.doBccList"
+ class="uri-element input-inline"
+ placeholder="&bccAddressList.placeholder;"
+ onblur="identityDoCcBccOnBlur(event);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ <description class="indent tip-caption"
+ data-l10n-id="account-prefs-show-address-row-description"/>
+ </html:fieldset>
+ </html:div>
+
+ <html:div>
+ <html:fieldset id="archivesGroup">
+ <html:legend>&archivesTitle.label;</html:legend>
+
+ <hbox pack="start">
+ <checkbox id="identity.archiveEnabled" wsm_persist="true"
+ label="&keepArchives.label;"
+ accesskey="&keepArchives.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archive_enabled"
+ oncommand="setupArchiveItems();"/>
+ </hbox>
+
+ <radiogroup id="messageArchives">
+ <hbox class="specialFolderPickerGrid">
+ <vbox>
+ <hbox flex="1" align="center">
+ <radio id="archive_selectAccount"
+ class="depends-on-archive"
+ value="0" label="&archivesFolderOn.label;"
+ accesskey="&archivesFolderOn.accesskey;"
+ oncommand="setPickersState('msgArchivesAccountPicker', 'msgArchivesFolderPicker', event)"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio id="archive_selectFolder"
+ class="depends-on-archive"
+ value="1" label="&archiveInOtherFolder.label;"
+ accesskey="&archiveInOtherFolder.accesskey;"
+ oncommand="setPickersState('msgArchivesFolderPicker', 'msgArchivesAccountPicker', event)"/>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <menulist id="msgArchivesAccountPicker"
+ class="folderMenuItem depends-on-archive"
+ aria-labelledby="archive_selectAccount">
+ <menupopup is="folder-menupopup" id="msgArchivesAccountPopup"
+ mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('archive', 'Account', event)"/>
+ </menulist>
+ <menulist id="msgArchivesFolderPicker"
+ class="folderMenuItem depends-on-archive"
+ aria-labelledby="archive_selectFolder"
+ displayformat="verbose">
+ <menupopup is="folder-menupopup" id="msgArchivesFolderPopup"
+ mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('archive', 'Folder', event)"/>
+ </menulist>
+ </vbox>
+ </hbox>
+ </radiogroup>
+ <hbox pack="end">
+ <button id="archiveHierarchyButton"
+ class="depends-on-archive"
+ label="&archiveHierarchyButton.label;"
+ accesskey="&archiveHierarchyButton.accesskey;"
+ oncommand="ChangeArchiveHierarchy();"/>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <html:div>
+ <html:fieldset id="foldersGroup">
+ <html:legend>&specialFolders.label;</html:legend>
+
+ <hbox align="center">
+ <label value="&keepDrafts2.label;" control="messageDrafts"/>
+ </hbox>
+
+ <radiogroup id="messageDrafts">
+ <hbox class="specialFolderPickerGrid">
+ <vbox>
+ <hbox flex="1" align="center">
+ <radio id="draft_selectAccount"
+ oncommand="setPickersState('msgDraftsAccountPicker', 'msgDraftsFolderPicker', event)"
+ value="0" label="&draftsFolderOn.label;"
+ accesskey="&draftsFolderOn.accesskey;"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio id="draft_selectFolder"
+ oncommand="setPickersState('msgDraftsFolderPicker', 'msgDraftsAccountPicker', event)"
+ value="1" label="&draftInOtherFolder.label;"
+ accesskey="&draftInOtherFolder.accesskey;"/>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <menulist id="msgDraftsAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="draft_selectAccount">
+ <menupopup is="folder-menupopup" id="msgDraftAccountPopup"
+ mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('draft', 'Account', event)"/>
+ </menulist>
+ <menulist id="msgDraftsFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="draft_selectFolder"
+ displayformat="verbose">
+ <menupopup is="folder-menupopup" id="msgDraftFolderPopup"
+ mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('draft', 'Folder', event)"/>
+ </menulist>
+ </vbox>
+ </hbox>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <label value="&keepTemplates.label;" control="messageTemplates"/>
+ </hbox>
+
+ <radiogroup id="messageTemplates">
+ <hbox class="specialFolderPickerGrid">
+ <vbox>
+ <hbox flex="1" align="center">
+ <radio id="tmpl_selectAccount"
+ oncommand="setPickersState('msgStationeryAccountPicker', 'msgStationeryFolderPicker', event)"
+ value="0" label="&templatesFolderOn.label;"
+ accesskey="&templatesFolderOn.accesskey;"/>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio id="tmpl_selectFolder"
+ oncommand="setPickersState('msgStationeryFolderPicker', 'msgStationeryAccountPicker', event)"
+ value="1" label="&templateInOtherFolder.label;"
+ accesskey="&templateInOtherFolder.accesskey;"/>
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <menulist id="msgStationeryAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="tmpl_selectAccount">
+ <menupopup is="folder-menupopup" id="msgFccAccountPopup"
+ mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('tmpl', 'Account', event)"/>
+ </menulist>
+ <menulist id="msgStationeryFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="tmpl_selectFolder"
+ displayformat="verbose">
+ <menupopup is="folder-menupopup" id="msgTemplFolderPopup"
+ mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('tmpl', 'Folder', event)"/>
+ </menulist>
+ </vbox>
+ </hbox>
+ </radiogroup>
+ <hbox align="center">
+ <checkbox id="identity.showSaveMsgDlg" wsm_persist="true" label="&saveMessageDlg.label;"
+ accesskey="&saveMessageDlg.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.showSaveMsgDlg"/>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+ </vbox>
diff --git a/comm/mailnews/base/prefs/content/am-copies.js b/comm/mailnews/base/prefs/content/am-copies.js
new file mode 100644
index 0000000000..f848bab0c0
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-copies.js
@@ -0,0 +1,555 @@
+/* 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 am-prefs.js */
+/* import-globals-from amUtils.js */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var gFccRadioElemChoice,
+ gDraftsRadioElemChoice,
+ gArchivesRadioElemChoice,
+ gTmplRadioElemChoice;
+var gFccRadioElemChoiceLocked,
+ gDraftsRadioElemChoiceLocked,
+ gArchivesRadioElemChoiceLocked,
+ gTmplRadioElemChoiceLocked;
+var gDefaultPickerMode = "1";
+
+var gFccFolderWithDelim,
+ gDraftsFolderWithDelim,
+ gArchivesFolderWithDelim,
+ gTemplatesFolderWithDelim;
+var gAccount;
+var gCurrentServerId;
+
+function onPreInit(account, accountValues) {
+ gAccount = account;
+ var type = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "type",
+ null,
+ false
+ );
+ hideShowControls(type);
+}
+
+/*
+ * Set the global radio element choices and initialize folder/account pickers.
+ * Also, initialize other UI elements (cc, bcc, fcc picker controller checkboxes).
+ */
+function onInit(aPageId, aServerId) {
+ gCurrentServerId = aServerId;
+ onInitCopiesAndFolders();
+}
+
+function onInitCopiesAndFolders() {
+ SetGlobalRadioElemChoices();
+
+ SetFolderDisplay(
+ gFccRadioElemChoice,
+ gFccRadioElemChoiceLocked,
+ "fcc",
+ "msgFccAccountPicker",
+ "identity.fccFolder",
+ "msgFccFolderPicker"
+ );
+
+ SetFolderDisplay(
+ gArchivesRadioElemChoice,
+ gArchivesRadioElemChoiceLocked,
+ "archive",
+ "msgArchivesAccountPicker",
+ "identity.archiveFolder",
+ "msgArchivesFolderPicker"
+ );
+
+ SetFolderDisplay(
+ gDraftsRadioElemChoice,
+ gDraftsRadioElemChoiceLocked,
+ "draft",
+ "msgDraftsAccountPicker",
+ "identity.draftFolder",
+ "msgDraftsFolderPicker"
+ );
+
+ SetFolderDisplay(
+ gTmplRadioElemChoice,
+ gTmplRadioElemChoiceLocked,
+ "tmpl",
+ "msgStationeryAccountPicker",
+ "identity.stationeryFolder",
+ "msgStationeryFolderPicker"
+ );
+
+ setupDoCcBccItems("identity.doCc", "identity.doCcList");
+ setupDoCcBccItems("identity.doBcc", "identity.doBccList");
+ setupFccItems();
+ setupArchiveItems();
+
+ SetSpecialFolderNamesWithDelims();
+}
+
+// Initialize the picker mode choices (account/folder picker) into global vars
+function SetGlobalRadioElemChoices() {
+ var pickerModeElement = document.getElementById(
+ "identity.fccFolderPickerMode"
+ );
+ gFccRadioElemChoice = pickerModeElement.getAttribute("value");
+ gFccRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gFccRadioElemChoice) {
+ gFccRadioElemChoice = gDefaultPickerMode;
+ }
+
+ pickerModeElement = document.getElementById(
+ "identity.archivesFolderPickerMode"
+ );
+ gArchivesRadioElemChoice = pickerModeElement.getAttribute("value");
+ gArchivesRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gArchivesRadioElemChoice) {
+ gArchivesRadioElemChoice = gDefaultPickerMode;
+ }
+
+ pickerModeElement = document.getElementById(
+ "identity.draftsFolderPickerMode"
+ );
+ gDraftsRadioElemChoice = pickerModeElement.getAttribute("value");
+ gDraftsRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gDraftsRadioElemChoice) {
+ gDraftsRadioElemChoice = gDefaultPickerMode;
+ }
+
+ pickerModeElement = document.getElementById("identity.tmplFolderPickerMode");
+ gTmplRadioElemChoice = pickerModeElement.getAttribute("value");
+ gTmplRadioElemChoiceLocked = pickerModeElement.getAttribute("disabled");
+ if (!gTmplRadioElemChoice) {
+ gTmplRadioElemChoice = gDefaultPickerMode;
+ }
+}
+
+/*
+ * Set Account and Folder elements based on the values read from
+ * preferences file. Default picker mode, if none specified at this stage, is
+ * set to 1 i.e., Other picker displaying the folder value read from the
+ * preferences file.
+ */
+function SetFolderDisplay(
+ pickerMode,
+ disableMode,
+ radioElemPrefix,
+ accountPickerId,
+ folderPickedField,
+ folderPickerId
+) {
+ if (!pickerMode) {
+ pickerMode = gDefaultPickerMode;
+ }
+
+ var selectAccountRadioId = radioElemPrefix + "_selectAccount";
+ var selectAccountRadioElem = document.getElementById(selectAccountRadioId);
+ var selectFolderRadioId = radioElemPrefix + "_selectFolder";
+ var selectFolderRadioElem = document.getElementById(selectFolderRadioId);
+ var accountPicker = document.getElementById(accountPickerId);
+ var folderPicker = document.getElementById(folderPickerId);
+ var rg = selectAccountRadioElem.radioGroup;
+ var folderPickedElement = document.getElementById(folderPickedField);
+ var uri = folderPickedElement.getAttribute("value");
+ // Get message folder from the given uri.
+ // There is no need to check for the existence of special folders as
+ // these folders are created on demand at runtime in case of imap accounts.
+ // For POP3 accounts, special folders are created at the account creation time.
+ var msgFolder = MailUtils.getOrCreateFolder(uri);
+ InitFolderDisplay(msgFolder.server.rootFolder, accountPicker);
+ InitFolderDisplay(msgFolder, folderPicker);
+
+ switch (pickerMode) {
+ case "0":
+ rg.selectedItem = selectAccountRadioElem;
+ SetPickerEnabling(accountPickerId, folderPickerId);
+ break;
+
+ case "1":
+ rg.selectedItem = selectFolderRadioElem;
+ SetPickerEnabling(folderPickerId, accountPickerId);
+ break;
+
+ default:
+ dump("Error in setting initial folder display on pickers\n");
+ break;
+ }
+
+ // Check to see if we need to lock page elements. Disable radio buttons
+ // and account/folder pickers when locked.
+ if (disableMode) {
+ selectAccountRadioElem.setAttribute("disabled", "true");
+ selectFolderRadioElem.setAttribute("disabled", "true");
+ accountPicker.setAttribute("disabled", "true");
+ folderPicker.setAttribute("disabled", "true");
+ }
+}
+
+// Initialize the folder display based on prefs values
+function InitFolderDisplay(folder, folderPicker) {
+ folderPicker.menupopup.selectFolder(folder);
+ folderPicker.folder = folder;
+}
+
+/**
+ * Capture any menulist changes and update the folder property.
+ *
+ * @param {string} aGroup - The prefix for the menulist we're handling
+ * (e.g. "drafts")
+ * @param {"Account"|"Folder"} aType - "Account" for the account picker or
+ * "Folder" for the folder picker.
+ * @param {Event} aEvent - The event that we're responding to.
+ */
+function noteSelectionChange(aGroup, aType, aEvent) {
+ var checkedElem = document.getElementById(aGroup + "_select" + aType);
+ var folder = aEvent.target._folder;
+ var modeValue = checkedElem.value;
+ var radioGroup = checkedElem.radioGroup.getAttribute("id");
+ var picker;
+
+ switch (radioGroup) {
+ case "doFcc":
+ gFccRadioElemChoice = modeValue;
+ picker = document.getElementById("msgFcc" + aType + "Picker");
+ break;
+
+ case "messageArchives":
+ gArchivesRadioElemChoice = modeValue;
+ picker = document.getElementById("msgArchives" + aType + "Picker");
+ updateArchiveHierarchyButton(folder);
+ break;
+
+ case "messageDrafts":
+ gDraftsRadioElemChoice = modeValue;
+ picker = document.getElementById("msgDrafts" + aType + "Picker");
+ break;
+
+ case "messageTemplates":
+ gTmplRadioElemChoice = modeValue;
+ picker = document.getElementById("msgStationery" + aType + "Picker");
+ break;
+ }
+
+ picker.folder = folder;
+ picker.menupopup.selectFolder(folder);
+}
+
+// Need to append special folders when account picker is selected.
+// Create a set of global special folder vars to be suffixed to the
+// server URI of the selected account.
+function SetSpecialFolderNamesWithDelims() {
+ var folderDelim = "/";
+ /* we use internal names known to everyone like "Sent", "Templates" and "Drafts" */
+
+ gFccFolderWithDelim = folderDelim + "Sent";
+ gArchivesFolderWithDelim = folderDelim + "Archives";
+ gDraftsFolderWithDelim = folderDelim + "Drafts";
+ gTemplatesFolderWithDelim = folderDelim + "Templates";
+}
+
+// Save all changes on this page
+function onSave() {
+ onSaveCopiesAndFolders();
+}
+
+function onSaveCopiesAndFolders() {
+ SaveFolderSettings(
+ gFccRadioElemChoice,
+ "doFcc",
+ gFccFolderWithDelim,
+ "msgFccAccountPicker",
+ "msgFccFolderPicker",
+ "identity.fccFolder",
+ "identity.fccFolderPickerMode"
+ );
+
+ SaveFolderSettings(
+ gArchivesRadioElemChoice,
+ "messageArchives",
+ gArchivesFolderWithDelim,
+ "msgArchivesAccountPicker",
+ "msgArchivesFolderPicker",
+ "identity.archiveFolder",
+ "identity.archivesFolderPickerMode"
+ );
+
+ SaveFolderSettings(
+ gDraftsRadioElemChoice,
+ "messageDrafts",
+ gDraftsFolderWithDelim,
+ "msgDraftsAccountPicker",
+ "msgDraftsFolderPicker",
+ "identity.draftFolder",
+ "identity.draftsFolderPickerMode"
+ );
+
+ SaveFolderSettings(
+ gTmplRadioElemChoice,
+ "messageTemplates",
+ gTemplatesFolderWithDelim,
+ "msgStationeryAccountPicker",
+ "msgStationeryFolderPicker",
+ "identity.stationeryFolder",
+ "identity.tmplFolderPickerMode"
+ );
+}
+
+// Save folder settings and radio element choices
+function SaveFolderSettings(
+ radioElemChoice,
+ radioGroupId,
+ folderSuffix,
+ accountPickerId,
+ folderPickerId,
+ folderElementId,
+ folderPickerModeId
+) {
+ var formElement = document.getElementById(folderElementId);
+ var uri;
+
+ if (
+ radioElemChoice == "0" ||
+ !document.getElementById(folderPickerId).value
+ ) {
+ // Default or revert to default if no folder chosen.
+ radioElemChoice = "0";
+ uri = document.getElementById(accountPickerId).folder.URI;
+ if (uri) {
+ // Create Folder URI.
+ uri = uri + folderSuffix;
+ }
+ } else if (radioElemChoice == "1") {
+ uri = document.getElementById(folderPickerId).folder.URI;
+ } else {
+ dump("Error saving folder preferences.\n");
+ return;
+ }
+
+ formElement.setAttribute("value", uri);
+
+ formElement = document.getElementById(folderPickerModeId);
+ formElement.setAttribute("value", radioElemChoice);
+}
+
+// Check the Fcc Self item and setup associated picker state
+function setupFccItems() {
+ let checked = document.getElementById("identity.doFcc").checked;
+ document.querySelectorAll(".depends-on-do-fcc").forEach(e => {
+ if (checked) {
+ e.removeAttribute("disabled");
+ } else {
+ e.setAttribute("disabled", "true");
+ }
+ });
+ if (!checked) {
+ return;
+ }
+
+ switch (gFccRadioElemChoice) {
+ case "0":
+ if (!gFccRadioElemChoiceLocked) {
+ SetPickerEnabling("msgFccAccountPicker", "msgFccFolderPicker");
+ }
+ SetRadioButtons("fcc_selectAccount", "fcc_selectFolder");
+ break;
+
+ case "1":
+ if (!gFccRadioElemChoiceLocked) {
+ SetPickerEnabling("msgFccFolderPicker", "msgFccAccountPicker");
+ }
+ SetRadioButtons("fcc_selectFolder", "fcc_selectAccount");
+ break;
+
+ default:
+ dump("Error in setting Fcc elements.\n");
+ break;
+ }
+}
+
+/**
+ * Handle the initial status and value of the Auto-Cc/Bcc text input fields.
+ *
+ * @param {string} checkboxId - The ID of an Auto-Cc/Bcc checkbox.
+ * @param {string} inputId - The ID of an Auto-Cc/Bcc text input element.
+ */
+function setupDoCcBccItems(checkboxId, inputId) {
+ // Enable address input according to the status of the checkbox.
+ let input = document.getElementById(inputId);
+ input.disabled = !document.getElementById(checkboxId).checked;
+ // Safeguard against space-padded address list to ensure list visibility.
+ input.value = input.value.trim();
+}
+
+/**
+ * Handle the command event of the Auto-Cc/Bcc checkboxes.
+ * Disable the respective text input element if the checkbox is not checked, and
+ * handle the default value of the input when the checkbox is toggled.
+ *
+ * @param {Event} event - The command event of the checkbox.
+ */
+function identityDoCcBccOnCommand(event) {
+ let checkbox = event.target;
+ let checked = checkbox.checked;
+ // For checkboxes #identity.doCc and #identity.doBcc, get the corresponding
+ // inputs: #identity.doCcList and #identity.doBccList.
+ let input = document.getElementById(`${checkbox.id}List`);
+ input.disabled = !checked;
+
+ // User toggled checkbox.
+ let identityEmailAddress = document.getElementById("identity.email").value;
+ if (checked) {
+ // If user checks the checkbox and there's no address, default to identity's
+ // email address.
+ if (!input.value) {
+ input.value = identityEmailAddress;
+ }
+ input.select();
+ return;
+ }
+
+ if (input.value == identityEmailAddress) {
+ // If user unchecks checkbox and the input has default address, clear input.
+ input.value = "";
+ }
+}
+
+/**
+ * Handle the blur event of the Auto-Cc/Bcc checkboxes.
+ *
+ * @param {Event} event - The blur event of the checkbox.
+ */
+function identityDoCcBccOnBlur(event) {
+ let input = event.target;
+ // Safeguard against space-padded address list to ensure list visibility.
+ input.value = input.value.trim();
+}
+
+// Enable and disable pickers based on the radio element clicked
+function SetPickerEnabling(enablePickerId, disablePickerId) {
+ var activePicker = document.getElementById(enablePickerId);
+ activePicker.removeAttribute("disabled");
+
+ var inactivePicker = document.getElementById(disablePickerId);
+ inactivePicker.setAttribute("disabled", "true");
+}
+
+// Set radio element choices and picker states
+function setPickersState(enablePickerId, disablePickerId, event) {
+ SetPickerEnabling(enablePickerId, disablePickerId);
+
+ var radioElemValue = event.target.value;
+
+ switch (event.target.id) {
+ case "fcc_selectAccount":
+ case "fcc_selectFolder":
+ gFccRadioElemChoice = radioElemValue;
+ break;
+ case "archive_selectAccount":
+ case "archive_selectFolder":
+ gArchivesRadioElemChoice = radioElemValue;
+ updateArchiveHierarchyButton(
+ document.getElementById(enablePickerId).folder
+ );
+ break;
+ case "draft_selectAccount":
+ case "draft_selectFolder":
+ gDraftsRadioElemChoice = radioElemValue;
+ break;
+ case "tmpl_selectAccount":
+ case "tmpl_selectFolder":
+ gTmplRadioElemChoice = radioElemValue;
+ break;
+ default:
+ dump("Error in setting picker state.\n");
+ }
+}
+
+// This routine is to restore the correct radio element
+// state when the fcc self checkbox broadcasts the change
+function SetRadioButtons(selectPickerId, unselectPickerId) {
+ var activeRadioElem = document.getElementById(selectPickerId);
+ activeRadioElem.radioGroup.selectedItem = activeRadioElem;
+}
+
+/**
+ * Enable/disable the archive hierarchy button depending on what folder is
+ * currently selected (Gmail IMAP folders should have the button disabled, since
+ * changing the archive hierarchy does nothing there.
+ *
+ * @param {nsIMsgFolder} archiveFolder - The currently-selected folder to store
+ * archives in
+ */
+function updateArchiveHierarchyButton(archiveFolder) {
+ let isGmailImap =
+ archiveFolder.server.type == "imap" &&
+ archiveFolder.server.QueryInterface(Ci.nsIImapIncomingServer).isGMailServer;
+ document.getElementById("archiveHierarchyButton").disabled = isGmailImap;
+}
+
+/**
+ * Enable or disable (as appropriate) the controls for setting archive options
+ */
+function setupArchiveItems() {
+ let checked = document.getElementById("identity.archiveEnabled").checked;
+ document.querySelectorAll(".depends-on-archive").forEach(e => {
+ if (checked) {
+ e.removeAttribute("disabled");
+ } else {
+ e.setAttribute("disabled", "true");
+ }
+ });
+ if (!checked) {
+ return;
+ }
+
+ switch (gArchivesRadioElemChoice) {
+ case "0":
+ if (!gArchivesRadioElemChoiceLocked) {
+ SetPickerEnabling(
+ "msgArchivesAccountPicker",
+ "msgArchivesFolderPicker"
+ );
+ }
+ SetRadioButtons("archive_selectAccount", "archive_selectFolder");
+ updateArchiveHierarchyButton(
+ document.getElementById("msgArchivesAccountPicker").folder
+ );
+ break;
+
+ case "1":
+ if (!gArchivesRadioElemChoiceLocked) {
+ SetPickerEnabling(
+ "msgArchivesFolderPicker",
+ "msgArchivesAccountPicker"
+ );
+ }
+ SetRadioButtons("archive_selectFolder", "archive_selectAccount");
+ updateArchiveHierarchyButton(
+ document.getElementById("msgArchivesFolderPicker").folder
+ );
+ break;
+
+ default:
+ dump("Error in setting Archive elements.\n");
+ }
+}
+
+/**
+ * Open a dialog to edit the folder hierarchy used when archiving messages.
+ */
+function ChangeArchiveHierarchy() {
+ let identity = parent.gIdentity || parent.getCurrentAccount().defaultIdentity;
+ let arg = { identity };
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/am-archiveoptions.xhtml",
+ undefined,
+ arg
+ );
+}
diff --git a/comm/mailnews/base/prefs/content/am-copies.xhtml b/comm/mailnews/base/prefs/content/am-copies.xhtml
new file mode 100644
index 0000000000..c142cb8eb5
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-copies.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-copies.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&copyAndFolderTitle.label;</title>
+ <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script>
+ <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-copies.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => { parent.onPanelLoaded('am-copies.xhtml'); });
+ </script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <vbox id="containerBox" flex="1">
+ <hbox class="dialogheader">
+ <label class="dialogheader-title" value="&copyAndFolderTitle.label;"/>
+ </hbox>
+#include am-copies.inc.xhtml
+ </vbox>
+</html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-identities-list.js b/comm/mailnews/base/prefs/content/am-identities-list.js
new file mode 100644
index 0000000000..02d20e1434
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-identities-list.js
@@ -0,0 +1,206 @@
+/* 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/. */
+
+var { Gloda } = ChromeUtils.import("resource:///modules/gloda/Gloda.jsm");
+
+var gIdentityListBox; // the root <richlistbox> node
+var gAddButton;
+var gEditButton;
+var gSetDefaultButton;
+var gDeleteButton;
+
+var gAccount = null; // the account we are showing the identities for
+
+window.addEventListener("DOMContentLoaded", onLoad);
+
+document.addEventListener("dialogaccept", onOk);
+document.addEventListener("dialogcancel", onOk);
+
+function onLoad() {
+ gIdentityListBox = document.getElementById("identitiesList");
+ gAddButton = document.getElementById("cmd_add");
+ gEditButton = document.getElementById("cmd_edit");
+ gSetDefaultButton = document.getElementById("cmd_default");
+ gDeleteButton = document.getElementById("cmd_delete");
+
+ // extract the account
+ gAccount = window.arguments[0].account;
+
+ var accountName = window.arguments[0].accountName;
+ document.title = document
+ .getElementById("bundle_prefs")
+ .getFormattedString("identity-list-title", [accountName]);
+
+ refreshIdentityList(0);
+}
+
+/**
+ * Rebuilds the listbox holding the list of identities.
+ *
+ * @param {number} aSelectIndex - Attempt to select the identity with this index.
+ */
+function refreshIdentityList(aSelectIndex) {
+ // Remove all children.
+ while (gIdentityListBox.hasChildNodes()) {
+ gIdentityListBox.lastChild.remove();
+ }
+
+ // Build the list from the identities array.
+ for (let identity of gAccount.identities) {
+ if (identity.valid) {
+ let label = document.createXULElement("label");
+ label.setAttribute("value", identity.identityName);
+
+ let listitem = document.createXULElement("richlistitem");
+ listitem.appendChild(label);
+ listitem.setAttribute("key", identity.key);
+ gIdentityListBox.appendChild(listitem);
+ }
+ }
+
+ // Ensure one identity is always selected.
+ if (!aSelectIndex || aSelectIndex < 0) {
+ aSelectIndex = 0;
+ } else if (aSelectIndex >= gIdentityListBox.itemCount) {
+ aSelectIndex = gIdentityListBox.itemCount - 1;
+ }
+
+ // This also fires the onselect event, which in turn calls updateButtons().
+ gIdentityListBox.selectedIndex = aSelectIndex;
+}
+
+/**
+ * Opens the identity editor dialog.
+ *
+ * @param {nsIMsgIdentity} identity - The identity (if any) to load in the dialog.
+ */
+function openIdentityEditor(identity) {
+ let args = { identity, account: gAccount, result: false };
+
+ let indexToSelect = identity
+ ? gIdentityListBox.selectedIndex
+ : gIdentityListBox.itemCount;
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/am-identity-edit.xhtml",
+ { closingCallback: onCloseIdentity },
+ args
+ );
+
+ function onCloseIdentity() {
+ if (args.result) {
+ refreshIdentityList(indexToSelect);
+ }
+ }
+}
+
+function getSelectedIdentity() {
+ if (gIdentityListBox.selectedItems.length != 1) {
+ return null;
+ }
+
+ let identityKey = gIdentityListBox.selectedItems[0].getAttribute("key");
+ return (
+ gAccount.identities.find(id => id.valid && id.key == identityKey) || null
+ );
+}
+
+function onEdit(event) {
+ var id = getSelectedIdentity();
+ openIdentityEditor(id);
+}
+
+/**
+ * Enable/disable buttons depending on number of identities and current selection.
+ */
+function updateButtons() {
+ // In this listbox there should always be one item selected.
+ if (
+ gIdentityListBox.selectedItems.length != 1 ||
+ gIdentityListBox.itemCount == 0
+ ) {
+ // But in case this is not met (e.g. there is no identity for some reason,
+ // or the list is being rebuilt), disable all buttons.
+ gEditButton.setAttribute("disabled", "true");
+ gDeleteButton.setAttribute("disabled", "true");
+ gSetDefaultButton.setAttribute("disabled", "true");
+ return;
+ }
+
+ gEditButton.setAttribute("disabled", "false");
+ gDeleteButton.setAttribute(
+ "disabled",
+ gIdentityListBox.itemCount <= 1 ? "true" : "false"
+ );
+ gSetDefaultButton.setAttribute(
+ "disabled",
+ gIdentityListBox.selectedIndex == 0 ? "true" : "false"
+ );
+ // The Add command is always enabled.
+}
+
+function onSetDefault(event) {
+ let identity = getSelectedIdentity();
+ if (!identity) {
+ return;
+ }
+
+ // If the first identity is selected, there is nothing to do.
+ if (gIdentityListBox.selectedIndex == 0) {
+ return;
+ }
+
+ gAccount.defaultIdentity = identity;
+ // Rebuilt the identity list and select the moved identity again.
+ refreshIdentityList(0);
+ // Update gloda's myContact with the new default identity.
+ Gloda._initMyIdentities();
+}
+
+function onDelete(event) {
+ if (gIdentityListBox.itemCount <= 1) {
+ // don't support deleting the last identity
+ return;
+ }
+
+ // get delete confirmation
+ let selectedIdentity = getSelectedIdentity();
+
+ let prefsBundle = document.getElementById("bundle_prefs");
+ let confirmTitle = prefsBundle.getFormattedString(
+ "identity-delete-confirm-title",
+ [window.arguments[0].accountName]
+ );
+ let confirmText = prefsBundle.getFormattedString("identity-delete-confirm", [
+ selectedIdentity.identityName,
+ ]);
+ let confirmButton = prefsBundle.getString("identity-delete-confirm-button");
+
+ if (
+ Services.prompt.confirmEx(
+ window,
+ confirmTitle,
+ confirmText,
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+ Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL,
+ confirmButton,
+ null,
+ null,
+ null,
+ {}
+ )
+ ) {
+ return;
+ }
+
+ let selectedItemIndex = gIdentityListBox.selectedIndex;
+
+ gAccount.removeIdentity(selectedIdentity);
+
+ refreshIdentityList(selectedItemIndex);
+}
+
+function onOk() {
+ window.arguments[0].result = true;
+}
diff --git a/comm/mailnews/base/prefs/content/am-identities-list.xhtml b/comm/mailnews/base/prefs/content/am-identities-list.xhtml
new file mode 100644
index 0000000000..243630bd75
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-identities-list.xhtml
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/shared/grid-layout.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-identities-list.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ style="min-width: 600px"
+ scrolling="false"
+>
+ <head>
+ <title><!-- identity-list-title --></title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-identities-list.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog
+ buttons="accept"
+ buttonlabelaccept="&identitiesListClose.label;"
+ buttonaccesskeyaccept="&identitiesListClose.accesskey;"
+ style="height: 100vh"
+ >
+ <stringbundle
+ id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"
+ />
+
+ <commandset>
+ <command id="cmd_add" oncommand="openIdentityEditor(null);" />
+ <command id="cmd_edit" oncommand="onEdit(event);" disabled="true" />
+ <command
+ id="cmd_default"
+ oncommand="onSetDefault(event);"
+ disabled="true"
+ />
+ <command id="cmd_delete" oncommand="onDelete(event);" disabled="true" />
+ </commandset>
+
+ <keyset>
+ <key keycode="VK_INSERT" command="cmd_add" />
+ <key keycode="VK_DELETE" command="cmd_delete" />
+ </keyset>
+
+ <label control="identitiesList">&identitiesListManageDesc.label;</label>
+
+ <separator class="thin" />
+
+ <html:div class="grid-two-column-auto-min" style="flex: 1">
+ <richlistbox
+ id="identitiesList"
+ ondblclick="onEdit(event);"
+ onselect="updateButtons();"
+ seltype="single"
+ flex="1"
+ style="min-height: 10em; max-height: 20em"
+ />
+
+ <html:div class="flex-content-column">
+ <button
+ id="addButton"
+ command="cmd_add"
+ label="&identitiesListAdd.label;"
+ accesskey="&identitiesListAdd.accesskey;"
+ />
+ <button
+ id="editButton"
+ disabled="true"
+ command="cmd_edit"
+ label="&identitiesListEdit.label;"
+ accesskey="&identitiesListEdit.accesskey;"
+ />
+ <button
+ id="setDefaultButton"
+ disabled="true"
+ command="cmd_default"
+ label="&identitiesListDefault.label;"
+ accesskey="&identitiesListDefault.accesskey;"
+ />
+ <button
+ id="deleteButton"
+ disabled="true"
+ command="cmd_delete"
+ label="&identitiesListDelete.label;"
+ accesskey="&identitiesListDelete.accesskey;"
+ />
+ </html:div>
+ </html:div>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-identity-edit.js b/comm/mailnews/base/prefs/content/am-identity-edit.js
new file mode 100644
index 0000000000..216edcaa53
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-identity-edit.js
@@ -0,0 +1,577 @@
+/* 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 am-addressing.js */
+/* import-globals-from am-copies.js */
+/* import-globals-from ../../../../mail/extensions/am-e2e/am-e2e.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gIdentity = null; // the identity we are editing (may be null for a new identity)
+var gAccount = null; // the account the identity is (or will be) associated with
+
+document.addEventListener("dialogaccept", onOk);
+
+function onLoadIdentityProperties() {
+ // extract the account
+ gIdentity = window.arguments[0].identity;
+ gAccount = window.arguments[0].account;
+ let prefBundle = document.getElementById("bundle_prefs");
+
+ if (gIdentity) {
+ let listName = gIdentity.identityName;
+ document.title = prefBundle.getFormattedString("identityDialogTitleEdit", [
+ listName,
+ ]);
+ } else {
+ document.title = prefBundle.getString("identityDialogTitleAdd");
+ }
+
+ loadSMTPServerList();
+
+ initIdentityValues(gIdentity);
+ initCopiesAndFolder(gIdentity);
+ initCompositionAndAddressing(gIdentity);
+ initE2EEncryption(gIdentity);
+
+ // E2E needs an email... hide until we have one.
+ document.getElementById("identityE2ETab").hidden = !gIdentity?.email;
+}
+
+// based on the values of gIdentity, initialize the identity fields we expose to the user
+function initIdentityValues(identity) {
+ function initSmtpServer(aServerKey) {
+ // Select a server in the SMTP server menulist by its key.
+ // The value of the identity.smtpServerKey is null when the
+ // "use default server" option is used so, if we get that passed in, select
+ // the useDefaultItem representing this option by using the value of "".
+ document.getElementById("identity.smtpServerKey").value = aServerKey || "";
+ }
+
+ if (identity) {
+ document.getElementById("identity.fullName").value = identity.fullName;
+ document.getElementById("identity.email").value = identity.email;
+ document.getElementById("identity.replyTo").value = identity.replyTo;
+ document.getElementById("identity.organization").value =
+ identity.organization;
+ document.getElementById("identity.attachSignature").checked =
+ identity.attachSignature;
+ document.getElementById("identity.htmlSigText").value =
+ identity.htmlSigText;
+ document.getElementById("identity.htmlSigFormat").checked =
+ identity.htmlSigFormat;
+
+ if (identity.signature) {
+ document.getElementById("identity.signature").value =
+ identity.signature.path;
+ }
+
+ document.getElementById("identity.attachVCard").checked =
+ identity.attachVCard;
+ document.getElementById("identity.escapedVCard").value =
+ identity.escapedVCard || "";
+
+ document.getElementById("identity.catchAll").checked = identity.catchAll;
+ document.getElementById("identity.catchAllHint").value =
+ identity.catchAllHint;
+
+ initSmtpServer(identity.smtpServerKey);
+
+ // In am-main.xhtml this field has no ID, because it's hidden by other means.
+ let catchAllBox = document.getElementById("identityCatchAllBox");
+ if (catchAllBox) {
+ let servers = MailServices.accounts.getServersForIdentity(identity);
+ catchAllBox.hidden = servers.length > 0 && servers[0].type == "nntp";
+ }
+
+ // This field does not exist for the default identity shown in the am-main.xhtml pane.
+ let idLabel = document.getElementById("identity.label");
+ if (idLabel) {
+ idLabel.value = identity.label;
+ }
+ } else {
+ // We're adding an identity, use the best default we have.
+ initSmtpServer(gAccount.defaultIdentity.smtpServerKey);
+
+ // Hide catchAll until we know what this identitity is associated with.
+ document.getElementById("identityCatchAllBox").hidden = true;
+ }
+
+ setupSignatureItems();
+}
+
+function initCopiesAndFolder(identity) {
+ // if we are editing an existing identity, use it...otherwise copy our values from the default identity
+ var copiesAndFoldersIdentity = identity ? identity : gAccount.defaultIdentity;
+
+ document.getElementById("identity.fccFolder").value =
+ copiesAndFoldersIdentity.fccFolder;
+ document.getElementById("identity.draftFolder").value =
+ copiesAndFoldersIdentity.draftFolder;
+ document.getElementById("identity.archiveFolder").value =
+ copiesAndFoldersIdentity.archiveFolder;
+ document.getElementById("identity.stationeryFolder").value =
+ copiesAndFoldersIdentity.stationeryFolder;
+
+ document.getElementById("identity.fccFolderPickerMode").value =
+ copiesAndFoldersIdentity.fccFolderPickerMode
+ ? copiesAndFoldersIdentity.fccFolderPickerMode
+ : 0;
+ document.getElementById("identity.draftsFolderPickerMode").value =
+ copiesAndFoldersIdentity.draftsFolderPickerMode
+ ? copiesAndFoldersIdentity.draftsFolderPickerMode
+ : 0;
+ document.getElementById("identity.archivesFolderPickerMode").value =
+ copiesAndFoldersIdentity.archivesFolderPickerMode
+ ? copiesAndFoldersIdentity.archivesFolderPickerMode
+ : 0;
+ document.getElementById("identity.tmplFolderPickerMode").value =
+ copiesAndFoldersIdentity.tmplFolderPickerMode
+ ? copiesAndFoldersIdentity.tmplFolderPickerMode
+ : 0;
+
+ document.getElementById("identity.doCc").checked =
+ copiesAndFoldersIdentity.doCc;
+ document.getElementById("identity.doCcList").value =
+ copiesAndFoldersIdentity.doCcList;
+ document.getElementById("identity.doBcc").checked =
+ copiesAndFoldersIdentity.doBcc;
+ document.getElementById("identity.doBccList").value =
+ copiesAndFoldersIdentity.doBccList;
+ document.getElementById("identity.doFcc").checked =
+ copiesAndFoldersIdentity.doFcc;
+ document.getElementById("identity.fccReplyFollowsParent").checked =
+ copiesAndFoldersIdentity.fccReplyFollowsParent;
+ document.getElementById("identity.showSaveMsgDlg").checked =
+ copiesAndFoldersIdentity.showSaveMsgDlg;
+ document.getElementById("identity.archiveEnabled").checked =
+ copiesAndFoldersIdentity.archiveEnabled;
+
+ onInitCopiesAndFolders(); // am-copies.js method
+}
+
+function initCompositionAndAddressing(identity) {
+ // if we are editing an existing identity, use it...otherwise copy our values from the default identity
+ var addressingIdentity = identity ? identity : gAccount.defaultIdentity;
+
+ document.getElementById("identity.directoryServer").value =
+ addressingIdentity.directoryServer;
+ document.getElementById("identity.overrideGlobal_Pref").value =
+ addressingIdentity.overrideGlobalPref;
+ let autoCompleteElement = document.getElementById(
+ "identity.autocompleteToMyDomain"
+ );
+ if (autoCompleteElement) {
+ // Thunderbird does not have this element.
+ autoCompleteElement.checked = addressingIdentity.autocompleteToMyDomain;
+ }
+
+ document.getElementById("identity.composeHtml").checked =
+ addressingIdentity.composeHtml;
+ document.getElementById("identity.autoQuote").checked =
+ addressingIdentity.autoQuote;
+ document.getElementById("identity.replyOnTop").value =
+ addressingIdentity.replyOnTop;
+ document.getElementById("identity.sig_bottom").value =
+ addressingIdentity.sigBottom;
+ document.getElementById("identity.sig_on_reply").checked =
+ addressingIdentity.sigOnReply;
+ document.getElementById("identity.sig_on_fwd").checked =
+ addressingIdentity.sigOnForward;
+
+ onInitCompositionAndAddressing(); // am-addressing.js method
+}
+
+function onOk(event) {
+ if (!validEmailAddress()) {
+ event.preventDefault();
+ return;
+ }
+
+ // if we are adding a new identity, create an identity, set the fields and add it to the
+ // account.
+ if (!gIdentity) {
+ // ask the account manager to create a new identity for us
+ gIdentity = MailServices.accounts.createIdentity();
+
+ // copy in the default identity settings so we inherit lots of stuff like the default drafts folder, etc.
+ gIdentity.copy(gAccount.defaultIdentity);
+
+ // assume the identity is valid by default?
+ gIdentity.valid = true;
+
+ // add the identity to the account
+ gAccount.addIdentity(gIdentity);
+
+ // now fall through to saveFields which will save our new values
+ }
+
+ // if we are modifying an existing identity, save the fields
+ saveIdentitySettings(gIdentity);
+ saveCopiesAndFolderSettings(gIdentity);
+ saveAddressingAndCompositionSettings(gIdentity);
+ saveE2EEncryptionSettings(gIdentity);
+
+ window.arguments[0].result = true;
+}
+
+// returns false and prompts the user if
+// the identity does not have an email address
+function validEmailAddress() {
+ var emailAddress = document.getElementById("identity.email").value;
+
+ // quickly test for an @ sign to test for an email address. We don't have
+ // to be anymore precise than that.
+ if (!emailAddress.includes("@")) {
+ // alert user about an invalid email address
+
+ var prefBundle = document.getElementById("bundle_prefs");
+
+ Services.prompt.alert(
+ window,
+ prefBundle.getString("identity-edit-req-title"),
+ prefBundle.getString("identity-edit-req")
+ );
+ return false;
+ }
+
+ return true;
+}
+
+function saveIdentitySettings(identity) {
+ if (identity) {
+ let idLabel = document.getElementById("identity.label");
+ if (idLabel) {
+ identity.label = idLabel.value;
+ }
+ identity.fullName = document.getElementById("identity.fullName").value;
+ identity.email = document.getElementById("identity.email").value;
+ identity.replyTo = document.getElementById("identity.replyTo").value;
+ identity.organization = document.getElementById(
+ "identity.organization"
+ ).value;
+ identity.attachSignature = document.getElementById(
+ "identity.attachSignature"
+ ).checked;
+ identity.htmlSigText = document.getElementById(
+ "identity.htmlSigText"
+ ).value;
+ identity.htmlSigFormat = document.getElementById(
+ "identity.htmlSigFormat"
+ ).checked;
+
+ identity.attachVCard = document.getElementById(
+ "identity.attachVCard"
+ ).checked;
+ identity.escapedVCard = document.getElementById(
+ "identity.escapedVCard"
+ ).value;
+ identity.catchAll = document.getElementById("identity.catchAll").checked;
+ identity.catchAllHint = document.getElementById(
+ "identity.catchAllHint"
+ ).value;
+ identity.smtpServerKey = document.getElementById(
+ "identity.smtpServerKey"
+ ).value;
+
+ let attachSignaturePath =
+ document.getElementById("identity.signature").value;
+ identity.signature = null; // this is important so we don't accidentally inherit the default
+
+ if (attachSignaturePath) {
+ // convert signature path back into a nsIFile
+ var sfile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ sfile.initWithPath(attachSignaturePath);
+ if (sfile.exists()) {
+ identity.signature = sfile;
+ }
+ }
+ }
+}
+
+function saveCopiesAndFolderSettings(identity) {
+ onSaveCopiesAndFolders(); // am-copies.js routine
+
+ identity.fccFolder = document.getElementById("identity.fccFolder").value;
+ identity.draftFolder = document.getElementById("identity.draftFolder").value;
+ identity.archiveFolder = document.getElementById(
+ "identity.archiveFolder"
+ ).value;
+ identity.stationeryFolder = document.getElementById(
+ "identity.stationeryFolder"
+ ).value;
+ identity.fccFolderPickerMode = document.getElementById(
+ "identity.fccFolderPickerMode"
+ ).value;
+ identity.draftsFolderPickerMode = document.getElementById(
+ "identity.draftsFolderPickerMode"
+ ).value;
+ identity.archivesFolderPickerMode = document.getElementById(
+ "identity.archivesFolderPickerMode"
+ ).value;
+ identity.tmplFolderPickerMode = document.getElementById(
+ "identity.tmplFolderPickerMode"
+ ).value;
+ identity.doCc = document.getElementById("identity.doCc").checked;
+ identity.doCcList = document.getElementById("identity.doCcList").value;
+ identity.doBcc = document.getElementById("identity.doBcc").checked;
+ identity.doBccList = document.getElementById("identity.doBccList").value;
+ identity.doFcc = document.getElementById("identity.doFcc").checked;
+ identity.fccReplyFollowsParent = document.getElementById(
+ "identity.fccReplyFollowsParent"
+ ).checked;
+ identity.showSaveMsgDlg = document.getElementById(
+ "identity.showSaveMsgDlg"
+ ).checked;
+ identity.archiveEnabled = document.getElementById(
+ "identity.archiveEnabled"
+ ).checked;
+}
+
+function saveAddressingAndCompositionSettings(identity) {
+ identity.directoryServer = document.getElementById(
+ "identity.directoryServer"
+ ).value;
+ identity.overrideGlobalPref =
+ document.getElementById("identity.overrideGlobal_Pref").value == "true";
+ let autoCompleteElement = document.getElementById(
+ "identity.autocompleteToMyDomain"
+ );
+ if (autoCompleteElement) {
+ // Thunderbird does not have this element.
+ identity.autocompleteToMyDomain = autoCompleteElement.checked;
+ }
+ identity.composeHtml = document.getElementById(
+ "identity.composeHtml"
+ ).checked;
+ identity.autoQuote = document.getElementById("identity.autoQuote").checked;
+ identity.replyOnTop = document.getElementById("identity.replyOnTop").value;
+ identity.sigBottom =
+ document.getElementById("identity.sig_bottom").value == "true";
+ identity.sigOnReply = document.getElementById(
+ "identity.sig_on_reply"
+ ).checked;
+ identity.sigOnForward = document.getElementById(
+ "identity.sig_on_fwd"
+ ).checked;
+}
+
+function selectFile() {
+ const nsIFilePicker = Ci.nsIFilePicker;
+
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+
+ var prefBundle = document.getElementById("bundle_prefs");
+ var title = prefBundle.getString("choosefile");
+ fp.init(window, title, nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ // Get current signature folder, if there is one.
+ // We can set that to be the initial folder so that users
+ // can maintain their signatures better.
+ var sigFolder = GetSigFolder();
+ if (sigFolder) {
+ fp.displayDirectory = sigFolder;
+ }
+
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.file) {
+ return;
+ }
+ document.getElementById("identity.signature").value = fp.file.path;
+ document
+ .getElementById("identity.signature")
+ .dispatchEvent(new CustomEvent("change"));
+ });
+}
+
+/**
+ * Adjust the catch-all hint so that is removes stars from the allowed pattern.
+ * We only allow to use stars for matching full domains *@example.com,
+ * not *foo@example.com.
+ *
+ * @param {Event} event - the oninput event of the catchAllHint input field.
+ */
+function handleInputCatchAllHint(event) {
+ let value = event.target.value;
+ event.target.value = value
+ .replace(/(\*[^@]+)/g, "*")
+ .replace(/(^|\s)@/g, "$1*@")
+ .replace(/\s*[;,]/g, ",")
+ .replace(/\s+/g, " ");
+}
+
+function GetSigFolder() {
+ var sigFolder = null;
+ try {
+ var account = parent.getCurrentAccount();
+ var identity = account.defaultIdentity;
+ var signatureFile = identity.signature;
+
+ if (signatureFile) {
+ signatureFile = signatureFile.QueryInterface(Ci.nsIFile);
+ sigFolder = signatureFile.parent;
+
+ if (!sigFolder.exists()) {
+ sigFolder = null;
+ }
+ }
+ } catch (ex) {
+ dump("failed to get signature folder..\n");
+ }
+ return sigFolder;
+}
+
+// Signature textbox is active unless option to select from file is checked.
+// If a signature is need to be attached, the associated items which
+// displays the absolute path to the signature (in a textbox) and the way
+// to select a new signature file (a button) are enabled. Otherwise, they
+// are disabled. Check to see if the attachSignature is locked to block
+// broadcasting events.
+function setupSignatureItems() {
+ var signature = document.getElementById("identity.signature");
+ var browse = document.getElementById("identity.sigbrowsebutton");
+ var htmlSigText = document.getElementById("identity.htmlSigText");
+ var htmlSigFormat = document.getElementById("identity.htmlSigFormat");
+ var attachSignature = document.getElementById("identity.attachSignature");
+ var checked = attachSignature.checked;
+
+ if (checked) {
+ htmlSigText.setAttribute("disabled", "disabled");
+ htmlSigFormat.setAttribute("disabled", "true");
+ } else {
+ htmlSigText.removeAttribute("disabled");
+ htmlSigFormat.removeAttribute("disabled");
+ }
+
+ if (checked && !getAccountValueIsLocked(signature)) {
+ signature.removeAttribute("disabled");
+ } else {
+ signature.setAttribute("disabled", "disabled");
+ }
+
+ if (checked && !getAccountValueIsLocked(browse)) {
+ browse.removeAttribute("disabled");
+ } else {
+ browse.setAttribute("disabled", "true");
+ }
+}
+
+function editVCard() {
+ // Read vCard hidden value from UI.
+ let escapedVCard = document.getElementById("identity.escapedVCard");
+ let dialog = top.document.getElementById("editVCardDialog");
+ let form = dialog.querySelector("form");
+ let vCardEdit = dialog.querySelector("vcard-edit");
+
+ vCardEdit.vCardString = decodeURIComponent(escapedVCard.value);
+
+ top.addEventListener("keydown", editVCardKeyDown, { capture: true });
+ form.addEventListener("submit", editVCardSubmit);
+ form.addEventListener("reset", editVCardReset);
+
+ top.gSubDialog._topDialog?._overlay.removeAttribute("topmost");
+ dialog.showModal();
+}
+
+function editVCardKeyDown(event) {
+ let dialog = top.document.getElementById("editVCardDialog");
+ if (event.keyCode == KeyboardEvent.DOM_VK_ESCAPE && dialog.open) {
+ // This is a bit of a hack to prevent other dialogs (particularly
+ // SubDialogs) from closing when the vCard dialog is open.
+ event.preventDefault();
+ editVCardReset();
+ }
+}
+
+function editVCardSubmit(event) {
+ let escapedVCard = document.getElementById("identity.escapedVCard");
+ let dialog = top.document.getElementById("editVCardDialog");
+ let form = dialog.querySelector("form");
+ let vCardEdit = dialog.querySelector("vcard-edit");
+
+ vCardEdit.saveVCard();
+ escapedVCard.value = encodeURIComponent(vCardEdit.vCardString);
+ // Trigger a change event so for the am-main view the event listener
+ // set up in AccountManager.js onLoad() can make sure to save the change.
+ escapedVCard.dispatchEvent(new CustomEvent("change"));
+
+ top.gSubDialog._topDialog?._overlay.setAttribute("topmost", "true");
+ dialog.close();
+
+ event.preventDefault();
+ form.removeEventListener("submit", editVCardSubmit);
+ form.removeEventListener("reset", editVCardReset);
+}
+
+function editVCardReset() {
+ let dialog = top.document.getElementById("editVCardDialog");
+ let form = dialog.querySelector("form");
+
+ top.gSubDialog._topDialog?._overlay.setAttribute("topmost", "true");
+ dialog.close();
+
+ form.removeEventListener("submit", editVCardSubmit);
+ form.removeEventListener("reset", editVCardReset);
+}
+
+function getAccountForFolderPickerState() {
+ return gAccount;
+}
+
+/**
+ * Build the SMTP server list for display.
+ */
+function loadSMTPServerList() {
+ var smtpServerList = document.getElementById("identity.smtpServerKey");
+ let defaultServer = MailServices.smtp.defaultServer;
+ let currentValue = smtpServerList.value;
+
+ var smtpPopup = smtpServerList.menupopup;
+ while (smtpPopup.lastChild.nodeName != "menuseparator") {
+ smtpPopup.lastChild.remove();
+ }
+
+ for (let server of MailServices.smtp.servers) {
+ let serverName = "";
+ if (server.description) {
+ serverName = server.description + " - ";
+ } else if (server.username) {
+ serverName = server.username + " - ";
+ }
+ serverName += server.hostname;
+
+ if (defaultServer.key == server.key) {
+ serverName +=
+ " " +
+ document
+ .getElementById("bundle_messenger")
+ .getString("defaultServerTag");
+ }
+
+ smtpServerList.appendItem(serverName, server.key);
+ }
+
+ smtpServerList.value = currentValue;
+}
+
+/**
+ * Open dialog for editing properties of currently selected SMTP server.
+ */
+function editCurrentSMTP() {
+ let smtpKey = document.getElementById("identity.smtpServerKey").value;
+ let server =
+ smtpKey === ""
+ ? MailServices.smtp.defaultServer
+ : MailServices.smtp.getServerByKey(smtpKey);
+ let args = { server, result: false, addSmtpServer: "" };
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/SmtpServerEdit.xhtml",
+ { closingCallback: loadSMTPServerList },
+ args
+ );
+}
diff --git a/comm/mailnews/base/prefs/content/am-identity-edit.xhtml b/comm/mailnews/base/prefs/content/am-identity-edit.xhtml
new file mode 100644
index 0000000000..d967b18202
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-identity-edit.xhtml
@@ -0,0 +1,239 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/openpgp/inlineNotification.css" type="text/css"?>
+
+<!DOCTYPE html [
+<!ENTITY % identityEditDTD SYSTEM "chrome://messenger/locale/am-identity-edit.dtd" >
+%identityEditDTD;
+<!ENTITY % identityDTD SYSTEM "chrome://messenger/locale/am-main.dtd" >
+%identityDTD;
+<!ENTITY % copiesDTD SYSTEM "chrome://messenger/locale/am-copies.dtd">
+%copiesDTD;
+<!ENTITY % addressingDTD SYSTEM "chrome://messenger/locale/am-addressing.dtd" >
+%addressingDTD;
+<!ENTITY % e2eDTD SYSTEM "chrome://messenger/locale/am-smime.dtd" >
+%e2eDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false"
+ style="height: 900px; min-width: 800px;">
+<head>
+ <title><!-- identityDialogTitleEdit --></title>
+ <script defer="defer" src="chrome://messenger/content/globalOverlay.js"></script>
+ <script defer="defer" src="chrome://global/content/editMenuOverlay.js"></script>
+ <script defer="defer" src="chrome://global/content/preferencesBindings.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-copies.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-addressing.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-e2e.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-identity-edit.js"></script>
+ <script>
+ // NOTE: am-identity-edit.js is not only used for this page.
+ window.addEventListener("DOMContentLoaded", onLoadIdentityProperties);
+ </script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<dialog id="identityDialog">
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+
+ <tabbox flex="1" style="overflow: hidden;">
+ <tabs id="identitySettings">
+ <tab id="identitySettingsTab" label="&settingsTab.label;"/>
+ <tab id="identityCopiesFoldersTab" label="&copiesFoldersTab.label;"/>
+ <tab id="identityAddressingTab" label="&addressingTab.label;"/>
+ <tab id="identityE2ETab" label="&e2eTitle.label;"/>
+ </tabs>
+
+ <tabpanels id="identityTabsPanels" flex="1">
+ <!-- Identity Settings Tab -->
+ <vbox flex="1" name="settings">
+ <html:div>
+ <html:fieldset>
+ <html:legend>&publicData.label;</html:legend>
+ <html:table class="identity-table">
+ <html:tr>
+ <html:th>
+ <label id="identity.fullName.label"
+ value="&name.label;"
+ control="identity.fullName"
+ accesskey="&name.accesskey;"/>
+ </html:th>
+ <html:td>
+ <html:input id="identity.fullName"
+ type="text"
+ class="input-inline"
+ aria-labelledby="identity.fullName.label"
+ size="30"/>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label id="identity.email.label"
+ value="&email.label;"
+ control="identity.email"
+ accesskey="&email.accesskey;"/>
+ </html:th>
+ <html:td>
+ <html:input id="identity.email"
+ type="email"
+ class="uri-element input-inline"
+ aria-labelledby="identity.email.label"/>
+
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label id="identity.replyTo.label"
+ value="&replyTo.label;"
+ accesskey="&replyTo.accesskey;"
+ control="identity.replyTo"/>
+ </html:th>
+ <html:td>
+ <html:input id="identity.replyTo"
+ type="text"
+ class="uri-element input-inline"
+ placeholder="&replyTo.placeholder;"
+ aria-labelledby="identity.replyTo.label"/>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label id="identity.organization.label"
+ value="&organization.label;"
+ control="identity.organization"
+ accesskey="&organization.accesskey;"/>
+ </html:th>
+ <html:td>
+ <html:input id="identity.organization"
+ type="text"
+ class="input-inline"
+ aria-labelledby="identity.organization.label"/>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label value="&signatureText.label;" control="identity.htmlSigText"
+ accesskey="&signatureText.accesskey;"/>
+ </html:th>
+ <html:td style="width:100%;">
+ <checkbox id="identity.htmlSigFormat" label="&signatureHtml.label;"
+ accesskey="&signatureHtml.accesskey;" style="width:100%;"/>
+ </html:td>
+ </html:tr>
+ </html:table>
+
+ <separator class="thin"/>
+
+ <hbox class="indent input-container" flex="1">
+ <html:textarea id="identity.htmlSigText" style="flex-grow: 1;" rows="4" class="signatureBox"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="identity.attachSignature" label="&signatureFile.label;"
+ flex="1" accesskey="&signatureFile.accesskey;"
+ oncommand="setupSignatureItems();"/>
+ </hbox>
+
+ <hbox align="center" class="indent input-container">
+ <html:input id="identity.signature"
+ type="text"
+ datatype="nsIFile"
+ name="identity.signature"
+ aria-labelledby="identity.attachSignature"
+ class="uri-element input-inline"/>
+ <button id="identity.sigbrowsebutton" class="push"
+ label="&choose.label;" accesskey="&choose.accesskey;"
+ oncommand="selectFile()"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="identity.attachVCard" label="&attachVCard.label;"
+ flex="1" accesskey="&attachVCard.accesskey;"/>
+ <button class="push" label="&editVCard.label;"
+ accesskey="&editVCard.accesskey;" oncommand="editVCard()"/>
+ <label id="identity.escapedVCard" hidden="true"/>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&privateData.label;</html:legend>
+
+ <hbox id="identityCatchAllBox" align="center" class="input-container">
+ <checkbox id="identity.catchAll"
+ label="&catchAll.label;"
+ accesskey="&catchAll.accesskey;"
+ style="margin-block:auto;"/>
+ <html:input id="identity.catchAllHint"
+ type="text"
+ oninput="handleInputCatchAllHint(event);"
+ placeholder="list@example.com, *@example.com"
+ class="input-inline"
+ aria-labelledby="identity.catchAll"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <label value="&smtpName.label;"
+ control="identity.smtpServerKey"
+ accesskey="&smtpName.accesskey;"/>
+ <hbox align="center">
+ <menulist id="identity.smtpServerKey" flex="1">
+ <menupopup id="smtpPopup">
+ <menuitem id="useDefaultItem" value=""
+ label="&smtpDefaultServer.label;"/>
+ <menuseparator/>
+ <!-- list will be inserted here -->
+ </menupopup>
+ </menulist>
+
+ <button id="editSmtp"
+ label="&smtpServerEdit.label;"
+ accesskey="&smtpServerEdit.accesskey;"
+ oncommand="editCurrentSMTP();"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center" class="input-container">
+ <label id="identity.input.label" value="&identityAlias.label;"
+ accesskey="&identityAlias.accesskey;"
+ style="margin-block: auto;"
+ control="identity.label"/>
+ <html:input id="identity.label"
+ type="text"
+ class="input-inline"
+ aria-labelledby="identity.input.label"/>
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ </vbox>
+
+ <!-- Copies & Folders Tab -->
+#include am-copies.inc.xhtml
+
+ <!-- Composition & Addressing Tab -->
+#include am-addressing.inc.xhtml
+
+ <!-- Security Tab -->
+#include ../../../../mail/extensions/am-e2e/am-e2e.inc.xhtml
+
+ </tabpanels>
+ </tabbox>
+</dialog>
+</html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-junk.js b/comm/mailnews/base/prefs/content/am-junk.js
new file mode 100644
index 0000000000..a102efdfae
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-junk.js
@@ -0,0 +1,335 @@
+/* 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 am-prefs.js */
+/* import-globals-from amUtils.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var gDeferredToAccount = "";
+
+function onInit(aPageId, aServerId) {
+ // manually adjust several pref UI elements
+ document.getElementById("server.spamLevel.visible").checked =
+ document.getElementById("server.spamLevel").value > 0;
+
+ let deferredToURI = null;
+ if (gDeferredToAccount) {
+ deferredToURI =
+ MailServices.accounts.getAccount(gDeferredToAccount).incomingServer
+ .serverURI;
+ }
+
+ let spamActionTargetAccountElement = document.getElementById(
+ "server.spamActionTargetAccount"
+ );
+ let spamActionTargetFolderElement = document.getElementById(
+ "server.spamActionTargetFolder"
+ );
+
+ let spamActionTargetAccount = spamActionTargetAccountElement.value;
+ let spamActionTargetFolder = spamActionTargetFolderElement.value;
+
+ let moveOnSpamCheckbox = document.getElementById("server.moveOnSpam");
+ let moveOnSpamValue = moveOnSpamCheckbox.checked;
+
+ // Check if there are any invalid junk targets and fix them.
+ [spamActionTargetAccount, spamActionTargetFolder, moveOnSpamValue] =
+ sanitizeJunkTargets(
+ spamActionTargetAccount,
+ spamActionTargetFolder,
+ deferredToURI || aServerId,
+ document.getElementById("server.moveTargetMode").value,
+ MailUtils.getOrCreateFolder(aServerId).server.spamSettings,
+ moveOnSpamValue
+ );
+
+ spamActionTargetAccountElement.value = spamActionTargetAccount;
+ spamActionTargetFolderElement.value = spamActionTargetFolder;
+ moveOnSpamCheckbox.checked = moveOnSpamValue;
+
+ let server = MailUtils.getOrCreateFolder(spamActionTargetAccount);
+ document.getElementById("actionAccountPopup").selectFolder(server);
+
+ let folder = MailUtils.getExistingFolder(spamActionTargetFolder);
+ document.getElementById("actionFolderPopup").selectFolder(folder);
+
+ var currentArray = [];
+ if (document.getElementById("server.useWhiteList").checked) {
+ currentArray = document
+ .getElementById("server.whiteListAbURI")
+ .value.split(" ");
+ }
+
+ // set up the whitelist UI
+ var wList = document.getElementById("whiteListAbURI");
+ // Ensure the whitelist is empty
+ while (wList.hasChildNodes()) {
+ wList.lastChild.remove();
+ }
+
+ // Populate the listbox with address books
+ let abItems = [];
+ for (let ab of MailServices.ab.directories) {
+ // We skip mailing lists and remote address books.
+ if (ab.isMailList || ab.isRemote) {
+ continue;
+ }
+
+ abItems.push({ label: ab.dirName, URI: ab.URI });
+ }
+
+ // Sort the list
+ function sortFunc(a, b) {
+ return a.label.localeCompare(b.label);
+ }
+ abItems.sort(sortFunc);
+
+ // And then append each item to the listbox
+ for (let abItem of abItems) {
+ let checkbox = document.createXULElement("checkbox");
+ checkbox.setAttribute("label", abItem.label);
+ checkbox.checked = currentArray.includes(abItem.URI);
+
+ let item = document.createXULElement("richlistitem");
+ item.appendChild(checkbox);
+ item.setAttribute("value", abItem.URI);
+ wList.appendChild(item);
+ }
+
+ wList.addEventListener("keypress", event => {
+ if ([" ", "Enter"].includes(event.key)) {
+ let checkbox = wList.currentItem.firstElementChild;
+ checkbox.checked = !checkbox.checked;
+ wList.dispatchEvent(new CustomEvent("command"));
+ }
+ });
+
+ // enable or disable the whitelist
+ onAdaptiveJunkToggle();
+
+ // set up trusted IP headers
+ var serverFilterList = document.getElementById("useServerFilterList");
+ serverFilterList.value = document.getElementById(
+ "server.serverFilterName"
+ ).value;
+ if (!serverFilterList.selectedItem) {
+ serverFilterList.selectedIndex = 0;
+ }
+
+ // enable or disable the useServerFilter checkbox
+ onCheckItem("useServerFilterList", ["server.useServerFilter"]);
+
+ updateJunkTargetsAndRetention();
+}
+
+function onPreInit(account, accountValues) {
+ if (
+ top.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "type",
+ null,
+ false
+ ) == "pop3"
+ ) {
+ gDeferredToAccount = top.getAccountValue(
+ account,
+ accountValues,
+ "pop3",
+ "deferredToAccount",
+ null,
+ false
+ );
+ }
+
+ buildServerFilterMenuList();
+}
+
+/**
+ * Called when someone checks or unchecks the adaptive junk mail checkbox.
+ * set the value of the hidden element accordingly
+ *
+ * @param {boolean} aValue - The boolean value of the checkbox.
+ */
+function updateSpamLevel(aValue) {
+ document.getElementById("server.spamLevel").value = aValue ? 100 : 0;
+ onAdaptiveJunkToggle();
+}
+
+/**
+ * Propagate changes to the server filter menu list back to
+ * our hidden wsm element.
+ */
+function onServerFilterListChange() {
+ document.getElementById("server.serverFilterName").value =
+ document.getElementById("useServerFilterList").value;
+}
+
+/**
+ * Called when someone checks or unchecks the adaptive junk mail checkbox.
+ * We need to enable or disable the whitelist accordingly.
+ */
+function onAdaptiveJunkToggle() {
+ onCheckItem("whiteListAbURI", ["server.spamLevel.visible"]);
+ onCheckItem("whiteListLabel", ["server.spamLevel.visible"]);
+
+ // Enable/disable individual listbox rows.
+ // Setting enable/disable on the parent listbox does not seem to work.
+ let wList = document.getElementById("whiteListAbURI");
+ let wListDisabled = wList.disabled;
+
+ for (let i = 0; i < wList.getRowCount(); i++) {
+ let item = wList.getItemAtIndex(i);
+ item.setAttribute("disabled", wListDisabled);
+ item.firstElementChild.setAttribute("disabled", wListDisabled);
+ }
+}
+
+/**
+ * Called when someone checks or unchecks the "move new junk messages to"
+ * Enable/disable the radio group accordingly.
+ */
+function updateJunkTargetsAndRetention() {
+ onCheckItem("server.moveTargetMode", ["server.moveOnSpam"]);
+ updateJunkTargets();
+ onCheckItem("server.purgeSpam", ["server.moveOnSpam"]);
+ document.getElementById("purgeLabel").disabled =
+ document.getElementById("server.purgeSpam").disabled;
+ updateJunkRetention();
+}
+
+/**
+ * Enable/disable the folder pickers depending on which radio item is selected.
+ */
+function updateJunkTargets() {
+ onCheckItem("actionTargetAccount", ["server.moveOnSpam", "moveTargetMode0"]);
+ onCheckItem("actionTargetFolder", ["server.moveOnSpam", "moveTargetMode1"]);
+}
+
+/**
+ * Enable/disable the junk deletion interval depending on the state
+ * of the controlling checkbox.
+ */
+function updateJunkRetention() {
+ onCheckItem("server.purgeSpamInterval", [
+ "server.purgeSpam",
+ "server.moveOnSpam",
+ ]);
+}
+
+function onSave() {
+ onSaveWhiteList();
+}
+
+/**
+ * Propagate changes to the whitelist menu list back to
+ * our hidden wsm element.
+ */
+function onSaveWhiteList() {
+ var wList = document.getElementById("whiteListAbURI");
+ var wlArray = [];
+
+ for (let i = 0; i < wList.getRowCount(); i++) {
+ // Due to bug 448582, do not trust any properties of the listitems
+ // as they may not return the right value or may even not exist.
+ // Always get the attributes only.
+ var wlNode = wList.getItemAtIndex(i);
+ if (wlNode.firstElementChild.getAttribute("checked") == "true") {
+ let abURI = wlNode.getAttribute("value");
+ wlArray.push(abURI);
+ }
+ }
+ var wlValue = wlArray.join(" ");
+ document
+ .getElementById("server.whiteListAbURI")
+ .setAttribute("value", wlValue);
+ document.getElementById("server.useWhiteList").checked = wlValue != "";
+}
+
+/**
+ * Called when a new value is chosen in one of the junk target folder pickers.
+ * Sets the menu label according to the folder name.
+ */
+function onActionTargetChange(aEvent, aWSMElementId) {
+ let folder = aEvent.target._folder;
+ document.getElementById(aWSMElementId).value = folder.URI;
+ document.getElementById("actionFolderPopup").selectFolder(folder);
+}
+
+/**
+ * Enumerates over the "ISPDL" directories, calling buildServerFilterListFromDir
+ * for each one.
+ */
+function buildServerFilterMenuList() {
+ const KEY_ISP_DIRECTORY_LIST = "ISPDL";
+ let ispHeaderList = document.getElementById("useServerFilterList");
+
+ // Ensure the menulist is empty.
+ ispHeaderList.removeAllItems();
+
+ // Now walk through the isp directories looking for sfd files.
+ let ispDirectories = Services.dirsvc.get(
+ KEY_ISP_DIRECTORY_LIST,
+ Ci.nsISimpleEnumerator
+ );
+
+ let menuEntries = [];
+ while (ispDirectories.hasMoreElements()) {
+ let ispDirectory = ispDirectories.getNext().QueryInterface(Ci.nsIFile);
+ if (ispDirectory) {
+ menuEntries.push.apply(
+ menuEntries,
+ buildServerFilterListFromDir(ispDirectory, menuEntries)
+ );
+ }
+ }
+
+ menuEntries.sort((a, b) => a.localeCompare(b));
+ for (let entry of menuEntries) {
+ ispHeaderList.appendItem(entry, entry);
+ }
+}
+
+/**
+ * Helper function called by buildServerFilterMenuList. Enumerates over the
+ * passed in directory looking for .sfd files. For each entry found, it gets
+ * appended to the menu list.
+ *
+ * @param {nsIFile} aDir - Directory to look for .sfd files
+ * @param {string[]} aExistingEntries - Filter names already found.
+ */
+function buildServerFilterListFromDir(aDir, aExistingEntries) {
+ let newEntries = [];
+ // Now iterate over each file in the directory looking for .sfd files.
+ const kSuffix = ".sfd";
+
+ for (let entry of aDir.directoryEntries) {
+ // we only care about files that end in .sfd
+ if (entry.isFile() && entry.leafName.endsWith(kSuffix)) {
+ let fileName = entry.leafName.slice(0, -kSuffix.length);
+ // If we've already added an item with this name, then don't add it again.
+ if (!aExistingEntries.includes(fileName)) {
+ newEntries.push(fileName);
+ }
+ }
+ }
+ return newEntries;
+}
+
+/**
+ * Open the Preferences dialog on the Junk settings tab.
+ */
+function showGlobalJunkPrefs() {
+ openPrefsFromAccountManager(
+ "panePrivacy",
+ "privacyJunkCategory",
+ null,
+ "junk_pane"
+ );
+}
diff --git a/comm/mailnews/base/prefs/content/am-junk.xhtml b/comm/mailnews/base/prefs/content/am-junk.xhtml
new file mode 100644
index 0000000000..216d3c0431
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-junk.xhtml
@@ -0,0 +1,293 @@
+<?xml version="1.0"?>
+
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE html [ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % junkMailDTD SYSTEM "chrome://messenger/locale/am-junk.dtd">
+%junkMailDTD; ]>
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <head>
+ <title>&junkSettings.label;</title>
+ <script defer="defer" src="chrome://messenger/content/am-junk.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => {
+ parent.onPanelLoaded("am-junk.xhtml");
+ });
+ </script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <vbox id="containerBox" flex="1">
+ <stringbundle
+ id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"
+ />
+
+ <hbox class="dialogheader">
+ <label class="dialogheader-title" value="&junkSettings.label;" />
+ </hbox>
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&junkClassification.label;</html:legend>
+
+ <label
+ hidden="true"
+ id="server.spamLevel"
+ wsm_persist="true"
+ pref="true"
+ preftype="int"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.spamLevel"
+ />
+ <label
+ hidden="true"
+ id="server.spamActionTargetAccount"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.spamActionTargetAccount"
+ />
+ <label
+ hidden="true"
+ id="server.spamActionTargetFolder"
+ wsm_persist="true"
+ pref="true"
+ preftype="wstring"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.spamActionTargetFolder"
+ />
+ <label
+ hidden="true"
+ id="server.whiteListAbURI"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.whiteListAbURI"
+ />
+ <label
+ hidden="true"
+ id="server.serverFilterName"
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.serverFilterName"
+ />
+
+ <checkbox
+ id="server.spamLevel.visible"
+ oncommand="updateSpamLevel(this.checked);"
+ accesskey="&level.accesskey;"
+ prefstring="mail.server.%serverkey%.spamLevel"
+ label="&level.label;"
+ />
+
+ <separator class="thin" />
+
+ <description width="1">&trainingDescription.label;</description>
+
+ <separator class="thin" />
+ <spacer height="3" />
+
+ <vbox class="indent">
+ <checkbox
+ hidden="true"
+ id="server.useWhiteList"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="bool"
+ prefstring="mail.server.%serverkey%.useWhiteList"
+ />
+ <label
+ id="whiteListLabel"
+ accesskey="&whitelistHeader.accesskey;"
+ control="whiteListAbURI"
+ >&whitelistHeader.label;</label
+ >
+ <richlistbox id="whiteListAbURI" height="200px" />
+ </vbox>
+
+ <separator />
+
+ <vbox>
+ <hbox>
+ <checkbox
+ id="server.useServerFilter"
+ label="&ispHeaders.label;"
+ accesskey="&ispHeaders.accesskey;"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="bool"
+ oncommand="onCheckItem('useServerFilterList', [this.id]);"
+ prefstring="mail.server.%serverkey%.useServerFilter"
+ />
+ <menulist
+ id="useServerFilterList"
+ oncommand="onServerFilterListChange();"
+ aria-labelledby="server.useServerFilter"
+ />
+ </hbox>
+ </vbox>
+
+ <separator class="thin" />
+
+ <description class="indent tip-caption" width="1"
+ >&ispHeadersWarning.label;</description
+ >
+ </html:fieldset>
+ </html:div>
+
+ <separator />
+ <separator />
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&junkActions.label;</html:legend>
+
+ <checkbox
+ id="server.moveOnSpam"
+ label="&move.label;"
+ accesskey="&move.accesskey;"
+ oncommand="updateJunkTargetsAndRetention();"
+ wsm_persist="true"
+ pref="true"
+ preftype="bool"
+ genericattr="true"
+ prefstring="mail.server.%serverkey%.moveOnSpam"
+ />
+
+ <radiogroup
+ id="server.moveTargetMode"
+ aria-labelledby="server.moveOnSpam"
+ prefstring="mail.server.%serverkey%.moveTargetMode"
+ wsm_persist="true"
+ pref="true"
+ preftype="int"
+ genericattr="true"
+ oncommand="updateJunkTargets();"
+ prefvalue="value"
+ >
+ <hbox class="specialFolderPickerGrid indent">
+ <vbox>
+ <hbox flex="1" align="center">
+ <radio
+ id="moveTargetMode0"
+ value="0"
+ label="&junkFolderOn.label;"
+ accesskey="&junkFolderOn.accesskey;"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio
+ id="moveTargetMode1"
+ value="1"
+ label="&otherFolder.label;"
+ accesskey="&otherFolder.accesskey;"
+ />
+ </hbox>
+ </vbox>
+ <vbox flex="1">
+ <menulist
+ id="actionTargetAccount"
+ class="folderMenuItem"
+ aria-labelledby="moveTargetMode0"
+ >
+ <menupopup
+ is="folder-menupopup"
+ id="actionAccountPopup"
+ class="menulist-menupopup"
+ expandFolders="false"
+ mode="filing"
+ oncommand="onActionTargetChange(event, 'server.spamActionTargetAccount');"
+ />
+ </menulist>
+ <menulist
+ id="actionTargetFolder"
+ class="folderMenuItem"
+ aria-labelledby="moveTargetMode1"
+ displayformat="verbose"
+ >
+ <menupopup
+ is="folder-menupopup"
+ id="actionFolderPopup"
+ mode="junk"
+ showFileHereLabel="true"
+ oncommand="onActionTargetChange(event, 'server.spamActionTargetFolder');"
+ />
+ </menulist>
+ </vbox>
+ </hbox>
+ </radiogroup>
+
+ <hbox align="center" class="indent">
+ <checkbox
+ id="server.purgeSpam"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="bool"
+ prefstring="mail.server.%serverkey%.purgeSpam"
+ accesskey="&purge1.accesskey;"
+ oncommand="updateJunkRetention();"
+ label="&purge1.label;"
+ />
+ <html:input
+ id="server.purgeSpamInterval"
+ type="number"
+ class="size3 input-inline"
+ min="1"
+ genericattr="true"
+ pref="true"
+ wsm_persist="true"
+ preftype="int"
+ aria-labelledby="server.purgeSpam server.purgeSpamInterval purgeLabel"
+ prefstring="mail.server.%serverkey%.purgeSpamInterval"
+ />
+ <label
+ id="purgeLabel"
+ value="&purge2.label;"
+ control="server.purgeSpamInterval"
+ />
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <separator class="thin" />
+
+ <hbox pack="end">
+ <button
+ id="globalJunkPrefsLink"
+ label="&globalJunkPrefs.label;"
+ accesskey="&globalJunkPrefs.accesskey;"
+ oncommand="showGlobalJunkPrefs();"
+ />
+ </hbox>
+ </vbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-main.js b/comm/mailnews/base/prefs/content/am-main.js
new file mode 100644
index 0000000000..b71ad49fb8
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-main.js
@@ -0,0 +1,110 @@
+/* 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 am-identity-edit.js */
+
+var gAccount;
+
+/**
+ * Initialize am-main account settings page when it gets shown.
+ * Update an account's main settings title and set up signature items.
+ */
+function onInit() {
+ setAccountTitle();
+ setupSignatureItems();
+ Services.obs.addObserver(
+ onDefaultIdentityChange,
+ "account-default-identity-changed"
+ );
+}
+
+window.addEventListener("unload", function () {
+ Services.obs.removeObserver(
+ onDefaultIdentityChange,
+ "account-default-identity-changed"
+ );
+});
+
+/**
+ * If the default identity for the current account changes, loads the values
+ * from the new default identity.
+ */
+function onDefaultIdentityChange(subject, topic, data) {
+ if (data == gAccount.key) {
+ initIdentityValues(subject.QueryInterface(Ci.nsIMsgIdentity));
+ }
+}
+
+/**
+ * Handle the blur event of the #server.prettyName pref input.
+ * Update account name in account manager tree and account settings' main title.
+ *
+ * @param {Event} event - Blur event from the pretty name input.
+ */
+function serverPrettyNameOnBlur(event) {
+ parent.setAccountLabel(gAccount.key, event.target.value);
+ setAccountTitle();
+}
+
+/**
+ * Update an account's main settings title with the account name if applicable.
+ */
+function setAccountTitle() {
+ let accountName = document.getElementById("server.prettyName");
+ let title = document.querySelector("#am-main-title .dialogheader-title");
+ let titleValue = title.getAttribute("defaultTitle");
+ if (accountName.value) {
+ titleValue += " - " + accountName.value;
+ }
+
+ title.setAttribute("value", titleValue);
+ document.title = titleValue;
+}
+
+function onPreInit(account, accountValues) {
+ gAccount = account;
+ loadSMTPServerList();
+ let type = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "type",
+ null,
+ false
+ );
+ hideShowControls(type);
+}
+
+function manageIdentities() {
+ // We want to save the current identity information before bringing up the multiple identities
+ // UI. This ensures that the changes are reflected in the identity list dialog
+ // onSave();
+
+ if (!gAccount) {
+ return;
+ }
+
+ var accountName = document.getElementById("server.prettyName").value;
+
+ var args = { account: gAccount, accountName, result: false };
+
+ // save the current identity settings so they show up correctly
+ // if the user just changed them in the manage identities dialog
+ var identity = gAccount.defaultIdentity;
+ saveIdentitySettings(identity);
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/am-identities-list.xhtml",
+ { closingCallback: onCloseIdentities },
+ args
+ );
+
+ function onCloseIdentities() {
+ if (args.result) {
+ // Refresh the SMTP list in case the user changed server properties
+ // from the identity dialog.
+ loadSMTPServerList();
+ }
+ }
+}
diff --git a/comm/mailnews/base/prefs/content/am-main.xhtml b/comm/mailnews/base/prefs/content/am-main.xhtml
new file mode 100644
index 0000000000..09f5453fb8
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-main.xhtml
@@ -0,0 +1,344 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-main.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <head>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-identity-edit.js"
+ ></script>
+ <script defer="defer" src="chrome://messenger/content/am-main.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => {
+ parent.onPanelLoaded("am-main.xhtml");
+ });
+ </script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <vbox id="containerBox" flex="1">
+ <stringbundle
+ id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"
+ />
+ <stringbundle
+ id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"
+ />
+
+ <hbox id="am-main-title" class="dialogheader">
+ <label class="dialogheader-title" defaultTitle="&accountTitle.label;" />
+ </hbox>
+
+ <separator class="thin" />
+
+ <hbox class="input-container">
+ <label
+ id="server.prettyName.label"
+ value="&accountName.label;"
+ control="server.prettyName"
+ accesskey="&accountName.accesskey;"
+ />
+ <html:input
+ id="server.prettyName"
+ type="text"
+ wsm_persist="true"
+ class="input-inline"
+ onblur="serverPrettyNameOnBlur(event);"
+ prefstring="mail.server.%serverkey%.name"
+ aria-labelledby="server.prettyName.label"
+ />
+ </hbox>
+
+ <separator />
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&identityTitle.label;</html:legend>
+ <description>&identityDesc.label;</description>
+ <separator class="thin" />
+ <html:table class="identity-table">
+ <html:tr>
+ <html:th>
+ <label
+ id="identity.fullName.label.label"
+ value="&name.label;"
+ control="identity.fullName"
+ accesskey="&name.accesskey;"
+ />
+ </html:th>
+ <html:td>
+ <html:input
+ id="identity.fullName"
+ type="text"
+ class="input-inline"
+ aria-labelledby="identity.fullName.label"
+ wsm_persist="true"
+ size="30"
+ prefstring="mail.identity.%identitykey%.fullName"
+ />
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label
+ id="identity.email.label"
+ value="&email.label;"
+ control="identity.email"
+ accesskey="&email.accesskey;"
+ />
+ </html:th>
+ <html:td>
+ <html:input
+ id="identity.email"
+ type="email"
+ wsm_persist="true"
+ prefstring="mail.identity.%identitykey%.useremail"
+ class="uri-element input-inline"
+ aria-labelledby="identity.email.label"
+ />
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label
+ id="identity.replyTo.label"
+ value="&replyTo.label;"
+ control="identity.replyTo"
+ accesskey="&replyTo.accesskey;"
+ />
+ </html:th>
+ <html:td>
+ <html:input
+ id="identity.replyTo"
+ type="text"
+ wsm_persist="true"
+ prefstring="mail.identity.%identitykey%.reply_to"
+ class="uri-element input-inline"
+ placeholder="&replyTo.placeholder;"
+ aria-labelledby="identity.replyTo.label"
+ />
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label
+ id="identity.organization.label"
+ value="&organization.label;"
+ control="identity.organization"
+ accesskey="&organization.accesskey;"
+ />
+ </html:th>
+ <html:td>
+ <html:input
+ id="identity.organization"
+ type="text"
+ wsm_persist="true"
+ prefstring="mail.identity.%identitykey%.organization"
+ class="input-inline"
+ aria-labelledby="identity.organization.label"
+ />
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:th>
+ <label
+ value="&signatureText.label;"
+ control="identity.htmlSigText"
+ accesskey="&signatureText.accesskey;"
+ />
+ </html:th>
+ <html:td style="width: 100%">
+ <hbox align="center">
+ <checkbox
+ id="identity.htmlSigFormat"
+ wsm_persist="true"
+ label="&signatureHtml.label;"
+ prefattribute="value"
+ accesskey="&signatureHtml.accesskey;"
+ style="width: 100%"
+ prefstring="mail.identity.%identitykey%.htmlSigFormat"
+ />
+ </hbox>
+ </html:td>
+ </html:tr>
+ </html:table>
+
+ <hbox
+ class="indent"
+ flex="1"
+ style="min-height: 100px; display: flex"
+ >
+ <html:textarea
+ id="identity.htmlSigText"
+ wsm_persist="true"
+ rows="4"
+ style="flex-grow: 1"
+ prefstring="mail.identity.%identitykey%.htmlSigText"
+ class="signatureBox"
+ />
+ </hbox>
+
+ <hbox align="center">
+ <checkbox
+ id="identity.attachSignature"
+ wsm_persist="true"
+ label="&signatureFile.label;"
+ flex="1"
+ accesskey="&signatureFile.accesskey;"
+ oncommand="setupSignatureItems();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.attach_signature"
+ />
+ </hbox>
+
+ <hbox align="center" class="indent input-container">
+ <html:input
+ id="identity.signature"
+ type="text"
+ datatype="nsIFile"
+ wsm_persist="true"
+ name="identity.signature"
+ aria-labelledby="identity.attachSignature"
+ prefstring="mail.identity.%identitykey%.sig_file"
+ class="uri-element input-inline"
+ />
+ <button
+ class="push"
+ name="browse"
+ label="&choose.label;"
+ accesskey="&choose.accesskey;"
+ oncommand="selectFile()"
+ wsm_persist="true"
+ id="identity.sigbrowsebutton"
+ prefstring="mail.identity.%identitykey%.sigbrowse.disable"
+ />
+ </hbox>
+
+ <hbox align="center">
+ <checkbox
+ wsm_persist="true"
+ id="identity.attachVCard"
+ label="&attachVCard.label;"
+ flex="1"
+ accesskey="&attachVCard.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.attach_vcard"
+ />
+ <button
+ class="push"
+ name="editVCard"
+ label="&editVCard.label;"
+ accesskey="&editVCard.accesskey;"
+ oncommand="editVCard()"
+ />
+ <html:input
+ id="identity.escapedVCard"
+ type="hidden"
+ value=""
+ wsm_persist="true"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.escapedVCard"
+ />
+ </hbox>
+
+ <separator class="thin" />
+
+ <hbox align="center" class="input-container" hidefor="nntp">
+ <checkbox
+ id="identity.catchAll"
+ wsm_persist="true"
+ prefattribute="value"
+ label="&catchAll.label;"
+ accesskey="&catchAll.accesskey;"
+ style="margin-block: auto"
+ prefstring="mail.identity.%identitykey%.catchAll"
+ />
+ <html:input
+ id="identity.catchAllHint"
+ type="text"
+ wsm_persist="true"
+ prefstring="mail.identity.%identitykey%.catchAllHint"
+ class="input-inline"
+ oninput="handleInputCatchAllHint(event);"
+ placeholder="list@example.com, *@example.com"
+ aria-labelledby="identity.catchAll"
+ />
+ </hbox>
+
+ <separator class="thin" />
+
+ <hbox align="center">
+ <label
+ value="&smtpName.label;"
+ control="identity.smtpServerKey"
+ accesskey="&smtpName.accesskey;"
+ />
+ <menulist
+ wsm_persist="true"
+ id="identity.smtpServerKey"
+ flex="1"
+ pref="true"
+ preftype="string"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.smtpServer"
+ >
+ <menupopup id="smtpPopup">
+ <menuitem
+ value=""
+ label="&smtpDefaultServer.label;"
+ id="useDefaultItem"
+ />
+ <menuseparator />
+ <!-- list will be inserted here -->
+ </menupopup>
+ </menulist>
+ <button
+ id="editSmtp"
+ label="&smtpServerEdit.label;"
+ accesskey="&smtpServerEdit.accesskey;"
+ oncommand="editCurrentSMTP();"
+ />
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <separator class="thin" />
+
+ <hbox align="center">
+ <spacer flex="1" />
+ <button
+ label="&manageIdentities.label;"
+ oncommand="manageIdentities(event);"
+ accesskey="&manageIdentities.accesskey;"
+ wsm_persist="true"
+ id="identity.manageIdentitiesbutton"
+ />
+ </hbox>
+ </vbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-offline.js b/comm/mailnews/base/prefs/content/am-offline.js
new file mode 100644
index 0000000000..72158edace
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-offline.js
@@ -0,0 +1,435 @@
+/* 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 am-prefs.js */
+/* import-globals-from ../../content/retention.js */
+
+var gIncomingServer;
+var gServerType;
+var gImapIncomingServer;
+var gPref = null;
+var gLockedPref = {};
+var gOfflineMap = null; // map of folder URLs to offline flags
+var gOfflineFolders; // initial state of allFoldersOffline checkbox
+var gToggleOccurred = false;
+
+function onInit(aPageId, aServerId) {
+ onLockPreference();
+
+ // init values here
+ initServerSettings();
+ initRetentionSettings();
+ initDownloadSettings();
+ initOfflineSettings();
+
+ onCheckItem1("offline.notDownloadMin", "offline.notDownload");
+ onCheckItem1("nntp.downloadMsgMin", "nntp.downloadMsg");
+ onCheckItem1("nntp.removeBodyMin", "nntp.removeBody");
+ onCheckKeepMsg();
+}
+
+/**
+ * Store initial offline flag for each folder and the allFoldersOffline
+ * checkbox. Use to restore the flags and checkbox if edits are canceled.
+ */
+function initOfflineSettings() {
+ gOfflineMap = collectOfflineFolders();
+ gOfflineFolders = document.getElementById("offline.folders").checked;
+ gToggleOccurred = false;
+}
+
+function initServerSettings() {
+ document.getElementById("offline.notDownload").checked =
+ gIncomingServer.limitOfflineMessageSize;
+ document.getElementById("autosyncNotDownload").checked =
+ gIncomingServer.limitOfflineMessageSize;
+ if (gIncomingServer.maxMessageSize > 0) {
+ document.getElementById("offline.notDownloadMin").value =
+ gIncomingServer.maxMessageSize;
+ } else {
+ document.getElementById("offline.notDownloadMin").value = "50";
+ }
+
+ if (gServerType == "imap") {
+ gImapIncomingServer = gIncomingServer.QueryInterface(
+ Ci.nsIImapIncomingServer
+ );
+ document.getElementById("offline.folders").checked =
+ gImapIncomingServer.offlineDownload;
+ }
+}
+
+function initRetentionSettings() {
+ let retentionSettings = gIncomingServer.retentionSettings;
+ initCommonRetentionSettings(retentionSettings);
+
+ document.getElementById("nntp.removeBody").checked =
+ retentionSettings.cleanupBodiesByDays;
+ document.getElementById("nntp.removeBodyMin").value =
+ retentionSettings.daysToKeepBodies > 0
+ ? retentionSettings.daysToKeepBodies
+ : 30;
+}
+
+function initDownloadSettings() {
+ let downloadSettings = gIncomingServer.downloadSettings;
+ document.getElementById("nntp.downloadMsg").checked =
+ downloadSettings.downloadByDate;
+ document.getElementById("nntp.notDownloadRead").checked =
+ downloadSettings.downloadUnreadOnly;
+ document.getElementById("nntp.downloadMsgMin").value =
+ downloadSettings.ageLimitOfMsgsToDownload > 0
+ ? downloadSettings.ageLimitOfMsgsToDownload
+ : 30;
+
+ // Figure out what the most natural division of the autosync pref into
+ // a value and an interval is.
+ let autosyncSelect = document.getElementById("autosyncSelect");
+ let autosyncInterval = document.getElementById("autosyncInterval");
+ let autosyncValue = document.getElementById("autosyncValue");
+ let autosyncPref = document.getElementById("imap.autoSyncMaxAgeDays");
+ let autosyncPrefValue =
+ autosyncPref.value == "" ? -1 : parseInt(autosyncPref.value, 10);
+
+ // Clear the preference until we're done initializing.
+ autosyncPref.value = "";
+
+ if (autosyncPrefValue <= 0) {
+ // Special-case values <= 0 to have an interval of "All" and disabled
+ // controls for value and interval.
+ autosyncSelect.value = 0;
+ autosyncInterval.value = 1;
+ autosyncInterval.disabled = true;
+ autosyncValue.value = 30;
+ autosyncValue.disabled = true;
+ } else {
+ // Otherwise, get the list of possible intervals, in order from
+ // largest to smallest.
+ let valuesToTest = [];
+ for (let i = autosyncInterval.itemCount - 1; i >= 0; i--) {
+ valuesToTest.push(autosyncInterval.getItemAtIndex(i).value);
+ }
+
+ // and find the first one that divides the preference evenly.
+ for (let i in valuesToTest) {
+ if (!(autosyncPrefValue % valuesToTest[i])) {
+ autosyncSelect.value = 1;
+ autosyncInterval.value = valuesToTest[i];
+ autosyncValue.value = autosyncPrefValue / autosyncInterval.value;
+ break;
+ }
+ }
+ autosyncInterval.disabled = false;
+ autosyncValue.disabled = false;
+ }
+ autosyncPref.value = autosyncPrefValue;
+}
+
+function onPreInit(account, accountValues) {
+ gServerType = top.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "type",
+ null,
+ false
+ );
+ hideShowControls(gServerType);
+ gIncomingServer = account.incomingServer;
+ gIncomingServer.type = gServerType;
+
+ // 10 is OFFLINE_SUPPORT_LEVEL_REGULAR, see nsIMsgIncomingServer.idl
+ // currently, there is no offline without diskspace
+ var titleStringID =
+ gIncomingServer.offlineSupportLevel >= 10
+ ? "prefPanel-synchronization"
+ : "prefPanel-diskspace";
+
+ var prefBundle = document.getElementById("bundle_prefs");
+ document
+ .querySelector("#headertitle > .dialogheader-title")
+ .setAttribute("value", prefBundle.getString(titleStringID));
+ document.title = prefBundle.getString(titleStringID);
+
+ if (gServerType == "pop3") {
+ var pop3Server = gIncomingServer.QueryInterface(Ci.nsIPop3IncomingServer);
+ // hide retention settings for deferred accounts
+ if (pop3Server.deferredToAccount.length) {
+ var retentionRadio = document.getElementById("retention.keepMsg");
+ retentionRadio.setAttribute("hidden", "true");
+ var retentionLabel = document.getElementById("retentionDescriptionPop");
+ retentionLabel.setAttribute("hidden", "true");
+ var applyToFlaggedCheckbox = document.getElementById(
+ "retention.applyToFlagged"
+ );
+ applyToFlaggedCheckbox.setAttribute("hidden", "true");
+ }
+ }
+}
+
+function onClickSelect() {
+ parent.gSubDialog.open(
+ "chrome://messenger/content/msgSelectOfflineFolders.xhtml"
+ );
+}
+
+/**
+ * Handle updates to the Autosync
+ */
+function onAutosyncChange() {
+ let autosyncSelect = document.getElementById("autosyncSelect");
+ let autosyncInterval = document.getElementById("autosyncInterval");
+ let autosyncValue = document.getElementById("autosyncValue");
+ let autosyncPref = document.getElementById("imap.autoSyncMaxAgeDays");
+
+ // If we're not done initializing, don't do anything.
+ // (See initDownloadSettings() for more details.)
+ if (autosyncPref.value == "") {
+ return;
+ }
+
+ // If the user selected the All option, disable the autosync and the
+ // textbox.
+ if (autosyncSelect.value == 0) {
+ autosyncPref.value = -1;
+ autosyncInterval.disabled = true;
+ autosyncValue.disabled = true;
+ return;
+ }
+
+ let max = 0x7fffffff / (60 * 60 * 24 * autosyncInterval.value);
+ autosyncValue.setAttribute("max", max);
+ if (autosyncValue.value > max) {
+ autosyncValue.value = Math.floor(max);
+ }
+
+ autosyncInterval.disabled = false;
+ autosyncValue.disabled = false;
+ autosyncPref.value = autosyncValue.value * autosyncInterval.value;
+}
+
+function onAutosyncNotDownload() {
+ // This function is called when the autosync version of offline.notDownload
+ // is changed it simply copies the new checkbox value over to the element
+ // driving the preference.
+ document.getElementById("offline.notDownload").checked =
+ document.getElementById("autosyncNotDownload").checked;
+ onCheckItem1("offline.notDownloadMin", "offline.notDownload");
+}
+
+function onCancel() {
+ // restore the offline flags for all folders
+ restoreOfflineFolders(gOfflineMap);
+ document.getElementById("offline.folders").checked = gOfflineFolders;
+}
+
+/**
+ * Prompt to avoid unexpected folder sync changes.
+ */
+function onLeave() {
+ let changed = false;
+ if (gToggleOccurred) {
+ for (let folder of gIncomingServer.rootFolder.descendants) {
+ if (
+ gOfflineMap[folder.folderURL] !=
+ folder.getFlag(Ci.nsMsgFolderFlags.Offline)
+ ) {
+ // A change to the Offline flag to a folder was made.
+ changed = true;
+ break;
+ }
+ }
+ gToggleOccurred = false;
+ }
+
+ if (changed) {
+ // The user changed the "Keep messages in all folders..." checkbox and
+ // caused changes in online/offline status for all folders in this
+ // account. Prompt whether to restore the original status.
+ let prefBundle = document.getElementById("bundle_prefs");
+ let title = prefBundle.getString("confirmSyncChangesTitle");
+ let question = prefBundle.getString("confirmSyncChanges");
+ let discard = prefBundle.getString("confirmSyncChangesDiscard");
+ let result = Services.prompt.confirmEx(
+ window,
+ title,
+ question,
+ Services.prompt.BUTTON_TITLE_SAVE * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1,
+ null,
+ discard,
+ null,
+ null,
+ { value: 0 }
+ );
+ if (result == 1) {
+ // User clicked Discard button, so restore the online/offline changes for
+ // the current account. Changes made through the "Advanced..." dialog to
+ // other accounts will not be restored.
+ onCancel();
+ return false;
+ }
+ }
+ return true;
+}
+
+function onSave() {
+ var downloadSettings = Cc[
+ "@mozilla.org/msgDatabase/downloadSettings;1"
+ ].createInstance(Ci.nsIMsgDownloadSettings);
+
+ gIncomingServer.limitOfflineMessageSize = document.getElementById(
+ "offline.notDownload"
+ ).checked;
+ gIncomingServer.maxMessageSize = document.getElementById(
+ "offline.notDownloadMin"
+ ).value;
+
+ var retentionSettings = saveCommonRetentionSettings(
+ gIncomingServer.retentionSettings
+ );
+
+ retentionSettings.daysToKeepBodies =
+ document.getElementById("nntp.removeBodyMin").value;
+ retentionSettings.cleanupBodiesByDays =
+ document.getElementById("nntp.removeBody").checked;
+
+ downloadSettings.downloadByDate =
+ document.getElementById("nntp.downloadMsg").checked;
+ downloadSettings.downloadUnreadOnly = document.getElementById(
+ "nntp.notDownloadRead"
+ ).checked;
+ downloadSettings.ageLimitOfMsgsToDownload = document.getElementById(
+ "nntp.downloadMsgMin"
+ ).value;
+
+ gIncomingServer.retentionSettings = retentionSettings;
+ gIncomingServer.downloadSettings = downloadSettings;
+
+ if (gImapIncomingServer) {
+ // Set the pref on the incomingserver, and set the flag on all folders.
+ gImapIncomingServer.offlineDownload =
+ document.getElementById("offline.folders").checked;
+ }
+}
+
+// Does the work of disabling an element given the array which contains xul id/prefstring pairs.
+// Also saves the id/locked state in an array so that other areas of the code can avoid
+// stomping on the disabled state indiscriminately.
+function disableIfLocked(prefstrArray) {
+ for (let i = 0; i < prefstrArray.length; i++) {
+ var id = prefstrArray[i].id;
+ var element = document.getElementById(id);
+ if (gPref.prefIsLocked(prefstrArray[i].prefstring)) {
+ element.disabled = true;
+ gLockedPref[id] = true;
+ } else {
+ element.removeAttribute("disabled");
+ gLockedPref[id] = false;
+ }
+ }
+}
+
+// Disables xul elements that have associated preferences locked.
+function onLockPreference() {
+ var initPrefString = "mail.server";
+ var finalPrefString;
+
+ // This panel does not use the code in AccountManager.js to handle
+ // the load/unload/disable. keep in mind new prefstrings and changes
+ // to code in AccountManager, and update these as well.
+ var allPrefElements = [
+ { prefstring: "limit_offline_message_size", id: "offline.notDownload" },
+ { prefstring: "limit_offline_message_size", id: "autosyncNotDownload" },
+ { prefstring: "max_size", id: "offline.notDownloadMin" },
+ { prefstring: "downloadUnreadOnly", id: "nntp.notDownloadRead" },
+ { prefstring: "downloadByDate", id: "nntp.downloadMsg" },
+ { prefstring: "ageLimit", id: "nntp.downloadMsgMin" },
+ { prefstring: "retainBy", id: "retention.keepMsg" },
+ { prefstring: "daysToKeepHdrs", id: "retention.keepOldMsgMin" },
+ { prefstring: "numHdrsToKeep", id: "retention.keepNewMsgMin" },
+ { prefstring: "daysToKeepBodies", id: "nntp.removeBodyMin" },
+ { prefstring: "cleanupBodies", id: "nntp.removeBody" },
+ { prefstring: "applyToFlagged", id: "retention.applyToFlagged" },
+ { prefstring: "disable_button.selectFolder", id: "selectNewsgroupsButton" },
+ {
+ prefstring: "disable_button.selectFolder",
+ id: "selectImapFoldersButton",
+ },
+ ];
+
+ finalPrefString = initPrefString + "." + gIncomingServer.key + ".";
+ gPref = Services.prefs.getBranch(finalPrefString);
+
+ disableIfLocked(allPrefElements);
+}
+
+// XXX TODO: Function should be merged with onCheckItem in bug 755885.
+function onCheckItem1(changeElementId, checkElementId) {
+ var element = document.getElementById(changeElementId);
+ var checked = document.getElementById(checkElementId).checked;
+ if (checked && !gLockedPref[checkElementId]) {
+ element.removeAttribute("disabled");
+ } else {
+ element.setAttribute("disabled", "true");
+ }
+}
+
+function toggleOffline() {
+ let offline = document.getElementById("offline.folders").checked;
+ for (let folder of gIncomingServer.rootFolder.descendants) {
+ if (offline) {
+ folder.setFlag(Ci.nsMsgFolderFlags.Offline);
+ } else {
+ folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+ }
+ }
+ gToggleOccurred = true;
+}
+
+function collectOfflineFolders() {
+ let offlineFolderMap = {};
+ for (let folder of gIncomingServer.rootFolder.descendants) {
+ offlineFolderMap[folder.folderURL] = folder.getFlag(
+ Ci.nsMsgFolderFlags.Offline
+ );
+ }
+
+ return offlineFolderMap;
+}
+
+function restoreOfflineFolders(offlineFolderMap) {
+ for (let folder of gIncomingServer.rootFolder.descendants) {
+ if (offlineFolderMap[folder.folderURL]) {
+ folder.setFlag(Ci.nsMsgFolderFlags.Offline);
+ } else {
+ folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+ }
+ }
+}
+
+/**
+ * Checks if the user selected a permanent removal of messages from a server
+ * listed in the confirmfor attribute and warns about it.
+ *
+ * @param {Element} aRadio - The radiogroup element containing the retention options.
+ */
+function warnServerRemove(aRadio) {
+ let confirmFor = aRadio.getAttribute("confirmfor");
+
+ if (
+ confirmFor &&
+ confirmFor.split(",").includes(gServerType) &&
+ aRadio.value != 1
+ ) {
+ let prefBundle = document.getElementById("bundle_prefs");
+ let title = prefBundle.getString("removeFromServerTitle");
+ let question = prefBundle.getString("removeFromServer");
+ if (!Services.prompt.confirm(window, title, question)) {
+ // If the user doesn't agree, fall back to not deleting anything.
+ aRadio.value = 1;
+ onCheckKeepMsg();
+ }
+ }
+}
diff --git a/comm/mailnews/base/prefs/content/am-offline.xhtml b/comm/mailnews/base/prefs/content/am-offline.xhtml
new file mode 100644
index 0000000000..0d3a12e396
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-offline.xhtml
@@ -0,0 +1,350 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-offline.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <head>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/retention.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-offline.js"
+ ></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => {
+ parent.onPanelLoaded("am-offline.xhtml");
+ });
+ </script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <vbox id="containerBox" flex="1">
+ <stringbundle
+ id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"
+ />
+
+ <label hidden="true" wsm_persist="true" id="server.type" />
+ <label
+ id="imap.autoSyncMaxAgeDays"
+ hidden="true"
+ wsm_persist="true"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.autosync_max_age_days"
+ />
+
+ <hbox id="headertitle" class="dialogheader">
+ <label class="dialogheader-title" />
+ </hbox>
+
+ <separator class="thin" />
+
+ <html:div>
+ <html:fieldset id="offline.titlebox" hidefor="pop3,none,rss">
+ <html:legend>&syncGroupTitle.label;</html:legend>
+
+ <vbox>
+ <checkbox
+ hidefor="pop3,nntp,none"
+ id="offline.folders"
+ label="&allFoldersOffline2.label;"
+ oncommand="toggleOffline()"
+ accesskey="&allFoldersOffline2.accesskey;"
+ />
+
+ <description hidefor="pop3,nntp,none,rss"
+ >&allFoldersOfflineNote.label;</description
+ >
+
+ <separator class="thin" hidefor="pop3,nntp,none" />
+
+ <hbox hidefor="pop3,nntp,none" pack="end">
+ <button
+ label="&offlineImapAdvancedOffline.label;"
+ accesskey="&offlineImapAdvancedOffline.accesskey;"
+ oncommand="onClickSelect()"
+ id="selectImapFoldersButton"
+ class="selectForOfflineUseButton"
+ />
+ </hbox>
+
+ <hbox hidefor="pop3,imap,none" pack="end">
+ <button
+ label="&offlineSelectNntp.label;"
+ accesskey="&offlineSelectNntp.accesskey;"
+ oncommand="onClickSelect()"
+ id="selectNewsgroupsButton"
+ class="selectForOfflineUseButton"
+ />
+ </hbox>
+ </vbox>
+ </html:fieldset>
+ </html:div>
+
+ <html:div>
+ <html:fieldset id="diskspace.titlebox">
+ <html:legend hidefor="pop3,none,rss"
+ >&diskspaceGroupTitle.label;</html:legend
+ >
+
+ <description hidefor="pop3,nntp,none,rss"
+ >&doNotDownloadImap.label;</description
+ >
+ <description hidefor="pop3,imap,none,rss"
+ >&doNotDownloadNntp.label;</description
+ >
+ <description hidefor="imap,nntp,none,rss"
+ >&doNotDownloadPop3Movemail.label;</description
+ >
+
+ <!-- IMAP Autosync Preference -->
+ <radiogroup
+ hidefor="pop3,nntp,none,rss"
+ id="autosyncSelect"
+ class="indent"
+ >
+ <radio
+ id="useAutosync.AllMsg"
+ value="0"
+ accesskey="&allAutosync.accesskey;"
+ label="&allAutosync.label;"
+ oncommand="onAutosyncChange();"
+ />
+ <hbox flex="1" align="center">
+ <radio
+ id="useAutosync.ByAge"
+ accesskey="&ageAutosync.accesskey;"
+ value="1"
+ label="&ageAutosyncBefore.label;"
+ oncommand="onAutosyncChange();"
+ />
+ <html:input
+ id="autosyncValue"
+ type="number"
+ class="size4 input-inline autosync"
+ min="1"
+ onchange="onAutosyncChange();"
+ aria-labelledby="ageAutosyncBefore autosyncValue ageAutosyncMiddle autosyncInterval ageAutosyncAfter"
+ />
+ <label
+ id="ageAutosyncMiddle"
+ control="autosyncValue"
+ value="&ageAutosyncMiddle.label;"
+ />
+ <menulist id="autosyncInterval" onselect="onAutosyncChange();">
+ <menupopup>
+ <menuitem label="&dayAgeInterval.label;" value="1" />
+ <menuitem label="&weekAgeInterval.label;" value="7" />
+ <menuitem label="&monthAgeInterval.label;" value="31" />
+ <menuitem label="&yearAgeInterval.label;" value="365" />
+ </menupopup>
+ </menulist>
+ <label
+ id="ageAutosyncAfter"
+ control="autosyncInterval"
+ value="&ageAutosyncAfter.label;"
+ />
+ </hbox>
+ </radiogroup>
+
+ <hbox align="center" class="indent" hidefor="rss">
+ <checkbox
+ hidefor="pop3,imap,none"
+ id="nntp.notDownloadRead"
+ wsm_persist="true"
+ label="&nntpNotDownloadRead.label;"
+ accesskey="&nntpNotDownloadRead.accesskey;"
+ />
+ </hbox>
+
+ <hbox align="center" class="indent" hidefor="none,rss">
+ <checkbox
+ wsm_persist="true"
+ id="offline.notDownload"
+ hidefor="imap"
+ label="&offlineNotDownload.label;"
+ accesskey="&offlineNotDownload.accesskey;"
+ oncommand="onCheckItem('offline.notDownloadMin', [this.id]);"
+ />
+ <checkbox
+ wsm_persist="true"
+ id="autosyncNotDownload"
+ hidefor="pop3,nntp"
+ label="&autosyncNotDownload.label;"
+ accesskey="&autosyncNotDownload.accesskey;"
+ oncommand="onAutosyncNotDownload();"
+ />
+ <html:input
+ id="offline.notDownloadMin"
+ type="number"
+ class="size4 input-inline"
+ min="1"
+ value="50"
+ wsm_persist="true"
+ aria-labelledby="offline.notDownload offline.notDownloadMin kbLabel"
+ />
+ <label
+ value="&kb.label;"
+ control="offline.notDownloadMin"
+ id="kbLabel"
+ />
+ </hbox>
+
+ <hbox align="center" class="indent" hidefor="pop3,imap,none,rss">
+ <checkbox
+ wsm_persist="true"
+ id="nntp.downloadMsg"
+ label="&nntpDownloadMsg.label;"
+ accesskey="&nntpDownloadMsg.accesskey;"
+ oncommand="onCheckItem('nntp.downloadMsgMin', [this.id]);"
+ />
+ <html:input
+ id="nntp.downloadMsgMin"
+ type="number"
+ class="size2 input-inline"
+ min="1"
+ value="30"
+ wsm_persist="true"
+ aria-labelledby="nntp.downloadMsg nntp.downloadMsgMin daysOldLabel"
+ />
+ <label
+ value="&daysOld.label;"
+ control="nntp.downloadMsgMin"
+ id="daysOldLabel"
+ />
+ </hbox>
+
+ <vbox align="start">
+ <separator hidefor="none,rss" />
+ <label
+ id="retentionDescription"
+ hidefor="imap,pop3"
+ class="desc"
+ control="retention.keepMsg"
+ >&retentionCleanup.label;</label
+ >
+ <label
+ id="retentionDescriptionImap"
+ hidefor="pop3,nntp,none,rss"
+ class="desc"
+ control="retention.keepMsg"
+ >&retentionCleanupImap.label;</label
+ >
+ <label
+ id="retentionDescriptionPop"
+ hidefor="imap,nntp,none,rss"
+ class="desc"
+ control="retention.keepMsg"
+ >&retentionCleanupPop.label;</label
+ >
+
+ <radiogroup
+ hidefor=""
+ confirmfor="imap,pop3"
+ id="retention.keepMsg"
+ class="indent"
+ oncommand="warnServerRemove(this);"
+ >
+ <radio
+ id="retention.keepAllMsg"
+ value="1"
+ accesskey="&retentionKeepAll.accesskey;"
+ label="&retentionKeepAll.label;"
+ oncommand="onCheckKeepMsg();"
+ />
+ <hbox flex="1" align="center">
+ <radio
+ id="retention.keepNewMsg"
+ accesskey="&retentionKeepRecent.accesskey;"
+ value="3"
+ label="&retentionKeepRecent.label;"
+ oncommand="onCheckKeepMsg();"
+ />
+ <html:input
+ id="retention.keepNewMsgMin"
+ type="number"
+ class="size4 input-inline"
+ min="1"
+ value="2000"
+ aria-labelledby="retention.keepNewMsg retention.keepNewMsgMin newMsgLabel"
+ />
+ <label
+ value="&message.label;"
+ control="retention.keepNewMsgMin"
+ id="newMsgLabel"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio
+ id="retention.keepOldMsg"
+ accesskey="&retentionKeepMsg.accesskey;"
+ value="2"
+ label="&retentionKeepMsg.label;"
+ oncommand="onCheckKeepMsg();"
+ />
+ <html:input
+ id="retention.keepOldMsgMin"
+ type="number"
+ class="size4 input-inline"
+ min="1"
+ value="30"
+ aria-labelledby="retention.keepOldMsg retention.keepOldMsgMin oldMsgLabel"
+ />
+ <label
+ value="&daysOld.label;"
+ control="retention.keepOldMsgMin"
+ id="oldMsgLabel"
+ />
+ </hbox>
+ </radiogroup>
+
+ <hbox align="center" class="indent">
+ <checkbox
+ id="retention.applyToFlagged"
+ label="&retentionApplyToFlagged.label;"
+ hidefor=""
+ accesskey="&retentionApplyToFlagged.accesskey;"
+ checked="true"
+ />
+ </hbox>
+ <hbox align="center" class="indent" hidefor="pop3,imap,none,rss">
+ <checkbox
+ id="nntp.removeBody"
+ accesskey="&nntpRemoveMsgBody.accesskey;"
+ label="&nntpRemoveMsgBody.label;"
+ oncommand="onCheckItem('nntp.removeBodyMin', [this.id]);"
+ />
+ <html:input
+ id="nntp.removeBodyMin"
+ type="number"
+ class="size2 input-inline"
+ min="1"
+ value="30"
+ aria-labelledby="nntp.removeBody nntp.removeBodyMin daysOldMsg"
+ />
+ <label
+ value="&daysOld.label;"
+ control="nntp.removeBodyMin"
+ id="daysOldMsg"
+ />
+ </hbox>
+ </vbox>
+ </html:fieldset>
+ </html:div>
+ </vbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-prefs.js b/comm/mailnews/base/prefs/content/am-prefs.js
new file mode 100644
index 0000000000..cff09a5d93
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-prefs.js
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+/* functions for disabling front end elements when the appropriate
+ back-end preference is locked. */
+
+/**
+ * Prefs in MailNews require dynamic portions to indicate
+ * which of multiple servers or identities. This function
+ * takes a string and a xul element.
+ *
+ * @param {string} aStr - The string is a prefstring with a token %tokenname%.
+ * @param {Element} aElement - The element has an attribute of name |tokenname|
+ * whose value is substituted into the string and returned by the function.
+ * Any tokens which do not have associated attribute value are not
+ * substituted, and left in the string as-is.
+ */
+function substPrefTokens(aStr, aElement) {
+ let tokenpat = /%(\w+)%/;
+ let token;
+ let newprefstr = "";
+
+ let prefPartsArray = aStr.split(".");
+ /* here's a little loop that goes through
+ each part of the string separated by a dot, and
+ if any parts are of the form %string%, it will replace
+ them with the value of the attribute of that name from
+ the xul object */
+ for (let i = 0; i < prefPartsArray.length; i++) {
+ token = prefPartsArray[i].match(tokenpat);
+ if (token) {
+ // We've got a %% match.
+ if (token[1]) {
+ if (aElement[token[1]]) {
+ newprefstr += aElement[token[1]] + "."; // here's where we get the info
+ } else {
+ // All we got was this stinkin %.
+ newprefstr += prefPartsArray[i] + ".";
+ }
+ }
+ } else {
+ // token is falsy.
+ newprefstr += prefPartsArray[i] + ".";
+ }
+ }
+ newprefstr = newprefstr.slice(0, -1); // remove the last char, a dot
+ if (newprefstr.length <= 0) {
+ newprefstr = null;
+ }
+
+ return newprefstr;
+}
+
+/**
+ * A simple function to check if a pref in an element is locked.
+ *
+ * @param {Element} aElement - An element with the pref related attributes
+ * (pref, preftype, prefstring)
+ * @returns {boolean} whether the prefstring specified in that element is
+ * locked (true/false). If it does not have a valid prefstring, a false is
+ * returned.
+ */
+function getAccountValueIsLocked(aElement) {
+ let prefstring = aElement.getAttribute("prefstring");
+ if (prefstring) {
+ let prefstr = substPrefTokens(prefstring, aElement);
+ // see if the prefstring is locked
+ if (prefstr) {
+ return Services.prefs.prefIsLocked(prefstr);
+ }
+ }
+ return false;
+}
+
+/**
+ * Enables/disables element (slave) according to the checked state
+ * of another elements (masters).
+ *
+ * @param {string} aChangeElementId - Slave element which should be enabled
+ * if all the checkElementIDs are checked. Otherwise it gets disabled.
+ * @param {string[]} aCheckElementIds - An array of IDs of the master elements.
+ *
+ * @see bug 728681 for the pattern on how this is used.
+ */
+function onCheckItem(aChangeElementId, aCheckElementIds) {
+ let elementToControl = document.getElementById(aChangeElementId);
+ let disabled = false;
+
+ for (let notifyId of aCheckElementIds) {
+ let notifyElement = document.getElementById(notifyId);
+ let notifyElementState = null;
+ if ("checked" in notifyElement) {
+ notifyElementState = notifyElement.checked;
+ } else if ("selected" in notifyElement) {
+ notifyElementState = notifyElement.selected;
+ } else {
+ console.error("Unknown type of control element: " + notifyElement.id);
+ }
+
+ if (!notifyElementState) {
+ disabled = true;
+ break;
+ }
+ }
+
+ if (!disabled && getAccountValueIsLocked(elementToControl)) {
+ disabled = true;
+ }
+
+ elementToControl.disabled = disabled;
+}
+
+/**
+ * Hides and shows elements relevant for the given server type.
+ *
+ * @param {string} serverType - Name of the server type for which to show/hide elements.
+ */
+function hideShowControls(serverType) {
+ let controls = document.querySelectorAll("[hidefor]");
+ for (let controlNo = 0; controlNo < controls.length; controlNo++) {
+ let control = controls[controlNo];
+ let hideFor = control.getAttribute("hidefor");
+
+ // Hide unsupported server types using hideFor="servertype1,servertype2".
+ let hide = false;
+ let hideForTokens = hideFor.split(",");
+ for (let tokenNo = 0; tokenNo < hideForTokens.length; tokenNo++) {
+ if (hideForTokens[tokenNo] == serverType) {
+ hide = true;
+ break;
+ }
+ }
+
+ if (hide) {
+ control.setAttribute("hidden", "true");
+ } else {
+ control.removeAttribute("hidden");
+ }
+ }
+}
diff --git a/comm/mailnews/base/prefs/content/am-server-advanced.js b/comm/mailnews/base/prefs/content/am-server-advanced.js
new file mode 100644
index 0000000000..a37a8eced0
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-server-advanced.js
@@ -0,0 +1,151 @@
+/* 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/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+window.addEventListener("DOMContentLoaded", onLoad);
+document.addEventListener("dialogaccept", onOk);
+
+// pull stuff out of window.arguments
+var gServerSettings = window.arguments[0];
+
+var gFirstDeferredAccount;
+// initialize the controls with the "gServerSettings" argument
+
+var gControls;
+function getControls() {
+ if (!gControls) {
+ gControls = document.getElementsByAttribute("amsa_persist", "true");
+ }
+ return gControls;
+}
+
+function getLocalFoldersAccount() {
+ return MailServices.accounts.FindAccountForServer(
+ MailServices.accounts.localFoldersServer
+ );
+}
+
+function onLoad() {
+ var prettyName = gServerSettings.serverPrettyName;
+
+ if (prettyName) {
+ document.getElementById("serverPrettyName").value = document
+ .getElementById("bundle_prefs")
+ .getFormattedString("forAccount", [prettyName]);
+ }
+
+ if (gServerSettings.serverType == "imap") {
+ document.getElementById("pop3Panel").hidden = true;
+ } else if (gServerSettings.serverType == "pop3") {
+ document.getElementById("imapPanel").hidden = true;
+ let radioGroup = document.getElementById("folderStorage");
+
+ gFirstDeferredAccount = gServerSettings.deferredToAccount;
+ let folderPopup = document.getElementById("deferredServerPopup");
+
+ // The current account should not be shown in the folder picker
+ // of the "other account" option.
+ folderPopup._teardown();
+ folderPopup.setAttribute(
+ "excludeServers",
+ gServerSettings.account.incomingServer.key
+ );
+ folderPopup._ensureInitialized();
+
+ if (gFirstDeferredAccount.length) {
+ // The current account is deferred.
+ let account = MailServices.accounts.getAccount(gFirstDeferredAccount);
+ radioGroup.value = "otherAccount";
+ folderPopup.selectFolder(account.incomingServer.rootFolder);
+ } else {
+ // Current account is not deferred.
+ radioGroup.value = "currentAccount";
+ // If there are no suitable accounts to defer to, then the menulist is
+ // disabled by the picker with an appropriate message.
+ folderPopup.selectFolder();
+ if (gServerSettings.account.incomingServer.isDeferredTo) {
+ // Some other account already defers to this account
+ // therefore this one can't be deferred further.
+ radioGroup.disabled = true;
+ }
+ }
+
+ let picker = document.getElementById("deferredServerFolderPicker");
+ picker.disabled = radioGroup.selectedIndex != 1;
+ }
+
+ var controls = getControls();
+
+ for (let i = 0; i < controls.length; i++) {
+ var slot = controls[i].id;
+ if (slot in gServerSettings) {
+ if (controls[i].localName == "checkbox") {
+ controls[i].checked = gServerSettings[slot];
+ } else {
+ controls[i].value = gServerSettings[slot];
+ }
+ }
+ }
+}
+
+function onOk(event) {
+ // Handle account deferral settings for POP3 accounts.
+ if (gServerSettings.serverType == "pop3") {
+ var radioGroup = document.getElementById("folderStorage");
+ var gPrefsBundle = document.getElementById("bundle_prefs");
+ let picker = document.getElementById("deferredServerFolderPicker");
+
+ // This account wasn't previously deferred, but is now deferred.
+ if (radioGroup.value != "currentAccount" && !gFirstDeferredAccount.length) {
+ // If the user hasn't selected a folder, keep the default.
+ if (!picker.selectedItem) {
+ return;
+ }
+
+ var confirmDeferAccount = gPrefsBundle.getString(
+ "confirmDeferAccountWarning"
+ );
+
+ var confirmTitle = gPrefsBundle.getString("confirmDeferAccountTitle");
+
+ if (!Services.prompt.confirm(window, confirmTitle, confirmDeferAccount)) {
+ event.preventDefault();
+ return;
+ }
+ }
+ switch (radioGroup.value) {
+ case "currentAccount":
+ gServerSettings.deferredToAccount = "";
+ break;
+ case "otherAccount":
+ let server = picker.selectedItem._folder.server;
+ let account = MailServices.accounts.FindAccountForServer(server);
+ gServerSettings.deferredToAccount = account.key;
+ break;
+ }
+ }
+
+ // Save the controls back to the "gServerSettings" array.
+ var controls = getControls();
+ for (let i = 0; i < controls.length; i++) {
+ var slot = controls[i].id;
+ if (slot in gServerSettings) {
+ if (controls[i].localName == "checkbox") {
+ gServerSettings[slot] = controls[i].checked;
+ } else {
+ gServerSettings[slot] = controls[i].value;
+ }
+ }
+ }
+}
+
+// Set radio element choices and picker states
+function updateInboxAccount(enablePicker) {
+ document.getElementById("deferredServerFolderPicker").disabled =
+ !enablePicker;
+ document.getElementById("deferGetNewMail").disabled = !enablePicker;
+}
diff --git a/comm/mailnews/base/prefs/content/am-server-advanced.xhtml b/comm/mailnews/base/prefs/content/am-server-advanced.xhtml
new file mode 100644
index 0000000000..b8349af38f
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-server-advanced.xhtml
@@ -0,0 +1,222 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-server-advanced.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false"
+>
+ <head>
+ <title>&serverAdvanced.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-server-advanced.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog buttons="accept,cancel" style="width: 100vw; height: 100vh">
+ <stringbundle
+ id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"
+ />
+
+ <label id="serverPrettyName" />
+
+ <separator class="thin" />
+
+ <!-- IMAP Panel -->
+ <vbox id="imapPanel">
+ <hbox align="center">
+ <label
+ id="serverDirectoryLabel"
+ value="&serverDirectory.label;"
+ accesskey="&serverDirectory.accesskey;"
+ control="serverDirectory"
+ />
+ <hbox class="input-container">
+ <html:input
+ id="serverDirectory"
+ type="text"
+ class="input-inline"
+ aria-labelledby="serverDirectoryLabel"
+ amsa_persist="true"
+ />
+ </hbox>
+ </hbox>
+
+ <checkbox
+ id="usingSubscription"
+ amsa_persist="true"
+ label="&usingSubscription.label;"
+ accesskey="&usingSubscription.accesskey;"
+ />
+
+ <checkbox
+ id="dualUseFolders"
+ amsa_persist="true"
+ label="&dualUseFolders.label;"
+ accesskey="&dualUseFolders.accesskey;"
+ />
+
+ <separator class="groove" />
+ <hbox align="center">
+ <label
+ id="maximumConnectionsNumberLabel"
+ control="maximumConnectionsNumber"
+ value="&maximumConnectionsNumber.label;"
+ accesskey="&maximumConnectionsNumber.accesskey;"
+ />
+ <html:input
+ id="maximumConnectionsNumber"
+ type="number"
+ class="size3 input-inline"
+ min="1"
+ max="1000"
+ amsa_persist="true"
+ aria-labelledby="maximumConnectionsNumberLabel"
+ />
+ </hbox>
+
+ <separator class="groove" />
+ <description>&namespaceDesc.label;</description>
+ <hbox class="indent">
+ <vbox>
+ <hbox flex="1" align="center">
+ <label
+ id="personalNamespaceLabel"
+ control="personalNamespace"
+ value="&personalNamespace.label;"
+ accesskey="&personalNamespace.accesskey;"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label
+ id="publicNamespaceLabel"
+ control="publicNamespace"
+ value="&publicNamespace.label;"
+ accesskey="&publicNamespace.accesskey;"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <label
+ id="otherUsersNamespaceLabel"
+ control="otherUsersNamespace"
+ value="&otherUsersNamespace.label;"
+ accesskey="&otherUsersNamespace.accesskey;"
+ />
+ </hbox>
+ </vbox>
+ <vbox>
+ <html:input
+ id="personalNamespace"
+ type="text"
+ class="input-inline"
+ aria-labelledby="personalNamespaceLabel"
+ amsa_persist="true"
+ />
+ <html:input
+ id="publicNamespace"
+ type="text"
+ class="input-inline"
+ aria-labelledby="publicNamespaceLabel"
+ amsa_persist="true"
+ />
+ <html:input
+ id="otherUsersNamespace"
+ type="text"
+ class="input-inline"
+ aria-labelledby="otherUsersNamespaceLabel"
+ amsa_persist="true"
+ />
+ </vbox>
+ </hbox>
+ <hbox class="indent">
+ <checkbox
+ id="overrideNamespaces"
+ amsa_persist="true"
+ label="&overrideNamespaces.label;"
+ accesskey="&overrideNamespaces.accesskey;"
+ />
+ </hbox>
+ </vbox>
+
+ <!-- POP3 Panel -->
+ <vbox id="pop3Panel">
+ <label flex="1" control="folderStorage"
+ >&pop3DeferringDesc.label;</label
+ >
+ <hbox align="center">
+ <radiogroup
+ id="folderStorage"
+ flex="1"
+ orient="horizontal"
+ amsa_persist="true"
+ onselect="updateInboxAccount(this.selectedIndex == 1);"
+ >
+ <vbox flex="1">
+ <hbox>
+ <radio
+ id="deferToCurrentAccount"
+ value="currentAccount"
+ label="&accountInbox.label;"
+ accesskey="&accountInbox.accesskey;"
+ />
+ </hbox>
+ <vbox flex="1">
+ <hbox>
+ <radio
+ id="deferToOtherAccount"
+ value="otherAccount"
+ label="&deferToServer.label;"
+ accesskey="&deferToServer.accesskey;"
+ >
+ </radio>
+ <menulist
+ id="deferredServerFolderPicker"
+ class="folderMenuItem"
+ flex="1"
+ aria-labelledby="deferToServer"
+ >
+ <menupopup
+ is="folder-menupopup"
+ id="deferredServerPopup"
+ expandFolders="false"
+ mode="deferred"
+ oncommand="this.selectFolder(event.target._folder);"
+ />
+ </menulist>
+ </hbox>
+ <checkbox
+ amsa_persist="true"
+ id="deferGetNewMail"
+ label="&deferGetNewMail.label;"
+ class="indent"
+ accesskey="&deferGetNewMail.accesskey;"
+ />
+ </vbox>
+ </vbox>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-server.js b/comm/mailnews/base/prefs/content/am-server.js
new file mode 100644
index 0000000000..f73535b35c
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-server.js
@@ -0,0 +1,615 @@
+/* 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 am-prefs.js */
+/* import-globals-from amUtils.js */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var gServer;
+var gOriginalStoreType;
+
+/**
+ * Called when the store type menu is clicked.
+ *
+ * @param {object} aStoreTypeElement - store type menu list element.
+ */
+function clickStoreTypeMenu(aStoreTypeElement) {
+ if (aStoreTypeElement.value == gOriginalStoreType) {
+ return;
+ }
+
+ // Response from migration dialog modal. If the conversion is complete
+ // 'response.newRootFolder' will hold the path to the new account root folder,
+ // otherwise 'response.newRootFolder' will be null.
+ let response = { newRootFolder: null };
+ // Send 'response' as an argument to converterDialog.xhtml.
+ window.browsingContext.topChromeWindow.openDialog(
+ "converterDialog.xhtml",
+ "mailnews:mailstoreconverter",
+ "modal,centerscreen,resizable=no,width=700,height=130",
+ gServer,
+ aStoreTypeElement.value,
+ response
+ );
+ changeStoreType(response);
+}
+
+/**
+ * Revert store type to the original store type if converter modal closes
+ * before migration is complete, otherwise change original store type to
+ * currently selected store type.
+ *
+ * @param {object} aResponse - response from migration dialog modal.
+ */
+function changeStoreType(aResponse) {
+ if (aResponse.newRootFolder) {
+ // The conversion is complete.
+ // Set local path to the new account root folder which is present
+ // in 'aResponse.newRootFolder'.
+ if (gServer.type == "nntp") {
+ let newRootFolder = aResponse.newRootFolder;
+ let lastSlash = newRootFolder.lastIndexOf("/");
+ let newsrc =
+ newRootFolder.slice(0, lastSlash) +
+ "/newsrc-" +
+ newRootFolder.slice(lastSlash + 1);
+ document.getElementById("nntp.newsrcFilePath").value = newsrc;
+ }
+
+ document.getElementById("server.localPath").value = aResponse.newRootFolder;
+ gOriginalStoreType = document.getElementById(
+ "server.storeTypeMenulist"
+ ).value;
+ MailUtils.restartApplication();
+ } else {
+ // The conversion failed or was cancelled.
+ // Restore selected item to what was selected before conversion.
+ document.getElementById("server.storeTypeMenulist").value =
+ gOriginalStoreType;
+ }
+}
+
+function onSave() {
+ let storeContractID = document.getElementById("server.storeTypeMenulist")
+ .selectedItem.value;
+ document
+ .getElementById("server.storeContractID")
+ .setAttribute("value", storeContractID);
+}
+
+function onInit(aPageId, aServerId) {
+ initServerType();
+
+ onCheckItem("server.biffMinutes", ["server.doBiff"]);
+ onCheckItem("nntp.maxArticles", ["nntp.notifyOn"]);
+ setupMailOnServerUI();
+ setupFixedUI();
+ let serverType = document.getElementById("server.type").getAttribute("value");
+ if (serverType == "imap") {
+ setupImapDeleteUI(aServerId);
+ }
+
+ // OAuth2 are only supported on IMAP and POP.
+ document.getElementById("authMethod-oauth2").hidden =
+ serverType != "imap" && serverType != "pop3";
+ // TLS Cert (External) only supported on IMAP.
+ document.getElementById("authMethod-external").hidden = serverType != "imap";
+
+ // "STARTTLS, if available" is vulnerable to MITM attacks so we shouldn't
+ // allow users to choose it anymore. Hide the option unless the user already
+ // has it set.
+ hideUnlessSelected(document.getElementById("connectionSecurityType-1"));
+
+ // UI for account store type.
+ let storeTypeElement = document.getElementById("server.storeTypeMenulist");
+ // set the menuitem to match the account
+ let currentStoreID = document
+ .getElementById("server.storeContractID")
+ .getAttribute("value");
+ let targetItem = storeTypeElement.getElementsByAttribute(
+ "value",
+ currentStoreID
+ );
+ storeTypeElement.selectedItem = targetItem[0];
+ // Disable store type change if store has not been used yet.
+ storeTypeElement.setAttribute(
+ "disabled",
+ gServer.getBoolValue("canChangeStoreType")
+ ? "false"
+ : !Services.prefs.getBoolPref("mail.store_conversion_enabled")
+ );
+ // Initialise 'gOriginalStoreType' to the item that was originally selected.
+ gOriginalStoreType = storeTypeElement.value;
+}
+
+function onPreInit(account, accountValues) {
+ var type = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "type",
+ null,
+ false
+ );
+ hideShowControls(type);
+
+ gServer = account.incomingServer;
+}
+
+function initServerType() {
+ var serverType = document.getElementById("server.type").getAttribute("value");
+ var propertyName = "serverType-" + serverType;
+
+ var messengerBundle = document.getElementById("bundle_messenger");
+ var verboseName;
+ try {
+ verboseName = messengerBundle.getString(propertyName);
+ } catch (e) {
+ // Addon-provided server types do not have a description string,
+ // then display the raw server type.
+ verboseName = serverType;
+ }
+ setDivText("servertypeVerbose", verboseName);
+
+ secureSelect(true);
+
+ setLabelFromStringBundle("authMethod-no", "authNo");
+ setLabelFromStringBundle("authMethod-old", "authOld");
+ setLabelFromStringBundle("authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("authMethod-external", "authExternal");
+ setLabelFromStringBundle("authMethod-ntlm", "authNTLM");
+ setLabelFromStringBundle("authMethod-oauth2", "authOAuth2");
+ setLabelFromStringBundle("authMethod-anysecure", "authAnySecure");
+ setLabelFromStringBundle("authMethod-any", "authAny");
+ setLabelFromStringBundle(
+ "authMethod-password-encrypted",
+ "authPasswordEncrypted"
+ );
+ // authMethod-password-cleartext already set in secureSelect()
+
+ // Hide deprecated/hidden auth options, unless selected
+ hideUnlessSelected(document.getElementById("authMethod-no"));
+ hideUnlessSelected(document.getElementById("authMethod-old"));
+ hideUnlessSelected(document.getElementById("authMethod-anysecure"));
+ hideUnlessSelected(document.getElementById("authMethod-any"));
+}
+
+function hideUnlessSelected(element) {
+ element.hidden = !element.selected;
+}
+
+function setLabelFromStringBundle(elementID, stringName) {
+ document.getElementById(elementID).label = document
+ .getElementById("bundle_messenger")
+ .getString(stringName);
+}
+
+function setDivText(divname, value) {
+ var div = document.getElementById(divname);
+ if (!div) {
+ return;
+ }
+ div.setAttribute("value", value);
+}
+
+function onAdvanced() {
+ // Store the server type and, if an IMAP or POP3 server,
+ // the settings needed for the IMAP/POP3 tab into the array
+ var serverSettings = {};
+ var serverType = document.getElementById("server.type").getAttribute("value");
+ serverSettings.serverType = serverType;
+
+ serverSettings.serverPrettyName = gServer.prettyName;
+ serverSettings.account = top.getCurrentAccount();
+
+ if (serverType == "imap") {
+ serverSettings.dualUseFolders = document.getElementById(
+ "imap.dualUseFolders"
+ ).checked;
+ serverSettings.usingSubscription = document.getElementById(
+ "imap.usingSubscription"
+ ).checked;
+ serverSettings.maximumConnectionsNumber = document
+ .getElementById("imap.maximumConnectionsNumber")
+ .getAttribute("value");
+ serverSettings.personalNamespace = document
+ .getElementById("imap.personalNamespace")
+ .getAttribute("value");
+ serverSettings.publicNamespace = document
+ .getElementById("imap.publicNamespace")
+ .getAttribute("value");
+ serverSettings.serverDirectory = document
+ .getElementById("imap.serverDirectory")
+ .getAttribute("value");
+ serverSettings.otherUsersNamespace = document
+ .getElementById("imap.otherUsersNamespace")
+ .getAttribute("value");
+ serverSettings.overrideNamespaces = document.getElementById(
+ "imap.overrideNamespaces"
+ ).checked;
+ } else if (serverType == "pop3") {
+ serverSettings.deferGetNewMail = document.getElementById(
+ "pop3.deferGetNewMail"
+ ).checked;
+ serverSettings.deferredToAccount = document
+ .getElementById("pop3.deferredToAccount")
+ .getAttribute("value");
+ }
+
+ let onCloseAdvanced = function () {
+ if (serverType == "imap") {
+ document.getElementById("imap.dualUseFolders").checked =
+ serverSettings.dualUseFolders;
+ document.getElementById("imap.usingSubscription").checked =
+ serverSettings.usingSubscription;
+ document
+ .getElementById("imap.maximumConnectionsNumber")
+ .setAttribute("value", serverSettings.maximumConnectionsNumber);
+ document
+ .getElementById("imap.personalNamespace")
+ .setAttribute("value", serverSettings.personalNamespace);
+ document
+ .getElementById("imap.publicNamespace")
+ .setAttribute("value", serverSettings.publicNamespace);
+ document
+ .getElementById("imap.serverDirectory")
+ .setAttribute("value", serverSettings.serverDirectory);
+ document
+ .getElementById("imap.otherUsersNamespace")
+ .setAttribute("value", serverSettings.otherUsersNamespace);
+ document.getElementById("imap.overrideNamespaces").checked =
+ serverSettings.overrideNamespaces;
+ } else if (serverType == "pop3") {
+ document.getElementById("pop3.deferGetNewMail").checked =
+ serverSettings.deferGetNewMail;
+ document
+ .getElementById("pop3.deferredToAccount")
+ .setAttribute("value", serverSettings.deferredToAccount);
+ let pop3Server = gServer.QueryInterface(Ci.nsIPop3IncomingServer);
+ // we're explicitly setting this so we'll go through the SetDeferredToAccount method
+ pop3Server.deferredToAccount = serverSettings.deferredToAccount;
+ // Setting the server to be deferred causes a rebuild of the account tree,
+ // losing the current selection. Reselect the current server again as it
+ // didn't really disappear.
+ parent.selectServer(
+ parent.getCurrentAccount().incomingServer,
+ parent.currentPageId
+ );
+
+ // Iterate over all accounts to see if any of their junk targets are now
+ // invalid (pointed to the account that is now deferred).
+ // If any such target is found it is reset to a new safe folder
+ // (the deferred to account or Local Folders). If junk was really moved
+ // to that folder (moveOnSpam = true) then moving junk is disabled
+ // (so that the user notices it and checks the settings).
+ // This is the same sanitization as in am-junk.js, just applied to all POP accounts.
+ let deferredURI =
+ serverSettings.deferredToAccount &&
+ MailServices.accounts.getAccount(serverSettings.deferredToAccount)
+ .incomingServer.serverURI;
+
+ for (let account of MailServices.accounts.accounts) {
+ let accountValues = parent.getValueArrayFor(account);
+ let type = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "type",
+ null,
+ false
+ );
+ // Try to keep this list of account types not having Junk settings
+ // synchronized with the list in AccountManager.js.
+ if (type != "nntp" && type != "rss" && type != "im") {
+ let spamActionTargetAccount = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "spamActionTargetAccount",
+ "string",
+ true
+ );
+ let spamActionTargetFolder = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "spamActionTargetFolder",
+ "wstring",
+ true
+ );
+ let moveOnSpam = parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "moveOnSpam",
+ "bool",
+ true
+ );
+
+ // Check if there are any invalid junk targets and fix them.
+ [spamActionTargetAccount, spamActionTargetFolder, moveOnSpam] =
+ sanitizeJunkTargets(
+ spamActionTargetAccount,
+ spamActionTargetFolder,
+ deferredURI || account.incomingServer.serverURI,
+ parent.getAccountValue(
+ account,
+ accountValues,
+ "server",
+ "moveTargetMode",
+ "int",
+ true
+ ),
+ account.incomingServer.spamSettings,
+ moveOnSpam
+ );
+
+ parent.setAccountValue(
+ accountValues,
+ "server",
+ "moveOnSpam",
+ moveOnSpam
+ );
+ parent.setAccountValue(
+ accountValues,
+ "server",
+ "spamActionTargetAccount",
+ spamActionTargetAccount
+ );
+ parent.setAccountValue(
+ accountValues,
+ "server",
+ "spamActionTargetFolder",
+ spamActionTargetFolder
+ );
+ }
+ }
+ }
+ document.dispatchEvent(new CustomEvent("prefchange"));
+ };
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/am-server-advanced.xhtml",
+ { closingCallback: onCloseAdvanced },
+ serverSettings
+ );
+}
+
+function secureSelect(aLoading) {
+ var socketType = document.getElementById("server.socketType").value;
+ var defaultPort = gServer.protocolInfo.getDefaultServerPort(false);
+ var defaultPortSecure = gServer.protocolInfo.getDefaultServerPort(true);
+ var port = document.getElementById("server.port");
+ var portDefault = document.getElementById("defaultPort");
+ var prevDefaultPort = portDefault.value;
+
+ if (socketType == Ci.nsMsgSocketType.SSL) {
+ portDefault.value = defaultPortSecure;
+ if (
+ port.value == "" ||
+ (!aLoading &&
+ port.value == defaultPort &&
+ prevDefaultPort != portDefault.value)
+ ) {
+ port.value = defaultPortSecure;
+ }
+ } else {
+ portDefault.value = defaultPort;
+ if (
+ port.value == "" ||
+ (!aLoading &&
+ port.value == defaultPortSecure &&
+ prevDefaultPort != portDefault.value)
+ ) {
+ port.value = defaultPort;
+ }
+ }
+
+ // switch "insecure password" label
+ setLabelFromStringBundle(
+ "authMethod-password-cleartext",
+ socketType == Ci.nsMsgSocketType.SSL ||
+ socketType == Ci.nsMsgSocketType.alwaysSTARTTLS
+ ? "authPasswordCleartextViaSSL"
+ : "authPasswordCleartextInsecurely"
+ );
+}
+
+function setupMailOnServerUI() {
+ onCheckItem("pop3.deleteMailLeftOnServer", ["pop3.leaveMessagesOnServer"]);
+ setupAgeMsgOnServerUI();
+}
+
+function setupAgeMsgOnServerUI() {
+ const kLeaveMsgsId = "pop3.leaveMessagesOnServer";
+ const kDeleteByAgeId = "pop3.deleteByAgeFromServer";
+ onCheckItem(kDeleteByAgeId, [kLeaveMsgsId]);
+ onCheckItem("daysEnd", [kLeaveMsgsId]);
+ onCheckItem("pop3.numDaysToLeaveOnServer", [kLeaveMsgsId, kDeleteByAgeId]);
+}
+
+function setupFixedUI() {
+ var controls = [
+ document.getElementById("fixedServerName"),
+ document.getElementById("fixedUserName"),
+ document.getElementById("fixedServerPort"),
+ ];
+
+ var len = controls.length;
+ for (let i = 0; i < len; i++) {
+ var fixedElement = controls[i];
+ var otherElement = document.getElementById(
+ fixedElement.getAttribute("use")
+ );
+
+ fixedElement.setAttribute("collapsed", "true");
+ otherElement.removeAttribute("collapsed");
+ }
+}
+
+function BrowseForNewsrc() {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ const nsIFile = Ci.nsIFile;
+
+ var newsrcTextBox = document.getElementById("nntp.newsrcFilePath");
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(
+ window,
+ document.getElementById("browseForNewsrc").getAttribute("filepickertitle"),
+ nsIFilePicker.modeSave
+ );
+
+ var currentNewsrcFile;
+ try {
+ currentNewsrcFile = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile);
+ currentNewsrcFile.initWithPath(newsrcTextBox.value);
+ } catch (e) {
+ dump("Failed to create nsIFile instance for the current newsrc file.\n");
+ }
+
+ if (currentNewsrcFile) {
+ fp.displayDirectory = currentNewsrcFile.parent;
+ fp.defaultString = currentNewsrcFile.leafName;
+ }
+
+ fp.appendFilters(nsIFilePicker.filterAll);
+
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.file) {
+ return;
+ }
+ newsrcTextBox.value = fp.file.path;
+ newsrcTextBox.dispatchEvent(new CustomEvent("change"));
+ });
+}
+
+function setupImapDeleteUI(aServerId) {
+ // read delete_model preference
+ let deleteModel = document
+ .getElementById("imap.deleteModel")
+ .getAttribute("value");
+ selectImapDeleteModel(deleteModel);
+
+ // read trash folder path preference
+ let trashFolderName = getTrashFolderName();
+
+ // set folderPicker menulist
+ let trashPopup = document.getElementById("msgTrashFolderPopup");
+ trashPopup._teardown();
+ trashPopup._parentFolder = MailUtils.getOrCreateFolder(aServerId);
+ trashPopup._ensureInitialized();
+
+ // Escape backslash and double-quote with another backslash before encoding.
+ let trashEscaped = trashFolderName.replace(/([\\"])/g, "\\$1");
+
+ // Convert the folder path from JS Unicode to MUTF-7 if necessary.
+ let imapServer = trashPopup._parentFolder.server.QueryInterface(
+ Ci.nsIImapIncomingServer
+ );
+
+ let trashFolder;
+ if (imapServer.utf8AcceptEnabled) {
+ // Trash folder with UTF8=ACCEPT capability in effect.
+ trashFolder = MailUtils.getOrCreateFolder(aServerId + "/" + trashEscaped);
+ } else {
+ // Traditional MUTF-7.
+ let manager = Cc["@mozilla.org/charset-converter-manager;1"].getService(
+ Ci.nsICharsetConverterManager
+ );
+ trashFolder = MailUtils.getOrCreateFolder(
+ aServerId + "/" + manager.unicodeToMutf7(trashEscaped)
+ );
+ }
+ trashPopup.selectFolder(trashFolder);
+ trashPopup.parentNode.folder = trashFolder;
+}
+
+function selectImapDeleteModel(choice) {
+ // set deleteModel to selected mode
+ document.getElementById("imap.deleteModel").setAttribute("value", choice);
+
+ switch (choice) {
+ case "0": // markDeleted
+ // disable folderPicker
+ document
+ .getElementById("msgTrashFolderPicker")
+ .setAttribute("disabled", "true");
+ break;
+ case "1": // moveToTrashFolder
+ // enable folderPicker
+ document
+ .getElementById("msgTrashFolderPicker")
+ .removeAttribute("disabled");
+ break;
+ case "2": // deleteImmediately
+ // disable folderPicker
+ document
+ .getElementById("msgTrashFolderPicker")
+ .setAttribute("disabled", "true");
+ break;
+ default:
+ dump("Error in enabling/disabling server.TrashFolderPicker\n");
+ break;
+ }
+}
+
+// Capture any menulist changes from folderPicker
+function folderPickerChange(aEvent) {
+ let folder = aEvent.target._folder;
+ // Since we need to deal with localised folder names, we simply use
+ // the path of the URI like we do in nsImapIncomingServer::DiscoveryDone().
+ // Note that the path is returned with a leading slash which we need to remove.
+ let folderPath = Services.io.newURI(folder.URI).pathQueryRef.substring(1);
+ let folderPathUnescaped = Services.io.unescapeString(
+ folderPath,
+ Ci.nsINetUtil.ESCAPE_URL_PATH
+ );
+
+ // Convert the folder path from MUTF-7 or UTF-8 to Unicode.
+ let imapServer = folder.server.QueryInterface(Ci.nsIImapIncomingServer);
+
+ let trashUnicode;
+ if (imapServer.utf8AcceptEnabled) {
+ // UTF8=ACCEPT capability in effect. Unescaping has brought back
+ // raw UTF-8 bytes, so convert them to JS Unicode.
+ let typedarray = new Uint8Array(folderPathUnescaped.length);
+ for (let i = 0; i < folderPathUnescaped.length; i++) {
+ typedarray[i] = folderPathUnescaped.charCodeAt(i);
+ }
+ let utf8Decoder = new TextDecoder("utf-8");
+ trashUnicode = utf8Decoder.decode(typedarray);
+ } else {
+ // We need to convert that from MUTF-7 to Unicode.
+ let manager = Cc["@mozilla.org/charset-converter-manager;1"].getService(
+ Ci.nsICharsetConverterManager
+ );
+ trashUnicode = manager.mutf7ToUnicode(folderPathUnescaped);
+ }
+
+ // Set the value to be persisted.
+ document
+ .getElementById("imap.trashFolderName")
+ .setAttribute("value", trashUnicode);
+
+ // Update the widget to show/do correct things even for subfolders.
+ let trashFolderPicker = document.getElementById("msgTrashFolderPicker");
+ trashFolderPicker.menupopup.selectFolder(folder);
+}
+
+// Get trash_folder_name from prefs. Despite its name this returns
+// a folder path, for example INBOX/Trash.
+function getTrashFolderName() {
+ let trashFolderName = document
+ .getElementById("imap.trashFolderName")
+ .getAttribute("value");
+ // if the preference hasn't been set, set it to a sane default
+ if (!trashFolderName) {
+ trashFolderName = "Trash"; // XXX Is this a useful default?
+ document
+ .getElementById("imap.trashFolderName")
+ .setAttribute("value", trashFolderName);
+ }
+ return trashFolderName;
+}
diff --git a/comm/mailnews/base/prefs/content/am-server.xhtml b/comm/mailnews/base/prefs/content/am-server.xhtml
new file mode 100644
index 0000000000..b41e93e290
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-server.xhtml
@@ -0,0 +1,674 @@
+<?xml version="1.0"?>
+<!-- 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 https://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/icons.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE html [ <!ENTITY % trashDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">
+%trashDTD; ]>
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <head>
+ <title>&serverSettings.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-server.js"
+ ></script>
+ <script defer="defer" src="chrome://messenger/content/am-prefs.js"></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/menulist-charsetpicker.js"
+ ></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => {
+ parent.onPanelLoaded("am-server.xhtml");
+ });
+ </script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <vbox id="containerBox" flex="1">
+ <stringbundle
+ id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"
+ />
+
+ <label hidden="true" wsm_persist="true" id="server.type" />
+ <label
+ hidden="true"
+ wsm_persist="true"
+ preftype="string"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.storeContractID"
+ genericattr="true"
+ id="server.storeContractID"
+ />
+
+ <hbox class="dialogheader">
+ <label class="dialogheader-title" value="&serverSettings.label;" />
+ </hbox>
+
+ <separator class="thin" />
+
+ <div xmlns="http://www.w3.org/1999/xhtml" id="amServerSetting">
+ <div>
+ <xul:label value="&serverType.label;" />
+ </div>
+ <div>
+ <xul:label id="servertypeVerbose" />
+ </div>
+ <div style="grid-row: 2">
+ <xul:label
+ value="&serverName.label;"
+ accesskey="&serverName.accesskey;"
+ control="server.hostName"
+ />
+ </div>
+ <div class="input-container" style="grid-row: 2">
+ <xul:label
+ id="fixedServerName"
+ collapsed="true"
+ use="server.hostName"
+ />
+ <html:input
+ id="server.hostName"
+ type="text"
+ wsm_persist="true"
+ size="20"
+ prefstring="mail.server.%serverkey%.hostname"
+ class="uri-element input-flex input-inline"
+ aria-labelledby="fixedServerName"
+ />
+ </div>
+ <div class="input-container" style="grid-row: 2">
+ <xul:label
+ value="&port.label;"
+ accesskey="&port.accesskey;"
+ control="server.port"
+ />
+ <xul:label id="fixedServerPort" collapsed="true" use="server.port" />
+ <html:input
+ id="server.port"
+ type="number"
+ class="size3"
+ min="1"
+ max="65535"
+ wsm_persist="true"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.port"
+ />
+ <xul:label value="&serverPortDefault.label;" />
+ <xul:label id="defaultPort" />
+ </div>
+ <div hidefor="nntp" style="grid-row: 3">
+ <xul:label
+ value="&userName.label;"
+ accesskey="&userName.accesskey;"
+ control="server.username"
+ />
+ </div>
+ <div class="input-container" hidefor="nntp" style="grid-row: 3">
+ <xul:label
+ id="fixedUserName"
+ collapsed="true"
+ use="server.username"
+ />
+ <html:input
+ id="server.username"
+ type="text"
+ wsm_persist="true"
+ size="20"
+ class="input-flex input-inline"
+ prefstring="mail.server.%serverkey%.username"
+ aria-labelledby="fixedUserName"
+ />
+ </div>
+ </div>
+
+ <separator />
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&securitySettings.label;</html:legend>
+ <html:table>
+ <html:tr>
+ <html:td>
+ <label
+ value="&connectionSecurity.label;"
+ accesskey="&connectionSecurity.accesskey;"
+ control="server.socketType"
+ />
+ </html:td>
+ <html:td>
+ <menulist
+ wsm_persist="true"
+ id="server.socketType"
+ oncommand="secureSelect();"
+ style="width: 100%"
+ >
+ <menupopup id="server.socketTypePopup">
+ <menuitem
+ value="0"
+ label="&connectionSecurityType-0.label;"
+ />
+ <menuitem
+ id="connectionSecurityType-1"
+ value="1"
+ label="&connectionSecurityType-1.label;"
+ disabled="true"
+ />
+ <menuitem
+ value="2"
+ label="&connectionSecurityType-2.label;"
+ hidefor="nntp"
+ />
+ <menuitem
+ value="3"
+ label="&connectionSecurityType-3.label;"
+ />
+ </menupopup>
+ </menulist>
+ </html:td>
+ </html:tr>
+ <html:tr>
+ <html:td>
+ <label
+ value="&authMethod.label;"
+ accesskey="&authMethod.accesskey;"
+ control="server.authMethod"
+ hidefor="nntp"
+ />
+ </html:td>
+ <html:td>
+ <menulist
+ id="server.authMethod"
+ wsm_persist="true"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.authMethod"
+ hidefor="nntp"
+ style="width: 100%"
+ >
+ <menupopup id="server.authMethodPopup">
+ <menuitem id="authMethod-no" value="1" />
+ <menuitem id="authMethod-old" value="2" />
+ <menuitem id="authMethod-password-cleartext" value="3" />
+ <menuitem id="authMethod-password-encrypted" value="4" />
+ <menuitem id="authMethod-kerberos" value="5" />
+ <menuitem id="authMethod-ntlm" value="6" />
+ <menuitem id="authMethod-external" value="7" />
+ <menuitem id="authMethod-oauth2" value="10" />
+ <menuitem id="authMethod-anysecure" value="8" />
+ <menuitem id="authMethod-any" value="9" />
+ </menupopup>
+ </menulist>
+ </html:td>
+ </html:tr>
+ </html:table>
+ </html:fieldset>
+ </html:div>
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&serverSettings.label;</html:legend>
+ <vbox align="start">
+ <checkbox
+ wsm_persist="true"
+ id="server.loginAtStartUp"
+ label="&loginAtStartup.label;"
+ accesskey="&loginAtStartup.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.login_at_startup"
+ />
+ </vbox>
+ <hbox align="center">
+ <checkbox
+ wsm_persist="true"
+ id="server.doBiff"
+ label="&biffStart.label;"
+ accesskey="&biffStart.accesskey;"
+ oncommand="onCheckItem('server.biffMinutes', [this.id]);"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.check_new_mail"
+ />
+ <html:input
+ id="server.biffMinutes"
+ type="number"
+ class="size3"
+ min="1"
+ wsm_persist="true"
+ aria-labelledby="server.doBiff server.biffMinutes biffEnd"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.check_time"
+ />
+ <label
+ id="biffEnd"
+ control="server.biffMinutes"
+ value="&biffEnd.label;"
+ />
+ </hbox>
+ <vbox align="start" hidefor="pop3,nntp">
+ <checkbox
+ wsm_persist="true"
+ id="imap.useIdle"
+ label="&useIdleNotifications.label;"
+ accesskey="&useIdleNotifications.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.use_idle"
+ />
+ </vbox>
+
+ <!-- Necessary for POP3 (Bug 480945) -->
+ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=480945 -->
+ <vbox align="start" hidefor="imap,nntp">
+ <checkbox
+ wsm_persist="true"
+ id="server.downloadOnBiff"
+ label="&downloadOnBiff.label;"
+ prefattribute="value"
+ accesskey="&downloadOnBiff.accesskey;"
+ prefstring="mail.server.%serverkey%.download_on_biff"
+ />
+ </vbox>
+ <!-- POP3 -->
+ <vbox align="start" hidefor="imap,nntp">
+ <checkbox
+ wsm_persist="true"
+ id="pop3.headersOnly"
+ label="&headersOnly.label;"
+ accesskey="&headersOnly.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.headers_only"
+ />
+
+ <checkbox
+ wsm_persist="true"
+ id="pop3.leaveMessagesOnServer"
+ label="&leaveOnServer.label;"
+ oncommand="setupMailOnServerUI();"
+ accesskey="&leaveOnServer.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.leave_on_server"
+ />
+
+ <hbox align="center">
+ <checkbox
+ wsm_persist="true"
+ id="pop3.deleteByAgeFromServer"
+ class="indent"
+ label="&deleteByAgeFromServer.label;"
+ oncommand="setupAgeMsgOnServerUI();"
+ accesskey="&deleteByAgeFromServer.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.delete_by_age_from_server"
+ />
+ <html:input
+ id="pop3.numDaysToLeaveOnServer"
+ type="number"
+ class="size3"
+ min="1"
+ wsm_persist="true"
+ aria-labelledby="pop3.deleteByAgeFromServer pop3.numDaysToLeaveOnServer daysEnd"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.num_days_to_leave_on_server"
+ />
+ <label
+ id="daysEnd"
+ control="pop3.numDaysToLeaveOnServer"
+ value="&daysEnd.label;"
+ />
+ </hbox>
+
+ <checkbox
+ wsm_persist="true"
+ id="pop3.deleteMailLeftOnServer"
+ class="indent"
+ label="&deleteOnServer2.label;"
+ accesskey="&deleteOnServer2.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.delete_mail_left_on_server"
+ />
+
+ <!-- hidden elements for data transfer to and from advanced... dialog -->
+ <hbox flex="1" hidefor="imap,nntp" hidden="true">
+ <checkbox
+ hidden="true"
+ wsm_persist="true"
+ id="pop3.deferGetNewMail"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.deferGetNewMail"
+ />
+ <label
+ hidden="true"
+ wsm_persist="true"
+ id="pop3.deferredToAccount"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.deferredToAccount"
+ />
+ </hbox>
+ </vbox>
+ <!-- IMAP -->
+ <label
+ hidden="true"
+ wsm_persist="true"
+ id="imap.trashFolderName"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.trash_folder_name"
+ />
+
+ <separator class="thin" />
+
+ <hbox align="center" hidefor="pop3,nntp">
+ <label
+ value="&deleteMessagePrefix.label;"
+ align="start"
+ control="imap.deleteModel"
+ />
+ </hbox>
+ <vbox>
+ <hbox
+ align="center"
+ id="imap.deleteModel.box"
+ hidefor="pop3,nntp"
+ flex="1"
+ >
+ <radiogroup
+ id="imap.deleteModel"
+ wsm_persist="true"
+ oncommand="selectImapDeleteModel(this.value);"
+ prefstring="mail.server.%serverkey%.delete_model"
+ >
+ <hbox class="specialFolderPickerGrid">
+ <vbox>
+ <hbox flex="1" align="center">
+ <radio
+ id="modelMoveToTrash"
+ value="1"
+ label="&modelMoveToTrash.label;"
+ accesskey="&modelMoveToTrash.accesskey;"
+ />
+ <menulist
+ id="msgTrashFolderPicker"
+ class="folderMenuItem"
+ style="max-width: 300px"
+ flex="1"
+ crop="center"
+ aria-labelledby="modelMoveToTrash"
+ displayformat="verbose"
+ >
+ <menupopup
+ is="folder-menupopup"
+ id="msgTrashFolderPopup"
+ mode="filing"
+ showFileHereLabel="true"
+ oncommand="folderPickerChange(event);"
+ />
+ </menulist>
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio
+ id="modelMarkDeleted"
+ value="0"
+ label="&modelMarkDeleted.label;"
+ accesskey="&modelMarkDeleted.accesskey;"
+ />
+ </hbox>
+ <hbox flex="1" align="center">
+ <radio
+ id="modelDeleteImmediately"
+ value="2"
+ label="&modelDeleteImmediately.label;"
+ accesskey="&modelDeleteImmediately.accesskey;"
+ />
+ </hbox>
+ </vbox>
+ </hbox>
+ </radiogroup>
+ </hbox>
+ <hbox pack="end">
+ <!-- This button should have identical attributes to the
+ server.popAdvancedButton except the hidefor attribute. -->
+ <button
+ label="&advancedButton.label;"
+ accesskey="&advancedButton.accesskey;"
+ oncommand="onAdvanced();"
+ wsm_persist="true"
+ id="server.imapAdvancedButton"
+ prefstring="mail.server.%serverkey%.advanced.disable"
+ hidefor="pop3,nntp"
+ />
+ </hbox>
+ </vbox>
+
+ <!-- Advanced IMAP settings -->
+ <hbox flex="1" hidefor="pop3,nntp" hidden="true">
+ <checkbox
+ hidden="true"
+ wsm_persist="true"
+ id="imap.dualUseFolders"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.dual_use_folders"
+ />
+ <checkbox
+ hidden="true"
+ wsm_persist="true"
+ id="imap.usingSubscription"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.using_subscription"
+ />
+ <label
+ hidden="true"
+ wsm_persist="true"
+ id="imap.maximumConnectionsNumber"
+ />
+ <label
+ hidden="true"
+ wsm_persist="true"
+ id="imap.personalNamespace"
+ />
+ <label hidden="true" wsm_persist="true" id="imap.publicNamespace" />
+ <label
+ hidden="true"
+ wsm_persist="true"
+ id="imap.otherUsersNamespace"
+ />
+ <label hidden="true" wsm_persist="true" id="imap.serverDirectory" />
+ <checkbox
+ hidden="true"
+ wsm_persist="true"
+ id="imap.overrideNamespaces"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.override_namespaces"
+ />
+ </hbox>
+
+ <!-- NNTP -->
+ <hbox hidefor="pop3,imap" align="center">
+ <checkbox
+ id="nntp.notifyOn"
+ wsm_persist="true"
+ label="&maxMessagesStart.label;"
+ accesskey="&maxMessagesStart.accesskey;"
+ oncommand="onCheckItem('nntp.maxArticles', [this.id]);"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.notify.on"
+ />
+ <html:input
+ id="nntp.maxArticles"
+ type="number"
+ class="size4"
+ min="1"
+ wsm_persist="true"
+ aria-labelledby="nntp.notifyOn nntp.maxArticles maxMessagesEnd"
+ preftype="int"
+ prefstring="mail.server.%serverkey%.max_articles"
+ />
+ <label
+ control="nntp.maxArticles"
+ value="&maxMessagesEnd.label;"
+ id="maxMessagesEnd"
+ />
+ </hbox>
+ <checkbox
+ hidefor="pop3,imap"
+ wsm_persist="true"
+ id="nntp.pushAuth"
+ label="&alwaysAuthenticate.label;"
+ accesskey="&alwaysAuthenticate.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.always_authenticate"
+ />
+ </html:fieldset>
+ </html:div>
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&messageStorage.label;</html:legend>
+
+ <hbox align="end">
+ <vbox align="start" flex="1" id="exitHandlingBox=">
+ <checkbox
+ hidefor="pop3,nntp"
+ wsm_persist="true"
+ id="imap.cleanupInboxOnExit"
+ label="&expungeOnExit.label;"
+ accesskey="&expungeOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.cleanup_inbox_on_exit"
+ />
+ <checkbox
+ hidefor="nntp"
+ wsm_persist="true"
+ id="server.emptyTrashOnExit"
+ label="&emptyTrashOnExit.label;"
+ accesskey="&emptyTrashOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.empty_trash_on_exit"
+ />
+ </vbox>
+ <button
+ label="&advancedButton.label;"
+ accesskey="&advancedButton.accesskey;"
+ oncommand="onAdvanced();"
+ wsm_persist="true"
+ id="server.popAdvancedButton"
+ prefstring="mail.server.%serverkey%.advanced.disable"
+ hidefor="imap,nntp"
+ />
+ </hbox>
+ <hbox align="center">
+ <label
+ value="&storeType.label;"
+ accesskey="&storeType.accesskey;"
+ control="server.storeTypeMenulist"
+ />
+ <menulist
+ id="server.storeTypeMenulist"
+ oncommand="clickStoreTypeMenu(this);"
+ >
+ <menupopup id="server.storeTypeMenupopup">
+ <menuitem
+ id="server.mboxStore"
+ value="@mozilla.org/msgstore/berkeleystore;1"
+ label="&mboxStore2.label;"
+ />
+ <menuitem
+ id="server.maildirStore"
+ value="@mozilla.org/msgstore/maildirstore;1"
+ label="&maildirStore.label;"
+ />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <hbox align="center" hidefor="imap,pop3">
+ <label
+ id="nntp.newsrcFilePath.label"
+ value="&newsrcFilePath1.label;"
+ control="nntp.newsrcFilePath"
+ />
+ <hbox class="input-container" flex="1">
+ <html:input
+ id="nntp.newsrcFilePath"
+ type="text"
+ readonly="readonly"
+ wsm_persist="true"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.newsrc.file"
+ class="uri-element input-inline"
+ aria-labelledby="nntp.newsrcFilePath.label"
+ />
+ </hbox>
+ <button
+ id="browseForNewsrc"
+ label="&browseNewsrc.label;"
+ filepickertitle="&newsrcPicker1.label;"
+ accesskey="&browseNewsrc.accesskey;"
+ oncommand="BrowseForNewsrc();"
+ />
+ </hbox>
+
+ <separator class="thin" />
+
+ <hbox align="center">
+ <label
+ id="server.localPath.label"
+ value="&localPath1.label;"
+ control="server.localPath"
+ />
+ <hbox class="input-container" flex="1">
+ <html:input
+ id="server.localPath"
+ type="text"
+ readonly="readonly"
+ wsm_persist="true"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory"
+ class="uri-element input-inline"
+ aria-labelledby="server.localPath.label"
+ />
+ </hbox>
+ <button
+ id="browseForLocalFolder"
+ label="&browseFolder.label;"
+ filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;"
+ oncommand="BrowseForLocalFolders();"
+ />
+ </hbox>
+ </html:fieldset>
+ </html:div>
+
+ <hbox hidefor="imap,pop3" align="center" iscontrolcontainer="true">
+ <separator class="thin" />
+ <label value="&serverDefaultCharset2.label;" control="nntp.charset" />
+ <menulist
+ is="menulist-charsetpicker-viewing"
+ id="nntp.charset"
+ hidable="true"
+ hidefor="imap,pop3"
+ wsm_persist="true"
+ preftype="string"
+ prefstring="mail.server.%serverkey%.charset"
+ />
+ </hbox>
+ </vbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-serverwithnoidentities.js b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.js
new file mode 100644
index 0000000000..12c1e7b837
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.js
@@ -0,0 +1,94 @@
+/* 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/. */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var gAccount;
+var gOriginalStoreType;
+
+/**
+ * Called when the store type menu is clicked.
+ *
+ * @param {object} aStoreTypeElement - store type menu list element.
+ */
+function clickStoreTypeMenu(aStoreTypeElement) {
+ if (aStoreTypeElement.value == gOriginalStoreType) {
+ return;
+ }
+
+ // Response from migration dialog modal. If the conversion is complete
+ // 'response.newRootFolder' will hold the path to the new account root folder,
+ // otherwise 'response.newRootFolder' will be null.
+ let response = { newRootFolder: null };
+ // Send 'response' as an argument to converterDialog.xhtml.
+ window.browsingContext.topChromeWindow.openDialog(
+ "converterDialog.xhtml",
+ "mailnews:mailstoreconverter",
+ "modal,centerscreen,resizable=no,width=700,height=130",
+ gAccount.incomingServer,
+ aStoreTypeElement.value,
+ response
+ );
+ changeStoreType(response);
+}
+
+/**
+ * Revert store type to the original store type if converter modal closes
+ * before migration is complete, otherwise change original store type to
+ * currently selected store type.
+ *
+ * @param {object} aResponse - response from migration dialog modal.
+ */
+function changeStoreType(aResponse) {
+ if (aResponse.newRootFolder) {
+ // The conversion is complete.
+ // Set local path to the new account root folder which is present
+ // in 'aResponse.newRootFolder'.
+ document.getElementById("server.localPath").value = aResponse.newRootFolder;
+ gOriginalStoreType = document.getElementById(
+ "server.storeTypeMenulist"
+ ).value;
+ MailUtils.restartApplication();
+ } else {
+ // The conversion failed or was cancelled.
+ // Restore selected item to what was selected before conversion.
+ document.getElementById("server.storeTypeMenulist").value =
+ gOriginalStoreType;
+ }
+}
+
+function onInit(aPageId, aServerId) {
+ // UI for account store type
+ let storeTypeElement = document.getElementById("server.storeTypeMenulist");
+ // set the menuitem to match the account
+ let currentStoreID = document
+ .getElementById("server.storeContractID")
+ .getAttribute("value");
+ let targetItem = storeTypeElement.getElementsByAttribute(
+ "value",
+ currentStoreID
+ );
+ storeTypeElement.selectedItem = targetItem[0];
+ // Disable store type change if store has not been used yet.
+ storeTypeElement.setAttribute(
+ "disabled",
+ gAccount.incomingServer.getBoolValue("canChangeStoreType")
+ ? "false"
+ : !Services.prefs.getBoolPref("mail.store_conversion_enabled")
+ );
+ // Initialise 'gOriginalStoreType' to the item that was originally selected.
+ gOriginalStoreType = storeTypeElement.value;
+}
+
+function onPreInit(account, accountValues) {
+ gAccount = account;
+}
+
+function onSave() {
+ let storeContractID = document.getElementById("server.storeTypeMenulist")
+ .selectedItem.value;
+ document
+ .getElementById("server.storeContractID")
+ .setAttribute("value", storeContractID);
+}
diff --git a/comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml
new file mode 100644
index 0000000000..1cdc36550f
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-serverwithnoidentities.xhtml
@@ -0,0 +1,152 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html [ <!ENTITY % accountNoIdentDTD SYSTEM "chrome://messenger/locale/am-serverwithnoidentities.dtd">
+%accountNoIdentDTD;
+<!ENTITY % accountServerTopDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">%accountServerTopDTD;
+]>
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <head>
+ <title>&accountTitle.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/am-serverwithnoidentities.js"
+ ></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script>
+ // FIXME: move to script file.
+ window.addEventListener("load", event => {
+ parent.onPanelLoaded("am-serverwithnoidentities.xhtml");
+ });
+ </script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <vbox id="containerBox" flex="1">
+ <hbox class="dialogheader">
+ <label class="dialogheader-title" value="&accountTitle.label;" />
+ </hbox>
+
+ <separator class="thin" />
+
+ <label
+ hidden="true"
+ wsm_persist="true"
+ preftype="string"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.storeContractID"
+ genericattr="true"
+ id="server.storeContractID"
+ />
+
+ <description class="secDesc">&accountSettingsDesc.label;</description>
+ <hbox class="input-container">
+ <label
+ id="server.prettyName.label"
+ value="&accountName.label;"
+ control="server.prettyName"
+ accesskey="&accountName.accesskey;"
+ />
+ <html:input
+ id="server.prettyName"
+ type="text"
+ wsm_persist="true"
+ class="input-inline"
+ onblur="parent.setAccountLabel(gAccount.key, this.value);"
+ prefstring="mail.server.%serverkey%.name"
+ aria-labelledby="server.prettyName.label"
+ />
+ </hbox>
+
+ <separator class="thin" />
+
+ <html:div>
+ <html:fieldset>
+ <html:legend>&messageStorage.label;</html:legend>
+
+ <vbox align="start">
+ <checkbox
+ wsm_persist="true"
+ id="server.emptyTrashOnExit"
+ label="&emptyTrashOnExit.label;"
+ accesskey="&emptyTrashOnExit.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.empty_trash_on_exit"
+ />
+ <hbox align="center">
+ <label
+ value="&storeType.label;"
+ accesskey="&storeType.accesskey;"
+ control="server.storeTypeMenulist"
+ />
+ <menulist
+ id="server.storeTypeMenulist"
+ oncommand="clickStoreTypeMenu(this);"
+ >
+ <menupopup id="server.storeTypeMenupopup">
+ <menuitem
+ id="server.mboxStore"
+ value="@mozilla.org/msgstore/berkeleystore;1"
+ label="&mboxStore2.label;"
+ />
+ <menuitem
+ id="server.maildirStore"
+ value="@mozilla.org/msgstore/maildirstore;1"
+ label="&maildirStore.label;"
+ />
+ </menupopup>
+ </menulist>
+ </hbox>
+ </vbox>
+
+ <separator class="thin" />
+
+ <hbox align="center">
+ <label
+ id="server.localPath.label"
+ value="&localPath1.label;"
+ control="server.localPath"
+ />
+ <hbox class="input-container" flex="1">
+ <html:input
+ id="server.localPath"
+ type="text"
+ readonly="readonly"
+ wsm_persist="true"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory"
+ class="uri-element input-inline"
+ aria-labelledby="server.localPath.label"
+ />
+ </hbox>
+ <button
+ id="browseForLocalFolder"
+ label="&browseFolder.label;"
+ filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;"
+ oncommand="BrowseForLocalFolders()"
+ />
+ </hbox>
+ </html:fieldset>
+ </html:div>
+ </vbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/am-smtp.js b/comm/mailnews/base/prefs/content/am-smtp.js
new file mode 100644
index 0000000000..4dd48fa795
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-smtp.js
@@ -0,0 +1,277 @@
+/* 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 amUtils.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+window.addEventListener("DOMContentLoaded", event => {
+ gSmtpServerListWindow.onLoad();
+});
+
+var gSmtpServerListWindow = {
+ mBundle: null,
+ mServerList: null,
+ mAddButton: null,
+ mEditButton: null,
+ mDeleteButton: null,
+ mSetDefaultServerButton: null,
+
+ onLoad() {
+ parent.onPanelLoaded("am-smtp.xhtml");
+
+ this.mBundle = document.getElementById("bundle_messenger");
+ this.mServerList = document.getElementById("smtpList");
+ this.mAddButton = document.getElementById("addButton");
+ this.mEditButton = document.getElementById("editButton");
+ this.mDeleteButton = document.getElementById("deleteButton");
+ this.mSetDefaultServerButton = document.getElementById("setDefaultButton");
+
+ this.refreshServerList("", false);
+
+ this.updateButtons();
+ },
+
+ onSelectionChanged(aEvent) {
+ var server = this.getSelectedServer();
+ if (!server) {
+ return;
+ }
+
+ this.updateButtons();
+ this.updateServerInfoBox(server);
+ },
+
+ onDeleteServer(aEvent) {
+ var server = this.getSelectedServer();
+ if (!server) {
+ return;
+ }
+
+ // confirm deletion
+ let cancel = Services.prompt.confirmEx(
+ window,
+ this.mBundle.getString("smtpServers-confirmServerDeletionTitle"),
+ this.mBundle.getFormattedString(
+ "smtpServers-confirmServerDeletion",
+ [server.hostname],
+ 1
+ ),
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null,
+ null,
+ null,
+ null,
+ {}
+ );
+
+ if (!cancel) {
+ // Remove password information first.
+ try {
+ server.forgetPassword();
+ } catch (e) {
+ /* It is OK if this fails. */
+ }
+ // Remove the server.
+ MailServices.smtp.deleteServer(server);
+ parent.replaceWithDefaultSmtpServer(server.key);
+ this.refreshServerList("", true);
+ }
+ },
+
+ onAddServer(aEvent) {
+ this.openServerEditor(null);
+ },
+
+ onEditServer(aEvent) {
+ let server = this.getSelectedServer();
+ if (!server) {
+ return;
+ }
+
+ this.openServerEditor(server);
+ },
+
+ onSetDefaultServer(aEvent) {
+ let server = this.getSelectedServer();
+ if (!server) {
+ return;
+ }
+
+ MailServices.smtp.defaultServer = server;
+ this.refreshServerList(MailServices.smtp.defaultServer.key, true);
+ },
+
+ updateButtons() {
+ let server = this.getSelectedServer();
+
+ // can't delete default server
+ if (server && MailServices.smtp.defaultServer == server) {
+ this.mSetDefaultServerButton.setAttribute("disabled", "true");
+ this.mDeleteButton.setAttribute("disabled", "true");
+ } else {
+ this.mSetDefaultServerButton.removeAttribute("disabled");
+ this.mDeleteButton.removeAttribute("disabled");
+ }
+
+ if (!server) {
+ this.mEditButton.setAttribute("disabled", "true");
+ } else {
+ this.mEditButton.removeAttribute("disabled");
+ }
+ },
+
+ updateServerInfoBox(aServer) {
+ var noneSelected = this.mBundle.getString("smtpServerList-NotSpecified");
+
+ document.getElementById("nameValue").textContent = aServer.hostname;
+ document.getElementById("descriptionValue").textContent =
+ aServer.description || noneSelected;
+ document.getElementById("portValue").textContent =
+ aServer.port || noneSelected;
+ document.getElementById("userNameValue").textContent =
+ aServer.username || noneSelected;
+ document.getElementById("useSecureConnectionValue").textContent =
+ this.mBundle.getString(
+ "smtpServer-ConnectionSecurityType-" + aServer.socketType
+ );
+
+ const AuthMethod = Ci.nsMsgAuthMethod;
+ const SocketType = Ci.nsMsgSocketType;
+ var authStr = "";
+ switch (aServer.authMethod) {
+ case AuthMethod.none:
+ authStr = "authNo";
+ break;
+ case AuthMethod.passwordEncrypted:
+ authStr = "authPasswordEncrypted";
+ break;
+ case AuthMethod.GSSAPI:
+ authStr = "authKerberos";
+ break;
+ case AuthMethod.NTLM:
+ authStr = "authNTLM";
+ break;
+ case AuthMethod.secure:
+ authStr = "authAnySecure";
+ break;
+ case AuthMethod.passwordCleartext:
+ authStr =
+ aServer.socketType == SocketType.SSL ||
+ aServer.socketType == SocketType.alwaysSTARTTLS
+ ? "authPasswordCleartextViaSSL"
+ : "authPasswordCleartextInsecurely";
+ break;
+ case AuthMethod.OAuth2:
+ authStr = "authOAuth2";
+ break;
+ default:
+ // leave empty
+ console.error(
+ "Warning: unknown value for smtpserver... authMethod: " +
+ aServer.authMethod
+ );
+ }
+ document.getElementById("authMethodValue").textContent = authStr
+ ? this.mBundle.getString(authStr)
+ : noneSelected;
+ },
+
+ refreshServerList(aServerKeyToSelect, aFocusList) {
+ while (this.mServerList.hasChildNodes()) {
+ this.mServerList.lastChild.remove();
+ }
+ for (let server of MailServices.smtp.servers) {
+ let listitem = this.createSmtpListItem(
+ server,
+ MailServices.smtp.defaultServer.key == server.key
+ );
+ this.mServerList.appendChild(listitem);
+ }
+
+ if (aServerKeyToSelect) {
+ this.setSelectedServer(
+ this.mServerList.querySelector('[key="' + aServerKeyToSelect + '"]')
+ );
+ } else {
+ // Select the default server.
+ this.setSelectedServer(
+ this.mServerList.querySelector('[default="true"]')
+ );
+ }
+
+ if (aFocusList) {
+ this.mServerList.focus();
+ }
+ },
+
+ createSmtpListItem(aServer, aIsDefault) {
+ var listitem = document.createXULElement("richlistitem");
+ var serverName = "";
+
+ if (aServer.description) {
+ serverName = aServer.description + " - ";
+ } else if (aServer.username) {
+ serverName = aServer.username + " - ";
+ }
+
+ serverName += aServer.hostname;
+
+ if (aIsDefault) {
+ serverName += " " + this.mBundle.getString("defaultServerTag");
+ listitem.setAttribute("default", "true");
+ }
+
+ let label = document.createXULElement("label");
+ label.setAttribute("value", serverName);
+ listitem.appendChild(label);
+ listitem.setAttribute("key", aServer.key);
+ listitem.setAttribute("class", "smtpServerListItem");
+
+ // give it some unique id
+ listitem.id = "smtpServer." + aServer.key;
+ return listitem;
+ },
+
+ openServerEditor(aServer) {
+ let args = editSMTPServer(aServer);
+
+ // now re-select the server which was just added
+ if (args.result) {
+ this.refreshServerList(aServer ? aServer.key : args.addSmtpServer, true);
+ }
+
+ return args.result;
+ },
+
+ setSelectedServer(aServer) {
+ if (!aServer) {
+ return;
+ }
+
+ setTimeout(
+ function (aServerList) {
+ aServerList.ensureElementIsVisible(aServer);
+ aServerList.selectItem(aServer);
+ },
+ 0,
+ this.mServerList
+ );
+ },
+
+ getSelectedServer() {
+ // The list of servers is a single selection listbox
+ // therefore 1 item is always selected.
+ // But if there are no SMTP servers defined yet, nothing will be selected.
+ let selection = this.mServerList.selectedItem;
+ if (!selection) {
+ return null;
+ }
+
+ let serverKey = selection.getAttribute("key");
+ return MailServices.smtp.getServerByKey(serverKey);
+ },
+};
diff --git a/comm/mailnews/base/prefs/content/am-smtp.xhtml b/comm/mailnews/base/prefs/content/am-smtp.xhtml
new file mode 100644
index 0000000000..851fb8da3e
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/am-smtp.xhtml
@@ -0,0 +1,121 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/am-advanced.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+>
+ <head>
+ <title>&smtpServer.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script defer="defer" src="chrome://messenger/content/amUtils.js"></script>
+ <script defer="defer" src="chrome://messenger/content/am-smtp.js"></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <stringbundle
+ id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"
+ />
+
+ <vbox flex="1" style="overflow: auto"
+ ><vbox id="containerBox" flex="1">
+ <hbox class="dialogheader">
+ <label class="dialogheader-title" value="&smtpServer.label;" />
+ </hbox>
+
+ <separator class="thin" />
+ <label control="smtpList">&smtpDescription.label;</label>
+ <separator class="thin" />
+
+ <hbox flex="1">
+ <richlistbox
+ id="smtpList"
+ onselect="gSmtpServerListWindow.onSelectionChanged(event);"
+ ondblclick="gSmtpServerListWindow.onEditServer(event);"
+ seltype="single"
+ flex="1"
+ style="height: 400px"
+ />
+
+ <vbox>
+ <button
+ id="addButton"
+ oncommand="gSmtpServerListWindow.onAddServer(event);"
+ label="&smtpListAdd.label;"
+ accesskey="&smtpListAdd.accesskey;"
+ />
+ <button
+ id="editButton"
+ oncommand="gSmtpServerListWindow.onEditServer(event);"
+ label="&smtpListEdit.label;"
+ accesskey="&smtpListEdit.accesskey;"
+ />
+ <separator />
+ <button
+ id="deleteButton"
+ disabled="true"
+ oncommand="gSmtpServerListWindow.onDeleteServer(event);"
+ label="&smtpListDelete.label;"
+ accesskey="&smtpListDelete.accesskey;"
+ />
+ <button
+ id="setDefaultButton"
+ disabled="true"
+ oncommand="gSmtpServerListWindow.onSetDefaultServer(event);"
+ label="&smtpListSetDefault.label;"
+ accesskey="&smtpListSetDefault.accesskey;"
+ />
+ </vbox>
+ </hbox>
+
+ <separator />
+
+ <label class="header">&serverDetails.label;</label>
+ <html:table id="smtpServerInfoBox">
+ <html:tr>
+ <html:th id="descriptionLabel">&serverDescription.label;</html:th>
+ <html:td id="descriptionValue"></html:td>
+ </html:tr>
+ <html:tr>
+ <html:th id="nameLabel">&serverName.label;</html:th>
+ <html:td id="nameValue"></html:td>
+ </html:tr>
+ <html:tr>
+ <html:th id="portLabel">&serverPort.label;</html:th>
+ <html:td id="portValue"></html:td>
+ </html:tr>
+ <html:tr>
+ <html:th id="userNameLabel">&userName.label;</html:th>
+ <html:td id="userNameValue"></html:td>
+ </html:tr>
+ <html:tr>
+ <html:th id="authMethodLabel">&authMethod.label;</html:th>
+ <html:td id="authMethodValue"></html:td>
+ </html:tr>
+ <html:tr>
+ <html:th id="connectionSecurityLabel"
+ >&connectionSecurity.label;</html:th
+ >
+ <html:td id="useSecureConnectionValue"></html:td>
+ </html:tr>
+ </html:table>
+ <separator flex="1" /> </vbox
+ ></vbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/amUtils.js b/comm/mailnews/base/prefs/content/amUtils.js
new file mode 100644
index 0000000000..b63f27b6c4
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/amUtils.js
@@ -0,0 +1,275 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 am-smtp.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+function BrowseForLocalFolders() {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ const nsIFile = Ci.nsIFile;
+
+ var currentFolderTextBox = document.getElementById("server.localPath");
+ var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+
+ fp.init(
+ window,
+ document
+ .getElementById("browseForLocalFolder")
+ .getAttribute("filepickertitle"),
+ nsIFilePicker.modeGetFolder
+ );
+
+ var currentFolder = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile);
+ try {
+ currentFolder.initWithPath(currentFolderTextBox.value);
+ fp.displayDirectory = currentFolder;
+ } catch (e) {
+ console.error(
+ `Failed to set folder path from value=${currentFolderTextBox.value}\n`
+ );
+ }
+
+ fp.open(rv => {
+ if (rv != nsIFilePicker.returnOK || !fp.file) {
+ return;
+ }
+ // Retrieve the selected folder.
+ let selectedFolder = fp.file;
+
+ // Check if the folder can be used for mail storage.
+ if (!top.checkDirectoryIsUsable(selectedFolder)) {
+ return;
+ }
+
+ currentFolderTextBox.value = selectedFolder.path;
+ currentFolderTextBox.dispatchEvent(new CustomEvent("change"));
+ });
+}
+
+/**
+ * Return server/folder name formatted with server name if needed.
+ *
+ * @param {nsIMsgFolder} aTargetFolder - nsIMsgFolder to format name for
+ @returns {string} THe formatted name.
+ * If target.isServer then only its name is returned.
+ * Otherwise return the name as "<foldername> on <servername>".
+ */
+function prettyFolderName(aTargetFolder) {
+ if (aTargetFolder.isServer) {
+ return aTargetFolder.prettyName;
+ }
+
+ return document
+ .getElementById("bundle_messenger")
+ .getFormattedString("verboseFolderFormat", [
+ aTargetFolder.prettyName,
+ aTargetFolder.server.prettyName,
+ ]);
+}
+
+/**
+ * Checks validity of junk target server name and folder.
+ *
+ * @param {string} aTargetURI - The URI specification to check.
+ * @param {boolean} aIsServer - true if the URI specifies only a server
+ * (without folder)
+ *
+ * @returns {string} the value of aTargetURI if it is valid (usable), otherwise null
+ */
+function checkJunkTargetFolder(aTargetURI, aIsServer) {
+ try {
+ // Does the target account exist?
+ let targetServer;
+ if (aIsServer) {
+ targetServer = MailUtils.getOrCreateFolder(aTargetURI + "/Junk").server;
+ } else {
+ targetServer = MailUtils.getExistingFolder(aTargetURI).server;
+ }
+
+ // If the target server has deferred storage, Junk can't be stored into it.
+ if (targetServer.rootFolder != targetServer.rootMsgFolder) {
+ return null;
+ }
+ } catch (e) {
+ return null;
+ }
+
+ return aTargetURI;
+}
+
+/**
+ * Finds a usable target for storing Junk mail.
+ * If the passed in server URI is not usable, choose Local Folders.
+ *
+ * @param {string} aTargetURI - The URI of a server or folder to try first
+ * @param {boolean} aIsServer - true if the URI specifies only a server (without folder)
+ *
+ * @returns {string} the server/folder URI of a usable target for storing Junk
+ */
+function chooseJunkTargetFolder(aTargetURI, aIsServer) {
+ let server = null;
+
+ if (aTargetURI) {
+ server = MailUtils.getOrCreateFolder(aTargetURI).server;
+ if (
+ !server.canCreateFoldersOnServer ||
+ !server.canSearchMessages ||
+ server.rootFolder != server.rootMsgFolder
+ ) {
+ server = null;
+ }
+ }
+ if (!server) {
+ server = MailServices.accounts.localFoldersServer;
+ }
+
+ return server.serverURI + (!aIsServer ? "/Junk" : "");
+}
+
+/**
+ * Fixes junk target folders if they point to an invalid/unusable (e.g. deferred)
+ * folder/account. Only returns the new safe values. It is up to the caller
+ * to push them to the proper elements/prefs.
+ *
+ * @param {string} aSpamActionTargetAccount - The value of the
+ * server.*.spamActionTargetAccount pref value (URI).
+ * @param {string} aSpamActionTargetFolder - The value of the
+ * server.*.spamActionTargetFolder pref value (URI).
+ * @param {string} aProposedTarget - The URI of a new target to try.
+ * @param {integer} aMoveTargetModeValue - The value of the
+ * server.*.moveTargetMode pref value (0/1).
+ * @param {nsISpamSettings} aServerSpamSettings - The nsISpamSettings object
+ * of any server (used just for the MOVE_TARGET_MODE_* constants).
+ * @param {boolean} aMoveOnSpam - The server.*.moveOnSpam pref value).
+ *
+ * @returns {object[]} an array containing:
+ * newTargetAccount new safe junk target account
+ * newTargetAccount new safe junk target folder
+ * newMoveOnSpam new moveOnSpam value
+ */
+function sanitizeJunkTargets(
+ aSpamActionTargetAccount,
+ aSpamActionTargetFolder,
+ aProposedTarget,
+ aMoveTargetModeValue,
+ aServerSpamSettings,
+ aMoveOnSpam
+) {
+ // Check if folder targets are valid.
+ aSpamActionTargetAccount = checkJunkTargetFolder(
+ aSpamActionTargetAccount,
+ true
+ );
+ if (!aSpamActionTargetAccount) {
+ // If aSpamActionTargetAccount is not valid,
+ // reset to default behavior to NOT move junk messages...
+ if (aMoveTargetModeValue == aServerSpamSettings.MOVE_TARGET_MODE_ACCOUNT) {
+ aMoveOnSpam = false;
+ }
+
+ // ... and find a good default target.
+ aSpamActionTargetAccount = chooseJunkTargetFolder(aProposedTarget, true);
+ }
+
+ aSpamActionTargetFolder = checkJunkTargetFolder(
+ aSpamActionTargetFolder,
+ false
+ );
+ if (!aSpamActionTargetFolder) {
+ // If aSpamActionTargetFolder is not valid,
+ // reset to default behavior to NOT move junk messages...
+ if (aMoveTargetModeValue == aServerSpamSettings.MOVE_TARGET_MODE_FOLDER) {
+ aMoveOnSpam = false;
+ }
+
+ // ... and find a good default target.
+ aSpamActionTargetFolder = chooseJunkTargetFolder(aProposedTarget, false);
+ }
+
+ return [aSpamActionTargetAccount, aSpamActionTargetFolder, aMoveOnSpam];
+}
+
+/**
+ * Opens Preferences (Options) dialog on the Advanced pane, General tab
+ * so that the user sees where the global receipts settings can be found.
+ *
+ * @param {string} aTBPaneId - Thunderbird pref paneID to open.
+ * @param {string} aTBScrollPaneTo - Thunderbird ID of the element to scroll into view.
+ * @param {any} aTBOtherArgs - Other arguments to send to the pref tab.
+ * @param {string} aSMPaneId - Seamonkey pref pane to open.
+ */
+function openPrefsFromAccountManager(
+ aTBPaneId,
+ aTBScrollPaneTo,
+ aTBOtherArgs,
+ aSMPaneId
+) {
+ let win =
+ Services.wm.getMostRecentWindow("mail:3pane") ||
+ Services.wm.getMostRecentWindow("mail:messageWindow") ||
+ Services.wm.getMostRecentWindow("msgcompose");
+ if (!win) {
+ return;
+ }
+
+ // If openOptionsDialog() exists, we are in Thunderbird.
+ if (typeof win.openOptionsDialog == "function") {
+ win.openOptionsDialog(aTBPaneId, aTBScrollPaneTo, aTBOtherArgs);
+ }
+ // If goPreferences() exists, we are in Seamonkey.
+ if (typeof win.goPreferences == "function") {
+ win.goPreferences(aSMPaneId);
+ }
+}
+
+/**
+ * Check if the given account name already exists in any account.
+ *
+ * @param {string} aAccountName - The account name string to look for.
+ * @param {string} [aAccountKey] - The key of an account that is skipped when
+ * searching the name. If unset, do not skip any account.
+ */
+function accountNameExists(aAccountName, aAccountKey) {
+ for (let account of MailServices.accounts.accounts) {
+ if (
+ account.key != aAccountKey &&
+ account.incomingServer &&
+ aAccountName == account.incomingServer.prettyName
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Open a dialog to edit properties of an SMTP server.
+ *
+ * @param {nsISmtpServer} aServer - The server to edit.
+ * @returns {object} Object with result member to indicate whether 'OK'
+ * was clicked and addSmtpServer with key of newly created server.
+ */
+function editSMTPServer(aServer) {
+ let args = { server: aServer, result: false, addSmtpServer: "" };
+
+ let onCloseSMTPDialog = function () {
+ if (args.result) {
+ gSmtpServerListWindow.refreshServerList(aServer, true);
+ }
+ };
+
+ parent.gSubDialog.open(
+ "chrome://messenger/content/SmtpServerEdit.xhtml",
+ { closingCallback: onCloseSMTPDialog },
+ args
+ );
+
+ return args;
+}
diff --git a/comm/mailnews/base/prefs/content/aw-accname.js b/comm/mailnews/base/prefs/content/aw-accname.js
new file mode 100644
index 0000000000..97dce27406
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/aw-accname.js
@@ -0,0 +1,28 @@
+/* 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 AccountWizard.js */
+
+function acctNamePageValidate() {
+ var accountname = document.getElementById("prettyName").value;
+ // Check if this accountname already exists. If so, return false so that
+ // user can enter a different unique account name.
+ document.querySelector("wizard").canAdvance =
+ !!accountname && !accountNameExists(accountname);
+}
+
+function acctNamePageUnload() {
+ var pageData = parent.GetPageData();
+ var accountname = document.getElementById("prettyName").value;
+ pageData.prettyName = accountname;
+ return true;
+}
+
+function acctNamePageInit() {
+ var accountNameInput = document.getElementById("prettyName");
+ if (accountNameInput.value == "") {
+ accountNameInput.value = parent.GetPageData().hostname;
+ }
+ acctNamePageValidate();
+}
diff --git a/comm/mailnews/base/prefs/content/aw-done.js b/comm/mailnews/base/prefs/content/aw-done.js
new file mode 100644
index 0000000000..93ac3994bc
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/aw-done.js
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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 AccountWizard.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function donePageInit() {
+ var pageData = parent.GetPageData();
+
+ var email = pageData.email;
+ setDivTextFromForm("identity.email", email);
+ setDivTextFromForm("server.username", getUsernameFromEmail(email));
+ setDivTextFromForm("account.name", pageData.prettyName);
+ setDivTextFromForm("newsServer.name", pageData.hostname);
+}
+
+function setDivTextFromForm(divid, value) {
+ // collapse the row if the div has no value
+ let label = document.getElementById(`${divid}.label`);
+ let text = document.getElementById(`${divid}.text`);
+
+ if (!value) {
+ text.style.display = "none";
+ label.style.display = "none";
+ return;
+ }
+
+ // otherwise fill in the .text element
+ text.style.display = null;
+ label.style.display = null;
+
+ // set the value
+ text.setAttribute("value", value);
+}
diff --git a/comm/mailnews/base/prefs/content/aw-identity.js b/comm/mailnews/base/prefs/content/aw-identity.js
new file mode 100644
index 0000000000..66d63d6e1e
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/aw-identity.js
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 AccountWizard.js */
+
+var gPrefsBundle;
+
+function identityPageValidate() {
+ document.querySelector("wizard").canAdvance =
+ document.getElementById("fullName").validity.valid &&
+ document.getElementById("email").validity.valid;
+}
+
+function identityPageUnload() {
+ var pageData = parent.GetPageData();
+ var name = document.getElementById("fullName").value;
+ let email = document.getElementById("email").value.trim();
+ pageData.fullName = name;
+ pageData.email = email;
+
+ return true;
+}
+
+function identityPageInit() {
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ setEmailDescriptionText();
+ checkForFullName();
+ checkForEmail();
+ identityPageValidate();
+}
+
+function setEmailDescriptionText() {
+ var emailDescText = document.getElementById("emailDescText");
+ var emailFieldLabel = document.getElementById("emailFieldLabel");
+
+ // Set the default field label
+ emailFieldLabel.setAttribute(
+ "value",
+ gPrefsBundle.getString("emailFieldText")
+ );
+
+ // Check for obtained values and set with default values if needed
+ var username = gPrefsBundle.getString("exampleEmailUserName");
+ var domain = gPrefsBundle.getString("exampleEmailDomain");
+
+ let displayText = gPrefsBundle.getFormattedString("defaultEmailText", [
+ username,
+ domain,
+ ]);
+
+ // Display the dynamically generated text for email description
+ emailDescText.textContent = displayText;
+}
+
+function checkForFullName() {
+ var name = document.getElementById("fullName");
+ if (name.value == "" && "@mozilla.org/userinfo;1" in Cc) {
+ name.value = Cc["@mozilla.org/userinfo;1"].getService(
+ Ci.nsIUserInfo
+ ).fullname;
+ }
+}
+
+function checkForEmail() {
+ var email = document.getElementById("email");
+ var pageData = parent.GetPageData();
+ if (pageData.email) {
+ email.value = pageData.email;
+ }
+ if (email.value == "" && "@mozilla.org/userinfo;1" in Cc) {
+ email.value = Cc["@mozilla.org/userinfo;1"].getService(
+ Ci.nsIUserInfo
+ ).emailAddress;
+ }
+}
diff --git a/comm/mailnews/base/prefs/content/aw-incoming.js b/comm/mailnews/base/prefs/content/aw-incoming.js
new file mode 100644
index 0000000000..a4f746d71d
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/aw-incoming.js
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 AccountWizard.js */
+
+var { cleanUpHostName, isLegalHostNameOrIP } = ChromeUtils.import(
+ "resource:///modules/hostnameUtils.jsm"
+);
+var { NntpUtils } = ChromeUtils.import("resource:///modules/NntpUtils.jsm");
+
+function incomingPageValidate() {
+ let hostName = cleanUpHostName(document.getElementById("newsServer").value);
+
+ let hasAccount = false;
+ let server = NntpUtils.findServer(hostName);
+ if (server) {
+ // It's OK if a server exists, as long as it's not used by any account.
+ hasAccount = MailServices.accounts.FindAccountForServer(server);
+ }
+ // Can advance if it's a legal host name and we do not already have a server
+ // in use with the same host name.
+ document.querySelector("wizard").canAdvance =
+ !!isLegalHostNameOrIP(hostName) && !hasAccount;
+}
+
+function incomingPageUnload() {
+ parent.GetPageData().hostname = cleanUpHostName(
+ document.getElementById("newsServer").value
+ );
+
+ return true;
+}
+
+function incomingPageInit() {
+ var pageData = parent.GetPageData();
+ if (pageData.hostname) {
+ document.getElementById("newsServer").value = pageData.hostname;
+ }
+ incomingPageValidate();
+}
diff --git a/comm/mailnews/base/prefs/content/converterDialog.js b/comm/mailnews/base/prefs/content/converterDialog.js
new file mode 100644
index 0000000000..e0ac74fd5b
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/converterDialog.js
@@ -0,0 +1,385 @@
+/* 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/. */
+
+/**
+ * This file contains functionality for the front-end part of the mail store
+ * type conversion.
+ */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "FolderUtils",
+ "resource:///modules/FolderUtils.jsm"
+);
+var MailstoreConverter = ChromeUtils.import(
+ "resource:///modules/mailstoreConverter.jsm"
+);
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+window.addEventListener("DOMContentLoaded", () => {
+ for (let img of document.querySelectorAll(".infoIcon")) {
+ img.setAttribute(
+ "src",
+ "chrome://messenger/skin/icons/new/activity/warning.svg"
+ );
+ }
+});
+
+var log = console.createInstance({
+ prefix: "mail.mailstoreconverter",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.mailstoreconverter.loglevel",
+});
+// {nsIMsgIncomingServer} server for the account to be migrated.
+var gServer;
+// {nsIMsgFolder} account root folder.
+var gFolder;
+// 'gResponse.newRootFolder' is path to the new account root folder if migration
+// is complete, else null.
+// 'gResponse' is set to the modified response parameter received from
+// am-server.js.
+var gResponse;
+// Array to hold deferred accounts.
+var gDeferredAccounts = [];
+// Value of Services.io.offline before migration.
+var gOriginalOffline;
+/**
+ * Place account name in migration dialog modal.
+ *
+ * @param {nsIMsgIncomingServer} aServer - account server.
+ */
+function placeAccountName(aServer) {
+ gOriginalOffline = Services.io.offline;
+
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/converterDialog.properties"
+ );
+
+ let brandShortName = Services.strings
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+
+ // 'deferredToRootFolder' holds path of rootMsgFolder of account to which
+ // other accounts have been deferred.
+ let deferredToRootFolder = aServer.rootMsgFolder.filePath.path;
+ // String to hold names of deferred accounts separated by commas.
+ let deferredAccountsString = "";
+ // Account to which other accounts have been deferred.
+ let deferredToAccount;
+ // Array of all accounts.
+ let accounts = FolderUtils.allAccountsSorted(true);
+
+ for (let account of accounts) {
+ if (
+ account.incomingServer.rootFolder.filePath.path == deferredToRootFolder
+ ) {
+ // Other accounts may be deferred to this account.
+ deferredToAccount = account;
+ } else if (
+ account.incomingServer.rootMsgFolder.filePath.path == deferredToRootFolder
+ ) {
+ // This is a deferred account.
+ gDeferredAccounts.push(account);
+ }
+ }
+
+ // String to hold the names of accounts to be converted separated by commas.
+ let accountsToConvert = "";
+
+ if (gDeferredAccounts.length >= 1) {
+ // Add account names to 'accountsToConvert' and 'deferredAccountsString'.
+ for (let i = 0; i < gDeferredAccounts.length; i++) {
+ if (i < gDeferredAccounts.length - 1) {
+ accountsToConvert +=
+ gDeferredAccounts[i].incomingServer.username + ", ";
+ deferredAccountsString +=
+ gDeferredAccounts[i].incomingServer.username + ", ";
+ } else {
+ accountsToConvert += gDeferredAccounts[i].incomingServer.username;
+ deferredAccountsString += gDeferredAccounts[i].incomingServer.username;
+ }
+ }
+
+ // Username of Local Folders is "nobody". So it's better to use
+ // its hostname which is "Local Folders".
+ // TODO: maybe test against .key == MailServices.accounts.localFoldersServer.key ?
+ if (deferredToAccount.incomingServer.hostName == "Local Folders") {
+ accountsToConvert += ", " + deferredToAccount.incomingServer.prettyName;
+ } else {
+ accountsToConvert += ", " + deferredToAccount.incomingServer.prettyName;
+ }
+ log.info(accountsToConvert + " will be converted");
+ let storeContractId = Services.prefs.getCharPref(
+ "mail.server." + deferredToAccount.incomingServer.key + ".storeContractID"
+ );
+
+ if (storeContractId == "@mozilla.org/msgstore/berkeleystore;1") {
+ storeContractId = "maildir";
+ } else {
+ storeContractId = "mbox";
+ }
+
+ // Username of Local Folders is "nobody". So it's better to use
+ // its hostname which is "Local Folders".
+ // TODO: maybe test against .key != MailServices.accounts.localFoldersServer.key ?
+ let deferredToAccountName = deferredToAccount.incomingServer.hostName;
+ if (deferredToAccountName != "Local Folders") {
+ deferredToAccountName = deferredToAccount.incomingServer.username;
+ }
+
+ if (aServer.rootFolder.filePath.path != deferredToRootFolder) {
+ document.getElementById("warningSpan").textContent =
+ bundle.formatStringFromName(
+ "converterDialog.warningForDeferredAccount",
+ [
+ aServer.username,
+ deferredToAccountName,
+ deferredToAccountName,
+ deferredAccountsString,
+ accountsToConvert,
+ storeContractId,
+ brandShortName,
+ ]
+ );
+ } else {
+ document.getElementById("warningSpan").textContent =
+ bundle.formatStringFromName(
+ "converterDialog.warningForDeferredToAccount",
+ [
+ deferredToAccountName,
+ deferredAccountsString,
+ accountsToConvert,
+ storeContractId,
+ brandShortName,
+ ]
+ );
+ }
+
+ document.getElementById("messageSpan").textContent =
+ bundle.formatStringFromName("converterDialog.messageForDeferredAccount", [
+ accountsToConvert,
+ storeContractId,
+ ]);
+ gServer = deferredToAccount.incomingServer;
+ } else {
+ // No account is deferred.
+ let storeContractId = Services.prefs.getCharPref(
+ "mail.server." + aServer.key + ".storeContractID"
+ );
+ if (storeContractId == "@mozilla.org/msgstore/berkeleystore;1") {
+ storeContractId = "maildir";
+ } else {
+ storeContractId = "mbox";
+ }
+
+ let tempName = aServer.username;
+ if (tempName == "nobody") {
+ tempName = "Local Folders";
+ } else if (!tempName) {
+ tempName = aServer.hostName;
+ }
+
+ document.getElementById("warningSpan").textContent =
+ bundle.formatStringFromName("converterDialog.warning", [
+ tempName,
+ storeContractId,
+ brandShortName,
+ ]);
+ document.getElementById("messageSpan").textContent =
+ bundle.formatStringFromName("converterDialog.message", [
+ tempName,
+ storeContractId,
+ ]);
+ gServer = aServer;
+ }
+
+ // Forces the resize of the dialog to the actual content
+ window.sizeToContent();
+}
+
+/**
+ * Start the conversion process.
+ *
+ * @param {string} aSelectedStoreType - mailstore type selected by user.
+ * @param {object} aResponse - response from the migration dialog modal.
+ */
+function startContinue(aSelectedStoreType, aResponse) {
+ gResponse = aResponse;
+ gFolder = gServer.rootFolder.filePath;
+
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/converterDialog.properties"
+ );
+
+ document
+ .getElementById("progress")
+ .addEventListener("progress", function (e) {
+ document.getElementById("progress").value = e.detail;
+ document.getElementById("progressPercent").textContent =
+ bundle.formatStringFromName("converterDialog.percentDone", [e.detail]);
+ });
+
+ document.getElementById("warningArea").hidden = true;
+ document.getElementById("progressArea").hidden = false;
+
+ // Storing original prefs and root folder path
+ // to revert changes in case of error.
+ let p1 = "mail.server." + gServer.key + ".directory";
+ let p2 = "mail.server." + gServer.key + ".directory-rel";
+ let p3 = "mail.server." + gServer.key + ".newsrc.file";
+ let p4 = "mail.server." + gServer.key + ".newsrc.file-rel";
+ let p5 = "mail.server." + gServer.key + ".storeContractID";
+
+ let originalDirectoryPref = Services.prefs.getCharPref(p1);
+ let originalDirectoryRelPref = Services.prefs.getCharPref(p2);
+ let originalNewsrcFilePref;
+ let originalNewsrcFileRelPref;
+ if (gServer.type == "nntp") {
+ originalNewsrcFilePref = Services.prefs.getCharPref(p3);
+ originalNewsrcFileRelPref = Services.prefs.getCharPref(p4);
+ }
+ let originalStoreContractID = Services.prefs.getCharPref(p5);
+ let originalRootFolderPath = gServer.rootFolder.filePath.path;
+
+ /**
+ * Called when promise returned by convertMailStoreTo() is rejected.
+ *
+ * @param {string} aReason - error because of which the promise was rejected.
+ */
+ function promiseRejected(aReason) {
+ log.error("Conversion to '" + aSelectedStoreType + "' failed: " + aReason);
+ document.getElementById("messageSpan").hidden = true;
+
+ document.getElementById("errorSpan").hidden = false;
+ gResponse.newRootFolder = null;
+
+ // Revert prefs.
+ Services.prefs.setCharPref(p1, originalDirectoryPref);
+ Services.prefs.setCharPref(p2, originalDirectoryRelPref);
+ if (gServer.type == "nntp") {
+ Services.prefs.setCharPref(p3, originalNewsrcFilePref);
+ Services.prefs.setCharPref(p4, originalNewsrcFileRelPref);
+ }
+ Services.prefs.setCharPref(p5, originalStoreContractID);
+ Services.prefs.savePrefFile(null);
+ if (gServer.rootFolder.filePath.path != originalRootFolderPath) {
+ gServer.rootFolder.filePath = new FileUtils.File(originalRootFolderPath);
+ }
+ Services.io.offline = gOriginalOffline;
+ }
+
+ /**
+ * Called when promise returned by convertMailStoreTo() is resolved.
+ *
+ * @param {string} aVal - path of the new account root folder with which the
+ * promise returned by convertMailStoreTo() is resolved.
+ */
+ function promiseResolved(aVal) {
+ log.info("Converted to '" + aSelectedStoreType + "' - " + aVal);
+
+ gResponse.newRootFolder = aVal;
+ for (let deferredAccount of gDeferredAccounts) {
+ let defServer = deferredAccount.incomingServer;
+ defServer.rootMsgFolder.filePath = new FileUtils.File(aVal);
+ Services.prefs.setCharPref(
+ "mail.server." + defServer.key + ".storeContractID",
+ aSelectedStoreType
+ );
+ }
+
+ Services.io.offline = gOriginalOffline;
+ document.getElementById("cancel").hidden = true;
+ document.getElementById("finish").hidden = false;
+ document.getElementById("messageSpan").hidden = true;
+ document.getElementById("completeSpan").hidden = false;
+ }
+
+ /**
+ * Check whether an mbox folder can be compacted or not.
+ *
+ * @param {nsIMsgFolder} aFolder - mbox folder that is to be checked.
+ */
+ function canCompact(aFolder) {
+ if (aFolder.expungedBytes != 0) {
+ return true;
+ }
+ if (aFolder.hasSubFolders) {
+ for (let subFolder of aFolder.subFolders) {
+ if (canCompact(subFolder)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Compaction (compactAll()) works only for mbox folders which satisfy one of
+ // the following 2 conditions -
+ // 1. Messages are moved out of the folder.
+ // 2. Messages are moved out of some descendant folder of the folder.
+ // If the account root folder can be compacted, start the conversion after
+ // compacting it.
+ if (
+ originalStoreContractID == "@mozilla.org/msgstore/berkeleystore;1" &&
+ canCompact(gServer.rootFolder)
+ ) {
+ let urlListener = {
+ OnStartRunningUrl(aUrl) {},
+ OnStopRunningUrl(aUrl, aExitCode) {
+ let pConvert = MailstoreConverter.convertMailStoreTo(
+ originalStoreContractID,
+ gServer,
+ document.getElementById("progress")
+ );
+ pConvert
+ .then(function (val) {
+ promiseResolved(val);
+ })
+ .catch(function (reason) {
+ promiseRejected(reason);
+ });
+ },
+ };
+ gServer.rootFolder.compactAll(urlListener, null);
+ } else {
+ let pConvert = MailstoreConverter.convertMailStoreTo(
+ originalStoreContractID,
+ gServer,
+ document.getElementById("progress")
+ );
+ pConvert
+ .then(function (val) {
+ promiseResolved(val);
+ })
+ .catch(function (reason) {
+ promiseRejected(reason);
+ });
+ }
+}
+
+/**
+ * Cancel the conversion.
+ *
+ * @param {object} aResponse - response param from the migration dialog modal.
+ */
+function cancelConversion(aResponse) {
+ gResponse = aResponse;
+ gResponse.newRootFolder = null;
+ MailstoreConverter.terminateWorkers();
+ Services.io.offline = gOriginalOffline;
+ window.close();
+}
+
+/**
+ * Called when "finish" button is clicked.
+ */
+function finishConversion() {
+ window.close();
+}
diff --git a/comm/mailnews/base/prefs/content/converterDialog.xhtml b/comm/mailnews/base/prefs/content/converterDialog.xhtml
new file mode 100644
index 0000000000..40944fd556
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/converterDialog.xhtml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/.
+-->
+<!DOCTYPE html [ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % converterDTD SYSTEM "chrome://messenger/locale/converterDialog.dtd">
+%converterDTD; ]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="UTF-8" />
+ <title>&converterDialog.title;</title>
+ <link
+ rel="stylesheet"
+ href="chrome://messenger/skin/shared/converterDialog.css"
+ />
+ <script src="chrome://messenger/content/converterDialog.js"></script>
+ </head>
+
+ <body onload="placeAccountName(window.arguments[0]);">
+ <div id="warningArea" class="convert-area">
+ <img class="infoIcon" alt="" />
+ <p id="warningSpan"></p>
+ <div class="controls">
+ <button onclick="cancelConversion(window.arguments[2]);">
+ &converterDialog.cancelButton;
+ </button>
+ <button
+ onclick="startContinue(window.arguments[1], window.arguments[2]);"
+ >
+ &converterDialog.continueButton;
+ </button>
+ </div>
+ </div>
+ <div id="progressArea" class="convert-area" hidden="hidden">
+ <img class="infoIcon" alt="" />
+ <p id="messageSpan"></p>
+ <p id="completeSpan" hidden="hidden">&converterDialog.complete;</p>
+ <p id="errorSpan" hidden="hidden">&converterDialog.error;</p>
+ <progress id="progress" value="0" max="100"></progress>
+ <span id="progressPercent"></span>
+ <div class="controls">
+ <button id="cancel" onclick="cancelConversion(window.arguments[1]);">
+ &converterDialog.cancelButton;
+ </button>
+ <button id="finish" onclick="finishConversion();" hidden="hidden">
+ &converterDialog.finishButton;
+ </button>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/comm/mailnews/base/prefs/content/removeAccount.js b/comm/mailnews/base/prefs/content/removeAccount.js
new file mode 100644
index 0000000000..724e45e139
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/removeAccount.js
@@ -0,0 +1,168 @@
+/* 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/. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gServer;
+var gDialog;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+
+document.addEventListener("dialogdisclosure", showInfo);
+document.addEventListener("dialogaccept", onAccept);
+document.subDialogSetDefaultFocus = isInitialFocus => {
+ gDialog.getButton("cancel").focus();
+ delete document.subDialogSetDefaultFocus;
+};
+
+function onLoad(event) {
+ gServer = window.arguments[0].account.incomingServer;
+ gDialog = document.querySelector("dialog");
+
+ let bundle = document.getElementById("bundle_removeAccount");
+ let removeQuestion = bundle.getFormattedString("removeQuestion", [
+ gServer.prettyName,
+ ]);
+ document.getElementById("accountName").textContent = removeQuestion;
+
+ // Allow to remove account data if it has a local storage.
+ let localDirectory = gServer.localPath;
+ if (localDirectory && localDirectory.exists()) {
+ localDirectory.normalize();
+
+ // Do not allow removal if localPath is outside of profile folder.
+ let profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ profilePath.normalize();
+
+ // TODO: bug 77652, decide what to do for deferred accounts.
+ // And inform the user if the account localPath is outside the profile.
+ if (
+ gServer.isDeferredTo ||
+ (gServer instanceof Ci.nsIPop3IncomingServer &&
+ gServer.deferredToAccount) ||
+ !profilePath.contains(localDirectory)
+ ) {
+ document.getElementById("removeData").disabled = true;
+ }
+ } else {
+ document.getElementById("removeDataPossibility").collapsed = true;
+ }
+
+ if (gServer.type == "im") {
+ let dataCheckbox = document.getElementById("removeData");
+ dataCheckbox.label = dataCheckbox.getAttribute("labelChat");
+ dataCheckbox.accessKey = dataCheckbox.getAttribute("accesskeyChat");
+ }
+
+ enableRemove();
+}
+
+function enableRemove() {
+ gDialog.getButton("accept").disabled =
+ !document.getElementById("removeAccount").checked &&
+ !document.getElementById("removeData").checked;
+}
+
+/**
+ * Show the local directory.
+ */
+function openLocalDirectory() {
+ let nsLocalFile = Components.Constructor(
+ "@mozilla.org/file/local;1",
+ "nsIFile",
+ "initWithPath"
+ );
+ let localDir = gServer.localPath.path;
+ try {
+ new nsLocalFile(localDir).reveal();
+ } catch (e) {
+ // Reveal may fail e.g. on Linux, then just show the path as a string.
+ document.getElementById("localDirectory").value = localDir;
+ document.getElementById("localDirectory").collapsed = false;
+ }
+}
+
+function showInfo() {
+ let descs = document.querySelectorAll("vbox.indent");
+ for (let desc of descs) {
+ desc.collapsed = false;
+ }
+
+ // TODO: bug 1238271, this should use showFor attributes if possible.
+ if (gServer.type == "imap" || gServer.type == "nntp") {
+ document.getElementById("serverAccount").collapsed = false;
+ } else if (gServer.type == "im") {
+ document.getElementById("chatAccount").collapsed = false;
+ } else {
+ document.getElementById("localAccount").collapsed = false;
+ }
+
+ parent.gSubDialog._topDialog.resizeDialog();
+ gDialog.getButton("disclosure").disabled = true;
+ gDialog.getButton("disclosure").blur();
+}
+
+function removeAccount() {
+ let removeAccount = document.getElementById("removeAccount").checked;
+ let removeData = document.getElementById("removeData").checked;
+ let account = window.arguments[0].account;
+ try {
+ // Remove the requested account data.
+ if (removeAccount) {
+ try {
+ // Remove password information first.
+ account.incomingServer.forgetPassword();
+ } catch (e) {
+ /* It is OK if this fails. */
+ }
+ // Remove account
+ MailServices.accounts.removeAccount(account, removeData);
+ account = null;
+ delete window.arguments[0].account;
+ gServer = null;
+ window.arguments[0].result = true;
+ } else if (removeData) {
+ // Remove files only.
+ // TODO: bug 1302193
+ window.arguments[0].result = false;
+ }
+
+ document.getElementById("success").hidden = false;
+ } catch (ex) {
+ document.getElementById("failure").hidden = false;
+ console.error("Failure to remove account: " + ex);
+ window.arguments[0].result = false;
+ }
+ document.getElementById("progress").hidden = true;
+}
+
+function onAccept(event) {
+ // If Cancel is disabled, we already tried to remove the account
+ // and can only close the dialog.
+ if (gDialog.getButton("cancel").disabled) {
+ return;
+ }
+
+ gDialog.getButton("accept").disabled = true;
+ gDialog.getButton("cancel").disabled = true;
+ gDialog.getButton("disclosure").disabled = true;
+
+ // Change the "Remove" to an "OK" button by clearing the custom label.
+ gDialog.removeAttribute("buttonlabelaccept");
+ gDialog.removeAttribute("buttonaccesskeyaccept");
+ gDialog.getButton("accept").removeAttribute("label");
+ gDialog.getButton("accept").removeAttribute("accesskey");
+ gDialog.buttons = "accept";
+
+ document.getElementById("removeAccountSection").hidden = true;
+ document.getElementById("confirmationSection").hidden = false;
+ window.sizeToContent();
+
+ removeAccount();
+
+ gDialog.getButton("accept").disabled = false;
+ event.preventDefault();
+}
diff --git a/comm/mailnews/base/prefs/content/removeAccount.xhtml b/comm/mailnews/base/prefs/content/removeAccount.xhtml
new file mode 100644
index 0000000000..510b3fef84
--- /dev/null
+++ b/comm/mailnews/base/prefs/content/removeAccount.xhtml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+<?xml-stylesheet href="chrome://global/skin/checkbox.css" type="text/css"?>
+
+<!DOCTYPE html [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % removalDTD SYSTEM "chrome://messenger/locale/removeAccount.dtd">
+%removalDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ width="600"
+ scrolling="false">
+<head>
+ <title>&dialogTitle;</title>
+ <link rel="localization" href="branding/brand.ftl" />
+ <script defer="defer" src="chrome://messenger/content/removeAccount.js"></script>
+</head>
+<html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<dialog id="removeAccountDialog"
+ buttons="accept,disclosure,cancel"
+ buttonlabelaccept="&removeButton.label;"
+ buttonaccesskeyaccept="&removeButton.accesskey;"
+ defaultButton="cancel"
+ style="width:100vw; height:100vh;">
+ <stringbundle id="bundle_removeAccount"
+ src="chrome://messenger/locale/removeAccount.properties"/>
+ <vbox id="removeAccountSection">
+ <label id="accountName"></label>
+ <separator class="thin"/>
+ <checkbox id="removeAccount"
+ label="&removeAccount.label;"
+ checked="true"
+ disabled="true"
+ accesskey="&removeAccount.accesskey;"
+ oncommand="enableRemove();"/>
+ <vbox class="indent" collapsed="true">
+ <description>
+ &removeAccount.desc;
+ </description>
+ </vbox>
+ <vbox id="removeDataPossibility" collapsed="false">
+ <checkbox id="removeData"
+ label="&removeData.label;"
+ labelChat="&removeDataChat.label;"
+ accesskey="&removeData.accesskey;"
+ accesskeyChat="&removeDataChat.accesskey;"
+ oncommand="enableRemove();"/>
+ <vbox id="removeAccountBox" class="indent" collapsed="true">
+ <description id="localAccount" collapsed="true">
+ &removeDataLocalAccount.desc;
+ </description>
+ <description id="serverAccount" collapsed="true">
+ &removeDataServerAccount.desc;
+ </description>
+ <description id="chatAccount" collapsed="true">
+ &removeDataChatAccount.desc;
+ </description>
+ <hbox align="center">
+ <button id="showLocalDirectory"
+ label="&showData.label;"
+ accesskey="&showData.accesskey;"
+ oncommand="openLocalDirectory();"/>
+ <label id="localDirectory" collapsed="true"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+ <vbox id="confirmationSection" align="center" pack="center" flex="1" hidden="true">
+ <vbox id="progress" align="center">
+ <label>&progressPending;</label>
+ <html:progress max="100"/>
+ </vbox>
+ <label id="success" hidden="true">&progressSuccess;</label>
+ <label id="failure" hidden="true">&progressFailure;</label>
+ </vbox>
+</dialog>
+</html:body>
+</html>