summaryrefslogtreecommitdiffstats
path: root/mailnews/base/prefs
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/base/prefs')
-rw-r--r--mailnews/base/prefs/content/AccountManager.js1628
-rw-r--r--mailnews/base/prefs/content/AccountManager.xul92
-rw-r--r--mailnews/base/prefs/content/AccountWizard.js992
-rw-r--r--mailnews/base/prefs/content/AccountWizard.xul371
-rw-r--r--mailnews/base/prefs/content/SmtpServerEdit.js46
-rw-r--r--mailnews/base/prefs/content/SmtpServerEdit.xul28
-rw-r--r--mailnews/base/prefs/content/accountUtils.js462
-rw-r--r--mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js41
-rw-r--r--mailnews/base/prefs/content/accountcreation/accountConfig.js259
-rw-r--r--mailnews/base/prefs/content/accountcreation/createInBackend.js333
-rw-r--r--mailnews/base/prefs/content/accountcreation/emailWizard.js1959
-rw-r--r--mailnews/base/prefs/content/accountcreation/emailWizard.xul493
-rw-r--r--mailnews/base/prefs/content/accountcreation/fetchConfig.js240
-rw-r--r--mailnews/base/prefs/content/accountcreation/fetchhttp.js267
-rw-r--r--mailnews/base/prefs/content/accountcreation/guessConfig.js1145
-rw-r--r--mailnews/base/prefs/content/accountcreation/readFromXML.js238
-rw-r--r--mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js207
-rw-r--r--mailnews/base/prefs/content/accountcreation/util.js304
-rw-r--r--mailnews/base/prefs/content/accountcreation/verifyConfig.js347
-rw-r--r--mailnews/base/prefs/content/am-addressing.js79
-rw-r--r--mailnews/base/prefs/content/am-addressing.xul18
-rw-r--r--mailnews/base/prefs/content/am-addressingOverlay.xul135
-rw-r--r--mailnews/base/prefs/content/am-archiveoptions.js69
-rw-r--r--mailnews/base/prefs/content/am-archiveoptions.xul99
-rw-r--r--mailnews/base/prefs/content/am-copies.js471
-rw-r--r--mailnews/base/prefs/content/am-copies.xul21
-rw-r--r--mailnews/base/prefs/content/am-copiesOverlay.xul311
-rw-r--r--mailnews/base/prefs/content/am-help.js76
-rw-r--r--mailnews/base/prefs/content/am-identities-list.js180
-rw-r--r--mailnews/base/prefs/content/am-identities-list.xul67
-rw-r--r--mailnews/base/prefs/content/am-identity-edit.js405
-rw-r--r--mailnews/base/prefs/content/am-identity-edit.xul154
-rw-r--r--mailnews/base/prefs/content/am-junk.js296
-rw-r--r--mailnews/base/prefs/content/am-junk.xul232
-rw-r--r--mailnews/base/prefs/content/am-main.js55
-rw-r--r--mailnews/base/prefs/content/am-main.xul147
-rw-r--r--mailnews/base/prefs/content/am-offline.js351
-rw-r--r--mailnews/base/prefs/content/am-offline.xul158
-rw-r--r--mailnews/base/prefs/content/am-prefs.js114
-rw-r--r--mailnews/base/prefs/content/am-server-advanced.js157
-rw-r--r--mailnews/base/prefs/content/am-server-advanced.xul146
-rw-r--r--mailnews/base/prefs/content/am-server-top.xul13
-rw-r--r--mailnews/base/prefs/content/am-server.js400
-rw-r--r--mailnews/base/prefs/content/am-server.xul468
-rw-r--r--mailnews/base/prefs/content/am-serverwithnoidentities.js34
-rw-r--r--mailnews/base/prefs/content/am-serverwithnoidentities.xul77
-rw-r--r--mailnews/base/prefs/content/am-smtp.js256
-rw-r--r--mailnews/base/prefs/content/am-smtp.xul112
-rw-r--r--mailnews/base/prefs/content/amUtils.js205
-rw-r--r--mailnews/base/prefs/content/aw-accname.js73
-rw-r--r--mailnews/base/prefs/content/aw-accounttype.js114
-rw-r--r--mailnews/base/prefs/content/aw-done.js215
-rw-r--r--mailnews/base/prefs/content/aw-identity.js212
-rw-r--r--mailnews/base/prefs/content/aw-incoming.js176
-rw-r--r--mailnews/base/prefs/content/aw-outgoing.js151
-rw-r--r--mailnews/base/prefs/content/ispUtils.js166
-rw-r--r--mailnews/base/prefs/content/removeAccount.js156
-rw-r--r--mailnews/base/prefs/content/removeAccount.xul87
-rw-r--r--mailnews/base/prefs/content/smtpEditOverlay.js182
-rw-r--r--mailnews/base/prefs/content/smtpEditOverlay.xul124
60 files changed, 16414 insertions, 0 deletions
diff --git a/mailnews/base/prefs/content/AccountManager.js b/mailnews/base/prefs/content/AccountManager.js
new file mode 100644
index 000000000..c5335039b
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountManager.js
@@ -0,0 +1,1628 @@
+/* -*- 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/. */
+
+/*
+ * here's how this dialog works:
+ * The main dialog contains a tree on the left (accounttree) and a
+ * deck on the right. Each card in the deck on the right contains an
+ * IFRAME which loads a particular preference document (such as am-main.xul)
+ *
+ * when the user clicks on items in the tree on the right, 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 card containing the relevant page is brought to the front
+ * - 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.
+ */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/folderUtils.jsm");
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+// 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.
+ * Extentions 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");
+ }
+}
+
+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");
+ }
+}
+
+// called when the whole document loads
+// perform initialization here
+function onLoad() {
+ var selectedServer;
+ var 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.
+ if ("arguments" in window && window.arguments[0]) {
+ selectedServer = window.arguments[0].server;
+ selectPage = window.arguments[0].selectPage;
+ }
+
+ accountArray = new Object();
+ gGenericAttributeTypes = new Object();
+
+ gAccountTree.load();
+
+ setTimeout(selectServer, 0, selectedServer, selectPage);
+
+ // Make sure the account manager window fits the screen.
+ document.getElementById("accountManager").style.maxHeight =
+ (window.screen.availHeight - 30) + "px";
+}
+
+function onUnload() {
+ gAccountTree.unload();
+}
+
+function selectServer(server, selectPageId)
+{
+ let childrenNode = document.getElementById("account-tree-children");
+
+ // Default to showing the first account.
+ let accountNode = childrenNode.firstChild;
+
+ // Find the tree-node for the account we want to select
+ if (server) {
+ for (let i = 0; i < childrenNode.childNodes.length; i++) {
+ let account = childrenNode.childNodes[i]._account;
+ if (account && server == account.incomingServer) {
+ accountNode = childrenNode.childNodes[i];
+ // Make sure all the panes of the account to be selected are shown.
+ accountNode.setAttribute("open", "true");
+ break;
+ }
+ }
+ }
+
+ let pageToSelect = accountNode;
+
+ if (selectPageId) {
+ // Find the page that also corresponds to this server.
+ // It either is the accountNode itself...
+ let pageId = accountNode.getAttribute("PageTag");
+ if (pageId != selectPageId) {
+ // ... or one of its children.
+ pageToSelect = accountNode.querySelector('[PageTag="' + selectPageId + '"]');
+ }
+ }
+
+ let accountTree = document.getElementById("accounttree");
+ let index = accountTree.contentView.getIndexOfItem(pageToSelect);
+ accountTree.view.selection.select(index);
+ accountTree.treeBoxObject.ensureRowIsVisible(index);
+
+ let lastItem = accountNode.lastChild.lastChild;
+ if (lastItem.localName == "treeitem")
+ index = accountTree.contentView.getIndexOfItem(lastItem);
+
+ accountTree.treeBoxObject.ensureRowIsVisible(index);
+}
+
+function replaceWithDefaultSmtpServer(deletedSmtpServerKey)
+{
+ // First we replace the smtpserverkey in every identity.
+ let am = MailServices.accounts;
+ for (let identity in fixIterator(am.allIdentities,
+ Components.interfaces.nsIMsgIdentity)) {
+ 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 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;
+ }
+
+ if (!onSave())
+ return false;
+
+ // hack hack - save the prefs file NOW in case we crash
+ Services.prefs.savePrefFile(null);
+
+ if (gRestartNeeded) {
+ gRestartNeeded = !BrowserUtils.restartApplication();
+ // returns false so that Account manager is not exited when restart failed
+ return !gRestartNeeded;
+ }
+
+ 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.indexOf(dirLeafName) != -1)
+ 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 aDirToCheck An object describing the special directory.
+ * The object has the following members:
+ * dirsvc : A path keyword to retrieve from the Directory service.
+ * dir : An absolute filesystem path.
+ * Only one of 'dirsvc' or 'dir' can be specified.
+ * 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
+ * safeSubdirs : An array of directory names that are allowed to be used
+ * under the tested directory.
+ * @param aLocalPath An nsIFile of the directory to check, intended for message storage.
+ */
+ function checkLocalDirectoryIsSafe(aDirToCheck, aLocalPath) {
+ if (aDirToCheck.OS) {
+ if (aDirToCheck.OS.split(",").indexOf(Services.appinfo.OS) == -1)
+ return true;
+ }
+
+ let testDir = null;
+ if ("dirsvc" in aDirToCheck) {
+ try {
+ testDir = Services.dirsvc.get(aDirToCheck.dirsvc, Components.interfaces.nsIFile);
+ } catch (e) {
+ Components.utils.reportError("The special folder " + aDirToCheck.dirsvc +
+ " cannot be retrieved on this platform: " + e);
+ }
+
+ if (!testDir)
+ return true;
+ }
+ else if ("dir" in aDirToCheck) {
+ testDir = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ testDir.initWithPath(aDirToCheck.dir);
+ if (!testDir.exists())
+ return true;
+ } else {
+ Components.utils.reportError("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.
+ let allServers = MailServices.accounts.allServers;
+
+ for (let server in fixIterator(allServers,
+ Components.interfaces.nsIMsgIncomingServer))
+ {
+ 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.
+ Components.utils.reportError("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 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.realHostName");
+ var userElem = getPageFormElement("server.realUsername");
+ if (typeElem && userElem && hostElem) {
+ var newType = getFormElementValue(typeElem);
+ var oldHost = getAccountValue(currentAccount, accountValues, "server", "realHostName",
+ null, false);
+ var newHost = getFormElementValue(hostElem);
+ var oldUser = getAccountValue(currentAccount, accountValues, "server", "realUsername",
+ 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
+ .findRealServer(newUser, newHost, newType, 0);
+ 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());
+ }
+ }
+ }
+
+ // Check the new value of the server.localPath field for validity.
+ var pathElem = getPageFormElement("server.localPath");
+ if (!pathElem)
+ return true;
+
+ if (!checkDirectoryIsUsable(getFormElementValue(pathElem))) {
+// return false; // Temporarily disable this. Just show warning but do not block. See bug 921371.
+ Components.utils.reportError("Local directory '" +
+ getFormElementValue(pathElem).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");
+
+ 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;
+}
+
+function onAddAccount() {
+ MsgAccountWizard();
+}
+
+function AddMailAccount()
+{
+ NewMailAccount(MailServices.mailSession.topmostMsgWindow);
+}
+
+function AddIMAccount()
+{
+ window.openDialog("chrome://messenger/content/chat/imAccountWizard.xul",
+ "", "chrome,modal,titlebar,centerscreen");
+}
+
+/**
+ * Highlight the default account row in the account tree,
+ * optionally un-highlight the previous one.
+ *
+ * @param newDefault The account that has become the new default.
+ * Can be given as null if there is none.
+ * @param 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 accountTreeNodes = document.getElementById("account-tree-children")
+ .childNodes;
+ for (let i = 0; i < accountTreeNodes.length; i++) {
+ let accountNode = accountTreeNodes[i];
+ if (newDefault && newDefault == accountNode._account) {
+ let props = accountNode.firstChild.firstChild.getAttribute("properties");
+ accountNode.firstChild.firstChild
+ .setAttribute("properties", props + " isDefaultServer-true");
+ }
+ if (oldDefault && oldDefault == accountNode._account) {
+ let props = accountNode.firstChild.firstChild.getAttribute("properties");
+ props = props.replace(/isDefaultServer-true/, "");
+ accountNode.firstChild.firstChild.setAttribute("properties", props);
+ }
+ }
+}
+
+/**
+ * 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 = getDefaultAccount();
+ MailServices.accounts.defaultAccount = currentAccount;
+ markDefaultServer(currentAccount, previousDefault);
+
+ // This is only needed on Seamonkey which has this button.
+ setEnabled(document.getElementById("setDefaultButton"), false);
+}
+
+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 accountTreeNode = document.getElementById("account-tree-children");
+ // build the list of servers in the account tree (order is important)
+ for (let i = 0; i < accountTreeNode.childNodes.length; i++) {
+ if ("_account" in accountTreeNode.childNodes[i]) {
+ let curServer = accountTreeNode.childNodes[i]._account.incomingServer;
+ if (serverList.indexOf(curServer) == -1)
+ 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: server, account: currentAccount,
+ result: false };
+
+ window.openDialog("chrome://messenger/content/removeAccount.xul",
+ "removeAccount",
+ "chrome,titlebar,modal,centerscreen,resizable=no",
+ removeArgs);
+
+ // 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(getDefaultAccount(), null);
+}
+
+function saveAccount(accountValues, account)
+{
+ var identity = null;
+ var server = null;
+
+ if (account) {
+ identity = account.defaultIdentity;
+ server = account.incomingServer;
+ }
+
+ for (var type in accountValues) {
+ var typeArray = accountValues[type];
+
+ for (var slot in typeArray) {
+ var dest;
+ try {
+ if (type == "identity")
+ dest = identity;
+ else if (type == "server")
+ dest = server;
+ else if (type == "pop3")
+ dest = server.QueryInterface(Components.interfaces.nsIPop3IncomingServer);
+ else if (type == "imap")
+ dest = server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
+ else if (type == "none")
+ dest = server.QueryInterface(Components.interfaces.nsINoIncomingServer);
+ else if (type == "nntp")
+ dest = server.QueryInterface(Components.interfaces.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;
+
+ 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: " + preftype + "\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.xul", account);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Set enabled/disabled state for account actions buttons.
+ * Called by all apps, but if the buttons do not exist, exits early.
+ */
+function updateButtons(tree, account) {
+ let addAccountButton = document.getElementById("addAccountButton");
+ let removeButton = document.getElementById("removeButton");
+ let setDefaultButton = document.getElementById("setDefaultButton");
+
+ if (!addAccountButton && !removeButton && !setDefaultButton)
+ return; // Thunderbird isn't using these.
+
+ updateItems(tree, account, addAccountButton, setDefaultButton, removeButton);
+ updateBlockedItems([addAccountButton, setDefaultButton, removeButton], false);
+}
+
+/**
+ * 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.childNodes, 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.view.selection.count >= 1)) {
+ // 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 != getDefaultAccount() &&
+ 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.
+ * SeaMonkey: Not currently handled by WSM or the main loop yet
+ * since these buttons aren't under the IFRAME.
+ *
+ * @param aItems an array or NodeList of elements to be checked
+ * @param aMustBeTrue if true then the pref must be boolean and set to true
+ * to trigger the disabling (TB requires this, SM not)
+ */
+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.view.selection.count < 1)
+ return false;
+
+ let node = tree.contentView.getItemAtIndex(tree.currentIndex);
+ 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();
+ }
+
+ // 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);
+ }
+
+ if (changeAccount)
+ updateButtons(tree, 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(".xul")[0];
+ chromePackageName = MailServices.accounts.getChromePackageName(packageName);
+ }
+ catch (ex) {
+ chromePackageName = "messenger";
+ }
+ return "chrome://" + chromePackageName + "/content/" + pageId;
+}
+
+function loadPage(pageId)
+{
+ const LOAD_FLAGS_NONE = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
+ document.getElementById("contentFrame").webNavigation.loadURI(pageURL(pageId),
+ LOAD_FLAGS_NONE, null, null, null);
+}
+
+// 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;
+
+ 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] = new Object();
+
+ accountValues[type][slot] = value;
+}
+
+function getAccountValue(account, accountValues, type, slot, preftype, isGeneric) {
+ if (!(type in accountValues))
+ accountValues[type] = new Object();
+
+ // 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(Components.interfaces.nsIPop3IncomingServer);
+ else if (type == "imap")
+ source = server.QueryInterface(Components.interfaces.nsIImapIncomingServer);
+ else if (type == "none")
+ source = server.QueryInterface(Components.interfaces.nsINoIncomingServer);
+ else if (type == "nntp")
+ source = server.QueryInterface(Components.interfaces.nsINntpIncomingServer);
+ else if (type == "smtp")
+ source = MailServices.smtp.defaultServer;
+ } catch (ex) {
+ }
+
+ if (source) {
+ if (isGeneric) {
+ if (!(type in gGenericAttributeTypes))
+ gGenericAttributeTypes[type] = new Object();
+
+ // 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 formElement A XUL 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 == "textbox" &&
+ formElement.getAttribute("datatype") == "nsIFile") {
+ if (formElement.value) {
+ let localfile = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+
+ localfile.initWithPath(formElement.value);
+ return localfile;
+ }
+ return null;
+ }
+ if ((type == "textbox") || ("value" in formElement)) {
+ return formElement.value;
+ }
+ return null;
+ }
+ catch (ex) {
+ Components.utils.reportError("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 formElement A XUL input element.
+ * @param 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;
+ }
+ // handle nsIFile
+ else if (type == "textbox" &&
+ formElement.getAttribute("datatype") == "nsIFile") {
+ if (value) {
+ let localfile = value.QueryInterface(Components.interfaces.nsIFile);
+ try {
+ formElement.value = localfile.path;
+ } catch (ex) {
+ dump("Still need to fix uninitialized nsIFile problem!\n");
+ }
+ } else {
+ formElement.value = "";
+ }
+ }
+ else if (type == "textbox") {
+ if (value == null)
+ formElement.value = null;
+ else
+ formElement.value = value;
+ }
+ else if (type == "label") {
+ formElement.value = value || "";
+ }
+ // let the form figure out what to do with it
+ else {
+ if (value == null)
+ 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;
+}
+
+/**
+ * Returns the default account without throwing exception if there is none.
+ * The account manager can be opened even if there are no account yet.
+ */
+function getDefaultAccount() {
+ try {
+ return MailServices.accounts.defaultAccount;
+ } catch (e) {
+ return null; // No default account yet.
+ }
+}
+
+/**
+ * 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 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] = new Object();
+ accountArray[serverId]._account = account;
+ }
+
+ return accountArray[serverId];
+}
+
+var gAccountTree = {
+ load: function at_load() {
+ this._build();
+
+ MailServices.accounts.addIncomingServerListener(this);
+ },
+ unload: function at_unload() {
+ MailServices.accounts.removeIncomingServerListener(this);
+ },
+ onServerLoaded: function at_onServerLoaded(aServer) {
+ this._build();
+ },
+ onServerUnloaded: function at_onServerUnloaded(aServer) {
+ this._build();
+ },
+ onServerChanged: function at_onServerChanged(aServer) {},
+
+ _dataStore: Components.classes["@mozilla.org/xul/xulstore;1"]
+ .getService(Components.interfaces.nsIXULStore),
+
+ /**
+ * Retrieve from XULStore.json whether the account should be expanded (open)
+ * in the account tree.
+ *
+ * @param aAccountKey key of the account to check
+ */
+ _getAccountOpenState: function at_getAccountOpenState(aAccountKey) {
+ if (!this._dataStore.hasValue(document.documentURI, aAccountKey, "open")) {
+ // If there was no value stored, use opened state.
+ return "true";
+ } else {
+ // 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: function at_build() {
+ const Ci = Components.interfaces;
+ var bundle = document.getElementById("bundle_prefs");
+ function getString(aString) { return bundle.getString(aString); }
+ var panels = [{string: getString("prefPanel-server"), src: "am-server.xul"},
+ {string: getString("prefPanel-copies"), src: "am-copies.xul"},
+ {string: getString("prefPanel-synchronization"), src: "am-offline.xul"},
+ {string: getString("prefPanel-diskspace"), src: "am-offline.xul"},
+ {string: getString("prefPanel-addressing"), src: "am-addressing.xul"},
+ {string: getString("prefPanel-junk"), src: "am-junk.xul"}];
+
+ let accounts = allAccountsSorted(false);
+
+ let mainTree = document.getElementById("account-tree-children");
+ // 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 addresssing
+ }
+
+ // 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
+ let catMan = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Ci.nsICategoryManager);
+ const CATEGORY = "mailnews-accountmanager-extensions";
+ let catEnum = catMan.enumerateCategory(CATEGORY);
+ while (catEnum.hasMoreElements()) {
+ let entryName = null;
+ try {
+ entryName = catEnum.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ let svc = Components.classes[catMan.getCategoryEntry(CATEGORY, entryName)]
+ .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 + ".xul"});
+ }
+ } catch(e) {
+ // Fetching of this extension panel failed so do not show it,
+ // just log error.
+ let extName = entryName || "(unknown)";
+ Components.utils.reportError("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);
+ Components.utils.reportError("Error accessing account " + accountID + ": " + e);
+ accountName = "Invalid account " + accountID;
+ panelsToKeep.length = 0;
+ }
+
+ // Create the top level tree-item.
+ var treeitem = document.createElement("treeitem");
+ mainTree.appendChild(treeitem);
+ var treerow = document.createElement("treerow");
+ treeitem.appendChild(treerow);
+ var treecell = document.createElement("treecell");
+ treerow.appendChild(treecell);
+ treecell.setAttribute("label", accountName);
+ treeitem.setAttribute("PageTag", amChrome);
+ // Add icons based on account type.
+ if (server) {
+ treecell.setAttribute("properties", "folderNameCol isServer-true" +
+ " serverType-" + server.type);
+ // For IM accounts, we can try to fetch a protocol specific icon.
+ if (server.type == "im") {
+ treecell.setAttribute("src", server.wrappedJSObject.imAccount
+ .protocol.iconBaseURI + "icon.png");
+ }
+ }
+
+ if (panelsToKeep.length > 0) {
+ var treekids = document.createElement("treechildren");
+ treeitem.appendChild(treekids);
+ for (let panel of panelsToKeep) {
+ var kidtreeitem = document.createElement("treeitem");
+ treekids.appendChild(kidtreeitem);
+ var kidtreerow = document.createElement("treerow");
+ kidtreeitem.appendChild(kidtreerow);
+ var kidtreecell = document.createElement("treecell");
+ kidtreerow.appendChild(kidtreecell);
+ kidtreecell.setAttribute("label", panel.string);
+ kidtreeitem.setAttribute("PageTag", panel.src);
+ kidtreeitem._account = account;
+ }
+ treeitem.setAttribute("container", "true");
+ treeitem.id = accountKey;
+ // Load the 'open' state of the account from XULStore.json.
+ treeitem.setAttribute("open", this._getAccountOpenState(accountKey));
+ // Let the XULStore.json automatically save the 'open' state of the
+ // account when it is changed.
+ treeitem.setAttribute("persist", "open");
+ }
+ treeitem._account = account;
+ }
+
+ markDefaultServer(getDefaultAccount(), null);
+
+ // Now add the outgoing server node.
+ var treeitem = document.createElement("treeitem");
+ mainTree.appendChild(treeitem);
+ var treerow = document.createElement("treerow");
+ treeitem.appendChild(treerow);
+ var treecell = document.createElement("treecell");
+ treerow.appendChild(treecell);
+ treecell.setAttribute("label", getString("prefPanel-smtp"));
+ treeitem.setAttribute("PageTag", "am-smtp.xul");
+ treecell.setAttribute("properties",
+ "folderNameCol isServer-true serverType-smtp");
+ }
+};
diff --git a/mailnews/base/prefs/content/AccountManager.xul b/mailnews/base/prefs/content/AccountManager.xul
new file mode 100644
index 000000000..b6fd93c32
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountManager.xul
@@ -0,0 +1,92 @@
+<?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/folderPane.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/AccountManager.dtd">
+<dialog id="accountManager"
+ windowtype="mailnews:accountmanager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&accountManagerTitle.label;"
+ style="&accountManager.size;"
+ persist="width height screenX screenY"
+ buttons="accept,cancel"
+ onload="onLoad(event);"
+ onunload="onUnload();"
+ ondialogaccept="return onAccept(true);">
+<stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+<stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+<script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
+<script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+<script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+<script type="application/javascript" src="chrome://messenger/content/am-help.js"/>
+<script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+
+ <hbox flex="1">
+ <vbox style="&accountTree.width;">
+ <tree flex="1" onselect="onAccountTreeSelect(null, null);" id="accounttree"
+ seltype="single" hidecolumnpicker="true">
+ <treecols>
+ <treecol id="AccountCol" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="account-tree-children"/>
+ </tree>
+
+#ifdef MOZ_THUNDERBIRD
+ <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="AddMailAccount(event); event.stopPropagation();"/>
+ <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"
+ label="&addOtherAccountButton.label;"
+ accesskey="&addOtherAccountButton.accesskey;"
+ prefstring="mail.disable_new_account_addition"
+ oncommand="onAddAccount(event); 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>
+#else
+ <button label="&addAccountButton.label;" oncommand="onAddAccount(event);" id="addAccountButton"
+ prefstring="mail.disable_new_account_addition"
+ accesskey="&addAccountButton.accesskey;"/>
+ <button label="&setDefaultButton.label;" oncommand="onSetDefault(event);" disabled="true" id="setDefaultButton"
+ prefstring="mail.disable_button.set_default_account"
+ accesskey="&setDefaultButton.accesskey;"/>
+ <button disabled="true" label="&removeButton.label;" oncommand="onRemoveAccount(event);" id="removeButton"
+ prefstring="mail.disable_button.delete_account"
+ accesskey="&removeButton.accesskey;"/>
+#endif
+ </vbox>
+
+ <iframe id="contentFrame" name="contentFrame" flex="1"/>
+ </hbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/AccountWizard.js b/mailnews/base/prefs/content/AccountWizard.js
new file mode 100644
index 000000000..1903cbc88
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountWizard.js
@@ -0,0 +1,992 @@
+/* -*- Mode: Java; 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/. */
+
+/* the okCallback is used for sending a callback for the parent window */
+var okCallback = null;
+/* The account wizard creates new accounts */
+
+/*
+ data flow into the account wizard like this:
+
+ For new accounts:
+ * pageData -> Array -> createAccount -> finishAccount
+
+ For accounts coming from the ISP setup:
+ * RDF -> Array -> pageData -> Array -> createAccount -> finishAccount
+
+ for "unfinished accounts"
+ * account -> Array -> pageData -> Array -> finishAccount
+
+ Where:
+ pageData - the actual pages coming out of the Widget State Manager
+ RDF - the ISP datasource
+ Array - associative array of attributes, that very closely
+ resembles the nsIMsgAccount/nsIMsgIncomingServer/nsIMsgIdentity
+ structure
+ createAccount() - creates an account from the above Array
+ finishAccount() - fills an existing account with data from the above Array
+
+*/
+
+/*
+ the account wizard path is something like:
+
+ accounttype -> identity -> server -> login -> accname -> done
+ \-> newsserver ----/
+
+ where the accounttype determines which path to take
+ (server vs. newsserver)
+*/
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var contentWindow;
+
+var gPageData;
+
+var nsIMsgIdentity = Components.interfaces.nsIMsgIdentity;
+var nsIMsgIncomingServer = Components.interfaces.nsIMsgIncomingServer;
+var gPrefsBundle, gMessengerBundle;
+
+// the current nsIMsgAccount
+var gCurrentAccount;
+
+// default account
+var gDefaultAccount;
+
+// the current associative array that
+// will eventually be dumped into the account
+var gCurrentAccountData;
+
+// default picker mode for copies and folders
+var gDefaultSpecialFolderPickerMode = "0";
+
+// event handlers
+function onAccountWizardLoad() {
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ gMessengerBundle = document.getElementById("bundle_messenger");
+
+ if ("testingIspServices" in this) {
+ if ("SetCustomizedWizardDimensions" in this && testingIspServices()) {
+ SetCustomizedWizardDimensions();
+ }
+ }
+
+ /* We are checking here for the callback argument */
+ if (window.arguments && window.arguments[0]) {
+ if(window.arguments[0].okCallback )
+ {
+ //dump("There is okCallback");
+ top.okCallback = window.arguments[0].okCallback;
+ }
+ }
+
+ checkForInvalidAccounts();
+
+ try {
+ gDefaultAccount = MailServices.accounts.defaultAccount;
+ }
+ catch (ex) {
+ // no default account, this is expected the first time you launch mail
+ // on a new profile
+ gDefaultAccount = null;
+ }
+
+ // Set default value for global inbox checkbox
+ var checkGlobalInbox = document.getElementById("deferStorage");
+ try {
+ checkGlobalInbox.checked = Services.prefs.getBoolPref("mail.accountwizard.deferstorage");
+ } catch(e) {}
+}
+
+function onCancel()
+{
+ if ("ActivationOnCancel" in this && ActivationOnCancel())
+ return false;
+ var firstInvalidAccount = getFirstInvalidAccount();
+ 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.identity.fullName || !pageData.identity.fullName.value) {
+ setPageData(pageData, "identity", "fullName", "");
+ }
+
+ // set the email if it doesn't exist
+ if (!pageData.identity.email || !pageData.identity.email.value) {
+ setPageData(pageData, "identity", "email", "user@domain.invalid");
+ }
+
+ // call FinishAccount() and not onFinish(), since the "finish"
+ // button may be disabled
+ FinishAccount();
+ }
+ else {
+ // 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
+ if (MailServices.accounts.accounts.length < 1) {
+ 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;
+ }
+
+ if(top.okCallback && closeWizard) {
+ var state = false;
+ top.okCallback(state);
+ }
+ }
+ return closeWizard;
+}
+
+function FinishAccount()
+{
+ try {
+ var pageData = GetPageData();
+
+ var accountData= gCurrentAccountData;
+
+ if (!accountData)
+ {
+ accountData = new Object;
+ // Time to set the smtpRequiresUsername attribute
+ if (!serverIsNntp(pageData))
+ accountData.smtpRequiresUsername = true;
+ }
+
+ // we may need local folders before account is "Finished"
+ // if it's a pop3 account which defers to Local Folders.
+ verifyLocalFoldersAccount();
+
+ PageDataToAccountData(pageData, accountData);
+
+ FixupAccountDataForIsp(accountData);
+
+ // we might be simply finishing another account
+ if (!gCurrentAccount)
+ gCurrentAccount = createAccount(accountData);
+
+ // transfer all attributes from the accountdata
+ finishAccount(gCurrentAccount, accountData);
+
+ setupCopiesAndFoldersServer(gCurrentAccount, getCurrentServerIsDeferred(pageData), accountData);
+
+ if (gCurrentAccount.incomingServer.canBeDefaultServer)
+ EnableCheckMailAtStartUpIfNeeded(gCurrentAccount);
+
+ if (!document.getElementById("downloadMsgs").hidden) {
+ // skip the default biff, we will load messages manually if needed
+ window.opener.gLoadStartFolder = false;
+ if (document.getElementById("downloadMsgs").checked) {
+ window.opener.gNewAccountToLoad = gCurrentAccount; // load messages for new POP account
+ }
+ }
+
+ // 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");
+ }
+ window.close();
+ if(top.okCallback)
+ {
+ var state = true;
+ //dump("finish callback");
+ top.okCallback(state);
+ }
+ }
+ 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)
+{
+ if (!accountData) {
+ dump("null account data! clearing..\n");
+ // handle null accountData as if it were an empty object
+ // so that we clear-out any old pagedata from a
+ // previous accountdata. The trick is that
+ // with an empty object, accountData.identity.slot is undefined,
+ // so this will clear out the prefill data in setPageData
+
+ accountData = new Object;
+ accountData.incomingServer = new Object;
+ accountData.identity = new Object;
+ accountData.smtp = new Object;
+ }
+
+ var server = accountData.incomingServer;
+
+ if (server.type == undefined) {
+ // clear out the old server data
+ //setPageData(pageData, "accounttype", "mailaccount", undefined);
+ // setPageData(pageData, "accounttype", "newsaccount", undefined);
+ setPageData(pageData, "server", "servertype", undefined);
+ setPageData(pageData, "server", "hostname", undefined);
+
+ }
+ else {
+ if (server.type == "nntp") {
+ setPageData(pageData, "accounttype", "newsaccount", true);
+ setPageData(pageData, "accounttype", "mailaccount", false);
+ setPageData(pageData, "newsserver", "hostname", server.hostName);
+ }
+ else {
+ setPageData(pageData, "accounttype", "mailaccount", true);
+ setPageData(pageData, "accounttype", "newsaccount", false);
+ setPageData(pageData, "server", "servertype", server.type);
+ setPageData(pageData, "server", "hostname", server.hostName);
+ }
+ setPageData(pageData, "accounttype", "otheraccount", false);
+ }
+
+ setPageData(pageData, "login", "username", server.username || "");
+ setPageData(pageData, "login", "password", server.password || "");
+ setPageData(pageData, "accname", "prettyName", server.prettyName || "");
+ setPageData(pageData, "accname", "userset", false);
+ setPageData(pageData, "ispdata", "supplied", false);
+
+ var identity;
+
+ if (accountData.identity) {
+ dump("This is an accountdata\n");
+ identity = accountData.identity;
+ }
+ else if (accountData.identities) {
+ identity = accountData.identities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
+ dump("this is an account, id= " + identity + "\n");
+ }
+
+ setPageData(pageData, "identity", "email", identity.email || "");
+ setPageData(pageData, "identity", "fullName", identity.fullName || "");
+
+ var smtp;
+
+ if (accountData.smtp) {
+ smtp = accountData.smtp;
+ setPageData(pageData, "server", "smtphostname", smtp.hostname);
+ setPageData(pageData, "login", "smtpusername", smtp.username);
+ }
+}
+
+// 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 = new Object;
+ if (!accountData.incomingServer)
+ accountData.incomingServer = new Object;
+ if (!accountData.smtp)
+ accountData.smtp = new Object;
+ if (!accountData.pop3)
+ accountData.pop3 = new Object;
+ if (!accountData.imap)
+ accountData.imap = new Object;
+
+ var identity = accountData.identity;
+ var server = accountData.incomingServer;
+ var smtp = accountData.smtp;
+ var pop3 = accountData.pop3;
+ var imap = accountData.imap;
+
+ if (pageData.identity.email)
+ identity.email = pageData.identity.email.value;
+ if (pageData.identity.fullName)
+ identity.fullName = pageData.identity.fullName.value;
+
+ server.type = getCurrentServerType(pageData);
+ server.hostName = getCurrentHostname(pageData);
+ if (getCurrentServerIsDeferred(pageData))
+ {
+ try
+ {
+ let localFoldersServer = MailServices.accounts.localFoldersServer;
+ let localFoldersAccount = MailServices.accounts.FindAccountForServer(localFoldersServer);
+ pop3.deferredToAccount = localFoldersAccount.key;
+ pop3.deferGetNewMail = true;
+ server["ServerType-pop3"] = pop3;
+ }
+ catch (ex) {dump ("exception setting up deferred account" + ex);}
+ }
+ if (serverIsNntp(pageData)) {
+ // this stuff probably not relevant
+ dump("not setting username/password/etc\n");
+ }
+ else {
+ if (pageData.login) {
+ if (pageData.login.username)
+ server.username = pageData.login.username.value;
+ if (pageData.login.password)
+ server.password = pageData.login.password.value;
+ if (pageData.login.smtpusername)
+ smtp.username = pageData.login.smtpusername.value;
+ }
+
+ dump("pageData.server = " + pageData.server + "\n");
+ if (pageData.server) {
+ dump("pageData.server.smtphostname.value = " + pageData.server.smtphostname + "\n");
+ if (pageData.server.smtphostname &&
+ pageData.server.smtphostname.value)
+ smtp.hostname = pageData.server.smtphostname.value;
+ }
+ if (pageData.identity && pageData.identity.smtpServerKey)
+ identity.smtpServerKey = pageData.identity.smtpServerKey.value;
+
+ if (pageData.server.port &&
+ pageData.server.port.value)
+ {
+ if (server.type == 'imap')
+ {
+ imap.port = pageData.server.port.value;
+ server["ServerType-imap"] = imap;
+ }
+ else if (server.type == 'pop3')
+ {
+ pop3.port = pageData.server.port.value;
+ server["ServerType-pop3"] = pop3;
+ }
+ }
+
+ if (pageData.server.leaveMessagesOnServer &&
+ pageData.server.leaveMessagesOnServer.value)
+ {
+ pop3.leaveMessagesOnServer = pageData.server.leaveMessagesOnServer.value;
+ server["ServerType-pop3"] = pop3;
+ }
+ }
+
+ if (pageData.accname) {
+ if (pageData.accname.prettyName)
+ server.prettyName = pageData.accname.prettyName.value;
+ }
+
+}
+
+// given an accountData structure, create an account
+// (but don't fill in any fields, that's for finishAccount()
+function createAccount(accountData)
+{
+ // Retrieve the server (data) from the account data.
+ var server = accountData.incomingServer;
+
+ // for news, username is always null
+ var username = (server.type == "nntp") ? null : server.username;
+ dump("MailServices.accounts.createIncomingServer(" +
+ username + ", " + server.hostName + ", " + server.type + ")\n");
+ // Create a (actual) server.
+ server = MailServices.accounts.createIncomingServer(username, server.hostName, server.type);
+
+ 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).
+ if (server.type == "nntp")
+ 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
+ dump("srcServer.ServerType-" + srcServer.type + " = " +
+ srcServer["ServerType-" + srcServer.type] + "\n");
+ if (srcServer["ServerType-" + srcServer.type]) {
+ // handle server-specific stuff
+ var IID;
+ try {
+ IID = destServer.protocolInfo.serverIID;
+ } catch (ex) {
+ Components.utils.reportError("Could not get IID for " + srcServer.type + ": " + ex);
+ }
+
+ if (IID) {
+ destProtocolServer = destServer.QueryInterface(IID);
+ 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;
+ }
+
+ // copy identity info
+ var destIdentity = account.identities.length ?
+ account.identities.queryElementAt(0, nsIMsgIdentity) :
+ 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
+ var 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 (accountData.smtp.hostname && !destIdentity.smtpServerKey)
+ {
+ // hostname + no key => create a new SMTP server.
+
+ let smtpServer = MailServices.smtp.createServer();
+ var isDefaultSmtpServer;
+ if (!MailServices.smtp.defaultServer.hostname) {
+ MailServices.smtp.defaultServer = smtpServer;
+ isDefaultSmtpServer = true;
+ }
+
+ copyObjectToInterface(smtpServer, accountData.smtp, false);
+
+ // If it's the default server we created, make the identity use
+ // "Use Default" by default.
+ destIdentity.smtpServerKey =
+ (isDefaultSmtpServer) ? "" : smtpServer.key;
+ }
+ } // if the account has an identity...
+
+ if (this.FinishAccountHook != undefined) {
+ 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, accountIsDeferred, accountData)
+{
+ try {
+ var server = account.incomingServer;
+
+ // This function sets up the default send preferences. The send preferences
+ // go on identities, so there is no need to continue without any identities.
+ if (server.type == "rss" || account.identities.length == 0)
+ return false;
+ let identity = account.identities.queryElementAt(0, Components.interfaces.nsIMsgIdentity);
+ // 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 = !accountIsDeferred && 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(Components.interfaces.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 AccountExists(userName, hostName, serverType)
+{
+ return MailServices.accounts.findRealServer(userName, hostName, serverType, 0);
+}
+
+function getFirstInvalidAccount()
+{
+ let invalidAccounts = getInvalidAccounts(MailServices.accounts.accounts);
+
+ if (invalidAccounts.length > 0)
+ return invalidAccounts[0];
+ else
+ return null;
+}
+
+function checkForInvalidAccounts()
+{
+ var firstInvalidAccount = getFirstInvalidAccount();
+
+ if (firstInvalidAccount) {
+ var pageData = GetPageData();
+ dump("We have an invalid account, " + firstInvalidAccount + ", let's use that!\n");
+ gCurrentAccount = firstInvalidAccount;
+
+ // there's a possibility that the invalid account has ISP defaults
+ // as well.. so first pre-fill accountData with ISP info, then
+ // overwrite it with the account data
+
+
+ var identity =
+ firstInvalidAccount.identities.queryElementAt(0, nsIMsgIdentity);
+
+ var accountData = null;
+ // If there is a email address already provided, try to get to other ISP defaults.
+ // If not, get pre-configured data, if any.
+ if (identity.email) {
+ dump("Invalid account: trying to get ISP data for " + identity.email + "\n");
+ accountData = getIspDefaultsForEmail(identity.email);
+ dump("Invalid account: Got " + accountData + "\n");
+
+ // account -> accountData -> pageData
+ accountData = AccountToAccountData(firstInvalidAccount, accountData);
+ }
+ else {
+ accountData = getPreConfigDataForAccount(firstInvalidAccount);
+ }
+
+ AccountDataToPageData(accountData, pageData);
+
+ gCurrentAccountData = accountData;
+
+ setupWizardPanels();
+ // Set the page index to identity page.
+ document.documentElement.pageIndex = 1;
+ }
+}
+
+// Transfer all invalid account information to AccountData. Also, get those special
+// preferences (not associated with any interfaces but preconfigurable via prefs or rdf files)
+// like whether not the smtp server associated with this account requires
+// a user name (mail.identity.<id_key>.smtpRequiresUsername) and the choice of skipping
+// panels (mail.identity.<id_key>.wizardSkipPanels).
+function getPreConfigDataForAccount(account)
+{
+ var accountData = new Object;
+ accountData = new Object;
+ accountData.incomingServer = new Object;
+ accountData.identity = new Object;
+ accountData.smtp = new Object;
+
+ accountData = AccountToAccountData(account, null);
+
+ let identity = account.identities.queryElementAt(0, nsIMsgIdentity);
+
+ try {
+ var skipPanelsPrefStr = "mail.identity." + identity.key + ".wizardSkipPanels";
+ accountData.wizardSkipPanels = Services.prefs.getCharPref(skipPanelsPrefStr);
+
+ if (identity.smtpServerKey) {
+ let smtpServer = MailServices.smtp.getServerByKey(identity.smtpServerKey);
+ accountData.smtp = smtpServer;
+
+ var smtpRequiresUsername = false;
+ var smtpRequiresPrefStr = "mail.identity." + identity.key + ".smtpRequiresUsername";
+ smtpRequiresUsername = Services.prefs.getBoolPref(smtpRequiresPrefStr);
+ accountData.smtpRequiresUsername = smtpRequiresUsername;
+ }
+ }
+ catch(ex) {
+ // reached here as special identity pre-configuration prefs
+ // (wizardSkipPanels, smtpRequiresUsername) are not defined.
+ }
+
+ return accountData;
+}
+
+function AccountToAccountData(account, defaultAccountData)
+{
+ dump("AccountToAccountData(" + account + ", " +
+ defaultAccountData + ")\n");
+ var accountData = defaultAccountData;
+ if (!accountData)
+ accountData = new Object;
+
+ accountData.incomingServer = account.incomingServer;
+ accountData.identity = account.identities.queryElementAt(0, nsIMsgIdentity);
+ accountData.smtp = MailServices.smtp.defaultServer;
+
+ return accountData;
+}
+
+// sets the page data, automatically creating the arrays as necessary
+function setPageData(pageData, tag, slot, value) {
+ if (!pageData[tag]) pageData[tag] = [];
+
+ if (value == undefined) {
+ // clear out this slot
+ if (pageData[tag][slot]) delete pageData[tag][slot];
+ }
+ else {
+ // pre-fill this slot
+ if (!pageData[tag][slot]) pageData[tag][slot] = [];
+ pageData[tag][slot].id = slot;
+ pageData[tag][slot].value = value;
+ }
+}
+
+// value of checkbox on the first page
+function serverIsNntp(pageData) {
+ if (pageData.accounttype.newsaccount)
+ return pageData.accounttype.newsaccount.value;
+ return false;
+}
+
+function getUsernameFromEmail(aEmail, aEnsureDomain)
+{
+ var username = aEmail.substr(0, aEmail.indexOf("@"));
+ if (aEnsureDomain && gCurrentAccountData && gCurrentAccountData.domain)
+ username += '@' + gCurrentAccountData.domain;
+ return username;
+}
+
+function getCurrentUserName(pageData)
+{
+ var userName = "";
+
+ if (pageData.login) {
+ if (pageData.login.username) {
+ userName = pageData.login.username.value;
+ }
+ }
+ if (userName == "") {
+ var email = pageData.identity.email.value;
+ userName = getUsernameFromEmail(email, false);
+ }
+ return userName;
+}
+
+function getCurrentServerType(pageData) {
+ var servertype = "pop3"; // hopefully don't resort to default!
+ if (serverIsNntp(pageData))
+ servertype = "nntp";
+ else if (pageData.server && pageData.server.servertype)
+ servertype = pageData.server.servertype.value;
+ return servertype;
+}
+
+function getCurrentServerIsDeferred(pageData) {
+ var serverDeferred = false;
+ if (getCurrentServerType(pageData) == "pop3" && pageData.server && pageData.server.deferStorage)
+ serverDeferred = pageData.server.deferStorage.value;
+
+ return serverDeferred;
+}
+
+function getCurrentHostname(pageData) {
+ if (serverIsNntp(pageData))
+ return pageData.newsserver.hostname.value;
+ else
+ return pageData.server.hostname.value;
+}
+
+function GetPageData()
+{
+ if (!gPageData)
+ gPageData = new Object;
+
+ return gPageData;
+}
+
+function PrefillAccountForIsp(ispName)
+{
+ dump("AccountWizard.prefillAccountForIsp(" + ispName + ")\n");
+
+ var ispData = getIspDefaultsForUri(ispName);
+
+ var pageData = GetPageData();
+
+ if (!ispData) {
+ SetCurrentAccountData(null);
+ return;
+ }
+
+ // prefill the rest of the wizard
+ dump("PrefillAccountForISP: filling with " + ispData + "\n");
+ SetCurrentAccountData(ispData);
+ AccountDataToPageData(ispData, pageData);
+
+ setPageData(pageData, "ispdata", "supplied", true);
+}
+
+// does any cleanup work for the the account data
+// - sets the username from the email address if it's not already set
+// - anything else?
+function FixupAccountDataForIsp(accountData)
+{
+ // no fixup for news
+ // setting the username does bad things
+ // see bugs #42105 and #154213
+ if (accountData.incomingServer.type == "nntp")
+ return;
+
+ var email = accountData.identity.email;
+
+ // The identity might not have an email address, which is what the rest of
+ // this function is looking for.
+ if (!email)
+ return;
+
+ // fix up the username
+ if (!accountData.incomingServer.username)
+ accountData.incomingServer.username =
+ getUsernameFromEmail(email, accountData.incomingServerUserNameRequiresDomain);
+
+ if (!accountData.smtp.username &&
+ accountData.smtpRequiresUsername) {
+ // fix for bug #107953
+ // if incoming hostname is same as smtp hostname
+ // use the server username (instead of the email username)
+ if (accountData.smtp.hostname == accountData.incomingServer.hostName &&
+ accountData.smtpUserNameRequiresDomain == accountData.incomingServerUserNameRequiresDomain)
+ accountData.smtp.username = accountData.incomingServer.username;
+ else
+ accountData.smtp.username = getUsernameFromEmail(email, accountData.smtpUserNameRequiresDomain);
+ }
+}
+
+function SetCurrentAccountData(accountData)
+{
+ // dump("Setting current account data (" + gCurrentAccountData + ") to " + accountData + "\n");
+ gCurrentAccountData = accountData;
+}
+
+// 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 exists and if that account is alllowed to be
+ // a default account. If no such account, make this one as the default account
+ // and turn on the new mail check at startup for the current account
+ if (!(gDefaultAccount && gDefaultAccount.incomingServer.canBeDefaultServer)) {
+ MailServices.accounts.defaultAccount = newAccount;
+ newAccount.incomingServer.loginAtStartUp = true;
+ newAccount.incomingServer.downloadOnBiff = true;
+ }
+}
+
+function SetSmtpRequiresUsernameAttribute(accountData)
+{
+ // If this is the default server, time to set the smtp user name
+ // Set the generic attribute for requiring user name for smtp to true.
+ // ISPs can override the pref via rdf files.
+ if (!(gDefaultAccount && gDefaultAccount.incomingServer.canBeDefaultServer)) {
+ accountData.smtpRequiresUsername = true;
+ }
+}
+
+function setNextPage(currentPageId, nextPageId) {
+ var currentPage = document.getElementById(currentPageId);
+ currentPage.next = nextPageId;
+}
diff --git a/mailnews/base/prefs/content/AccountWizard.xul b/mailnews/base/prefs/content/AccountWizard.xul
new file mode 100644
index 000000000..45da359ab
--- /dev/null
+++ b/mailnews/base/prefs/content/AccountWizard.xul
@@ -0,0 +1,371 @@
+<?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/accountWizard.css" type="text/css"?>
+
+<!DOCTYPE wizard SYSTEM "chrome://messenger/locale/AccountWizard.dtd">
+
+<wizard id="AccountWizard" title="&windowTitle.label;"
+ onwizardcancel="return onCancel();"
+ onwizardfinish="return FinishAccount();"
+#ifdef MOZ_THUNDERBIRD
+ onload="onAccountWizardLoad(); initAccountWizardTB(window.arguments);"
+#else
+ onload="onAccountWizardLoad();"
+#endif
+ style="&accountWizard.size;"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ <script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/AccountWizard.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/ispUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-accounttype.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-identity.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-incoming.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-outgoing.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-accname.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/aw-done.js"/>
+
+ <!-- Account Type page : Displays choices of mail and news accounts that user can create -->
+ <wizardpage id="accounttype" pageid="accounttype"
+ label="&accountTypeTitle.label;"
+ onpageshow="document.documentElement.canAdvance = true;"
+ onpageadvanced="return acctTypePageUnload();">
+ <vbox flex="1">
+ <description>&accountSetupInfo2.label;</description>
+ <description>&accountTypeDesc2.label;</description>
+ <label control="acctyperadio">&accountTypeDirections.label;</label>
+ <separator/>
+ <radiogroup id="acctyperadio" >
+#ifndef MOZ_THUNDERBIRD
+ <radio id="mailaccount" value="mailaccount"
+ label="&accountTypeMail.label;" accesskey="&accountTypeMail.accesskey;"
+ selected="true"/>
+#endif
+ <vbox datasources="rdf:ispdefaults"
+ containment="http://home.netscape.com/NC-rdf#providers"
+ id="ispBox"
+ ref="NC:ispinfo">
+ <template>
+ <rule nc:wizardShow="true">
+ <radio uri="..."
+ value="rdf:http://home.netscape.com/NC-rdf#wizardShortName"
+ label="rdf:http://home.netscape.com/NC-rdf#wizardLongName"
+ accesskey="rdf:http://home.netscape.com/NC-rdf#wizardLongNameAccesskey"/>
+ </rule>
+ </template>
+ </vbox>
+ <radio id="newsaccount" value="newsaccount"
+ label="&accountTypeNews.label;" accesskey="&accountTypeNews.accesskey;"/>
+ </radiogroup>
+ </vbox>
+ </wizardpage>
+
+ <!-- Identity page : Collects user's full name and email address -->
+ <wizardpage id="identitypage" pageid="identitypage"
+ label="&identityTitle.label;"
+ onpageshow="return identityPageInit();"
+ onpageadvanced="return identityPageUnload();">
+ <vbox flex="1">
+ <description>&identityDesc.label;</description>
+ <separator/>
+ <description>&fullnameDesc.label; &fullnameExample.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="awIdentityLabel" value="&fullnameLabel.label;"
+ accesskey="&fullnameLabel.accesskey;" control="fullName"/>
+ <textbox mailtype="identity" wsm_persist="true" name="fullName" id="fullName" flex="1" oninput="identityPageValidate();"/>
+ </hbox>
+ <separator/>
+ <grid>
+ <columns>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <description id="emailDescText"/>
+ </row>
+ <separator class="thin"/>
+ <row>
+ <hbox align="center">
+ <label class="awIdentityLabel" id="emailFieldLabel" value="&emailLabel.label;"
+ accesskey="&emailLabel.accesskey;" control="email"/>
+ <hbox class="uri-element" align="center" flex="1">
+ <textbox wsm_persist="true" mailtype="identity" name="email"
+ oninput="identityPageValidate();"
+ id="email" flex="6" class="uri-element"/>
+ <label id="postEmailText"/>
+ </hbox>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+ </wizardpage>
+
+ <!-- Incoming page : User can choose to create mail account of his choice, POP3 or IMAP -->
+ <!-- Collects incoming server name and login name. -->
+ <!-- Login name is prefilled with user id from the email address provided in identity page -->
+ <!-- User can enter a login name here if it is different from the user id of his/her email address -->
+ <wizardpage id="incomingpage" pageid="incomingpage"
+ label="&incomingTitle.label;"
+ onpageshow="return incomingPageInit();"
+ onpageadvanced="return incomingPageUnload();">
+ <vbox flex="1">
+ <vbox id="serverTypeBox">
+ <label control="servertype">&incomingServerTypeDesc.label;</label>
+ <separator class="thin"/>
+ <hbox align="center" class="serverDataBox">
+ <!-- The initial value for the servertype radiogroup is set in onInit() -->
+ <radiogroup id="servertype" wsm_persist="true" orient="horizontal">
+ <radio group="servertype" value="pop3" id="pop3" label="&popType.label;"
+ wsm_persist="true" oncommand="setServerType();" accesskey="&popType.accesskey;"/>
+ <radio group="servertype" value="imap" id="imap" label="&imapType.label;"
+ wsm_persist="true" oncommand="setServerType();" accesskey="&imapType.accesskey;"/>
+ </radiogroup>
+ <label id="serverPortLabel" control="serverPort"
+ accesskey="&portNum.accesskey;"
+ value="&portNum.label;"/>
+ <textbox id="serverPort" type="number" size="3" max="65535"/>
+ <label id="defaultPortLabel" value="&defaultPortLabel.label;"/>
+ <label id="defaultPortValue" value="&defaultPortValue.label;"/>
+ </hbox>
+ <separator/>
+ </vbox>
+
+ <vbox id="incomingServerbox">
+ <description>&incomingServer.description;</description>
+ <hbox align="center" class="serverDataBox">
+ <label class="label, serverLabel"
+ value="&incomingServer.label;"
+ accesskey="&incomingServer.accesskey;"
+ control="incomingServer"/>
+ <textbox wsm_persist="true"
+ id="incomingServer"
+ flex="1"
+ class="uri-element"
+ oninput="incomingPageValidate();"/>
+ </hbox>
+ <hbox id="leaveMsgsOnSrvrBox" class="indent">
+ <checkbox id="leaveMessagesOnServer"
+ label="&leaveMsgsOnSrvr.label;"
+ accesskey="&leaveMsgsOnSrvr.accesskey;"
+ wsm_persist="true"
+ oncommand="setServerPrefs(this);"
+ checked="true"/>
+ </hbox>
+ <separator/>
+ </vbox>
+ <description>&incomingUsername.description;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label"
+ value="&incomingUsername.label;"
+ style="width: 8em;"
+ accesskey="&incomingUsername.accesskey;"
+ control="username"/>
+ <textbox id="username"
+ wsm_persist="true"
+ flex="1"
+ oninput="incomingPageValidate();"/>
+ </hbox>
+ <vbox id="deferStorageBox">
+ <separator class="groove"/>
+ <description> &deferStorageDesc.label;</description>
+ <hbox>
+ <checkbox id="deferStorage"
+ label="&deferStorage.label;"
+ accesskey="&deferStorage.accesskey;"
+ checked="true"
+ wsm_persist="true"
+ oncommand="setServerPrefs(this);"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </wizardpage>
+
+ <!-- Outgoing page : Collects outgoing server name and login name. -->
+ <!-- Outgoing server name is collected if there isn't one already -->
+ <!-- Login name is prefilled with user id from the email address provided in identity page -->
+ <!-- User can enter a login name here if it is different from the user id of his/her email address -->
+ <wizardpage id="outgoingpage" pageid="outgoingpage"
+ label="&outgoingTitle.label;"
+ onpageshow="return outgoingPageInit();"
+ onpageadvanced="return outgoingPageUnload();">
+ <vbox flex="1">
+ <vbox id="noSmtp">
+ <description>&outgoingServer.description;</description>
+ <hbox align="center" class="serverDataBox">
+ <label class="label, serverLabel"
+ value="&outgoingServer.label;"
+ accesskey="&outgoingServer.accesskey;"
+ control="smtphostname"/>
+ <textbox id="smtphostname"
+ wsm_persist="true"
+ flex="1"
+ class="uri-element"
+ oninput="outgoingPageValidate();"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="haveSmtp">
+ <description id="smtpStaticText1"
+ style="width: 200px;"
+ prefix="&haveSmtp1.prefix;"
+ suffix="&haveSmtp1.suffix3; &modifyOutgoing.suffix;">*</description>
+ </vbox>
+
+ <vbox id="loginSet1">
+ <description>&outgoingUsername.description;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label"
+ value="&outgoingUsername.label;"
+ style="width: 8em;"
+ accesskey="&outgoingUsername.accesskey;"
+ control="smtpusername"/>
+ <textbox id="smtpusername" wsm_persist="true" flex="1"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="loginSet2" hidden="true">
+ <description id="smtpStaticText2" style="width: 200px;" prefix="&haveSmtp2.prefix;"
+ suffix="&haveSmtp2.suffix3; &modifyOutgoing.suffix;">*</description>
+ </vbox>
+
+ <vbox id="loginSet3" hidden="true">
+ <description id="smtpStaticText3" style="width: 200px;" prefix="&haveSmtp3.prefix;"
+ suffix="&haveSmtp3.suffix3; &modifyOutgoing.suffix;">*</description>
+ </vbox>
+
+ </vbox>
+ </wizardpage>
+
+ <!-- News Server page : Collects the News groups server name -->
+ <wizardpage id="newsserver" pageid="newsserver"
+ label="&incomingTitle.label;"
+ onpageshow="return incomingPageInit();"
+ onpageadvanced="return incomingPageUnload();">
+ <vbox flex="1">
+ <description>&newsServerNameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label control="newsServer" value="&newsServerLabel.label;" accesskey="&newsServerLabel.accesskey;" style="width: 8em;"/>
+ <textbox id="newsServer"
+ wsm_persist="true"
+ flex="1"
+ class="uri-element"
+ 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;"
+ onpageshow="return acctNamePageInit();"
+ onpageadvanced="return acctNamePageUnload();">
+ <vbox flex="1">
+ <description>&accnameDesc.label;</description>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label class="label" value="&accnameLabel.label;" style="width: 8em;"
+ accesskey="&accnameLabel.accesskey;" control="prettyName"/>
+ <textbox id="prettyName" size="40" wsm_persist="true" flex="1" 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;"
+ onpageshow="return donePageInit();">
+ <vbox flex="1">
+ <description>&completionText.label;</description>
+ <separator class="thin"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center" id="account.name">
+ <label class="label" flex="1" id="account.name.label" value="&accnameLabel.label;"/>
+ <label class="label" id="account.name.text"/>
+ </row>
+ <row align="center" id="identity.email">
+ <label class="label" flex="1" id="identity.email.label" value="&emailLabel.label;"/>
+ <label class="label" id="identity.email.text"/>
+ </row>
+ <row align="center" id="server.username">
+ <label class="label" flex="1" id="server.username.label" value="&incomingUsername.label;"/>
+ <label class="label" id="server.username.text"/>
+ </row>
+ <row align="center" id="server.name">
+ <label class="label" flex="1" id="server.name.label" value="&serverNamePrefix.label;"/>
+ <label class="label" id="server.name.text"/>
+ </row>
+ <row align="center" id="server.type">
+ <label class="label" flex="1" id="server.type.label" value="&serverTypePrefix.label;"/>
+ <label class="label" id="server.type.text"/>
+ </row>
+ <row align="center" id="server.port">
+ <label class="label" id="server.port.label" flex="1" value="&portNum.label;"/>
+ <label class="label" id="server.port.text"/>
+ </row>
+ <row align="center" id="newsServer.name">
+ <label class="label" flex="1" id="newsServer.name.label" value="&newsServerNamePrefix.label;"/>
+ <label class="label" id="newsServer.name.text"/>
+ </row>
+ <row align="center" id="smtpServer.username">
+ <label class="label" flex="1" id="smtpServer.username.label" value="&outgoingUsername.label;"/>
+ <label class="label" id="smtpServer.username.text"/>
+ </row>
+ <row align="center" id="smtpServer.name">
+ <label class="label" flex="1" id="smtpServer.name.label" value="&smtpServerNamePrefix.label;"/>
+ <label class="label" id="smtpServer.name.text"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <hbox id="downloadMsgsBox">
+ <checkbox id="downloadMsgs"
+ label="&downloadOnLogin.label;"
+ accesskey="&downloadOnLogin.accesskey;"
+ hidden="true"
+ checked="true"/>
+ </hbox>
+ <spacer flex="1"/>
+#ifndef XP_MACOSX
+ <description>&clickFinish.label;</description>
+#else
+ <description>&clickFinish.labelMac;</description>
+#endif
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="ispPage1"/>
+ <wizardpage id="ispPage2"/>
+ <wizardpage id="ispPage3"/>
+ <wizardpage id="ispPage4"/>
+ <wizardpage id="ispPage5"/>
+ <wizardpage id="ispPage6"/>
+ <wizardpage id="ispPage7"/>
+ <wizardpage id="ispPage8"/>
+ <wizardpage id="ispPage9"/>
+ <wizardpage id="ispPage10"/>
+ <wizardpage id="ispPage11"/>
+ <wizardpage id="ispPage12"/>
+ <wizardpage id="ispPage13"/>
+ <wizardpage id="ispPage14"/>
+ <wizardpage id="ispPage15"/>
+ <wizardpage id="ispPage16"/>
+
+</wizard>
diff --git a/mailnews/base/prefs/content/SmtpServerEdit.js b/mailnews/base/prefs/content/SmtpServerEdit.js
new file mode 100644
index 000000000..f84bbd00b
--- /dev/null
+++ b/mailnews/base/prefs/content/SmtpServerEdit.js
@@ -0,0 +1,46 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gSmtpServer;
+
+function onLoad(event)
+{
+ gSmtpServer = window.arguments[0].server;
+ initSmtpSettings(gSmtpServer);
+}
+
+function onAccept()
+{
+ 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;
+ return false;
+ }
+
+ // 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) {
+ Components.utils.reportError("Error saving smtp server: " + ex);
+ }
+
+ window.arguments[0].result = true;
+ return true;
+}
diff --git a/mailnews/base/prefs/content/SmtpServerEdit.xul b/mailnews/base/prefs/content/SmtpServerEdit.xul
new file mode 100644
index 000000000..767020d4d
--- /dev/null
+++ b/mailnews/base/prefs/content/SmtpServerEdit.xul
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xul-overlay href="chrome://messenger/content/smtpEditOverlay.xul"?>
+
+<!-- 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/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/smtpEditOverlay.dtd">
+
+<dialog title="&smtpEditTitle.label;"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ ondialogaccept="return onAccept();">
+ <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"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/SmtpServerEdit.js"/>
+ <vbox id="smtpServerEditor"/>
+</dialog>
diff --git a/mailnews/base/prefs/content/accountUtils.js b/mailnews/base/prefs/content/accountUtils.js
new file mode 100644
index 000000000..8b5020915
--- /dev/null
+++ b/mailnews/base/prefs/content/accountUtils.js
@@ -0,0 +1,462 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+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 numAccounts = accounts.length;
+ let invalidAccounts = new Array;
+ let numIdentities = 0;
+ for (let i = 0; i < numAccounts; i++) {
+ let account = accounts.queryElementAt(i, Components.interfaces.nsIMsgAccount);
+ try {
+ if (!account.incomingServer.valid) {
+ invalidAccounts[invalidAccounts.length] = account;
+ // skip to the next account
+ continue;
+ }
+ } catch (ex) {
+ // this account is busted, just keep going
+ continue;
+ }
+
+ var identities = account.identities;
+ numIdentities = identities.length;
+
+ for (var j = 0; j < numIdentities; j++) {
+ let identity = identities.queryElementAt(j, Components.interfaces.nsIMsgIdentity);
+ if (identity.valid) {
+ gAnyValidIdentity = true;
+ }
+ else {
+ invalidAccounts[invalidAccounts.length] = account;
+ }
+ }
+ }
+ return invalidAccounts;
+}
+
+function showMailIntegrationDialog() {
+ const nsIShellService = Components.interfaces.nsIShellService;
+
+ try {
+ var shellService = Components.classes["@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.openDialog("chrome://communicator/content/defaultClientDialog.xul",
+ "DefaultClient", "modal,centerscreen,chrome,resizable=no");
+ } catch (ex) {}
+}
+
+/**
+ * Verify that there is at least one account. If not, open a new account wizard.
+ *
+ * @param wizardCallback if the wizard is run, callback when it is done.
+ * @param needsIdentity True only when verifyAccounts is called from the
+ * compose window. This last condition is so that we open
+ * the account wizard if the user does not have any
+ * identities defined and tries to compose mail.
+ * @param wizardOpen optional param that allows the caller to specify a
+ * different method to open a wizard. The wizardOpen method
+ * takes wizardCallback as an argument. The wizardCallback
+ * doesn't take any arguments.
+ */
+function verifyAccounts(wizardCallback, needsIdentity, wizardOpen)
+{
+ var openWizard = false;
+ var prefillAccount;
+ var state=true;
+ var ret = true;
+
+ try {
+ // migrate quoting preferences from global to per account. This function returns
+ // true if it had to migrate, which we will use to mean this is a just migrated
+ // or new profile
+ var newProfile = migrateGlobalQuotingPrefs(MailServices.accounts.allIdentities);
+
+ var accounts = MailServices.accounts.accounts;
+
+ // as long as we have some accounts, we're fine.
+ var accountCount = accounts.length;
+ var invalidAccounts = getInvalidAccounts(accounts);
+ if (invalidAccounts.length > 0 && invalidAccounts.length == accountCount) {
+ prefillAccount = invalidAccounts[0];
+ }
+
+ // if there are no accounts, or all accounts are "invalid"
+ // then kick off the account migration. Or if this is a new (to Mozilla) profile.
+ // MCD can set up accounts without the profile being used yet
+ if (newProfile) {
+ // check if MCD is configured. If not, say this is not a new profile
+ // so that we don't accidentally remigrate non MCD profiles.
+ var adminUrl;
+ try {
+ adminUrl = Services.prefs.getCharPref("autoadmin.global_config_url");
+ }
+ catch (ex) {}
+ if (!adminUrl)
+ newProfile = false;
+ }
+ if ((newProfile && !accountCount) || accountCount == invalidAccounts.length)
+ openWizard = true;
+
+ // openWizard is true if messenger migration returns some kind of
+ // error (including those cases where there is nothing to migrate).
+ // prefillAccount is non-null if there is at least one invalid account.
+ // gAnyValidIdentity is true when you've got at least one *valid*
+ // identity. Since local and RSS folders are identity-less accounts, if you
+ // only have one of those, it will be false.
+ // needsIdentity is true only when verifyAccounts is called from the
+ // compose window. This last condition is so that we open the account
+ // wizard if the user does not have any identities defined and tries to
+ // compose mail.
+
+ if (openWizard || prefillAccount || ((!gAnyValidIdentity) && needsIdentity))
+ {
+ if (wizardOpen != undefined)
+ wizardOpen(wizardCallback)
+ else
+ MsgAccountWizard(wizardCallback);
+ ret = false;
+ }
+ else
+ {
+ var localFoldersExists;
+ try
+ {
+ localFoldersExists = MailServices.accounts.localFoldersServer;
+ }
+ catch (ex)
+ {
+ localFoldersExists = false;
+ }
+
+ // we didn't create the MsgAccountWizard - we need to verify that local folders exists.
+ if (!localFoldersExists)
+ MailServices.accounts.createLocalMailAccount();
+ }
+
+ // This will do nothing on platforms without a shell service
+ const NS_SHELLSERVICE_CID = "@mozilla.org/suite/shell-service;1"
+ if (NS_SHELLSERVICE_CID in Components.classes)
+ {
+ // hack, set a time out to do this, so that the window can load first
+ setTimeout(showMailIntegrationDialog, 0);
+ }
+ return ret;
+ }
+ catch (ex) {
+ dump("error verifying accounts " + ex + "\n");
+ return false;
+ }
+}
+
+// we do this from a timer because if this is called from the onload=
+// handler, then the parent window doesn't appear until after the wizard
+// has closed, and this is confusing to the user
+function MsgAccountWizard(wizardCallback)
+{
+ setTimeout(function() { msgOpenAccountWizard(wizardCallback); }, 0);
+}
+
+/**
+ * Open the Old Mail Account Wizard, or focus it if it's already open.
+ *
+ * @param wizardCallback if the wizard is run, callback when it is done.
+ * @param type - optional account type token, for Tb.
+ * @see msgNewMailAccount below for the new implementation.
+ */
+function msgOpenAccountWizard(wizardCallback, type)
+{
+ gNewAccountToLoad = null;
+
+ window.openDialog("chrome://messenger/content/AccountWizard.xul", "AccountWizard",
+ "chrome,modal,titlebar,centerscreen",
+ {okCallback: wizardCallback, acctType: type});
+
+ loadInboxForNewAccount();
+
+ // If we started with no servers at all and "smtp servers" list selected,
+ // refresh display somehow. Bug 58506.
+ // TODO Better fix: select newly created account (in all cases)
+ if (typeof(getCurrentAccount) == "function" && // in AccountManager, not menu
+ !getCurrentAccount())
+ selectServer(null, null);
+}
+
+function initAccountWizardTB(args) {
+ let type = args[0] && args[0].acctType;
+ let selType = type == "newsgroups" ? "newsaccount" :
+ type == "movemail" ? "Movemail" : null;
+ let accountwizard = document.getElementById("AccountWizard");
+ let acctyperadio = document.getElementById("acctyperadio");
+ let feedRadio = acctyperadio.querySelector("radio[value='Feeds']");
+ if (feedRadio)
+ feedRadio.remove();
+ if (selType) {
+ acctyperadio.selectedItem = acctyperadio.querySelector("radio[value='"+selType+"']");
+ accountwizard.advance("identitypage");
+ }
+ else
+ acctyperadio.selectedItem = acctyperadio.getItemAtIndex(0);
+}
+
+function AddFeedAccount() {
+ window.openDialog("chrome://messenger-newsblog/content/feedAccountWizard.xul",
+ "", "chrome,modal,titlebar,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 selectPage The xul file name for the viewing page or
+ * null for the account main page. Other pages are
+ * 'am-server.xul', 'am-copies.xul', 'am-offline.xul',
+ * 'am-addressing.xul', 'am-smtp.xul'
+ * @param aServer The server of the account to select. Optional.
+ */
+function MsgAccountManager(selectPage, aServer)
+{
+ var existingAccountManager = Services.wm.getMostRecentWindow("mailnews:accountmanager");
+
+ if (existingAccountManager)
+ existingAccountManager.focus();
+ else {
+ if (!aServer) {
+ if (typeof GetSelectedMsgFolders === "function") {
+ let folders = GetSelectedMsgFolders();
+ if (folders.length > 0)
+ aServer = folders[0].server;
+ }
+ if (!aServer && (typeof GetDefaultAccountRootFolder === "function")) {
+ let folder = GetDefaultAccountRootFolder();
+ if (folder instanceof Components.interfaces.nsIMsgFolder)
+ aServer = folder.server;
+ }
+ }
+
+ window.openDialog("chrome://messenger/content/AccountManager.xul",
+ "AccountManager",
+ "chrome,centerscreen,modal,titlebar,resizable",
+ { server: aServer, selectPage: selectPage });
+ }
+}
+
+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 = Components.interfaces.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 = 0;
+ var migrated = false;
+ try {
+ quotingPrefs = Services.prefs.getIntPref("mailnews.quotingPrefs.version");
+ } catch (ex) {}
+
+ // 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) {
+ let numIdentities = allIdentities.length;
+ var identity = null;
+ for (var j = 0; j < numIdentities; j++) {
+ identity = allIdentities.queryElementAt(j, Components.interfaces.nsIMsgIdentity);
+ if (identity.valid) {
+ identity.autoQuote = auto_quote;
+ identity.replyOnTop = reply_on_top;
+ }
+ }
+ }
+ Services.prefs.setIntPref("mailnews.quotingPrefs.version", 1);
+ }
+ return migrated;
+}
+
+// we do this from a timer because if this is called from the onload=
+// handler, then the parent window doesn't appear until after the wizard
+// has closed, and this is confusing to the user
+function NewMailAccount(msgWindow, okCallback, extraData)
+{
+ if (!msgWindow)
+ throw new Error("NewMailAccount must be given a msgWindow.");
+
+ // Populate the extra data.
+ if (!extraData)
+ extraData = {};
+ extraData.msgWindow = msgWindow;
+
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+
+ if (!extraData.NewMailAccount)
+ extraData.NewMailAccount = NewMailAccount;
+
+ if (!extraData.msgNewMailAccount)
+ extraData.msgNewMailAccount = msgNewMailAccount;
+
+ if (!extraData.NewComposeMessage)
+ extraData.NewComposeMessage = mail3Pane.ComposeMessage;
+
+ if (!extraData.openAddonsMgr)
+ extraData.openAddonsMgr = mail3Pane.openAddonsMgr;
+
+ if (!extraData.okCallback)
+ extraData.okCallback = null;
+
+ if (!extraData.success)
+ extraData.success = false;
+
+ setTimeout(extraData.msgNewMailAccount, 0, msgWindow, okCallback, extraData);
+}
+
+function NewMailAccountProvisioner(aMsgWindow, args) {
+ if (!args)
+ args = {};
+ if (!aMsgWindow)
+ aMsgWindow = MailServices.mailSession.topmostMsgWindow;
+
+ args.msgWindow = aMsgWindow;
+
+ let mail3Pane = Services.wm.getMostRecentWindow("mail:3pane");
+
+ // If we couldn't find a 3pane, bail out.
+ if (!mail3Pane) {
+ Components.utils.reportError("Could not find a 3pane to connect to.");
+ return;
+ }
+
+ let tabmail = mail3Pane.document.getElementById("tabmail");
+
+ if (!tabmail) {
+ Components.utils.reportError("Could not find a tabmail in the 3pane!");
+ return;
+ }
+
+ // If there's already an accountProvisionerTab open, just focus it instead
+ // of opening a new dialog.
+ let apTab = tabmail.getTabInfoForCurrentOrFirstModeInstance(
+ tabmail.tabModes["accountProvisionerTab"]);
+
+ if (apTab) {
+ tabmail.switchToTab(apTab);
+ return;
+ }
+
+ // XXX make sure these are all defined in all contexts... to be on the safe
+ // side, just get a mail:3pane and borrow the functions from it?
+ if (!args.NewMailAccount)
+ args.NewMailAccount = NewMailAccount;
+
+ if (!args.msgNewMailAccount)
+ args.msgNewMailAccount = msgNewMailAccount;
+
+ if (!args.NewComposeMessage)
+ args.NewComposeMessage = mail3Pane.ComposeMessage;
+
+ if (!args.openAddonsMgr)
+ args.openAddonsMgr = mail3Pane.openAddonsMgr;
+
+ if (!args.okCallback)
+ args.okCallback = null;
+
+ let windowParams = "chrome,titlebar,centerscreen,width=640,height=480";
+
+ if (!args.success) {
+ args.success = false;
+ // If we're not opening up the success dialog, then our window should be
+ // modal.
+ windowParams = "modal," + windowParams;
+ }
+
+ // NOTE: If you're a developer, and you notice that the jQuery code in
+ // accountProvisioner.xhtml isn't throwing errors or warnings, that's due
+ // to bug 688273. Just make the window non-modal to get those errors and
+ // warnings back, and then clear this comment when bug 688273 is closed.
+ window.openDialog(
+ "chrome://messenger/content/newmailaccount/accountProvisioner.xhtml",
+ "AccountCreation",
+ windowParams,
+ args);
+}
+
+/**
+ * Open the New Mail Account Wizard, or focus it if it's already open.
+ *
+ * @param msgWindow a msgWindow for us to use to verify the accounts.
+ * @param okCallback an optional callback for us to call back to if
+ * everything's okay.
+ * @param extraData an optional param that allows us to pass data in and
+ * out. Used in the upcoming AccountProvisioner add-on.
+ * @see msgOpenAccountWizard above for the previous implementation.
+ */
+function msgNewMailAccount(msgWindow, okCallback, extraData)
+{
+ if (!msgWindow)
+ throw new Error("msgNewMailAccount must be given a msgWindow.");
+
+ let existingWindow = Services.wm.getMostRecentWindow("mail:autoconfig");
+ if (existingWindow) {
+ existingWindow.focus();
+ } else {
+ // disabling modal for the time being, see 688273 REMOVEME
+ window.openDialog("chrome://messenger/content/accountcreation/emailWizard.xul",
+ "AccountSetup", "chrome,titlebar,centerscreen",
+ {msgWindow:msgWindow,
+ okCallback:okCallback,
+ extraData:extraData});
+ }
+
+ /*
+ // TODO: Enable this block of code once the dialog above is made modal.
+ // If we started with no servers at all and "smtp servers" list selected,
+ // refresh display somehow. Bug 58506.
+ // TODO Better fix: select newly created account (in all cases)
+ let existingAccountManager =
+ Services.wm.getMostRecentWindow("mailnews:accountmanager");
+ // in AccountManager, not menu
+ if (existingAccountManager && typeof(getCurrentAccount) == "function" &&
+ !getCurrentAccount()) {
+ selectServer(null, null);
+ }
+ */
+}
diff --git a/mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js b/mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js
new file mode 100644
index 000000000..9f4c5034e
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/MyBadCertHandler.js
@@ -0,0 +1,41 @@
+/* -*- 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/. */
+
+/**
+ * This class implements nsIBadCertListener. It's job is to prevent "bad cert"
+ * security dialogs from being shown to the user. We call back to the
+ * 'callback' object's method "processCertError" so that it can deal with it as
+ * needed (in the case of autoconfig, setting up temporary overrides).
+ */
+function BadCertHandler(callback)
+{
+ this._init(callback);
+}
+
+BadCertHandler.prototype =
+{
+ _init: function(callback) {
+ this._callback = callback;
+ },
+
+ // Suppress any certificate errors
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ return this._callback.processCertError(socketInfo, status, targetSite);
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsISupports
+ QueryInterface: function(iid) {
+ if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
+ !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
+ !iid.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
diff --git a/mailnews/base/prefs/content/accountcreation/accountConfig.js b/mailnews/base/prefs/content/accountcreation/accountConfig.js
new file mode 100644
index 000000000..3a757d8ee
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/accountConfig.js
@@ -0,0 +1,259 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * This file creates the class AccountConfig, which is a JS object that holds
+ * a configuration for a certain account. It is *not* created in the backend
+ * yet (use aw-createAccount.js for that), and it may be incomplete.
+ *
+ * Several AccountConfig objects may co-exist, e.g. for autoconfig.
+ * One AccountConfig object is used to prefill and read the widgets
+ * in the Wizard UI.
+ * When we autoconfigure, we autoconfig writes the values into a
+ * new object and returns that, and the caller can copy these
+ * values into the object used by the UI.
+ *
+ * See also
+ * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
+ * for values stored.
+ */
+
+function AccountConfig()
+{
+ this.incoming = this.createNewIncoming();
+ this.incomingAlternatives = [];
+ this.outgoing = this.createNewOutgoing();
+ this.outgoingAlternatives = [];
+ this.identity =
+ {
+ // displayed real name of user
+ realname : "%REALNAME%",
+ // email address of user, as shown in From of outgoing mails
+ emailAddress : "%EMAILADDRESS%",
+ };
+ this.inputFields = [];
+ this.domains = [];
+};
+AccountConfig.prototype =
+{
+ // @see createNewIncoming()
+ incoming : null,
+ // @see createNewOutgoing()
+ outgoing : null,
+ /**
+ * Other servers which can be used instead of |incoming|,
+ * in order of decreasing preference.
+ * (|incoming| itself should not be included here.)
+ * { Array of incoming/createNewIncoming() }
+ */
+ incomingAlternatives : null,
+ outgoingAlternatives : null,
+ // OAuth2 configuration, if needed.
+ oauthSettings : null,
+ // just an internal string to refer to this. Do not show to user.
+ id : null,
+ // who created the config.
+ // { one of kSource* }
+ source : 0,
+ displayName : null,
+ // { Array of { varname (value without %), displayName, exampleValue } }
+ inputFields : null,
+ // email address domains for which this config is applicable
+ // { Array of Strings }
+ domains : null,
+
+ /**
+ * Factory function for incoming and incomingAlternatives
+ */
+ createNewIncoming : function()
+ {
+ return {
+ // { String-enum: "pop3", "imap", "nntp" }
+ type : null,
+ hostname : null,
+ // { Integer }
+ port : null,
+ // May be a placeholder (starts and ends with %). { String }
+ username : null,
+ password : null,
+ // { enum: 1 = plain, 2 = SSL/TLS, 3 = STARTTLS always, 0 = not inited }
+ // ('TLS when available' is insecure and not supported here)
+ socketType : 0,
+ /**
+ * true when the cert is invalid (and thus SSL useless), because it's
+ * 1) not from an accepted CA (including self-signed certs)
+ * 2) for a different hostname or
+ * 3) expired.
+ * May go back to false when user explicitly accepted the cert.
+ */
+ badCert : false,
+ /**
+ * How to log in to the server: plaintext or encrypted pw, GSSAPI etc.
+ * Defined by Ci.nsMsgAuthMethod
+ * Same as server pref "authMethod".
+ */
+ auth : 0,
+ /**
+ * Other auth methods that we think the server supports.
+ * They are ordered by descreasing preference.
+ * (|auth| itself is not included in |authAlternatives|)
+ * {Array of Ci.nsMsgAuthMethod} (same as .auth)
+ */
+ authAlternatives : null,
+ // in minutes { Integer }
+ checkInterval : 10,
+ loginAtStartup : true,
+ // POP3 only:
+ // Not yet implemented. { Boolean }
+ useGlobalInbox : false,
+ leaveMessagesOnServer : true,
+ daysToLeaveMessagesOnServer : 14,
+ deleteByAgeFromServer : true,
+ // When user hits delete, delete from local store and from server
+ deleteOnServerWhenLocalDelete : true,
+ downloadOnBiff : true,
+ };
+ },
+ /**
+ * Factory function for outgoing and outgoingAlternatives
+ */
+ createNewOutgoing : function()
+ {
+ return {
+ type : "smtp",
+ hostname : null,
+ port : null, // see incoming
+ username : null, // see incoming. may be null, if auth is 0.
+ password : null, // see incoming. may be null, if auth is 0.
+ socketType : 0, // see incoming
+ badCert : false, // see incoming
+ auth : 0, // see incoming
+ authAlternatives : null, // see incoming
+ addThisServer : true, // if we already have an SMTP server, add this
+ // if we already have an SMTP server, use it.
+ useGlobalPreferredServer : false,
+ // we should reuse an already configured SMTP server.
+ // nsISmtpServer.key
+ existingServerKey : null,
+ // user display value for existingServerKey
+ existingServerLabel : null,
+ };
+ },
+
+ /**
+ * Returns a deep copy of this object,
+ * i.e. modifying the copy will not affect the original object.
+ */
+ copy : function()
+ {
+ // Workaround: deepCopy() fails to preserve base obj (instanceof)
+ var result = new AccountConfig();
+ for (var prop in this)
+ result[prop] = deepCopy(this[prop]);
+
+ return result;
+ },
+ isComplete : function()
+ {
+ return (!!this.incoming.hostname && !!this.incoming.port &&
+ !!this.incoming.socketType && !!this.incoming.auth &&
+ !!this.incoming.username &&
+ (!!this.outgoing.existingServerKey ||
+ (!!this.outgoing.hostname && !!this.outgoing.port &&
+ !!this.outgoing.socketType && !!this.outgoing.auth &&
+ !!this.outgoing.username)));
+ },
+};
+
+
+// enum consts
+
+// .source
+AccountConfig.kSourceUser = 1; // user manually entered the config
+AccountConfig.kSourceXML = 2; // config from XML from ISP or Mozilla DB
+AccountConfig.kSourceGuess = 3; // guessConfig()
+
+
+/**
+ * Some fields on the account config accept placeholders (when coming from XML).
+ *
+ * These are the predefined ones
+ * * %EMAILADDRESS% (full email address of the user, usually entered by user)
+ * * %EMAILLOCALPART% (email address, part before @)
+ * * %EMAILDOMAIN% (email address, part after @)
+ * * %REALNAME%
+ * as well as those defined in account.inputFields.*.varname, with % added
+ * before and after.
+ *
+ * These must replaced with real values, supplied by the user or app,
+ * before the account is created. This is done here. You call this function once
+ * you have all the data - gathered the standard vars mentioned above as well as
+ * all listed in account.inputFields, and pass them in here. This function will
+ * insert them in the fields, returning a fully filled-out account ready to be
+ * created.
+ *
+ * @param account {AccountConfig}
+ * The account data to be modified. It may or may not contain placeholders.
+ * After this function, it should not contain placeholders anymore.
+ * This object will be modified in-place.
+ *
+ * @param emailfull {String}
+ * Full email address of this account, e.g. "joe@example.com".
+ * Empty of incomplete email addresses will/may be rejected.
+ *
+ * @param realname {String}
+ * Real name of user, as will appear in From of outgoing messages
+ *
+ * @param password {String}
+ * The password for the incoming server and (if necessary) the outgoing server
+ */
+function replaceVariables(account, realname, emailfull, password)
+{
+ sanitize.nonemptystring(emailfull);
+ let emailsplit = emailfull.split("@");
+ assert(emailsplit.length == 2,
+ "email address not in expected format: must contain exactly one @");
+ let emaillocal = sanitize.nonemptystring(emailsplit[0]);
+ let emaildomain = sanitize.hostname(emailsplit[1]);
+ sanitize.label(realname);
+ sanitize.nonemptystring(realname);
+
+ let otherVariables = {};
+ otherVariables.EMAILADDRESS = emailfull;
+ otherVariables.EMAILLOCALPART = emaillocal;
+ otherVariables.EMAILDOMAIN = emaildomain;
+ otherVariables.REALNAME = realname;
+
+ if (password) {
+ account.incoming.password = password;
+ account.outgoing.password = password; // set member only if auth required?
+ }
+ account.incoming.username = _replaceVariable(account.incoming.username,
+ otherVariables);
+ account.outgoing.username = _replaceVariable(account.outgoing.username,
+ otherVariables);
+ account.incoming.hostname =
+ _replaceVariable(account.incoming.hostname, otherVariables);
+ if (account.outgoing.hostname) // will be null if user picked existing server.
+ account.outgoing.hostname =
+ _replaceVariable(account.outgoing.hostname, otherVariables);
+ account.identity.realname =
+ _replaceVariable(account.identity.realname, otherVariables);
+ account.identity.emailAddress =
+ _replaceVariable(account.identity.emailAddress, otherVariables);
+ account.displayName = _replaceVariable(account.displayName, otherVariables);
+}
+
+function _replaceVariable(variable, values)
+{
+ let str = variable;
+ if (typeof(str) != "string")
+ return str;
+
+ for (let varname in values)
+ str = str.replace("%" + varname + "%", values[varname]);
+
+ return str;
+}
diff --git a/mailnews/base/prefs/content/accountcreation/createInBackend.js b/mailnews/base/prefs/content/accountcreation/createInBackend.js
new file mode 100644
index 000000000..d959c3ae9
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/createInBackend.js
@@ -0,0 +1,333 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * Takes an |AccountConfig| JS object and creates that account in the
+ * Thunderbird backend (which also writes it to prefs).
+ *
+ * @param config {AccountConfig} The account to create
+ *
+ * @return - the account created.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function createAccountInBackend(config)
+{
+ // incoming server
+ let inServer = MailServices.accounts.createIncomingServer(
+ config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type, ["pop3", "imap", "nntp"]));
+ inServer.port = config.incoming.port;
+ inServer.authMethod = config.incoming.auth;
+ inServer.password = config.incoming.password;
+ if (config.rememberPassword && config.incoming.password.length)
+ rememberPassword(inServer, config.incoming.password);
+
+ if (inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
+ inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
+ inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
+ }
+
+ // SSL
+ if (config.incoming.socketType == 1) // plain
+ inServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.incoming.socketType == 2) // SSL / TLS
+ inServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.incoming.socketType == 3) // STARTTLS
+ inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+ //inServer.prettyName = config.displayName;
+ inServer.prettyName = config.identity.emailAddress;
+
+ inServer.doBiff = true;
+ inServer.biffMinutes = config.incoming.checkInterval;
+ const loginAtStartupPrefTemplate =
+ "mail.server.%serverkey%.login_at_startup";
+ var loginAtStartupPref =
+ loginAtStartupPrefTemplate.replace("%serverkey%", inServer.key);
+ Services.prefs.setBoolPref(loginAtStartupPref,
+ config.incoming.loginAtStartup);
+ if (config.incoming.type == "pop3")
+ {
+ const leaveOnServerPrefTemplate =
+ "mail.server.%serverkey%.leave_on_server";
+ const daysToLeaveOnServerPrefTemplate =
+ "mail.server.%serverkey%.num_days_to_leave_on_server";
+ const deleteFromServerPrefTemplate =
+ "mail.server.%serverkey%.delete_mail_left_on_server";
+ const deleteByAgeFromServerPrefTemplate =
+ "mail.server.%serverkey%.delete_by_age_from_server";
+ const downloadOnBiffPrefTemplate =
+ "mail.server.%serverkey%.download_on_biff";
+ var leaveOnServerPref =
+ leaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var ageFromServerPref =
+ deleteByAgeFromServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var daysToLeaveOnServerPref =
+ daysToLeaveOnServerPrefTemplate.replace("%serverkey%", inServer.key);
+ var deleteFromServerPref =
+ deleteFromServerPrefTemplate.replace("%serverkey%", inServer.key);
+ let downloadOnBiffPref =
+ downloadOnBiffPrefTemplate.replace("%serverkey%", inServer.key);
+ Services.prefs.setBoolPref(leaveOnServerPref,
+ config.incoming.leaveMessagesOnServer);
+ Services.prefs.setIntPref(daysToLeaveOnServerPref,
+ config.incoming.daysToLeaveMessagesOnServer);
+ Services.prefs.setBoolPref(deleteFromServerPref,
+ config.incoming.deleteOnServerWhenLocalDelete);
+ Services.prefs.setBoolPref(ageFromServerPref,
+ config.incoming.deleteByAgeFromServer);
+ Services.prefs.setBoolPref(downloadOnBiffPref,
+ config.incoming.downloadOnBiff);
+ }
+ inServer.valid = true;
+
+ let username = config.outgoing.auth > 1 ? config.outgoing.username : null;
+ let outServer = MailServices.smtp.findServer(username, config.outgoing.hostname);
+ assert(config.outgoing.addThisServer ||
+ config.outgoing.useGlobalPreferredServer ||
+ config.outgoing.existingServerKey,
+ "No SMTP server: inconsistent flags");
+
+ if (config.outgoing.addThisServer && !outServer)
+ {
+ outServer = MailServices.smtp.createServer();
+ outServer.hostname = config.outgoing.hostname;
+ outServer.port = config.outgoing.port;
+ outServer.authMethod = config.outgoing.auth;
+ if (config.outgoing.auth > 1)
+ {
+ outServer.username = username;
+ outServer.password = config.incoming.password;
+ if (config.rememberPassword && config.incoming.password.length)
+ rememberPassword(outServer, config.incoming.password);
+ }
+
+ if (outServer.authMethod == Ci.nsMsgAuthMethod.OAuth2) {
+ let pref = "mail.smtpserver." + outServer.key + ".";
+ Services.prefs.setCharPref(pref + "oauth2.scope",
+ config.oauthSettings.scope);
+ Services.prefs.setCharPref(pref + "oauth2.issuer",
+ config.oauthSettings.issuer);
+ }
+
+ if (config.outgoing.socketType == 1) // no SSL
+ outServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.outgoing.socketType == 2) // SSL / TLS
+ outServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.outgoing.socketType == 3) // STARTTLS
+ outServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ // API problem: <http://mxr.mozilla.org/seamonkey/source/mailnews/compose/public/nsISmtpServer.idl#93>
+ outServer.description = config.displayName;
+ if (config.password)
+ outServer.password = config.outgoing.password;
+
+ // If this is the first SMTP server, set it as default
+ if (!MailServices.smtp.defaultServer ||
+ !MailServices.smtp.defaultServer.hostname)
+ MailServices.smtp.defaultServer = outServer;
+ }
+
+ // identity
+ // TODO accounts without identity?
+ let identity = MailServices.accounts.createIdentity();
+ identity.fullName = config.identity.realname;
+ identity.email = config.identity.emailAddress;
+
+ // for new accounts, default to replies being positioned above the quote
+ // if a default account is defined already, take its settings instead
+ if (config.incoming.type == "imap" || config.incoming.type == "pop3")
+ {
+ identity.replyOnTop = 1;
+ // identity.sigBottom = false; // don't set this until Bug 218346 is fixed
+
+ if (MailServices.accounts.accounts.length &&
+ MailServices.accounts.defaultAccount)
+ {
+ let defAccount = MailServices.accounts.defaultAccount;
+ let defIdentity = defAccount.defaultIdentity;
+ if (defAccount.incomingServer.canBeDefaultServer &&
+ defIdentity && defIdentity.valid)
+ {
+ identity.replyOnTop = defIdentity.replyOnTop;
+ identity.sigBottom = defIdentity.sigBottom;
+ }
+ }
+ }
+
+ // due to accepted conventions, news accounts should default to plain text
+ if (config.incoming.type == "nntp")
+ identity.composeHtml = false;
+
+ identity.valid = true;
+
+ if (config.outgoing.existingServerKey)
+ identity.smtpServerKey = config.outgoing.existingServerKey;
+ else if (!config.outgoing.useGlobalPreferredServer)
+ identity.smtpServerKey = outServer.key;
+
+ // account and hook up
+ // Note: Setting incomingServer will cause the AccountManager to refresh
+ // itself, which could be a problem if we came from it and we haven't set
+ // the identity (see bug 521955), so make sure everything else on the
+ // account is set up before you set the incomingServer.
+ let account = MailServices.accounts.createAccount();
+ account.addIdentity(identity);
+ account.incomingServer = inServer;
+ if (inServer.canBeDefaultServer && (!MailServices.accounts.defaultAccount ||
+ !MailServices.accounts.defaultAccount
+ .incomingServer.canBeDefaultServer))
+ MailServices.accounts.defaultAccount = account;
+
+ verifyLocalFoldersAccount(MailServices.accounts);
+ setFolders(identity, inServer);
+
+ // save
+ MailServices.accounts.saveAccountInfo();
+ try {
+ Services.prefs.savePrefFile(null);
+ } catch (ex) {
+ ddump("Could not write out prefs: " + ex);
+ }
+ return account;
+}
+
+function setFolders(identity, server)
+{
+ // TODO: support for local folders for global inbox (or use smart search
+ // folder instead)
+
+ var baseURI = server.serverURI + "/";
+
+ // Names will be localized in UI, not in folder names on server/disk
+ // TODO allow to override these names in the XML config file,
+ // in case e.g. Google or AOL use different names?
+ // Workaround: Let user fix it :)
+ var fccName = "Sent";
+ var draftName = "Drafts";
+ var templatesName = "Templates";
+
+ identity.draftFolder = baseURI + draftName;
+ identity.stationeryFolder = baseURI + templatesName;
+ identity.fccFolder = baseURI + fccName;
+
+ identity.fccFolderPickerMode = 0;
+ identity.draftsFolderPickerMode = 0;
+ identity.tmplFolderPickerMode = 0;
+}
+
+function rememberPassword(server, password)
+{
+ if (server instanceof Components.interfaces.nsIMsgIncomingServer)
+ var passwordURI = server.localStoreType + "://" + server.hostName;
+ else if (server instanceof Components.interfaces.nsISmtpServer)
+ var passwordURI = "smtp://" + server.hostname;
+ else
+ throw new NotReached("Server type not supported");
+
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
+ .createInstance(Ci.nsILoginInfo);
+ login.init(passwordURI, null, passwordURI, server.username, password, "", "");
+ try {
+ Services.logins.addLogin(login);
+ } catch (e) {
+ if (e.message.includes("This login already exists")) {
+ // TODO modify
+ } else {
+ throw e;
+ }
+ }
+}
+
+/**
+ * Check whether the user's setup already has an incoming server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ * (We also check the email address as username.)
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsIMsgIncomingServer} If it already exists, the server
+ * object is returned.
+ * If it's a new server, |null| is returned.
+ */
+function checkIncomingServerAlreadyExists(config)
+{
+ assert(config instanceof AccountConfig);
+ let incoming = config.incoming;
+ let existing = MailServices.accounts.findRealServer(incoming.username,
+ incoming.hostname,
+ sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+ incoming.port);
+
+ // if username does not have an '@', also check the e-mail
+ // address form of the name.
+ if (!existing && !incoming.username.includes("@"))
+ existing = MailServices.accounts.findRealServer(config.identity.emailAddress,
+ incoming.hostname,
+ sanitize.enum(incoming.type, ["pop3", "imap", "nntp"]),
+ incoming.port);
+ return existing;
+};
+
+/**
+ * Check whether the user's setup already has an outgoing server
+ * which matches (hostname, port, username) the primary one
+ * in the config.
+ *
+ * @param config {AccountConfig} filled in (no placeholders)
+ * @return {nsISmtpServer} If it already exists, the server
+ * object is returned.
+ * If it's a new server, |null| is returned.
+ */
+function checkOutgoingServerAlreadyExists(config)
+{
+ assert(config instanceof AccountConfig);
+ let smtpServers = MailServices.smtp.servers;
+ while (smtpServers.hasMoreElements())
+ {
+ let existingServer = smtpServers.getNext()
+ .QueryInterface(Ci.nsISmtpServer);
+ // TODO check username with full email address, too, like for incoming
+ if (existingServer.hostname == config.outgoing.hostname &&
+ existingServer.port == config.outgoing.port &&
+ existingServer.username == config.outgoing.username)
+ return existingServer;
+ }
+ return null;
+};
+
+/**
+ * Check if there already is a "Local Folders". If not, create it.
+ * Copied from AccountWizard.js with minor updates.
+ */
+function verifyLocalFoldersAccount(am)
+{
+ let localMailServer;
+ try {
+ localMailServer = am.localFoldersServer;
+ }
+ catch (ex) {
+ localMailServer = null;
+ }
+
+ try {
+ if (!localMailServer)
+ {
+ // creates a copy of the identity you pass in
+ am.createLocalMailAccount();
+ try {
+ localMailServer = am.localFoldersServer;
+ }
+ catch (ex) {
+ ddump("Error! we should have found the local mail server " +
+ "after we created it.");
+ }
+ }
+ }
+ catch (ex) { ddump("Error in verifyLocalFoldersAccount " + ex); }
+}
diff --git a/mailnews/base/prefs/content/accountcreation/emailWizard.js b/mailnews/base/prefs/content/accountcreation/emailWizard.js
new file mode 100644
index 000000000..b4e6854da
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.js
@@ -0,0 +1,1959 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+
+/**
+ * This is the dialog opened by menu File | New account | Mail... .
+ *
+ * It gets the user's realname, email address and password,
+ * and tries to automatically configure the account from that,
+ * using various mechanisms. If all fails, the user can enter/edit
+ * the config, then we create the account.
+ *
+ * Steps:
+ * - User enters realname, email address and password
+ * - check for config files on disk
+ * (shipping with Thunderbird, for enterprise deployments)
+ * - (if fails) try to get the config file from the ISP via a
+ * fixed URL on the domain of the email address
+ * - (if fails) try to get the config file from our own database
+ * at MoMo servers, maintained by the community
+ * - (if fails) try to guess the config, by guessing hostnames,
+ * probing ports, checking config via server's CAPS line etc..
+ * - verify the setup, by trying to login to the configured servers
+ * - let user verify and maybe edit the server names and ports
+ * - If user clicks OK, create the account
+ */
+
+
+// from http://xyfer.blogspot.com/2005/01/javascript-regexp-email-validator.html
+var emailRE = /^[-_a-z0-9\'+*$^&%=~!?{}]+(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*@(?:[-a-z0-9.]+\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d+)?$/i;
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+
+var gStringsBundle;
+var gMessengerBundle;
+var gBrandShortName;
+
+/*********************
+TODO for bug 549045
+- autodetect protocol
+Polish
+- reformat code style to match
+<https://developer.mozilla.org/En/Mozilla_Coding_Style_Guide#Control_Structures>
+- bold status
+- remove status when user edited in manual edit
+- add and adapt test from bug 534588
+Bugs
+- SSL cert errors
+ - invalid cert (hostname mismatch) doesn't trigger warning dialog as it should
+ - accept self-signed cert (e.g. imap.mail.ru) doesn't work
+ (works without my patch),
+ verifyConfig.js line 124 has no inServer, for whatever reason,
+ although I didn't change verifyConfig.js at all
+ (the change you see in that file is irrelevant: that was an attempt to fix
+ the bug and clean up the code).
+- Set radio IMAP vs. POP3, see TODO in code
+Things to test (works for me):
+- state transitions, buttons enable, status msgs
+ - stop button
+ - showes up again after stopping detection and restarting it
+ - when stopping [retest]: buttons proper?
+ - enter nonsense domain. guess fails, (so automatically) manual,
+ change domain to real one (not in DB), guess succeeds.
+ former bug: goes to manual first shortly, then to result
+**********************/
+
+// To debug, set mail.wizard.logging.dump (or .console)="All" and kDebug = true
+
+function e(elementID)
+{
+ return document.getElementById(elementID);
+};
+
+function _hide(id)
+{
+ e(id).hidden = true;
+}
+
+function _show(id)
+{
+ e(id).hidden = false;
+}
+
+function _enable(id)
+{
+ e(id).disabled = false;
+}
+
+function _disable(id)
+{
+ e(id).disabled = true;
+}
+
+function setText(id, value)
+{
+ var element = e(id);
+ assert(element, "setText() on non-existant element ID");
+
+ if (element.localName == "textbox" || element.localName == "label") {
+ element.value = value;
+ } else if (element.localName == "description") {
+ element.textContent = value;
+ } else {
+ throw new NotReached("XUL element type not supported");
+ }
+}
+
+function setLabelFromStringBundle(elementID, stringName)
+{
+ e(elementID).label = gMessengerBundle.getString(stringName);
+};
+
+function EmailConfigWizard()
+{
+ this._init();
+}
+EmailConfigWizard.prototype =
+{
+ _init : function EmailConfigWizard__init()
+ {
+ gEmailWizardLogger.info("Initializing setup wizard");
+ this._abortable = null;
+ },
+
+ onLoad : function()
+ {
+ /**
+ * this._currentConfig is the config we got either from the XML file or
+ * from guessing or from the user. Unless it's from the user, it contains
+ * placeholders like %EMAILLOCALPART% in username and other fields.
+ *
+ * The config here must retain these placeholders, to be able to
+ * adapt when the user enters a different realname, or password or
+ * email local part. (A change of the domain name will trigger a new
+ * detection anyways.)
+ * That means, before you actually use the config (e.g. to create an
+ * account or to show it to the user), you need to run replaceVariables().
+ */
+ this._currentConfig = null;
+
+ let userFullname;
+ try {
+ let userInfo = Cc["@mozilla.org/userinfo;1"].getService(Ci.nsIUserInfo);
+ userFullname = userInfo.fullname;
+ } catch(e) {
+ // nsIUserInfo may not be implemented on all platforms, and name might
+ // not be avaialble even if it is.
+ }
+
+ this._domain = "";
+ this._email = "";
+ this._realname = (userFullname) ? userFullname : "";
+ e("realname").value = this._realname;
+ this._password = "";
+ this._okCallback = null;
+
+ if (window.arguments && window.arguments[0]) {
+ if (window.arguments[0].msgWindow) {
+ this._parentMsgWindow = window.arguments[0].msgWindow;
+ }
+ if (window.arguments[0].okCallback) {
+ this._okCallback = window.arguments[0].okCallback;
+ }
+ }
+
+ gEmailWizardLogger.info("Email account setup dialog loaded.");
+
+ gStringsBundle = e("strings");
+ gMessengerBundle = e("bundle_messenger");
+ gBrandShortName = e("bundle_brand").getString("brandShortName");
+
+ setLabelFromStringBundle("in-authMethod-password-cleartext",
+ "authPasswordCleartextViaSSL"); // will warn about insecure later
+ setLabelFromStringBundle("in-authMethod-password-encrypted",
+ "authPasswordEncrypted");
+ setLabelFromStringBundle("in-authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("in-authMethod-ntlm", "authNTLM");
+ setLabelFromStringBundle("in-authMethod-oauth2", "authOAuth2");
+ setLabelFromStringBundle("out-authMethod-no", "authNo");
+ setLabelFromStringBundle("out-authMethod-password-cleartext",
+ "authPasswordCleartextViaSSL"); // will warn about insecure later
+ setLabelFromStringBundle("out-authMethod-password-encrypted",
+ "authPasswordEncrypted");
+ setLabelFromStringBundle("out-authMethod-kerberos", "authKerberos");
+ setLabelFromStringBundle("out-authMethod-ntlm", "authNTLM");
+ setLabelFromStringBundle("out-authMethod-oauth2", "authOAuth2");
+
+ e("incoming_port").value = gStringsBundle.getString("port_auto");
+ this.fillPortDropdown("smtp");
+
+ // If the account provisioner is preffed off, don't display
+ // the account provisioner button.
+ if (!Services.prefs.getBoolPref("mail.provider.enabled"))
+ _hide("provisioner_button");
+
+ // Populate SMTP server dropdown with already configured SMTP servers from
+ // other accounts.
+ var menulist = e("outgoing_hostname");
+ let smtpServers = MailServices.smtp.servers;
+ while (smtpServers.hasMoreElements()) {
+ let server = smtpServers.getNext().QueryInterface(Ci.nsISmtpServer);
+ let label = server.displayname;
+ let key = server.key;
+ if (MailServices.smtp.defaultServer &&
+ MailServices.smtp.defaultServer.key == key) {
+ label += " " + gStringsBundle.getString("default_server_tag");
+ }
+ let menuitem = menulist.appendItem(label, key, ""); // label,value,descr
+ menuitem.serverKey = key;
+ }
+ // Add the entry for the new host to the menulist
+ let menuitem = menulist.insertItemAt(0, "", "-new-"); // pos,label,value
+ menuitem.serverKey = null;
+
+ // admin-locked prefs hurray
+ if (!Services.prefs.getBoolPref("signon.rememberSignons")) {
+ let rememberPasswordE = e("remember_password");
+ rememberPasswordE.checked = false;
+ rememberPasswordE.disabled = true;
+ }
+
+ // First, unhide the main window areas, and store the width,
+ // so that we don't resize wildly when we unhide areas.
+ // switchToMode() will then hide the unneeded parts again.
+ // We will add some leeway of 10px, in case some of the <description>s wrap,
+ // e.g. outgoing username != incoming username.
+ _show("status_area");
+ _show("result_area");
+ _hide("manual-edit_area");
+ window.sizeToContent();
+ e("mastervbox").setAttribute("style",
+ "min-width: " + document.width + "px; " +
+ "min-height: " + (document.height + 10) + "px;");
+
+ this.switchToMode("start");
+ e("realname").select();
+ },
+
+ /**
+ * Changes the window configuration to the different modes we have.
+ * Shows/hides various window parts and buttons.
+ * @param modename {String-enum}
+ * "start" : Just the realname, email address, password fields
+ * "find-config" : detection step, adds the progress message/spinner
+ * "result" : We found a config and display it to the user.
+ * The user may create the account.
+ * "manual-edit" : The user wants (or needs) to manually enter their
+ * the server hostname and other settings. We'll use them as provided.
+ * Additionally, there are the following sub-modes which can be entered after
+ * you entered the main mode:
+ * "manual-edit-have-hostname" : user entered a hostname for both servers
+ * that we can use
+ * "manual-edit-testing" : User pressed the [Re-test] button and
+ * we're currently detecting the "Auto" values
+ * "manual-edit-complete" : user entered (or we tested) all necessary
+ * values, and we're ready to create to account
+ * Currently, this doesn't cover the warning dialogs etc.. It may later.
+ */
+ switchToMode : function(modename)
+ {
+ if (modename == this._currentModename) {
+ return;
+ }
+ this._currentModename = modename;
+ gEmailWizardLogger.info("switching to UI mode " + modename)
+
+ //_show("initialSettings"); always visible
+ //_show("cancel_button"); always visible
+ if (modename == "start") {
+ _hide("status_area");
+ _hide("result_area");
+ _hide("manual-edit_area");
+
+ _show("next_button");
+ _disable("next_button"); // will be enabled by code
+ _hide("half-manual-test_button");
+ _hide("create_button");
+ _hide("stop_button");
+ _hide("manual-edit_button");
+ _hide("advanced-setup_button");
+ } else if (modename == "find-config") {
+ _show("status_area");
+ _hide("result_area");
+ _hide("manual-edit_area");
+
+ _show("next_button");
+ _disable("next_button");
+ _hide("half-manual-test_button");
+ _hide("create_button");
+ _show("stop_button");
+ this.onStop = this.onStopFindConfig;
+ _show("manual-edit_button");
+ _hide("advanced-setup_button");
+ } else if (modename == "result") {
+ _show("status_area");
+ _show("result_area");
+ _hide("manual-edit_area");
+
+ _hide("next_button");
+ _hide("half-manual-test_button");
+ _show("create_button");
+ _enable("create_button");
+ _hide("stop_button");
+ _show("manual-edit_button");
+ _hide("advanced-setup_button");
+ } else if (modename == "manual-edit") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+
+ _hide("next_button");
+ _show("half-manual-test_button");
+ _disable("half-manual-test_button");
+ _show("create_button");
+ _disable("create_button");
+ _hide("stop_button");
+ _hide("manual-edit_button");
+ _show("advanced-setup_button");
+ _disable("advanced-setup_button");
+ } else if (modename == "manual-edit-have-hostname") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+ _hide("manual-edit_button");
+ _hide("next_button");
+ _show("create_button");
+
+ _show("half-manual-test_button");
+ _enable("half-manual-test_button");
+ _disable("create_button");
+ _hide("stop_button");
+ _show("advanced-setup_button");
+ _disable("advanced-setup_button");
+ } else if (modename == "manual-edit-testing") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+ _hide("manual-edit_button");
+ _hide("next_button");
+ _show("create_button");
+
+ _show("half-manual-test_button");
+ _disable("half-manual-test_button");
+ _disable("create_button");
+ _show("stop_button");
+ this.onStop = this.onStopHalfManualTesting;
+ _show("advanced-setup_button");
+ _disable("advanced-setup_button");
+ } else if (modename == "manual-edit-complete") {
+ _show("status_area");
+ _hide("result_area");
+ _show("manual-edit_area");
+ _hide("manual-edit_button");
+ _hide("next_button");
+ _show("create_button");
+
+ _show("half-manual-test_button");
+ _enable("half-manual-test_button");
+ _enable("create_button");
+ _hide("stop_button");
+ _show("advanced-setup_button");
+ _enable("advanced-setup_button");
+ } else {
+ throw new NotReached("unknown mode");
+ }
+ // If we're offline, we're going to disable the create button, but enable
+ // the advanced config button if we have a current config.
+ if (Services.io.offline) {
+ if (this._currentConfig != null) {
+ _show("advanced-setup_button");
+ _enable("advanced-setup_button");
+ _hide("half-manual-test_button");
+ _hide("create_button");
+ _hide("manual-edit_button");
+ }
+ }
+ window.sizeToContent();
+ },
+
+ /**
+ * Start from beginning with possibly new email address.
+ */
+ onStartOver : function()
+ {
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.switchToMode("start");
+ },
+
+ getConcreteConfig : function()
+ {
+ var result = this._currentConfig.copy();
+ replaceVariables(result, this._realname, this._email, this._password);
+ result.rememberPassword = e("remember_password").checked &&
+ !!this._password;
+ return result;
+ },
+
+ /*
+ * This checks if the email address is at least possibly valid, meaning it
+ * has an '@' before the last char.
+ */
+ validateEmailMinimally : function(emailAddr)
+ {
+ let atPos = emailAddr.lastIndexOf("@");
+ return atPos > 0 && atPos + 1 < emailAddr.length;
+ },
+
+ /*
+ * This checks if the email address is syntactically valid,
+ * as far as we can determine. We try hard to make full checks.
+ *
+ * OTOH, we have a very small chance of false negatives,
+ * because the RFC822 address spec is insanely complicated,
+ * but rarely needed, so when this here fails, we show an error message,
+ * but don't stop the user from continuing.
+ * In contrast, if validateEmailMinimally() fails, we stop the user.
+ */
+ validateEmail : function(emailAddr)
+ {
+ return emailRE.test(emailAddr);
+ },
+
+ /**
+ * onInputEmail and onInputRealname are called on input = keypresses, and
+ * enable/disable the next button based on whether there's a semi-proper
+ * e-mail address and non-blank realname to start with.
+ *
+ * A change to the email address also automatically restarts the
+ * whole process.
+ */
+ onInputEmail : function()
+ {
+ this._email = e("email").value;
+ this.onStartOver();
+ this.checkStartDone();
+ },
+ onInputRealname : function()
+ {
+ this._realname = e("realname").value;
+ this.checkStartDone();
+ },
+
+ onInputPassword : function()
+ {
+ this._password = e("password").value;
+ },
+
+ /**
+ * This does very little other than to check that a name was entered at all
+ * Since this is such an insignificant test we should be using a very light
+ * or even jovial warning.
+ */
+ onBlurRealname : function()
+ {
+ let realnameEl = e("realname");
+ if (this._realname) {
+ this.clearError("nameerror");
+ _show("nametext");
+ realnameEl.removeAttribute("error");
+ // bug 638790: don't show realname error until user enter an email address
+ } else if (this.validateEmailMinimally(this._email)) {
+ _hide("nametext");
+ this.setError("nameerror", "please_enter_name");
+ realnameEl.setAttribute("error", "true");
+ }
+ },
+
+ /**
+ * This check is only done as an informative warning.
+ * We don't want to block the person, if they've entered an email address
+ * that doesn't conform to our regex.
+ */
+ onBlurEmail : function()
+ {
+ if (!this._email) {
+ return;
+ }
+ var emailEl = e("email");
+ if (this.validateEmail(this._email)) {
+ this.clearError("emailerror");
+ emailEl.removeAttribute("error");
+ this.onBlurRealname();
+ } else {
+ this.setError("emailerror", "double_check_email");
+ emailEl.setAttribute("error", "true");
+ }
+ },
+
+ /**
+ * If the user just tabbed through the password input without entering
+ * anything, set the type back to text so we don't wind up showing the
+ * emptytext as bullet characters.
+ */
+ onBlurPassword : function()
+ {
+ if (!this._password) {
+ e("password").type = "text";
+ }
+ },
+
+ /**
+ * @see onBlurPassword()
+ */
+ onFocusPassword : function()
+ {
+ e("password").type = "password";
+ },
+
+ /**
+ * Check whether the user entered the minimum of information
+ * needed to leave the "start" mode (entering of name, email, pw)
+ * and is allowed to proceed to detection step.
+ */
+ checkStartDone : function()
+ {
+ if (this.validateEmailMinimally(this._email) &&
+ this._realname) {
+ this._domain = this._email.split("@")[1].toLowerCase();
+ _enable("next_button");
+ } else {
+ _disable("next_button");
+ }
+ },
+
+ /**
+ * When the [Continue] button is clicked, we move from the initial account
+ * information stage to using that information to configure account details.
+ */
+ onNext : function()
+ {
+ this.findConfig(this._domain, this._email);
+ },
+
+
+ /////////////////////////////////////////////////////////////////
+ // Detection step
+
+ /**
+ * Try to find an account configuration for this email address.
+ * This is the function which runs the autoconfig.
+ */
+ findConfig : function(domain, email)
+ {
+ gEmailWizardLogger.info("findConfig()");
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.switchToMode("find-config");
+ this.startSpinner("looking_up_settings_disk");
+ var self = this;
+ this._abortable = fetchConfigFromDisk(domain,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_disk");
+ },
+ function(e) // fetchConfigFromDisk failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ gEmailWizardLogger.info("fetchConfigFromDisk failed: " + e);
+ self.startSpinner("looking_up_settings_isp");
+ self._abortable = fetchConfigFromISP(domain, email,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_isp");
+ },
+ function(e) // fetchConfigFromISP failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ gEmailWizardLogger.info("fetchConfigFromISP failed: " + e);
+ logException(e);
+ self.startSpinner("looking_up_settings_db");
+ self._abortable = fetchConfigFromDB(domain,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_db");
+ },
+ function(e) // fetchConfigFromDB failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ logException(e);
+ gEmailWizardLogger.info("fetchConfigFromDB failed: " + e);
+ self.startSpinner("looking_up_settings_db");
+ self._abortable = fetchConfigForMX(domain,
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner("found_settings_db");
+ },
+ function(e) // fetchConfigForMX failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ logException(e);
+ gEmailWizardLogger.info("fetchConfigForMX failed: " + e);
+ var initialConfig = new AccountConfig();
+ self._prefillConfig(initialConfig);
+ self._guessConfig(domain, initialConfig);
+ });
+ });
+ });
+ });
+ },
+
+ /**
+ * Just a continuation of findConfig()
+ */
+ _guessConfig : function(domain, initialConfig)
+ {
+ this.startSpinner("looking_up_settings_guess")
+ var self = this;
+ self._abortable = guessConfig(domain,
+ function(type, hostname, port, ssl, done, config) // progress
+ {
+ gEmailWizardLogger.info("progress callback host " + hostname +
+ " port " + port + " type " + type);
+ },
+ function(config) // success
+ {
+ self._abortable = null;
+ self.foundConfig(config);
+ self.stopSpinner(Services.io.offline ?
+ "guessed_settings_offline" : "found_settings_guess");
+ window.sizeToContent();
+ },
+ function(e, config) // guessconfig failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ self._abortable = null;
+ gEmailWizardLogger.info("guessConfig failed: " + e);
+ self.showErrorStatus("failed_to_find_settings");
+ self.editConfigDetails();
+ },
+ initialConfig, "both");
+ },
+
+ /**
+ * When findConfig() was successful, it calls this.
+ * This displays the config to the user.
+ */
+ foundConfig : function(config)
+ {
+ gEmailWizardLogger.info("foundConfig()");
+ assert(config instanceof AccountConfig,
+ "BUG: Arg 'config' needs to be an AccountConfig object");
+
+ this._haveValidConfigForDomain = this._email.split("@")[1];
+
+ if (!this._realname || !this._email) {
+ return;
+ }
+ this._foundConfig2(config);
+ },
+
+ // Continuation of foundConfig2() after custom fields.
+ _foundConfig2 : function(config)
+ {
+ this.displayConfigResult(config);
+ },
+
+ /**
+ * [Stop] button click handler.
+ * This allows the user to abort any longer operation, esp. network activity.
+ * We currently have 3 such cases here:
+ * 1. findConfig(), i.e. fetch config from DB, guessConfig etc.
+ * 2. onHalfManualTest(), i.e. the [Retest] button in manual config.
+ * 3. verifyConfig() - We can't stop this yet, so irrelevant here currently.
+ * Given that these need slightly different actions, this function will be set
+ * to a function (i.e. overwritten) by whoever enables the stop button.
+ *
+ * We also call this from the code when the user started a different action
+ * without explicitly clicking [Stop] for the old one first.
+ */
+ onStop : function()
+ {
+ throw new NotReached("onStop should be overridden by now");
+ },
+ _onStopCommon : function()
+ {
+ if (!this._abortable) {
+ throw new NotReached("onStop called although there's nothing to stop");
+ }
+ gEmailWizardLogger.info("onStop cancelled _abortable");
+ this._abortable.cancel(new UserCancelledException());
+ this._abortable = null;
+ this.stopSpinner();
+ },
+ onStopFindConfig : function()
+ {
+ this._onStopCommon();
+ this.switchToMode("start");
+ this.checkStartDone();
+ },
+ onStopHalfManualTesting : function()
+ {
+ this._onStopCommon();
+ this.validateManualEditComplete();
+ },
+
+
+
+ ///////////////////////////////////////////////////////////////////
+ // status area
+
+ startSpinner : function(actionStrName)
+ {
+ e("status_area").setAttribute("status", "loading");
+ gEmailWizardLogger.warn("spinner start " + actionStrName);
+ this._showStatusTitle(actionStrName);
+ },
+
+ stopSpinner : function(actionStrName)
+ {
+ e("status_area").setAttribute("status", "result");
+ _hide("stop_button");
+ this._showStatusTitle(actionStrName);
+ gEmailWizardLogger.warn("all spinner stop " + actionStrName);
+ },
+
+ showErrorStatus : function(actionStrName)
+ {
+ e("status_area").setAttribute("status", "error");
+ gEmailWizardLogger.warn("status error " + actionStrName);
+ this._showStatusTitle(actionStrName);
+ },
+
+ _showStatusTitle : function(msgName)
+ {
+ let msg = " "; // assure height. Do via min-height in CSS, for 2 lines?
+ try {
+ if (msgName) {
+ msg = gStringsBundle.getFormattedString(msgName, [gBrandShortName]);
+ }
+ } catch(ex) {
+ gEmailWizardLogger.error("missing string for " + msgName);
+ msg = msgName + " (missing string in translation!)";
+ }
+
+ e("status_msg").textContent = msg;
+ gEmailWizardLogger.info("status msg: " + msg);
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // Result area
+
+ /**
+ * Displays a (probed) config to the user,
+ * in the result config details area.
+ *
+ * @param config {AccountConfig} The config to present to user
+ */
+ displayConfigResult : function(config)
+ {
+ assert(config instanceof AccountConfig);
+ this._currentConfig = config;
+ var configFilledIn = this.getConcreteConfig();
+
+ var unknownString = gStringsBundle.getString("resultUnknown");
+
+ function _makeHostDisplayString(server, stringName)
+ {
+ let type = gStringsBundle.getString(sanitize.translate(server.type,
+ { imap : "resultIMAP", pop3 : "resultPOP3", smtp : "resultSMTP" }),
+ unknownString);
+ let host = server.hostname +
+ (isStandardPort(server.port) ? "" : ":" + server.port);
+ let ssl = gStringsBundle.getString(sanitize.translate(server.socketType,
+ { 1 : "resultNoEncryption", 2 : "resultSSL", 3 : "resultSTARTTLS" }),
+ unknownString);
+ let certStatus = gStringsBundle.getString(server.badCert ?
+ "resultSSLCertWeak" : "resultSSLCertOK");
+ // TODO: we should really also display authentication method here.
+ return gStringsBundle.getFormattedString(stringName,
+ [ type, host, ssl, certStatus ]);
+ };
+
+ var incomingResult = unknownString;
+ if (configFilledIn.incoming.hostname) {
+ incomingResult = _makeHostDisplayString(configFilledIn.incoming,
+ "resultIncoming");
+ }
+
+ var outgoingResult = unknownString;
+ if (!config.outgoing.existingServerKey) {
+ if (configFilledIn.outgoing.hostname) {
+ outgoingResult = _makeHostDisplayString(configFilledIn.outgoing,
+ "resultOutgoing");
+ }
+ } else {
+ outgoingResult = gStringsBundle.getString("resultOutgoingExisting");
+ }
+
+ var usernameResult;
+ if (configFilledIn.incoming.username == configFilledIn.outgoing.username) {
+ usernameResult = gStringsBundle.getFormattedString("resultUsernameBoth",
+ [ configFilledIn.incoming.username || unknownString ]);
+ } else {
+ usernameResult = gStringsBundle.getFormattedString(
+ "resultUsernameDifferent",
+ [ configFilledIn.incoming.username || unknownString,
+ configFilledIn.outgoing.username || unknownString ]);
+ }
+
+ setText("result-incoming", incomingResult);
+ setText("result-outgoing", outgoingResult);
+ setText("result-username", usernameResult);
+
+ gEmailWizardLogger.info(debugObject(config, "config"));
+ // IMAP / POP dropdown
+ var lookForAltType =
+ config.incoming.type == "imap" ? "pop3" : "imap";
+ var alternative = null;
+ for (let i = 0; i < config.incomingAlternatives.length; i++) {
+ let alt = config.incomingAlternatives[i];
+ if (alt.type == lookForAltType) {
+ alternative = alt;
+ break;
+ }
+ }
+ if (alternative) {
+ _show("result_imappop");
+ e("result_select_" + alternative.type).configIncoming = alternative;
+ e("result_select_" + config.incoming.type).configIncoming =
+ config.incoming;
+ e("result_imappop").value =
+ config.incoming.type == "imap" ? 1 : 2;
+ } else {
+ _hide("result_imappop");
+ }
+
+ this.switchToMode("result");
+ },
+
+ /**
+ * Handle the user switching between IMAP and POP3 settings using the
+ * radio buttons.
+ *
+ * Note: This function must only be called by user action, not by setting
+ * the value or selectedItem or selectedIndex of the radiogroup!
+ * This is why we use the oncommand attribute of the radio elements
+ * instead of the onselect attribute of the radiogroup.
+ */
+ onResultIMAPOrPOP3 : function()
+ {
+ var config = this._currentConfig;
+ var radiogroup = e("result_imappop");
+ // add current server as best alternative to start of array
+ config.incomingAlternatives.unshift(config.incoming);
+ // use selected server (stored as special property on the <radio> node)
+ config.incoming = radiogroup.selectedItem.configIncoming;
+ // remove newly selected server from list of alternatives
+ config.incomingAlternatives = config.incomingAlternatives.filter(
+ function(e) { return e != config.incoming; });
+ this.displayConfigResult(config);
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // Manual Edit area
+
+ /**
+ * Gets the values from the user in the manual edit area.
+ *
+ * Realname and password are not part of that area and still
+ * placeholders, but hostname and username are concrete and
+ * no placeholders anymore.
+ */
+ getUserConfig : function()
+ {
+ var config = this.getConcreteConfig();
+ if (!config) {
+ config = new AccountConfig();
+ }
+ config.source = AccountConfig.kSourceUser;
+
+ // Incoming server
+ try {
+ var inHostnameField = e("incoming_hostname");
+ config.incoming.hostname = sanitize.hostname(inHostnameField.value);
+ inHostnameField.value = config.incoming.hostname;
+ } catch (e) { gEmailWizardLogger.warn(e); }
+ try {
+ config.incoming.port = sanitize.integerRange(e("incoming_port").value,
+ kMinPort, kMaxPort);
+ } catch (e) {
+ config.incoming.port = undefined; // incl. default "Auto"
+ }
+ config.incoming.type = sanitize.translate(e("incoming_protocol").value,
+ { 1: "imap", 2 : "pop3", 0 : null });
+ config.incoming.socketType = sanitize.integer(e("incoming_ssl").value);
+ config.incoming.auth = sanitize.integer(e("incoming_authMethod").value);
+ config.incoming.username = e("incoming_username").value;
+
+ // Outgoing server
+
+ // Did the user select one of the already configured SMTP servers from the
+ // drop-down list? If so, use it.
+ var outHostnameCombo = e("outgoing_hostname");
+ var outMenuitem = outHostnameCombo.selectedItem;
+ if (outMenuitem && outMenuitem.serverKey) {
+ config.outgoing.existingServerKey = outMenuitem.serverKey;
+ config.outgoing.existingServerLabel = outMenuitem.label;
+ config.outgoing.addThisServer = false;
+ config.outgoing.useGlobalPreferredServer = false;
+ } else {
+ config.outgoing.existingServerKey = null;
+ config.outgoing.addThisServer = true;
+ config.outgoing.useGlobalPreferredServer = false;
+
+ try {
+ config.outgoing.hostname = sanitize.hostname(
+ outHostnameCombo.inputField.value);
+ outHostnameCombo.inputField.value = config.outgoing.hostname;
+ } catch (e) { gEmailWizardLogger.warn(e); }
+ try {
+ config.outgoing.port = sanitize.integerRange(e("outgoing_port").value,
+ kMinPort, kMaxPort);
+ } catch (e) {
+ config.outgoing.port = undefined; // incl. default "Auto"
+ }
+ config.outgoing.socketType = sanitize.integer(e("outgoing_ssl").value);
+ config.outgoing.auth = sanitize.integer(e("outgoing_authMethod").value);
+ }
+ config.outgoing.username = e("outgoing_username").value;
+
+ return config;
+ },
+
+ /**
+ * [Manual Config] button click handler. This turns the config details area
+ * into an editable form and makes the (Go) button appear. The edit button
+ * should only be available after the config probing is completely finished,
+ * replacing what was the (Stop) button.
+ */
+ onManualEdit : function()
+ {
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.editConfigDetails();
+ },
+
+ /**
+ * Setting the config details form so it can be edited. We also disable
+ * (and hide) the create button during this time because we don't know what
+ * might have changed. The function called from the button that restarts
+ * the config check should be enabling the config button as needed.
+ */
+ editConfigDetails : function()
+ {
+ gEmailWizardLogger.info("manual edit");
+
+ if (!this._currentConfig) {
+ this._currentConfig = new AccountConfig();
+ this._currentConfig.incoming.type = "imap";
+ this._currentConfig.incoming.username = "%EMAILLOCALPART%";
+ this._currentConfig.outgoing.username = "%EMAILLOCALPART%";
+ this._currentConfig.incoming.hostname = ".%EMAILDOMAIN%";
+ this._currentConfig.outgoing.hostname = ".%EMAILDOMAIN%";
+ }
+ // Although we go manual, and we need to display the concrete username,
+ // however the realname and password is not part of manual config and
+ // must stay a placeholder in _currentConfig. @see getUserConfig()
+
+ this._fillManualEditFields(this.getConcreteConfig());
+
+ // _fillManualEditFields() indirectly calls validateManualEditComplete(),
+ // but it's important to not forget it in case the code is rewritten,
+ // so calling it explicitly again. Doesn't do harm, speed is irrelevant.
+ this.validateManualEditComplete();
+ },
+
+ /**
+ * Fills the manual edit textfields with the provided config.
+ * @param config {AccountConfig} The config to present to user
+ */
+ _fillManualEditFields : function(config)
+ {
+ assert(config instanceof AccountConfig);
+
+ // incoming server
+ e("incoming_protocol").value = sanitize.translate(config.incoming.type,
+ { "imap" : 1, "pop3" : 2 }, 1);
+ e("incoming_hostname").value = config.incoming.hostname;
+ e("incoming_ssl").value = sanitize.enum(config.incoming.socketType,
+ [ 0, 1, 2, 3 ], 0);
+ e("incoming_authMethod").value = sanitize.enum(config.incoming.auth,
+ [ 0, 3, 4, 5, 6, 10 ], 0);
+ e("incoming_username").value = config.incoming.username;
+ if (config.incoming.port) {
+ e("incoming_port").value = config.incoming.port;
+ } else {
+ this.adjustIncomingPortToSSLAndProtocol(config);
+ }
+ this.fillPortDropdown(config.incoming.type);
+
+ // If the hostname supports OAuth2 and imap is enabled, enable OAuth2.
+ let iDetails = OAuth2Providers.getHostnameDetails(config.incoming.hostname);
+ gEmailWizardLogger.info("OAuth2 details for incoming hostname " +
+ config.incoming.hostname + " is " + iDetails);
+ e("in-authMethod-oauth2").hidden = !(iDetails && e("incoming_protocol").value == 1);
+ if (!e("in-authMethod-oauth2").hidden) {
+ config.oauthSettings = {};
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = iDetails;
+ // oauthsettings are not stored nor changable in the user interface, so just
+ // store them in the base configuration.
+ this._currentConfig.oauthSettings = config.oauthSettings;
+ }
+
+ // outgoing server
+ e("outgoing_hostname").value = config.outgoing.hostname;
+ e("outgoing_username").value = config.outgoing.username;
+ // While sameInOutUsernames is true we synchronize values of incoming
+ // and outgoing username.
+ this.sameInOutUsernames = true;
+ e("outgoing_ssl").value = sanitize.enum(config.outgoing.socketType,
+ [ 0, 1, 2, 3 ], 0);
+ e("outgoing_authMethod").value = sanitize.enum(config.outgoing.auth,
+ [ 0, 1, 3, 4, 5, 6, 10 ], 0);
+ if (config.outgoing.port) {
+ e("outgoing_port").value = config.outgoing.port;
+ } else {
+ this.adjustOutgoingPortToSSLAndProtocol(config);
+ }
+
+ // If the hostname supports OAuth2 and imap is enabled, enable OAuth2.
+ let oDetails = OAuth2Providers.getHostnameDetails(config.outgoing.hostname);
+ gEmailWizardLogger.info("OAuth2 details for outgoing hostname " +
+ config.outgoing.hostname + " is " + oDetails);
+ e("out-authMethod-oauth2").hidden = !oDetails;
+ if (!e("out-authMethod-oauth2").hidden) {
+ config.oauthSettings = {};
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = oDetails;
+ // oauthsettings are not stored nor changable in the user interface, so just
+ // store them in the base configuration.
+ this._currentConfig.oauthSettings = config.oauthSettings;
+ }
+
+ // populate fields even if existingServerKey, in case user changes back
+ if (config.outgoing.existingServerKey) {
+ let menulist = e("outgoing_hostname");
+ // We can't use menulist.value = config.outgoing.existingServerKey
+ // because would overwrite the text field, so have to do it manually:
+ let menuitems = menulist.menupopup.childNodes;
+ for (let menuitem of menuitems) {
+ if (menuitem.serverKey == config.outgoing.existingServerKey) {
+ menulist.selectedItem = menuitem;
+ break;
+ }
+ }
+ }
+ this.onChangedOutgoingDropdown(); // show/hide outgoing port, SSL, ...
+ },
+
+ /**
+ * Automatically fill port field in manual edit,
+ * unless user entered a non-standard port.
+ * @param config {AccountConfig}
+ */
+ adjustIncomingPortToSSLAndProtocol : function(config)
+ {
+ var autoPort = gStringsBundle.getString("port_auto");
+ var incoming = config.incoming;
+ // we could use getHostEntry() here, but that API is bad, so don't bother
+ var newInPort = undefined;
+ if (!incoming.port || isStandardPort(incoming.port)) {
+ if (incoming.type == "imap") {
+ if (incoming.socketType == 1 || incoming.socketType == 3) {
+ newInPort = 143;
+ } else if (incoming.socketType == 2) { // Normal SSL
+ newInPort = 993;
+ } else { // auto
+ newInPort = autoPort;
+ }
+ } else if (incoming.type == "pop3") {
+ if (incoming.socketType == 1 || incoming.socketType == 3) {
+ newInPort = 110;
+ } else if (incoming.socketType == 2) { // Normal SSLs
+ newInPort = 995;
+ } else { // auto
+ newInPort = autoPort;
+ }
+ }
+ }
+ if (newInPort != undefined) {
+ e("incoming_port").value = newInPort;
+ e("incoming_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * @see adjustIncomingPortToSSLAndProtocol()
+ */
+ adjustOutgoingPortToSSLAndProtocol : function(config)
+ {
+ var autoPort = gStringsBundle.getString("port_auto");
+ var outgoing = config.outgoing;
+ var newOutPort = undefined;
+ if (!outgoing.port || isStandardPort(outgoing.port)) {
+ if (outgoing.socketType == 1 || outgoing.socketType == 3) {
+ // standard port is 587 *or* 25, so set to auto
+ // unless user or config already entered one of these two ports.
+ if (outgoing.port != 25 && outgoing.port != 587) {
+ newOutPort = autoPort;
+ }
+ } else if (outgoing.socketType == 2) { // Normal SSL
+ newOutPort = 465;
+ } else { // auto
+ newOutPort = autoPort;
+ }
+ }
+ if (newOutPort != undefined) {
+ e("outgoing_port").value = newOutPort;
+ e("outgoing_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * If the user changed the port manually, adjust the SSL value,
+ * (only) if the new port is impossible with the old SSL value.
+ * @param config {AccountConfig}
+ */
+ adjustIncomingSSLToPort : function(config)
+ {
+ var incoming = config.incoming;
+ var newInSocketType = undefined;
+ if (!incoming.port || // auto
+ !isStandardPort(incoming.port)) {
+ return;
+ }
+ if (incoming.type == "imap") {
+ // normal SSL impossible
+ if (incoming.port == 143 && incoming.socketType == 2) {
+ newInSocketType = 0; // auto
+ // must be normal SSL
+ } else if (incoming.port == 993 && incoming.socketType != 2) {
+ newInSocketType = 2;
+ }
+ } else if (incoming.type == "pop3") {
+ // normal SSL impossible
+ if (incoming.port == 110 && incoming.socketType == 2) {
+ newInSocketType = 0; // auto
+ // must be normal SSL
+ } else if (incoming.port == 995 && incoming.socketType != 2) {
+ newInSocketType = 2;
+ }
+ }
+ if (newInSocketType != undefined) {
+ e("incoming_ssl").value = newInSocketType;
+ e("incoming_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * @see adjustIncomingSSLToPort()
+ */
+ adjustOutgoingSSLToPort : function(config)
+ {
+ var outgoing = config.outgoing;
+ var newOutSocketType = undefined;
+ if (!outgoing.port || // auto
+ !isStandardPort(outgoing.port)) {
+ return;
+ }
+ // normal SSL impossible
+ if ((outgoing.port == 587 || outgoing.port == 25) &&
+ outgoing.socketType == 2) {
+ newOutSocketType = 0; // auto
+ // must be normal SSL
+ } else if (outgoing.port == 465 && outgoing.socketType != 2) {
+ newOutSocketType = 2;
+ }
+ if (newOutSocketType != undefined) {
+ e("outgoing_ssl").value = newOutSocketType;
+ e("outgoing_authMethod").value = 0; // auto
+ }
+ },
+
+ /**
+ * Sets the prefilled values of the port fields.
+ * Filled statically with the standard ports for the given protocol,
+ * plus "Auto".
+ */
+ fillPortDropdown : function(protocolType)
+ {
+ var menu = e(protocolType == "smtp" ? "outgoing_port" : "incoming_port");
+
+ // menulist.removeAllItems() is nice, but nicely clears the user value, too
+ var popup = menu.menupopup;
+ while (popup.hasChildNodes())
+ popup.lastChild.remove();
+
+ // add standard ports
+ var autoPort = gStringsBundle.getString("port_auto");
+ menu.appendItem(autoPort, autoPort, ""); // label,value,descr
+ for (let port of getStandardPorts(protocolType)) {
+ menu.appendItem(port, port, ""); // label,value,descr
+ }
+ },
+
+ onChangedProtocolIncoming : function()
+ {
+ var config = this.getUserConfig();
+ this.adjustIncomingPortToSSLAndProtocol(config);
+ this.fillPortDropdown(config.incoming.type);
+ this.onChangedManualEdit();
+ },
+ onChangedPortIncoming : function()
+ {
+ gEmailWizardLogger.info("incoming port changed");
+ this.adjustIncomingSSLToPort(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedPortOutgoing : function()
+ {
+ gEmailWizardLogger.info("outgoing port changed");
+ this.adjustOutgoingSSLToPort(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedSSLIncoming : function()
+ {
+ this.adjustIncomingPortToSSLAndProtocol(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedSSLOutgoing : function()
+ {
+ this.adjustOutgoingPortToSSLAndProtocol(this.getUserConfig());
+ this.onChangedManualEdit();
+ },
+ onChangedInAuth : function()
+ {
+ this.onChangedManualEdit();
+ },
+ onChangedOutAuth : function(aSelectedAuth)
+ {
+ if (aSelectedAuth) {
+ e("outgoing_label").hidden = e("outgoing_username").hidden =
+ (aSelectedAuth.id == "out-authMethod-no");
+ }
+ this.onChangedManualEdit();
+ },
+ onInputInUsername : function()
+ {
+ if (this.sameInOutUsernames)
+ e("outgoing_username").value = e("incoming_username").value;
+ this.onChangedManualEdit();
+ },
+ onInputOutUsername : function()
+ {
+ this.sameInOutUsernames = false;
+ this.onChangedManualEdit();
+ },
+ onInputHostname : function()
+ {
+ this.onChangedManualEdit();
+ },
+
+ /**
+ * Sets the label of the first entry of the dropdown which represents
+ * the new outgoing server.
+ */
+ onOpenOutgoingDropdown : function()
+ {
+ var menulist = e("outgoing_hostname");
+ // If the menulist is not editable, there is nothing to update
+ // and menulist.inputField does not even exist.
+ if (!menulist.editable)
+ return;
+
+ var menuitem = menulist.getItemAtIndex(0);
+ assert(!menuitem.serverKey, "I wanted the special item for the new host");
+ menuitem.label = menulist.inputField.value;
+ },
+
+ /**
+ * User selected an existing SMTP server (or deselected it).
+ * This changes only the UI. The values are read in getUserConfig().
+ */
+ onChangedOutgoingDropdown : function()
+ {
+ var menulist = e("outgoing_hostname");
+ var menuitem = menulist.selectedItem;
+ if (menuitem && menuitem.serverKey) {
+ // an existing server has been selected from the dropdown
+ menulist.editable = false;
+ _hide("outgoing_port");
+ _hide("outgoing_ssl");
+ _hide("outgoing_authMethod");
+ } else {
+ // new server, with hostname, port etc.
+ menulist.editable = true;
+ _show("outgoing_port");
+ _show("outgoing_ssl");
+ _show("outgoing_authMethod");
+ }
+
+ this.onChangedManualEdit();
+ },
+
+ onChangedManualEdit : function()
+ {
+ if (this._abortable) {
+ this.onStop();
+ }
+ this.validateManualEditComplete();
+ },
+
+ /**
+ * This enables the buttons which allow the user to proceed
+ * once he has entered enough information.
+ *
+ * We can easily and fairly surely autodetect everything apart from the
+ * hostname (and username). So, once the user has entered
+ * proper hostnames, change to "manual-edit-have-hostname" mode
+ * which allows to press [Re-test], which starts the detection
+ * of the other values.
+ * Once the user has entered (or we detected) all values, he may
+ * do [Create Account] (tests login and if successful creates the account)
+ * or [Advanced Setup] (goes to Account Manager). Esp. in the latter case,
+ * we will not second-guess his setup and just to as told, so here we make
+ * sure that he at least entered all values.
+ */
+ validateManualEditComplete : function()
+ {
+ // getUserConfig() is expensive, but still OK, not a problem
+ var manualConfig = this.getUserConfig();
+ this._currentConfig = manualConfig;
+ if (manualConfig.isComplete()) {
+ this.switchToMode("manual-edit-complete");
+ } else if (!!manualConfig.incoming.hostname &&
+ !!manualConfig.outgoing.hostname) {
+ this.switchToMode("manual-edit-have-hostname");
+ } else {
+ this.switchToMode("manual-edit");
+ }
+ },
+
+ /**
+ * [Switch to provisioner] button click handler. Always active, allows
+ * one to switch to the account provisioner screen.
+ */
+ onSwitchToProvisioner : function ()
+ {
+ // We have to close this window first, otherwise msgNewMailAccount
+ // in accountUtils.js will think that this window still
+ // exists when it's called from the account provisioner window.
+ // This is because the account provisioner window is modal,
+ // and therefore blocks. Therefore, we override the _okCallback
+ // with a function that spawns the account provisioner, and then
+ // close the window.
+ this._okCallback = function() {
+ NewMailAccountProvisioner(window.arguments[0].msgWindow, window.arguments[0].extraData);
+ }
+ window.close();
+ },
+
+ /**
+ * [Advanced Setup...] button click handler
+ * Only active in manual edit mode, and goes straight into
+ * Account Settings (pref UI) dialog. Requires a backend account,
+ * which requires proper hostname, port and protocol.
+ */
+ onAdvancedSetup : function()
+ {
+ assert(this._currentConfig instanceof AccountConfig);
+ let configFilledIn = this.getConcreteConfig();
+
+ if (checkIncomingServerAlreadyExists(configFilledIn)) {
+ alertPrompt(gStringsBundle.getString("error_creating_account"),
+ gStringsBundle.getString("incoming_server_exists"));
+ return;
+ }
+
+ gEmailWizardLogger.info("creating account in backend");
+ let newAccount = createAccountInBackend(configFilledIn);
+
+ let existingAccountManager = Services.wm
+ .getMostRecentWindow("mailnews:accountmanager");
+ if (existingAccountManager) {
+ existingAccountManager.focus();
+ } else {
+ window.openDialog("chrome://messenger/content/AccountManager.xul",
+ "AccountManager", "chrome,centerscreen,modal,titlebar",
+ { server: newAccount.incomingServer,
+ selectPage: "am-server.xul" });
+ }
+ window.close();
+ },
+
+ /**
+ * [Re-test] button click handler.
+ * Restarts the config guessing process after a person editing the server
+ * fields.
+ * It's called "half-manual", because we take the user-entered values
+ * as given and will not second-guess them, to respect the user wishes.
+ * (Yes, Sir! Will do as told!)
+ * The values that the user left empty or on "Auto" will be guessed/probed
+ * here. We will also check that the user-provided values work.
+ */
+ onHalfManualTest : function()
+ {
+ var newConfig = this.getUserConfig();
+ gEmailWizardLogger.info(debugObject(newConfig, "manualConfigToTest"));
+ this.startSpinner("looking_up_settings_halfmanual");
+ this.switchToMode("manual-edit-testing");
+ // if (this._userPickedOutgoingServer) TODO
+ var self = this;
+ this._abortable = guessConfig(this._domain,
+ function(type, hostname, port, ssl, done, config) // progress
+ {
+ gEmailWizardLogger.info("progress callback host " + hostname +
+ " port " + port + " type " + type);
+ },
+ function(config) // success
+ {
+ self._abortable = null;
+ self._fillManualEditFields(config);
+ self.switchToMode("manual-edit-complete");
+ self.stopSpinner("found_settings_halfmanual");
+ },
+ function(e, config) // guessconfig failed
+ {
+ if (e instanceof CancelledException) {
+ return;
+ }
+ self._abortable = null;
+ gEmailWizardLogger.info("guessConfig failed: " + e);
+ self.showErrorStatus("failed_to_find_settings");
+ self.switchToMode("manual-edit-have-hostname");
+ },
+ newConfig,
+ newConfig.outgoing.existingServerKey ? "incoming" : "both");
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // UI helper functions
+
+ _prefillConfig : function(initialConfig)
+ {
+ var emailsplit = this._email.split("@");
+ assert(emailsplit.length > 1);
+ var emaillocal = sanitize.nonemptystring(emailsplit[0]);
+ initialConfig.incoming.username = emaillocal;
+ initialConfig.outgoing.username = emaillocal;
+ return initialConfig;
+ },
+
+ clearError : function(which)
+ {
+ _hide(which);
+ _hide(which + "icon");
+ e(which).textContent = "";
+ },
+
+ setError : function(which, msg_name)
+ {
+ try {
+ _show(which);
+ _show(which + "icon");
+ e(which).textContent = gStringsBundle.getString(msg_name);
+ window.sizeToContent();
+ } catch (ex) { alertPrompt("missing error string", msg_name); }
+ },
+
+
+
+ /////////////////////////////////////////////////////////////////
+ // Finish & dialog close functions
+
+ onKeyDown : function(event)
+ {
+ let key = event.keyCode;
+ if (key == 27) { // Escape key
+ this.onCancel();
+ return true;
+ }
+ if (key == 13) { // OK key
+ let buttons = [
+ { id: "next_button", action: makeCallback(this, this.onNext) },
+ { id: "create_button", action: makeCallback(this, this.onCreate) },
+ { id: "half-manual-test_button",
+ action: makeCallback(this, this.onHalfManualTest) },
+ ];
+ for (let button of buttons) {
+ button.e = e(button.id);
+ if (button.e.hidden || button.e.disabled) {
+ continue;
+ }
+ button.action();
+ return true;
+ }
+ }
+ return false;
+ },
+
+ onCancel : function()
+ {
+ window.close();
+ // The window onclose handler will call onWizardShutdown for us.
+ },
+
+ onWizardShutdown : function()
+ {
+ if (this._abortable) {
+ this._abortable.cancel(new UserCancelledException());
+ }
+
+ if (this._okCallback) {
+ this._okCallback();
+ }
+ gEmailWizardLogger.info("Shutting down email config dialog");
+ },
+
+
+ onCreate : function()
+ {
+ try {
+ gEmailWizardLogger.info("Create button clicked");
+
+ var configFilledIn = this.getConcreteConfig();
+ var self = this;
+ // If the dialog is not needed, it will go straight to OK callback
+ gSecurityWarningDialog.open(this._currentConfig, configFilledIn, true,
+ function() // on OK
+ {
+ self.validateAndFinish(configFilledIn);
+ },
+ function() {}); // on cancel, do nothing
+ } catch (ex) {
+ gEmailWizardLogger.error("Error creating account. ex=" + ex +
+ ", stack=" + ex.stack);
+ alertPrompt(gStringsBundle.getString("error_creating_account"), ex);
+ }
+ },
+
+ // called by onCreate()
+ validateAndFinish : function()
+ {
+ var configFilledIn = this.getConcreteConfig();
+
+ if (checkIncomingServerAlreadyExists(configFilledIn)) {
+ alertPrompt(gStringsBundle.getString("error_creating_account"),
+ gStringsBundle.getString("incoming_server_exists"));
+ return;
+ }
+
+ if (configFilledIn.outgoing.addThisServer) {
+ let existingServer = checkOutgoingServerAlreadyExists(configFilledIn);
+ if (existingServer) {
+ configFilledIn.outgoing.addThisServer = false;
+ configFilledIn.outgoing.existingServerKey = existingServer.key;
+ }
+ }
+
+ // TODO use a UI mode (switchToMode()) for verfication, too.
+ // But we need to go back to the previous mode, because we might be in
+ // "result" or "manual-edit-complete" mode.
+ _disable("create_button");
+ _disable("half-manual-test_button");
+ _disable("advanced-setup_button");
+ // no stop button: backend has no ability to stop :-(
+ var self = this;
+ this.startSpinner("checking_password");
+ // logic function defined in verifyConfig.js
+ verifyConfig(
+ configFilledIn,
+ // guess login config?
+ configFilledIn.source != AccountConfig.kSourceXML,
+ // TODO Instead, the following line would be correct, but I cannot use it,
+ // because some other code doesn't adhere to the expectations/specs.
+ // Find out what it was and fix it.
+ //concreteConfig.source == AccountConfig.kSourceGuess,
+ this._parentMsgWindow,
+ function(successfulConfig) // success
+ {
+ self.stopSpinner(successfulConfig.incoming.password ?
+ "password_ok" : null);
+
+ // the auth might have changed, so we
+ // should back-port it to the current config.
+ self._currentConfig.incoming.auth = successfulConfig.incoming.auth;
+ self._currentConfig.outgoing.auth = successfulConfig.outgoing.auth;
+ self._currentConfig.incoming.username = successfulConfig.incoming.username;
+ self._currentConfig.outgoing.username = successfulConfig.outgoing.username;
+
+ // We loaded dynamic client registration, fill this data back in to the
+ // config set.
+ if (successfulConfig.oauthSettings)
+ self._currentConfig.oauthSettings = successfulConfig.oauthSettings;
+
+ self.finish();
+ },
+ function(e) // failed
+ {
+ self.showErrorStatus("config_unverifiable");
+ // TODO bug 555448: wrong error msg, there may be a 1000 other
+ // reasons why this failed, and this is misleading users.
+ self.setError("passworderror", "user_pass_invalid");
+ // TODO use switchToMode(), see above
+ // give user something to proceed after fixing
+ _enable("create_button");
+ // hidden in non-manual mode, so it's fine to enable
+ _enable("half-manual-test_button");
+ _enable("advanced-setup_button");
+ });
+ },
+
+ finish : function()
+ {
+ gEmailWizardLogger.info("creating account in backend");
+ createAccountInBackend(this.getConcreteConfig());
+ window.close();
+ },
+};
+
+var gEmailConfigWizard = new EmailConfigWizard();
+
+function serverMatches(a, b)
+{
+ return a.type == b.type &&
+ a.hostname == b.hostname &&
+ a.port == b.port &&
+ a.socketType == b.socketType &&
+ a.auth == b.auth;
+}
+
+var _gStandardPorts = {};
+_gStandardPorts["imap"] = [ 143, 993 ];
+_gStandardPorts["pop3"] = [ 110, 995 ];
+_gStandardPorts["smtp"] = [ 587, 25, 465 ]; // order matters
+var _gAllStandardPorts = _gStandardPorts["smtp"]
+ .concat(_gStandardPorts["imap"]).concat(_gStandardPorts["pop3"]);
+
+function isStandardPort(port)
+{
+ return _gAllStandardPorts.indexOf(port) != -1;
+}
+
+function getStandardPorts(protocolType)
+{
+ return _gStandardPorts[protocolType];
+}
+
+
+/**
+ * Warning dialog, warning user about lack of, or inappropriate, encryption.
+ *
+ * This is effectively a separate dialog, but implemented as part of
+ * this dialog. It works by hiding the main dialog part and unhiding
+ * the this part, and vice versa, and resizing the dialog.
+ */
+function SecurityWarningDialog()
+{
+ this._acknowledged = new Array();
+}
+SecurityWarningDialog.prototype =
+{
+ /**
+ * {Array of {(incoming or outgoing) server part of {AccountConfig}}
+ * A list of the servers for which we already showed this dialog and the
+ * user approved the configs. For those, we won't show the warning again.
+ * (Make sure to store a copy in case the underlying object is changed.)
+ */
+ _acknowledged : null,
+
+ _inSecurityBad: 0x0001,
+ _inCertBad: 0x0010,
+ _outSecurityBad: 0x0100,
+ _outCertBad: 0x1000,
+
+ /**
+ * Checks whether we need to warn about this config.
+ *
+ * We (currently) warn if
+ * - the mail travels unsecured (no SSL/STARTTLS)
+ * - the SSL certificate is not proper
+ * - (We don't warn about unencrypted passwords specifically,
+ * because they'd be encrypted with SSL and without SSL, we'd
+ * warn anyways.)
+ *
+ * We may not warn despite these conditions if we had shown the
+ * warning for that server before and the user acknowledged it.
+ * (Given that this dialog object is static/global and persistent,
+ * we can store that approval state here in this object.)
+ *
+ * @param configSchema @see open()
+ * @param configFilledIn @see open()
+ * @returns {Boolean} true when the dialog should be shown
+ * (call open()). if false, the dialog can and should be skipped.
+ */
+ needed : function(configSchema, configFilledIn)
+ {
+ assert(configSchema instanceof AccountConfig);
+ assert(configFilledIn instanceof AccountConfig);
+ assert(configSchema.isComplete());
+ assert(configFilledIn.isComplete());
+
+ var incomingBad = ((configFilledIn.incoming.socketType > 1) ? 0 : this._inSecurityBad) |
+ ((configFilledIn.incoming.badCert) ? this._inCertBad : 0);
+ var outgoingBad = 0;
+ if (!configFilledIn.outgoing.existingServerKey) {
+ outgoingBad = ((configFilledIn.outgoing.socketType > 1) ? 0 : this._outSecurityBad) |
+ ((configFilledIn.outgoing.badCert) ? this._outCertBad : 0);
+ }
+
+ if (incomingBad > 0) {
+ if (this._acknowledged.some(
+ function(ackServer) {
+ return serverMatches(ackServer, configFilledIn.incoming);
+ }))
+ incomingBad = 0;
+ }
+ if (outgoingBad > 0) {
+ if (this._acknowledged.some(
+ function(ackServer) {
+ return serverMatches(ackServer, configFilledIn.outgoing);
+ }))
+ outgoingBad = 0;
+ }
+
+ return incomingBad | outgoingBad;
+ },
+
+ /**
+ * Opens the dialog, fills it with values, and shows it to the user.
+ *
+ * The function is async: it returns immediately, and when the user clicks
+ * OK or Cancel, the callbacks are called. There the callers proceed as
+ * appropriate.
+ *
+ * @param configSchema The config, with placeholders not replaced yet.
+ * This object may be modified to store the user's confirmations, but
+ * currently that's not the case.
+ * @param configFilledIn The concrete config with placeholders replaced.
+ * @param onlyIfNeeded {Boolean} If there is nothing to warn about,
+ * call okCallback() immediately (and sync).
+ * @param okCallback {function(config {AccountConfig})}
+ * Called when the user clicked OK and approved the config including
+ * the warnings. |config| is without placeholders replaced.
+ * @param cancalCallback {function()}
+ * Called when the user decided to heed the warnings and not approve.
+ */
+ open : function(configSchema, configFilledIn, onlyIfNeeded,
+ okCallback, cancelCallback)
+ {
+ assert(typeof(okCallback) == "function");
+ assert(typeof(cancelCallback) == "function");
+ // needed() also checks the parameters
+ var needed = this.needed(configSchema, configFilledIn);
+ if ((needed == 0) && onlyIfNeeded) {
+ okCallback();
+ return;
+ }
+
+ assert(needed > 0 , "security dialog opened needlessly");
+ this._currentConfigFilledIn = configFilledIn;
+ this._okCallback = okCallback;
+ this._cancelCallback = cancelCallback;
+ var incoming = configFilledIn.incoming;
+ var outgoing = configFilledIn.outgoing;
+
+ _hide("mastervbox");
+ _show("warningbox");
+ // reset dialog, in case we've shown it before
+ e("acknowledge_warning").checked = false;
+ _disable("iknow");
+ e("incoming_technical").removeAttribute("expanded");
+ e("incoming_details").setAttribute("collapsed", true);
+ e("outgoing_technical").removeAttribute("expanded");
+ e("outgoing_details").setAttribute("collapsed", true);
+
+ if (needed & this._inSecurityBad) {
+ setText("warning_incoming", gStringsBundle.getFormattedString(
+ "cleartext_warning", [incoming.hostname]));
+ setText("incoming_details", gStringsBundle.getString(
+ "cleartext_details"));
+ _show("incoming_box");
+ } else if (needed & this._inCertBad) {
+ setText("warning_incoming", gStringsBundle.getFormattedString(
+ "selfsigned_warning", [incoming.hostname]));
+ setText("incoming_details", gStringsBundle.getString(
+ "selfsigned_details"));
+ _show("incoming_box");
+ } else {
+ _hide("incoming_box");
+ }
+
+ if (needed & this._outSecurityBad) {
+ setText("warning_outgoing", gStringsBundle.getFormattedString(
+ "cleartext_warning", [outgoing.hostname]));
+ setText("outgoing_details", gStringsBundle.getString(
+ "cleartext_details"));
+ _show("outgoing_box");
+ } else if (needed & this._outCertBad) {
+ setText("warning_outgoing", gStringsBundle.getFormattedString(
+ "selfsigned_warning", [outgoing.hostname]));
+ setText("outgoing_details", gStringsBundle.getString(
+ "selfsigned_details"));
+ _show("outgoing_box");
+ } else {
+ _hide("outgoing_box");
+ }
+ _show("acknowledge_warning");
+ assert(!e("incoming_box").hidden || !e("outgoing_box").hidden,
+ "warning dialog shown for unknown reason");
+
+ window.sizeToContent();
+ },
+
+ toggleDetails : function (id)
+ {
+ let details = e(id + "_details");
+ let tech = e(id + "_technical");
+ if (details.getAttribute("collapsed")) {
+ details.removeAttribute("collapsed");
+ tech.setAttribute("expanded", true);
+ } else {
+ details.setAttribute("collapsed", true);
+ tech.removeAttribute("expanded");
+ }
+ },
+
+ /**
+ * user checked checkbox that he understood it and wishes
+ * to ignore the warning.
+ */
+ toggleAcknowledge : function()
+ {
+ if (e("acknowledge_warning").checked) {
+ _enable("iknow");
+ } else {
+ _disable("iknow");
+ }
+ },
+
+ /**
+ * [Cancel] button pressed. Get me out of here!
+ */
+ onCancel : function()
+ {
+ _hide("warningbox");
+ _show("mastervbox");
+ window.sizeToContent();
+
+ this._cancelCallback();
+ },
+
+ /**
+ * [OK] button pressed.
+ * Implies that the user toggled the acknowledge checkbox,
+ * i.e. approved the config and ignored the warnings,
+ * otherwise the button would have been disabled.
+ */
+ onOK : function()
+ {
+ assert(e("acknowledge_warning").checked);
+
+ var overrideOK = this.showCertOverrideDialog(this._currentConfigFilledIn);
+ if (!overrideOK) {
+ this.onCancel();
+ return;
+ }
+
+ // need filled in, in case hostname is placeholder
+ var storeConfig = this._currentConfigFilledIn.copy();
+ this._acknowledged.push(storeConfig.incoming);
+ this._acknowledged.push(storeConfig.outgoing);
+
+ _show("mastervbox");
+ _hide("warningbox");
+ window.sizeToContent();
+
+ this._okCallback();
+ },
+
+ /**
+ * Shows a(nother) dialog which allows the user to see and override
+ * (manually accept) a bad certificate. It also optionally adds it
+ * permanently to the "good certs" store of NSS in the profile.
+ * Only shows the dialog, if there are bad certs. Otherwise, it's a no-op.
+ *
+ * The dialog is the standard PSM cert override dialog.
+ *
+ * @param config {AccountConfig} concrete
+ * @returns true, if all certs are fine or the user accepted them.
+ * false, if the user cancelled.
+ *
+ * static function
+ * sync function: blocks until the dialog is closed.
+ */
+ showCertOverrideDialog : function(config)
+ {
+ if (config.incoming.socketType > 1 && // SSL or STARTTLS
+ config.incoming.badCert) {
+ var params = {
+ exceptionAdded : false,
+ prefetchCert : true,
+ location : config.incoming.targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ if (params.exceptionAdded) { // set by dialog
+ config.incoming.badCert = false;
+ } else {
+ return false;
+ }
+ }
+ if (!config.outgoing.existingServerKey) {
+ if (config.outgoing.socketType > 1 && // SSL or STARTTLS
+ config.outgoing.badCert) {
+ var params = {
+ exceptionAdded : false,
+ prefetchCert : true,
+ location : config.outgoing.targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ if (params.exceptionAdded) { // set by dialog
+ config.outgoing.badCert = false;
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+ },
+}
+var gSecurityWarningDialog = new SecurityWarningDialog();
diff --git a/mailnews/base/prefs/content/accountcreation/emailWizard.xul b/mailnews/base/prefs/content/accountcreation/emailWizard.xul
new file mode 100644
index 000000000..0777d1651
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/emailWizard.xul
@@ -0,0 +1,493 @@
+<?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/" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountCreation.css"
+ type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % acDTD SYSTEM "chrome://messenger/locale/accountCreation.dtd">
+ %acDTD;
+]>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="autoconfigWizard"
+ windowtype="mail:autoconfig"
+ title="&autoconfigWizard.title;"
+ onload="gEmailConfigWizard.onLoad();"
+ onkeypress="gEmailConfigWizard.onKeyDown(event);"
+ onclose="gEmailConfigWizard.onWizardShutdown();"
+ onunload="gEmailConfigWizard.onWizardShutdown();"
+ >
+
+ <stringbundleset>
+ <stringbundle id="bundle_brand"
+ src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="strings"
+ src="chrome://messenger/locale/accountCreation.properties"/>
+ <stringbundle id="utilstrings"
+ src="chrome://messenger/locale/accountCreationUtil.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+ </stringbundleset>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/util.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/accountConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/emailWizard.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/sanitizeDatatypes.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/fetchhttp.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/readFromXML.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/guessConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/verifyConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/fetchConfig.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/createInBackend.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountcreation/MyBadCertHandler.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/accountUtils.js" />
+
+ <keyset id="mailKeys">
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+ <panel id="insecureserver-cleartext-panel" class="popup-panel">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox flex="1">
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureUnencrypted.description;</description>
+ </vbox>
+ </hbox>
+ </panel>
+ <panel id="insecureserver-selfsigned-panel" class="popup-panel">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox flex="1">
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureSelfSigned.description;</description>
+ </vbox>
+ </hbox>
+ </panel>
+ <panel id="secureserver-panel" class="popup-panel">
+ <hbox>
+ <image class="secureLarry"/>
+ <vbox flex="1">
+ <description class="title">&secureServer.description;</description>
+ </vbox>
+ </hbox>
+ </panel>
+
+ <tooltip id="insecureserver-cleartext">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox>
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureServer.tooltip.details;</description>
+ </vbox>
+ </hbox>
+ </tooltip>
+ <tooltip id="insecureserver-selfsigned">
+ <hbox>
+ <image class="insecureLarry"/>
+ <vbox>
+ <description class="title">&insecureServer.tooltip.title;</description>
+ <description class="details">
+ &insecureServer.tooltip.details;</description>
+ </vbox>
+ </hbox>
+ </tooltip>
+ <tooltip id="secureservertooltip">
+ <hbox>
+ <image class="secureLarry"/>
+ <description class="title">&secureServer.description;</description>
+ </hbox>
+ </tooltip>
+ <tooltip id="optional-password">
+ <description>&password.text;</description>
+ </tooltip>
+
+ <spacer id="fullwidth"/>
+
+ <vbox id="mastervbox" class="mastervbox" flex="1">
+ <grid id="initialSettings">
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label accesskey="&name.accesskey;"
+ class="autoconfigLabel"
+ value="&name.label;"
+ control="realname"/>
+ <textbox id="realname"
+ class="padded"
+ placeholder="&name.placeholder;"
+ oninput="gEmailConfigWizard.onInputRealname();"
+ onblur="gEmailConfigWizard.onBlurRealname();"/>
+ <hbox>
+ <description id="nametext" class="initialDesc">&name.text;</description>
+ <image id="nameerroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="nameerror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label accesskey="&email.accesskey;"
+ class="autoconfigLabel"
+ value="&email.label;"
+ control="email"/>
+ <textbox id="email"
+ class="padded uri-element"
+ placeholder="&email.placeholder;"
+ oninput="gEmailConfigWizard.onInputEmail();"
+ onblur="gEmailConfigWizard.onBlurEmail();"/>
+ <hbox>
+ <image id="emailerroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="emailerror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <!-- this starts out as text so the emptytext shows, but then
+ changes to type=password once it's not empty -->
+ <label accesskey="&password.accesskey;"
+ class="autoconfigLabel"
+ value="&password.label;"
+ control="password"
+ tooltip="optional-password"/>
+ <textbox id="password"
+ class="padded"
+ placeholder="&password.placeholder;"
+ type="text"
+ oninput="gEmailConfigWizard.onInputPassword();"
+ onfocus="gEmailConfigWizard.onFocusPassword();"
+ onblur="gEmailConfigWizard.onBlurPassword();"/>
+ <hbox>
+ <image id="passworderroricon"
+ hidden="true"
+ class="warningicon"/>
+ <description id="passworderror" class="errordescription" hidden="true"/>
+ </hbox>
+ </row>
+ <row align="center" pack="start">
+ <label class="autoconfigLabel"/>
+ <checkbox id="remember_password"
+ label="&rememberPassword.label;"
+ accesskey="&rememberPassword.accesskey;"
+ checked="true"/>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1" />
+
+ <hbox id="status_area" flex="1">
+ <vbox id="status_img_before" pack="start"/>
+ <description id="status_msg">&#160;</description>
+ <!-- Include 160 = nbsp, to make the element occupy the
+ full height, for at least one line. With a normal space,
+ it does not have sufficient height. -->
+ <vbox id="status_img_after" pack="start"/>
+ </hbox>
+
+ <groupbox id="result_area" hidden="true">
+ <radiogroup id="result_imappop" orient="horizontal">
+ <radio id="result_select_imap" label="&imapLong.label;" value="1"
+ oncommand="gEmailConfigWizard.onResultIMAPOrPOP3();"/>
+ <radio id="result_select_pop3" label="&pop3Long.label;" value="2"
+ oncommand="gEmailConfigWizard.onResultIMAPOrPOP3();"/>
+ </radiogroup>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label class="textbox-label" value="&incoming.label;"
+ control="result-incoming"/>
+ <textbox id="result-incoming" disabled="true" flex="1"/>
+ </row>
+ <row align="center">
+ <label class="textbox-label" value="&outgoing.label;"
+ control="result-outgoing"/>
+ <textbox id="result-outgoing" disabled="true" flex="1"/>
+ </row>
+ <row align="center">
+ <label class="textbox-label" value="&username.label;"
+ control="result-username"/>
+ <textbox id="result-username" disabled="true" flex="1"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox id="manual-edit_area" hidden="true">
+ <grid>
+ <columns>
+ <column/><!-- row label, e.g. "incoming" -->
+ <column/><!-- protocol, e.g. "IMAP" -->
+ <column flex="1"/><!-- hostname / username -->
+ <column/><!-- port -->
+ <column/><!-- SSL -->
+ <column/><!-- auth method -->
+ </columns>
+ <rows>
+ <row id="labels_row" align="center">
+ <spacer/>
+ <spacer/>
+ <label value="&hostname.label;" class="columnHeader"/>
+ <label value="&port.label;" class="columnHeader"/>
+ <label value="&ssl.label;" class="columnHeader"/>
+ <label value="&auth.label;" class="columnHeader"/>
+ </row>
+ <row id="incoming_server_area">
+ <hbox align="center" pack="end">
+ <label class="textbox-label"
+ value="&incoming.label;"
+ control="incoming_hostname"/>
+ </hbox>
+ <menulist id="incoming_protocol"
+ oncommand="gEmailConfigWizard.onChangedProtocolIncoming();"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&imap.label;" value="1"/>
+ <menuitem label="&pop3.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <textbox id="incoming_hostname"
+ oninput="gEmailConfigWizard.onInputHostname();"
+ class="host uri-element"/>
+ <menulist id="incoming_port"
+ editable="true"
+ oninput="gEmailConfigWizard.onChangedPortIncoming();"
+ oncommand="gEmailConfigWizard.onChangedPortIncoming();"
+ class="port">
+ <menupopup/>
+ </menulist>
+ <menulist id="incoming_ssl"
+ class="security"
+ oncommand="gEmailConfigWizard.onChangedSSLIncoming();"
+ sizetopopup="always">
+ <menupopup>
+ <!-- values defined in nsMsgSocketType -->
+ <menuitem label="&autodetect.label;" value="0"/>
+ <menuitem label="&noEncryption.label;" value="1"/>
+ <menuitem label="&starttls.label;" value="3"/>
+ <menuitem label="&sslTls.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <menulist id="incoming_authMethod"
+ class="auth"
+ oncommand="gEmailConfigWizard.onChangedInAuth();"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&autodetect.label;" value="0"/>
+ <!-- values defined in nsMsgAuthMethod -->
+ <!-- labels set from messenger.properties
+ to avoid duplication -->
+ <menuitem id="in-authMethod-password-cleartext" value="3"/>
+ <menuitem id="in-authMethod-password-encrypted" value="4"/>
+ <menuitem id="in-authMethod-kerberos" value="5"/>
+ <menuitem id="in-authMethod-ntlm" value="6"/>
+ <menuitem id="in-authMethod-oauth2" value="10" hidden="true"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="outgoing_server_area" align="center">
+ <label class="textbox-label"
+ value="&outgoing.label;"
+ control="outgoing_hostname"/>
+ <label id="outgoing_protocol"
+ value="&smtp.label;"/>
+ <menulist id="outgoing_hostname"
+ editable="true"
+ sizetopopup="none"
+ oninput="gEmailConfigWizard.onInputHostname();"
+ oncommand="gEmailConfigWizard.onChangedOutgoingDropdown();"
+ onpopupshowing="gEmailConfigWizard.onOpenOutgoingDropdown();"
+ class="host uri-element">
+ <menupopup id="outgoing_hostname_popup"/>
+ </menulist>
+ <menulist id="outgoing_port"
+ editable="true"
+ oninput="gEmailConfigWizard.onChangedPortOutgoing();"
+ oncommand="gEmailConfigWizard.onChangedPortOutgoing();"
+ class="port">
+ <menupopup/>
+ </menulist>
+ <menulist id="outgoing_ssl"
+ class="security"
+ oncommand="gEmailConfigWizard.onChangedSSLOutgoing();"
+ sizetopopup="always">
+ <menupopup>
+ <!-- @see incoming -->
+ <menuitem label="&autodetect.label;" value="0"/>
+ <menuitem label="&noEncryption.label;" value="1"/>
+ <menuitem label="&starttls.label;" value="3"/>
+ <menuitem label="&sslTls.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ <menulist id="outgoing_authMethod"
+ class="auth"
+ oncommand="gEmailConfigWizard.onChangedOutAuth(this.selectedItem);"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&autodetect.label;" value="0"/>
+ <!-- @see incoming -->
+ <menuitem id="out-authMethod-no" value="1"/>
+ <menuitem id="out-authMethod-password-cleartext" value="3"/>
+ <menuitem id="out-authMethod-password-encrypted" value="4"/>
+ <menuitem id="out-authMethod-kerberos" value="5"/>
+ <menuitem id="out-authMethod-ntlm" value="6"/>
+ <menuitem id="out-authMethod-oauth2" value="10" hidden="true"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="username_area" align="center">
+ <label class="textbox-label"
+ value="&username.label;"/>
+ <label class="columnHeader"
+ value="&incoming.label;"
+ control="incoming_username"/>
+ <textbox id="incoming_username"
+ oninput="gEmailConfigWizard.onInputInUsername();"
+ class="username"/>
+ <spacer/>
+ <label class="columnHeader"
+ id="outgoing_label"
+ value="&outgoing.label;"
+ control="outgoing_username"/>
+ <textbox id="outgoing_username"
+ oninput="gEmailConfigWizard.onInputOutUsername();"
+ class="username"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <spacer flex="1" />
+ <hbox id="buttons_area">
+ <hbox id="left_buttons_area" align="center" pack="start">
+ <button id="provisioner_button"
+ label="&switch-to-provisioner.label;"
+ accesskey="&switch-to-provisioner.accesskey;"
+ class="larger-button"
+ oncommand="gEmailConfigWizard.onSwitchToProvisioner();"/>
+ <button id="manual-edit_button"
+ label="&manual-edit.label;"
+ accesskey="&manual-edit.accesskey;"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onManualEdit();"/>
+ <button id="advanced-setup_button"
+ label="&advancedSetup.label;"
+ accesskey="&advancedSetup.accesskey;"
+ disabled="true"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onAdvancedSetup();"/>
+ </hbox>
+ <spacer flex="1"/>
+ <hbox id="right_buttons_area" align="center" pack="end">
+ <button id="stop_button"
+ label="&stop.label;"
+ accesskey="&stop.accesskey;"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onStop();"/>
+ <button id="cancel_button"
+ label="&cancel.label;"
+ accesskey="&cancel.accesskey;"
+ oncommand="gEmailConfigWizard.onCancel();"/>
+ <button id="half-manual-test_button"
+ label="&half-manual-test.label;"
+ accesskey="&half-manual-test.accesskey;"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onHalfManualTest();"/>
+ <button id="next_button"
+ label="&continue.label;"
+ accesskey="&continue.accesskey;"
+ hidden="false"
+ oncommand="gEmailConfigWizard.onNext();"/>
+ <button id="create_button"
+ label="&doneAccount.label;"
+ accesskey="&doneAccount.accesskey;"
+ class="important-button"
+ hidden="true"
+ oncommand="gEmailConfigWizard.onCreate();"/>
+ </hbox>
+ </hbox>
+ </vbox>
+
+
+ <vbox id="warningbox" hidden="true" flex="1">
+ <hbox class="warning" flex="1">
+ <vbox class="larrybox">
+ <image id="insecure_larry" class="insecureLarry"/>
+ </vbox>
+ <vbox flex="1" class="warning_text">
+ <label class="warning-heading">&warning.label;</label>
+ <vbox id="incoming_box">
+ <hbox>
+ <label class="warning_settings" value="&incomingSettings.label;"/>
+ <description id="warning_incoming"/>
+ </hbox>
+ <label id="incoming_technical"
+ class="technical_details"
+ value="&technicaldetails.label;"
+ onclick="gSecurityWarningDialog.toggleDetails('incoming');"/>
+ <description id="incoming_details" collapsed="true"/>
+ </vbox>
+ <vbox id="outgoing_box">
+ <hbox>
+ <label class="warning_settings" value="&outgoingSettings.label;"/>
+ <description id="warning_outgoing"/>
+ </hbox>
+ <label id="outgoing_technical"
+ class="technical_details"
+ value="&technicaldetails.label;"
+ onclick="gSecurityWarningDialog.toggleDetails('outgoing');"/>
+ <description id="outgoing_details" collapsed="true"/>
+ </vbox>
+ <spacer flex="10"/>
+ <description id="findoutmore">
+ &contactYourProvider.description;</description>
+ <spacer flex="100"/>
+ <checkbox id="acknowledge_warning"
+ label="&confirmWarning.label;"
+ accesskey="&confirmWarning.accesskey;"
+ class="acknowledge_checkbox"
+ oncommand="gSecurityWarningDialog.toggleAcknowledge()"/>
+ <hbox>
+ <button id="getmeoutofhere"
+ label="&changeSettings.label;"
+ accesskey="&changeSettings.accesskey;"
+ oncommand="gSecurityWarningDialog.onCancel()"/>
+ <spacer flex="1"/>
+ <button id="iknow"
+ label="&doneAccount.label;"
+ accesskey="&doneAccount.accesskey;"
+ disabled="true"
+ oncommand="gSecurityWarningDialog.onOK()"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ </vbox>
+</window>
diff --git a/mailnews/base/prefs/content/accountcreation/fetchConfig.js b/mailnews/base/prefs/content/accountcreation/fetchConfig.js
new file mode 100644
index 000000000..07a9f5586
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/fetchConfig.js
@@ -0,0 +1,240 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * Tries to find a configuration for this ISP on the local harddisk, in the
+ * application install directory's "isp" subdirectory.
+ * Params @see fetchConfigFromISP()
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/JXON.js");
+
+
+function fetchConfigFromDisk(domain, successCallback, errorCallback)
+{
+ return new TimeoutAbortable(runAsync(function()
+ {
+ try {
+ // <TB installdir>/isp/example.com.xml
+ var configLocation = Services.dirsvc.get("CurProcD", Ci.nsIFile);
+ configLocation.append("isp");
+ configLocation.append(sanitize.hostname(domain) + ".xml");
+
+ var contents =
+ readURLasUTF8(Services.io.newFileURI(configLocation));
+ let domParser = Cc["@mozilla.org/xmlextras/domparser;1"]
+ .createInstance(Ci.nsIDOMParser);
+ successCallback(readFromXML(JXON.build(
+ domParser.parseFromString(contents, "text/xml"))));
+ } catch (e) { errorCallback(e); }
+ }));
+}
+
+/**
+ * Tries to get a configuration from the ISP / mail provider directly.
+ *
+ * Disclaimers:
+ * - To support domain hosters, we cannot use SSL. That means we
+ * rely on insecure DNS and http, which means the results may be
+ * forged when under attack. The same is true for guessConfig(), though.
+ *
+ * @param domain {String} The domain part of the user's email address
+ * @param emailAddress {String} The user's email address
+ * @param successCallback {Function(config {AccountConfig}})} A callback that
+ * will be called when we could retrieve a configuration.
+ * The AccountConfig object will be passed in as first parameter.
+ * @param errorCallback {Function(ex)} A callback that
+ * will be called when we could not retrieve a configuration,
+ * for whatever reason. This is expected (e.g. when there's no config
+ * for this domain at this location),
+ * so do not unconditionally show this to the user.
+ * The first paramter will be an exception object or error string.
+ */
+function fetchConfigFromISP(domain, emailAddress, successCallback,
+ errorCallback)
+{
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.fetchFromISP.enabled")) {
+ errorCallback("ISP fetch disabled per user preference");
+ return;
+ }
+
+ let url1 = "http://autoconfig." + sanitize.hostname(domain) +
+ "/mail/config-v1.1.xml";
+ // .well-known/ <http://tools.ietf.org/html/draft-nottingham-site-meta-04>
+ let url2 = "http://" + sanitize.hostname(domain) +
+ "/.well-known/autoconfig/mail/config-v1.1.xml";
+ let sucAbortable = new SuccessiveAbortable();
+ var time = Date.now();
+ var urlArgs = { emailaddress: emailAddress };
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.fetchFromISP.sendEmailAddress")) {
+ delete urlArgs.emailaddress;
+ }
+ let fetch1 = new FetchHTTP(url1, urlArgs, false,
+ function(result)
+ {
+ successCallback(readFromXML(result));
+ },
+ function(e1) // fetch1 failed
+ {
+ ddump("fetchisp 1 <" + url1 + "> took " + (Date.now() - time) +
+ "ms and failed with " + e1);
+ time = Date.now();
+ if (e1 instanceof CancelledException)
+ {
+ errorCallback(e1);
+ return;
+ }
+
+ let fetch2 = new FetchHTTP(url2, urlArgs, false,
+ function(result)
+ {
+ successCallback(readFromXML(result));
+ },
+ function(e2)
+ {
+ ddump("fetchisp 2 <" + url2 + "> took " + (Date.now() - time) +
+ "ms and failed with " + e2);
+ // return the error for the primary call,
+ // unless the fetch was cancelled
+ errorCallback(e2 instanceof CancelledException ? e2 : e1);
+ });
+ sucAbortable.current = fetch2;
+ fetch2.start();
+ });
+ sucAbortable.current = fetch1;
+ fetch1.start();
+ return sucAbortable;
+}
+
+/**
+ * Tries to get a configuration for this ISP from a central database at
+ * Mozilla servers.
+ * Params @see fetchConfigFromISP()
+ */
+
+function fetchConfigFromDB(domain, successCallback, errorCallback)
+{
+ let url = Services.prefs.getCharPref("mailnews.auto_config_url");
+ domain = sanitize.hostname(domain);
+
+ // If we don't specify a place to put the domain, put it at the end.
+ if (!url.includes("{{domain}}"))
+ url = url + domain;
+ else
+ url = url.replace("{{domain}}", domain);
+ url = url.replace("{{accounts}}", MailServices.accounts.accounts.length);
+
+ if (!url.length)
+ return errorCallback("no fetch url set");
+ let fetch = new FetchHTTP(url, null, false,
+ function(result)
+ {
+ successCallback(readFromXML(result));
+ },
+ errorCallback);
+ fetch.start();
+ return fetch;
+}
+
+/**
+ * Does a lookup of DNS MX, to get the server who is responsible for
+ * recieving mail for this domain. Then it takes the domain of that
+ * server, and does another lookup (in ISPDB and possible at ISP autoconfig
+ * server) and if such a config is found, returns that.
+ *
+ * Disclaimers:
+ * - DNS is unprotected, meaning the results could be forged.
+ * The same is true for fetchConfigFromISP() and guessConfig(), though.
+ * - DNS MX tells us the incoming server, not the mailbox (IMAP) server.
+ * They are different. This mechnism is only an approximation
+ * for hosted domains (yourname.com is served by mx.hoster.com and
+ * therefore imap.hoster.com - that "therefore" is exactly the
+ * conclusional jump we make here.) and alternative domains
+ * (e.g. yahoo.de -> yahoo.com).
+ * - We make a look up for the base domain. E.g. if MX is
+ * mx1.incoming.servers.hoster.com, we look up hoster.com.
+ * Thanks to Services.eTLD, we also get bbc.co.uk right.
+ *
+ * Params @see fetchConfigFromISP()
+ */
+function fetchConfigForMX(domain, successCallback, errorCallback)
+{
+ domain = sanitize.hostname(domain);
+
+ var sucAbortable = new SuccessiveAbortable();
+ var time = Date.now();
+ sucAbortable.current = getMX(domain,
+ function(mxHostname) // success
+ {
+ ddump("getmx took " + (Date.now() - time) + "ms");
+ let sld = Services.eTLD.getBaseDomainFromHost(mxHostname);
+ ddump("base domain " + sld + " for " + mxHostname);
+ if (sld == domain)
+ {
+ errorCallback("MX lookup would be no different from domain");
+ return;
+ }
+ sucAbortable.current = fetchConfigFromDB(sld, successCallback,
+ errorCallback);
+ },
+ errorCallback);
+ return sucAbortable;
+}
+
+/**
+ * Queries the DNS MX for the domain
+ *
+ * The current implementation goes to a web service to do the
+ * DNS resolve for us, because Mozilla unfortunately has no implementation
+ * to do it. That's just a workaround. Once bug 545866 is fixed, we make
+ * the DNS query directly on the client. The API of this function should not
+ * change then.
+ *
+ * Returns (in successCallback) the hostname of the MX server.
+ * If there are several entires with different preference values,
+ * only the most preferred (i.e. those with the lowest value)
+ * is returned. If there are several most preferred servers (i.e.
+ * round robin), only one of them is returned.
+ *
+ * @param domain @see fetchConfigFromISP()
+ * @param successCallback {function(hostname {String})
+ * Called when we found an MX for the domain.
+ * For |hostname|, see description above.
+ * @param errorCallback @see fetchConfigFromISP()
+ * @returns @see fetchConfigFromISP()
+ */
+function getMX(domain, successCallback, errorCallback)
+{
+ domain = sanitize.hostname(domain);
+
+ let url = Services.prefs.getCharPref("mailnews.mx_service_url");
+ if (!url)
+ errorCallback("no URL for MX service configured");
+ url += domain;
+
+ let fetch = new FetchHTTP(url, null, false,
+ function(result)
+ {
+ // result is plain text, with one line per server.
+ // So just take the first line
+ ddump("MX query result: \n" + result + "(end)");
+ assert(typeof(result) == "string");
+ let first = result.split("\n")[0];
+ first.toLowerCase().replace(/[^a-z0-9\-_\.]*/g, "");
+ if (first.length == 0)
+ {
+ errorCallback("no MX found");
+ return;
+ }
+ successCallback(first);
+ },
+ errorCallback);
+ fetch.start();
+ return fetch;
+}
diff --git a/mailnews/base/prefs/content/accountcreation/fetchhttp.js b/mailnews/base/prefs/content/accountcreation/fetchhttp.js
new file mode 100644
index 000000000..04f5272cd
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/fetchhttp.js
@@ -0,0 +1,267 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * This is a small wrapper around XMLHttpRequest, which solves various
+ * inadequacies of the API, e.g. error handling. It is entirely generic and
+ * can be used for purposes outside of even mail.
+ *
+ * It does not provide download progress, but assumes that the
+ * fetched resource is so small (<1 10 KB) that the roundtrip and
+ * response generation is far more significant than the
+ * download time of the response. In other words, it's fine for RPC,
+ * but not for bigger file downloads.
+ */
+
+Components.utils.import("resource://gre/modules/JXON.js");
+
+/**
+ * Set up a fetch.
+ *
+ * @param url {String} URL of the server function.
+ * ATTENTION: The caller needs to make sure that the URL is secure to call.
+ * @param urlArgs {Object, associative array} Parameters to add
+ * to the end of the URL as query string. E.g.
+ * { foo: "bla", bar: "blub blub" } will add "?foo=bla&bar=blub%20blub"
+ * to the URL
+ * (unless the URL already has a "?", then it adds "&foo...").
+ * The values will be urlComponentEncoded, so pass them unencoded.
+ * @param post {Boolean} HTTP GET or POST
+ * Only influences the HTTP request method,
+ * i.e. first line of the HTTP request, not the body or parameters.
+ * Use POST when you modify server state,
+ * GET when you only request information.
+ *
+ * @param successCallback {Function(result {String})}
+ * Called when the server call worked (no errors).
+ * |result| will contain the body of the HTTP reponse, as string.
+ * @param errorCallback {Function(ex)}
+ * Called in case of error. ex contains the error
+ * with a user-displayable but not localized |.message| and maybe a
+ * |.code|, which can be either
+ * - an nsresult error code,
+ * - an HTTP result error code (0...1000) or
+ * - negative: 0...-100 :
+ * -2 = can't resolve server in DNS etc.
+ * -4 = response body (e.g. XML) malformed
+ */
+/* not yet supported:
+ * @param headers {Object, associative array} Like urlArgs,
+ * just that the params will be added as HTTP headers.
+ * { foo: "blub blub" } will add "Foo: Blub blub"
+ * The values will be urlComponentEncoded, apart from space,
+ * so pass them unencoded.
+ * @param headerArgs {Object, associative array} Like urlArgs,
+ * just that the params will be added as HTTP headers.
+ * { foo: "blub blub" } will add "X-Moz-Arg-Foo: Blub blub"
+ * The values will be urlComponentEncoded, apart from space,
+ * so pass them unencoded.
+ * @param bodyArgs {Object, associative array} Like urlArgs,
+ * just that the params will be sent x-url-encoded in the body,
+ * like a HTML form post.
+ * The values will be urlComponentEncoded, so pass them unencoded.
+ * This cannot be used together with |uploadBody|.
+ * @param uploadbody {Object} Arbitrary object, which to use as
+ * body of the HTTP request. Will also set the mimetype accordingly.
+ * Only supported object types, currently only E4X is supported
+ * (sending XML).
+ * Usually, you have nothing to upload, so just pass |null|.
+ */
+function FetchHTTP(url, urlArgs, post, successCallback, errorCallback)
+{
+ assert(typeof(successCallback) == "function", "BUG: successCallback");
+ assert(typeof(errorCallback) == "function", "BUG: errorCallback");
+ this._url = sanitize.string(url);
+ if (!urlArgs)
+ urlArgs = {};
+
+ this._urlArgs = urlArgs;
+ this._post = sanitize.boolean(post);
+ this._successCallback = successCallback;
+ this._errorCallback = errorCallback;
+}
+FetchHTTP.prototype =
+{
+ __proto__: Abortable.prototype,
+ _url : null, // URL as passed to ctor, without arguments
+ _urlArgs : null,
+ _post : null,
+ _successCallback : null,
+ _errorCallback : null,
+ _request : null, // the XMLHttpRequest object
+ result : null,
+
+ start : function()
+ {
+ var url = this._url;
+ for (var name in this._urlArgs)
+ {
+ url += (!url.includes("?") ? "?" : "&") +
+ name + "=" + encodeURIComponent(this._urlArgs[name]);
+ }
+ this._request = new XMLHttpRequest();
+ let request = this._request;
+ request.open(this._post ? "POST" : "GET", url);
+ request.channel.loadGroup = null;
+ // needs bug 407190 patch v4 (or higher) - uncomment if that lands.
+ // try {
+ // var channel = request.channel.QueryInterface(Ci.nsIHttpChannel2);
+ // channel.connectTimeout = 5;
+ // channel.requestTimeout = 5;
+ // } catch (e) { dump(e + "\n"); }
+
+ var me = this;
+ request.onload = function() { me._response(true); }
+ request.onerror = function() { me._response(false); }
+ request.send(null);
+ },
+ _response : function(success, exStored)
+ {
+ try
+ {
+ var errorCode = null;
+ var errorStr = null;
+
+ if (success && this._request.status >= 200 &&
+ this._request.status < 300) // HTTP level success
+ {
+ try
+ {
+ // response
+ var mimetype = this._request.getResponseHeader("Content-Type");
+ if (!mimetype)
+ mimetype = "";
+ mimetype = mimetype.split(";")[0];
+ if (mimetype == "text/xml" ||
+ mimetype == "application/xml" ||
+ mimetype == "text/rdf")
+ {
+ this.result = JXON.build(this._request.responseXML);
+ }
+ else
+ {
+ //ddump("mimetype: " + mimetype + " only supported as text");
+ this.result = this._request.responseText;
+ }
+ //ddump("result:\n" + this.result);
+ }
+ catch (e)
+ {
+ success = false;
+ errorStr = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties")
+ .GetStringFromName("bad_response_content.error");
+ errorCode = -4;
+ }
+ }
+ else
+ {
+ success = false;
+ try
+ {
+ errorCode = this._request.status;
+ errorStr = this._request.statusText;
+ } catch (e) {
+ // If we can't resolve the hostname in DNS etc., .statusText throws
+ errorCode = -2;
+ errorStr = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties")
+ .GetStringFromName("cannot_contact_server.error");
+ ddump(errorStr);
+ }
+ }
+
+ // Callbacks
+ if (success)
+ {
+ try {
+ this._successCallback(this.result);
+ } catch (e) {
+ logException(e);
+ this._error(e);
+ }
+ }
+ else if (exStored)
+ this._error(exStored);
+ else
+ this._error(new ServerException(errorStr, errorCode, this._url));
+
+ if (this._finishedCallback)
+ {
+ try {
+ this._finishedCallback(this);
+ } catch (e) {
+ logException(e);
+ this._error(e);
+ }
+ }
+
+ } catch (e) {
+ // error in our fetchhttp._response() code
+ logException(e);
+ this._error(e);
+ }
+ },
+ _error : function(e)
+ {
+ try {
+ this._errorCallback(e);
+ } catch (e) {
+ // error in errorCallback, too!
+ logException(e);
+ alertPrompt("Error in errorCallback for fetchhttp", e);
+ }
+ },
+ /**
+ * Call this between start() and finishedCallback fired.
+ */
+ cancel : function(ex)
+ {
+ assert(!this.result, "Call already returned");
+
+ this._request.abort();
+
+ // Need to manually call error handler
+ // <https://bugzilla.mozilla.org/show_bug.cgi?id=218236#c11>
+ this._response(false, ex ? ex : new UserCancelledException());
+ },
+ /**
+ * Allows caller or lib to be notified when the call is done.
+ * This is useful to enable and disable a Cancel button in the UI,
+ * which allows to cancel the network request.
+ */
+ setFinishedCallback : function(finishedCallback)
+ {
+ this._finishedCallback = finishedCallback;
+ }
+}
+
+function CancelledException(msg)
+{
+ Exception.call(this, msg);
+}
+CancelledException.prototype = Object.create(Exception.prototype);
+CancelledException.prototype.constructor = CancelledException;
+
+function UserCancelledException(msg)
+{
+ // The user knows they cancelled so I don't see a need
+ // for a message to that effect.
+ if (!msg)
+ msg = "User cancelled";
+ CancelledException.call(this, msg);
+}
+UserCancelledException.prototype = Object.create(CancelledException.prototype);
+UserCancelledException.prototype.constructor = UserCancelledException;
+
+function ServerException(msg, code, uri)
+{
+ Exception.call(this, msg);
+ this.code = code;
+ this.uri = uri;
+}
+ServerException.prototype = Object.create(Exception.prototype);
+ServerException.prototype.constructor = ServerException;
+
diff --git a/mailnews/base/prefs/content/accountcreation/guessConfig.js b/mailnews/base/prefs/content/accountcreation/guessConfig.js
new file mode 100644
index 000000000..755c499cd
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/guessConfig.js
@@ -0,0 +1,1145 @@
+/* -*- Mode: Java; 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/. */
+
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+var TIMEOUT = 10; // in seconds
+
+// This is a bit ugly - we set outgoingDone to false
+// when emailWizard.js cancels the outgoing probe because the user picked
+// an outoing server. It does this by poking the probeAbortable object,
+// so we need outgoingDone to have global scope.
+var outgoingDone = false;
+
+/**
+ * Try to guess the config, by:
+ * - guessing hostnames (pop3.<domain>, pop.<domain>, imap.<domain>,
+ * mail.<domain> etc.)
+ * - probing known ports (for IMAP, POP3 etc., with SSL, STARTTLS etc.)
+ * - opening a connection via the right protocol and checking the
+ * protocol-specific CAPABILITIES like that the server returns.
+ *
+ * Final verification is not done here, but in verifyConfig().
+ *
+ * This function is async.
+ * @param domain {String} the domain part of the email address
+ * @param progressCallback {function(type, hostname, port, ssl, done)}
+ * Called when we try a new hostname/port.
+ * type {String-enum} @see AccountConfig type - "imap", "pop3", "smtp"
+ * hostname {String}
+ * port {Integer}
+ * socketType {Integer-enum} @see AccountConfig.incoming.socketType
+ * 1 = plain, 2 = SSL, 3 = STARTTLS
+ * done {Boolean} false, if we start probing this host/port, true if we're
+ * done and the host is good. (there is no notification when a host is
+ * bad, we'll just tell about the next host tried)
+ * @param successCallback {function(config {AccountConfig})}
+ * Called when we could guess the config.
+ * param accountConfig {AccountConfig} The guessed account config.
+ * username, password, realname, emailaddress etc. are not filled out,
+ * but placeholders to be filled out via replaceVariables().
+ * @param errorCallback function(ex)
+ * Called when we could guess not the config, either
+ * because we have not found anything or
+ * because there was an error (e.g. no network connection).
+ * The ex.message will contain a user-presentable message.
+ * @param resultConfig {AccountConfig} (optional)
+ * A config which may be partially filled in. If so, it will be used as base
+ * for the guess.
+ * @param which {String-enum} (optional) "incoming", "outgoing", or "both".
+ * Default "both". Whether to guess only the incoming or outgoing server.
+ * @result {Abortable} Allows you to cancel the guess
+ */
+function guessConfig(domain, progressCallback, successCallback, errorCallback,
+ resultConfig, which)
+{
+ assert(typeof(progressCallback) == "function", "need progressCallback");
+ assert(typeof(successCallback) == "function", "need successCallback");
+ assert(typeof(errorCallback) == "function", "need errorCallback");
+
+ // Servers that we know enough that they support OAuth2 do not need guessing.
+ if (resultConfig.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) {
+ successCallback(resultConfig);
+ return null;
+ }
+
+ if (!resultConfig)
+ resultConfig = new AccountConfig();
+ resultConfig.source = AccountConfig.kSourceGuess;
+
+ if (!Services.prefs.getBoolPref(
+ "mailnews.auto_config.guess.enabled")) {
+ errorCallback("Guessing config disabled per user preference");
+ return;
+ }
+
+ var incomingHostDetector = null;
+ var outgoingHostDetector = null;
+ var incomingEx = null; // if incoming had error, store ex here
+ var outgoingEx = null; // if incoming had error, store ex here
+ var incomingDone = (which == "outgoing");
+ var outgoingDone = (which == "incoming");
+ // If we're offline, we're going to pick the most common settings.
+ // (Not the "best" settings, but common).
+ if (Services.io.offline)
+ {
+ resultConfig.source = AccountConfig.kSourceUser;
+ resultConfig.incoming.hostname = "mail." + domain;
+ resultConfig.incoming.username = resultConfig.identity.emailAddress;
+ resultConfig.outgoing.username = resultConfig.identity.emailAddress;
+ resultConfig.incoming.type = "imap";
+ resultConfig.incoming.port = 143;
+ resultConfig.incoming.socketType = 3; // starttls
+ resultConfig.incoming.auth = Ci.nsMsgAuthMethod.passwordCleartext;
+ resultConfig.outgoing.hostname = "smtp." + domain;
+ resultConfig.outgoing.socketType = 1;
+ resultConfig.outgoing.port = 587;
+ resultConfig.outgoing.auth = Ci.nsMsgAuthMethod.passwordCleartext;
+ resultConfig.incomingAlternatives.push({
+ hostname: "mail." + domain,
+ username: resultConfig.identity.emailAddress,
+ type: "pop3",
+ port: 110,
+ socketType: 3,
+ auth: Ci.nsMsgAuthMethod.passwordCleartext
+ });
+ successCallback(resultConfig);
+ return null;
+ }
+ var progress = function(thisTry)
+ {
+ progressCallback(protocolToString(thisTry.protocol), thisTry.hostname,
+ thisTry.port, sslConvertToSocketType(thisTry.ssl), false,
+ resultConfig);
+ };
+
+ var updateConfig = function(config)
+ {
+ resultConfig = config;
+ };
+
+ var errorInCallback = function(e)
+ {
+ // The caller's errorCallback threw.
+ // hopefully shouldn't happen for users.
+ alertPrompt("Error in errorCallback for guessConfig()", e);
+ };
+
+ var checkDone = function()
+ {
+ if (incomingEx)
+ {
+ try {
+ errorCallback(incomingEx, resultConfig);
+ } catch (e) { errorInCallback(e); }
+ return;
+ }
+ if (outgoingEx)
+ {
+ try {
+ errorCallback(outgoingEx, resultConfig);
+ } catch (e) { errorInCallback(e); }
+ return;
+ }
+ if (incomingDone && outgoingDone)
+ {
+ try {
+ successCallback(resultConfig);
+ } catch (e) {
+ try {
+ errorCallback(e);
+ } catch (e) { errorInCallback(e); }
+ }
+ return;
+ }
+ };
+
+ var logger = Log4Moz.getConfiguredLogger("mail.wizard");
+ var HostTryToAccountServer = function(thisTry, server)
+ {
+ server.type = protocolToString(thisTry.protocol);
+ server.hostname = thisTry.hostname;
+ server.port = thisTry.port;
+ server.socketType = sslConvertToSocketType(thisTry.ssl);
+ server.auth = chooseBestAuthMethod(thisTry.authMethods);
+ server.authAlternatives = thisTry.authMethods;
+ // TODO
+ // cert is also bad when targetSite is set. (Same below for incoming.)
+ // Fix SSLErrorHandler and security warning dialog in emailWizard.js.
+ server.badCert = thisTry.selfSignedCert;
+ server.targetSite = thisTry.targetSite;
+ logger.info("CHOOSING " + server.type + " "+ server.hostname + ":" +
+ server.port + ", auth method " + server.auth + " " +
+ server.authAlternatives.join(",") + ", SSL " + server.socketType +
+ (server.badCert ? " (bad cert!)" : ""));
+ };
+
+ var outgoingSuccess = function(thisTry, alternativeTries)
+ {
+ assert(thisTry.protocol == SMTP, "I only know SMTP for outgoing");
+ // Ensure there are no previously saved outgoing errors, if we've got
+ // success here.
+ outgoingEx = null;
+ HostTryToAccountServer(thisTry, resultConfig.outgoing);
+
+ for (let alternativeTry of alternativeTries)
+ {
+ // resultConfig.createNewOutgoing(); misses username etc., so copy
+ let altServer = deepCopy(resultConfig.outgoing);
+ HostTryToAccountServer(alternativeTry, altServer);
+ assert(resultConfig.outgoingAlternatives);
+ resultConfig.outgoingAlternatives.push(altServer);
+ }
+
+ progressCallback(resultConfig.outgoing.type,
+ resultConfig.outgoing.hostname, resultConfig.outgoing.port,
+ resultConfig.outgoing.socketType, true, resultConfig);
+ outgoingDone = true;
+ checkDone();
+ };
+
+ var incomingSuccess = function(thisTry, alternativeTries)
+ {
+ // Ensure there are no previously saved incoming errors, if we've got
+ // success here.
+ incomingEx = null;
+ HostTryToAccountServer(thisTry, resultConfig.incoming);
+
+ for (let alternativeTry of alternativeTries)
+ {
+ // resultConfig.createNewIncoming(); misses username etc., so copy
+ let altServer = deepCopy(resultConfig.incoming);
+ HostTryToAccountServer(alternativeTry, altServer);
+ assert(resultConfig.incomingAlternatives);
+ resultConfig.incomingAlternatives.push(altServer);
+ }
+
+ progressCallback(resultConfig.incoming.type,
+ resultConfig.incoming.hostname, resultConfig.incoming.port,
+ resultConfig.incoming.socketType, true, resultConfig);
+ incomingDone = true;
+ checkDone();
+ };
+
+ var incomingError = function(ex)
+ {
+ incomingEx = ex;
+ checkDone();
+ incomingHostDetector.cancel(new CancelOthersException());
+ outgoingHostDetector.cancel(new CancelOthersException());
+ };
+
+ var outgoingError = function(ex)
+ {
+ outgoingEx = ex;
+ checkDone();
+ incomingHostDetector.cancel(new CancelOthersException());
+ outgoingHostDetector.cancel(new CancelOthersException());
+ };
+
+ incomingHostDetector = new IncomingHostDetector(progress, incomingSuccess,
+ incomingError);
+ outgoingHostDetector = new OutgoingHostDetector(progress, outgoingSuccess,
+ outgoingError);
+ if (which == "incoming" || which == "both")
+ {
+ incomingHostDetector.start(resultConfig.incoming.hostname ?
+ resultConfig.incoming.hostname : domain,
+ !!resultConfig.incoming.hostname, resultConfig.incoming.type,
+ resultConfig.incoming.port, resultConfig.incoming.socketType);
+ }
+ if (which == "outgoing" || which == "both")
+ {
+ outgoingHostDetector.start(resultConfig.outgoing.hostname ?
+ resultConfig.outgoing.hostname : domain,
+ !!resultConfig.outgoing.hostname, "smtp",
+ resultConfig.outgoing.port, resultConfig.outgoing.socketType);
+ }
+
+ return new GuessAbortable(incomingHostDetector, outgoingHostDetector,
+ updateConfig);
+}
+
+function GuessAbortable(incomingHostDetector, outgoingHostDetector,
+ updateConfig)
+{
+ Abortable.call(this);
+ this._incomingHostDetector = incomingHostDetector;
+ this._outgoingHostDetector = outgoingHostDetector;
+ this._updateConfig = updateConfig;
+}
+GuessAbortable.prototype = Object.create(Abortable.prototype);
+GuessAbortable.prototype.constructor = GuessAbortable;
+GuessAbortable.prototype.cancel = function(ex)
+{
+ this._incomingHostDetector.cancel(ex);
+ this._outgoingHostDetector.cancel(ex);
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Implementation
+//
+// Objects, functions and constants that follow are not to be used outside
+// this file.
+
+var kNotTried = 0;
+var kOngoing = 1;
+var kFailed = 2;
+var kSuccess = 3;
+
+/**
+ * Internal object holding one server that we should try or did try.
+ * Used as |thisTry|.
+ *
+ * Note: The consts it uses for protocol and ssl are defined towards the end
+ * of this file and not the same as those used in AccountConfig (type,
+ * socketType). (fix this)
+ */
+function HostTry()
+{
+}
+HostTry.prototype =
+{
+ // IMAP, POP or SMTP
+ protocol : UNKNOWN,
+ // {String}
+ hostname : undefined,
+ // {Integer}
+ port : undefined,
+ // NONE, SSL or TLS
+ ssl : UNKNOWN,
+ // {String} what to send to server
+ commands : null,
+ // {Integer-enum} kNotTried, kOngoing, kFailed or kSuccess
+ status : kNotTried,
+ // {Abortable} allows to cancel the socket comm
+ abortable : null,
+
+ // {Array of {Integer-enum}} @see _advertisesAuthMethods() result
+ // Info about the server, from the protocol and SSL chat
+ authMethods : null,
+ // {String} Whether the SSL cert is not from a proper CA
+ selfSignedCert : false,
+ // {String} Which host the SSL cert is made for, if not hostname.
+ // If set, this is an SSL error.
+ targetSite : null,
+};
+
+/**
+ * When the success or errorCallbacks are called to abort the other requests
+ * which happened in parallel, this ex is used as param for cancel(), so that
+ * the cancel doesn't trigger another callback.
+ */
+function CancelOthersException()
+{
+ CancelledException.call(this, "we're done, cancelling the other probes");
+}
+CancelOthersException.prototype = Object.create(CancelledException.prototype);
+CancelOthersException.prototype.constructor = CancelOthersException;
+
+/**
+ * @param successCallback {function(result {HostTry}, alts {Array of HostTry})}
+ * Called when the config is OK
+ * |result| is the most preferred server.
+ * |alts| currently exists only for |IncomingHostDetector| and contains
+ * some servers of the other type (POP3 instead of IMAP), if available.
+ * @param errorCallback {function(ex)} Called when we could not find a config
+ * @param progressCallback { function(server {HostTry}) } Called when we tried
+ * (will try?) a new hostname and port
+ */
+function HostDetector(progressCallback, successCallback, errorCallback)
+{
+ this.mSuccessCallback = successCallback;
+ this.mProgressCallback = progressCallback;
+ this.mErrorCallback = errorCallback;
+ this._cancel = false;
+ // {Array of {HostTry}}, ordered by decreasing preference
+ this._hostsToTry = new Array();
+
+ // init logging
+ this._log = Log4Moz.getConfiguredLogger("mail.wizard");
+ this._log.info("created host detector");
+}
+
+HostDetector.prototype =
+{
+ cancel : function(ex)
+ {
+ this._cancel = true;
+ // We have to actively stop the network calls, as they may result in
+ // callbacks e.g. to the cert handler. If the dialog is gone by the time
+ // this happens, the javascript stack is horked.
+ for (let i = 0; i < this._hostsToTry.length; i++)
+ {
+ let thisTry = this._hostsToTry[i]; // {HostTry}
+ if (thisTry.abortable)
+ thisTry.abortable.cancel(ex);
+ thisTry.status = kFailed; // or don't set? Maybe we want to continue.
+ }
+ if (ex instanceof CancelOthersException)
+ return;
+ if (!ex)
+ ex = new CancelledException();
+ this.mErrorCallback(ex);
+ },
+
+ /**
+ * Start the detection
+ *
+ * @param domain {String} to be used as base for guessing.
+ * Should be a domain (e.g. yahoo.co.uk).
+ * If hostIsPrecise == true, it should be a full hostname
+ * @param hostIsPrecise {Boolean} (default false) use only this hostname,
+ * do not guess hostnames.
+ * @param type {String-enum}@see AccountConfig type
+ * (Optional. default, 0, undefined, null = guess it)
+ * @param port {Integer} (Optional. default, 0, undefined, null = guess it)
+ * @param socketType {Integer-enum}@see AccountConfig socketType
+ * (Optional. default, 0, undefined, null = guess it)
+ */
+ start : function(domain, hostIsPrecise, type, port, socketType)
+ {
+ domain = domain.replace(/\s*/g, ""); // Remove whitespace
+ if (!hostIsPrecise)
+ hostIsPrecise = false;
+ var protocol = sanitize.translate(type,
+ { "imap" : IMAP, "pop3" : POP, "smtp" : SMTP }, UNKNOWN);
+ if (!port)
+ port = UNKNOWN;
+ var ssl = ConvertSocketTypeToSSL(socketType);
+ this._cancel = false;
+ this._log.info("doing auto detect for protocol " + protocol +
+ ", domain " + domain + ", (exactly: " + hostIsPrecise +
+ "), port " + port + ", ssl " + ssl);
+
+ // fill this._hostsToTry
+ this._hostsToTry = [];
+ var hostnamesToTry = [];
+ // if hostIsPrecise is true, it's because that's what the user input
+ // explicitly, and we'll just try it, nothing else.
+ if (hostIsPrecise)
+ hostnamesToTry.push(domain);
+ else
+ hostnamesToTry = this._hostnamesToTry(protocol, domain);
+
+ for (let i = 0; i < hostnamesToTry.length; i++)
+ {
+ let hostname = hostnamesToTry[i];
+ let hostEntries = this._portsToTry(hostname, protocol, ssl, port);
+ for (let j = 0; j < hostEntries.length; j++)
+ {
+ let hostTry = hostEntries[j]; // from getHostEntry()
+ hostTry.hostname = hostname;
+ hostTry.status = kNotTried;
+ this._hostsToTry.push(hostTry);
+ }
+ }
+
+ this._hostsToTry = sortTriesByPreference(this._hostsToTry);
+ this._tryAll();
+ },
+
+ // We make all host/port combinations run in parallel, store their
+ // results in an array, and as soon as one finishes successfully and all
+ // higher-priority ones have failed, we abort all lower-priority ones.
+
+ _tryAll : function()
+ {
+ if (this._cancel)
+ return;
+ var me = this;
+ for (let i = 0; i < this._hostsToTry.length; i++)
+ {
+ let thisTry = this._hostsToTry[i]; // {HostTry}
+ if (thisTry.status != kNotTried)
+ continue;
+ this._log.info("poking at " + thisTry.hostname + " port " +
+ thisTry.port + " ssl "+ thisTry.ssl + " protocol " +
+ protocolToString(thisTry.protocol));
+ if (i == 0) // showing 50 servers at once is pointless
+ this.mProgressCallback(thisTry);
+
+ thisTry.abortable = SocketUtil(
+ thisTry.hostname, thisTry.port, thisTry.ssl,
+ thisTry.commands, TIMEOUT,
+ new SSLErrorHandler(thisTry, this._log),
+ function(wiredata) // result callback
+ {
+ if (me._cancel)
+ return; // don't use response anymore
+ me.mProgressCallback(thisTry);
+ me._processResult(thisTry, wiredata);
+ me._checkFinished();
+ },
+ function(e) // error callback
+ {
+ if (me._cancel)
+ return; // who set cancel to true already called mErrorCallback()
+ me._log.warn(e);
+ thisTry.status = kFailed;
+ me._checkFinished();
+ });
+ thisTry.status = kOngoing;
+ }
+ },
+
+ /**
+ * @param thisTry {HostTry}
+ * @param wiredata {Array of {String}} what the server returned
+ * in response to our protocol chat
+ */
+ _processResult : function(thisTry, wiredata)
+ {
+ if (thisTry._gotCertError)
+ {
+ if (thisTry._gotCertError == Ci.nsICertOverrideService.ERROR_MISMATCH)
+ {
+ thisTry._gotCertError = 0;
+ thisTry.status = kFailed;
+ return;
+ }
+
+ if (thisTry._gotCertError == Ci.nsICertOverrideService.ERROR_UNTRUSTED ||
+ thisTry._gotCertError == Ci.nsICertOverrideService.ERROR_TIME)
+ {
+ this._log.info("TRYING AGAIN, hopefully with exception recorded");
+ thisTry._gotCertError = 0;
+ thisTry.selfSignedCert = true; // _next_ run gets this exception
+ thisTry.status = kNotTried; // try again (with exception)
+ this._tryAll();
+ return;
+ }
+ }
+
+ if (wiredata == null || wiredata === undefined)
+ {
+ this._log.info("no data");
+ thisTry.status = kFailed;
+ return;
+ }
+ this._log.info("wiredata: " + wiredata.join(""));
+ thisTry.authMethods =
+ this._advertisesAuthMethods(thisTry.protocol, wiredata);
+ if (thisTry.ssl == TLS && !this._hasTLS(thisTry, wiredata))
+ {
+ this._log.info("STARTTLS wanted, but not offered");
+ thisTry.status = kFailed;
+ return;
+ }
+ this._log.info("success with " + thisTry.hostname + ":" +
+ thisTry.port + " " + protocolToString(thisTry.protocol) +
+ " ssl " + thisTry.ssl +
+ (thisTry.selfSignedCert ? " (selfSignedCert)" : ""));
+ thisTry.status = kSuccess;
+
+ if (thisTry.selfSignedCert) { // eh, ERROR_UNTRUSTED or ERROR_TIME
+ // We clear the temporary override now after success. If we clear it
+ // earlier we get into an infinite loop, probably because the cert
+ // remembering is temporary and the next try gets a new connection which
+ // isn't covered by that temporariness.
+ this._log.info("clearing validity override for " + thisTry.hostname);
+ Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService)
+ .clearValidityOverride(thisTry.hostname, thisTry.port);
+ }
+ },
+
+ _checkFinished : function()
+ {
+ var successfulTry = null;
+ var successfulTryAlternative = null; // POP3
+ var unfinishedBusiness = false;
+ // this._hostsToTry is ordered by decreasing preference
+ for (let i = 0; i < this._hostsToTry.length; i++)
+ {
+ let thisTry = this._hostsToTry[i];
+ if (thisTry.status == kNotTried || thisTry.status == kOngoing)
+ unfinishedBusiness = true;
+ // thisTry is good, and all higher preference tries failed, so use this
+ else if (thisTry.status == kSuccess && !unfinishedBusiness)
+ {
+ if (!successfulTry)
+ {
+ successfulTry = thisTry;
+ if (successfulTry.protocol == SMTP)
+ break;
+ }
+ else if (successfulTry.protocol != thisTry.protocol)
+ {
+ successfulTryAlternative = thisTry;
+ break;
+ }
+ }
+ }
+ if (successfulTry && (successfulTryAlternative || !unfinishedBusiness))
+ {
+ this.mSuccessCallback(successfulTry,
+ successfulTryAlternative ? [ successfulTryAlternative ] : []);
+ this.cancel(new CancelOthersException());
+ }
+ else if (!unfinishedBusiness) // all failed
+ {
+ this._log.info("ran out of options");
+ var errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_find_server.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ // no need to cancel, all failed
+ }
+ // else let ongoing calls continue
+ },
+
+
+ /**
+ * Which auth mechanism the server claims to support.
+ * (That doesn't necessarily reflect reality, it is more an upper bound.)
+ *
+ * @param protocol {Integer-enum} IMAP, POP or SMTP
+ * @param capaResponse {Array of {String}} on the wire data
+ * that the server returned. May be the full exchange or just capa.
+ * @returns {Array of {Integer-enum} values for AccountConfig.incoming.auth
+ * (or outgoing), in decreasing order of preference.
+ * E.g. [ 5, 4 ] for a server that supports only Kerberos and
+ * encrypted passwords.
+ */
+ _advertisesAuthMethods : function(protocol, capaResponse)
+ {
+ // for imap, capabilities include e.g.:
+ // "AUTH=CRAM-MD5", "AUTH=NTLM", "AUTH=GSSAPI", "AUTH=MSN"
+ // for pop3, the auth mechanisms are returned in capa as the following:
+ // "CRAM-MD5", "NTLM", "MSN", "GSSAPI"
+ // For smtp, EHLO will return AUTH and then a list of the
+ // mechanism(s) supported, e.g.,
+ // AUTH LOGIN NTLM MSN CRAM-MD5 GSSAPI
+ var result = new Array();
+ var line = capaResponse.join("\n").toUpperCase();
+ var prefix = "";
+ if (protocol == POP)
+ prefix = "";
+ else if (protocol == IMAP)
+ prefix = "AUTH=";
+ else if (protocol == SMTP)
+ prefix = "AUTH.*";
+ else
+ throw NotReached("must pass protocol");
+ // add in decreasing order of preference
+ if (new RegExp(prefix + "GSSAPI").test(line))
+ result.push(Ci.nsMsgAuthMethod.GSSAPI);
+ if (new RegExp(prefix + "CRAM-MD5").test(line))
+ result.push(Ci.nsMsgAuthMethod.passwordEncrypted);
+ if (new RegExp(prefix + "(NTLM|MSN)").test(line))
+ result.push(Ci.nsMsgAuthMethod.NTLM);
+ if (protocol != IMAP || !line.includes("LOGINDISABLED"))
+ result.push(Ci.nsMsgAuthMethod.passwordCleartext);
+ return result;
+ },
+
+ _hasTLS : function(thisTry, wiredata)
+ {
+ var capa = thisTry.protocol == POP ? "STLS" : "STARTTLS";
+ return thisTry.ssl == TLS &&
+ wiredata.join("").toUpperCase().includes(capa);
+ },
+}
+
+/**
+ * @param authMethods @see return value of _advertisesAuthMethods()
+ * Note: the returned auth method will be removed from the array.
+ * @return one of them, the preferred one
+ * Note: this might be Kerberos, which might not actually work,
+ * so you might need to try the others, too.
+ */
+function chooseBestAuthMethod(authMethods)
+{
+ if (!authMethods || !authMethods.length)
+ return Ci.nsMsgAuthMethod.passwordCleartext;
+ return authMethods.shift(); // take first (= most preferred)
+}
+
+
+function IncomingHostDetector(
+ progressCallback, successCallback, errorCallback)
+{
+ HostDetector.call(this, progressCallback, successCallback, errorCallback);
+}
+IncomingHostDetector.prototype =
+{
+ __proto__: HostDetector.prototype,
+ _hostnamesToTry : function(protocol, domain)
+ {
+ var hostnamesToTry = [];
+ if (protocol != POP)
+ hostnamesToTry.push("imap." + domain);
+ if (protocol != IMAP)
+ {
+ hostnamesToTry.push("pop3." + domain);
+ hostnamesToTry.push("pop." + domain);
+ }
+ hostnamesToTry.push("mail." + domain);
+ hostnamesToTry.push(domain);
+ return hostnamesToTry;
+ },
+ _portsToTry : getIncomingTryOrder,
+}
+
+function OutgoingHostDetector(
+ progressCallback, successCallback, errorCallback)
+{
+ HostDetector.call(this, progressCallback, successCallback, errorCallback);
+}
+OutgoingHostDetector.prototype =
+{
+ __proto__: HostDetector.prototype,
+ _hostnamesToTry : function(protocol, domain)
+ {
+ var hostnamesToTry = [];
+ hostnamesToTry.push("smtp." + domain);
+ hostnamesToTry.push("mail." + domain);
+ hostnamesToTry.push(domain);
+ return hostnamesToTry;
+ },
+ _portsToTry : getOutgoingTryOrder,
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Encode protocol ports and order of preference
+
+// Protocol Types
+var UNKNOWN = -1;
+var IMAP = 0;
+var POP = 1;
+var SMTP = 2;
+// Security Types
+var NONE = 0; // no encryption
+//1 would be "TLS if available"
+var TLS = 2; // STARTTLS
+var SSL = 3; // SSL / TLS
+
+var IMAP_PORTS = {}
+IMAP_PORTS[NONE] = 143;
+IMAP_PORTS[TLS] = 143;
+IMAP_PORTS[SSL] = 993;
+
+var POP_PORTS = {}
+POP_PORTS[NONE] = 110;
+POP_PORTS[TLS] = 110;
+POP_PORTS[SSL] = 995;
+
+var SMTP_PORTS = {}
+SMTP_PORTS[NONE] = 587;
+SMTP_PORTS[TLS] = 587;
+SMTP_PORTS[SSL] = 465;
+
+var CMDS = {}
+CMDS[IMAP] = ["1 CAPABILITY\r\n", "2 LOGOUT\r\n"];
+CMDS[POP] = ["CAPA\r\n", "QUIT\r\n"];
+CMDS[SMTP] = ["EHLO we-guess.mozilla.org\r\n", "QUIT\r\n"];
+
+/**
+ * Sort by preference of SSL, IMAP etc.
+ * @param tries {Array of {HostTry}}
+ * @returns {Array of {HostTry}}
+ */
+function sortTriesByPreference(tries)
+{
+ return tries.sort(function __sortByPreference(a, b)
+ {
+ // -1 = a is better; 1 = b is better; 0 = equal
+ // Prefer SSL/TLS above all else
+ if (a.ssl != NONE && b.ssl == NONE)
+ return -1;
+ if (b.ssl != NONE && a.ssl == NONE)
+ return 1;
+ // Prefer IMAP over POP
+ if (a.protocol == IMAP && b.protocol == POP)
+ return -1;
+ if (b.protocol == IMAP && a.protocol == POP)
+ return 1;
+ // For hostnames, leave existing sorting, as in _hostnamesToTry()
+ // For ports, leave existing sorting, as in getOutgoingTryOrder()
+ return 0;
+ });
+};
+
+// TODO prefer SSL over STARTTLS,
+// either in sortTriesByPreference or in getIncomingTryOrder() (and outgoing)
+
+/**
+ * @returns {Array of {HostTry}}
+ */
+function getIncomingTryOrder(host, protocol, ssl, port)
+{
+ var lowerCaseHost = host.toLowerCase();
+
+ if (protocol == UNKNOWN &&
+ (lowerCaseHost.startsWith("pop.") || lowerCaseHost.startsWith("pop3.")))
+ protocol = POP;
+ else if (protocol == UNKNOWN && lowerCaseHost.startsWith("imap."))
+ protocol = IMAP;
+
+ if (protocol != UNKNOWN) {
+ if (ssl == UNKNOWN)
+ return [getHostEntry(protocol, TLS, port),
+ //getHostEntry(protocol, SSL, port),
+ getHostEntry(protocol, NONE, port)];
+ return [getHostEntry(protocol, ssl, port)];
+ }
+ if (ssl == UNKNOWN)
+ return [getHostEntry(IMAP, TLS, port),
+ //getHostEntry(IMAP, SSL, port),
+ getHostEntry(POP, TLS, port),
+ //getHostEntry(POP, SSL, port),
+ getHostEntry(IMAP, NONE, port),
+ getHostEntry(POP, NONE, port)];
+ return [getHostEntry(IMAP, ssl, port),
+ getHostEntry(POP, ssl, port)];
+};
+
+/**
+ * @returns {Array of {HostTry}}
+ */
+function getOutgoingTryOrder(host, protocol, ssl, port)
+{
+ assert(protocol == SMTP, "need SMTP as protocol for outgoing");
+ if (ssl == UNKNOWN)
+ {
+ if (port == UNKNOWN)
+ // neither SSL nor port known
+ return [getHostEntry(SMTP, TLS, UNKNOWN),
+ getHostEntry(SMTP, TLS, 25),
+ //getHostEntry(SMTP, SSL, UNKNOWN),
+ getHostEntry(SMTP, NONE, UNKNOWN),
+ getHostEntry(SMTP, NONE, 25)];
+ // port known, SSL not
+ return [getHostEntry(SMTP, TLS, port),
+ //getHostEntry(SMTP, SSL, port),
+ getHostEntry(SMTP, NONE, port)];
+ }
+ // SSL known, port not
+ if (port == UNKNOWN)
+ {
+ if (ssl == SSL)
+ return [getHostEntry(SMTP, SSL, UNKNOWN)];
+ else // TLS or NONE
+ return [getHostEntry(SMTP, ssl, UNKNOWN),
+ getHostEntry(SMTP, ssl, 25)];
+ }
+ // SSL and port known
+ return [getHostEntry(SMTP, ssl, port)];
+};
+
+/**
+ * @returns {HostTry} with proper default port and commands,
+ * but without hostname.
+ */
+function getHostEntry(protocol, ssl, port)
+{
+ if (!port || port == UNKNOWN) {
+ switch (protocol) {
+ case POP:
+ port = POP_PORTS[ssl];
+ break;
+ case IMAP:
+ port = IMAP_PORTS[ssl];
+ break;
+ case SMTP:
+ port = SMTP_PORTS[ssl];
+ break;
+ default:
+ throw new NotReached("unsupported protocol " + protocol);
+ }
+ }
+
+ var r = new HostTry();
+ r.protocol = protocol;
+ r.ssl = ssl;
+ r.port = port;
+ r.commands = CMDS[protocol];
+ return r;
+};
+
+
+// Convert consts from those used here to those from AccountConfig
+// TODO adjust consts to match AccountConfig
+
+// here -> AccountConfig
+function sslConvertToSocketType(ssl)
+{
+ if (ssl == NONE)
+ return 1;
+ if (ssl == SSL)
+ return 2;
+ if (ssl == TLS)
+ return 3;
+ throw new NotReached("unexpected SSL type");
+}
+
+// AccountConfig -> here
+function ConvertSocketTypeToSSL(socketType)
+{
+ if (socketType == 1)
+ return NONE;
+ if (socketType == 2)
+ return SSL;
+ if (socketType == 3)
+ return TLS;
+ return UNKNOWN;
+}
+
+// here -> AccountConfig
+function protocolToString(type)
+{
+ if (type == IMAP)
+ return "imap";
+ if (type == POP)
+ return "pop3";
+ if (type == SMTP)
+ return "smtp";
+ throw new NotReached("unexpected protocol");
+}
+
+
+
+/////////////////////////////////////////////////////////
+// SSL cert error handler
+
+/**
+ * Called by MyBadCertHandler.js, which called by PSM
+ * to tell us about SSL certificate errors.
+ * @param thisTry {HostTry}
+ * @param logger {Log4Moz logger}
+ */
+function SSLErrorHandler(thisTry, logger)
+{
+ this._try = thisTry;
+ this._log = logger;
+ // _ gotCertError will be set to an error code (one of those defined in
+ // nsICertOverrideService)
+ this._gotCertError = 0;
+}
+SSLErrorHandler.prototype =
+{
+ processCertError : function(socketInfo, status, targetSite)
+ {
+ this._log.error("Got Cert error for "+ targetSite);
+
+ if (!status)
+ return true;
+
+ let cert = status.QueryInterface(Ci.nsISSLStatus).serverCert;
+ let flags = 0;
+
+ let parts = targetSite.split(":");
+ let host = parts[0];
+ let port = parts[1];
+
+ /* The following 2 cert problems are unfortunately common:
+ * 1) hostname mismatch:
+ * user is custeromer at a domain hoster, he owns yourname.org,
+ * and the IMAP server is imap.hoster.com (but also reachable as
+ * imap.yourname.org), and has a cert for imap.hoster.com.
+ * 2) self-signed:
+ * a company has an internal IMAP server, and it's only for
+ * 30 employees, and they didn't want to buy a cert, so
+ * they use a self-signed cert.
+ *
+ * We would like the above to pass, somehow, with user confirmation.
+ * The following case should *not* pass:
+ *
+ * 1) MITM
+ * User has @gmail.com, and an attacker is between the user and
+ * the Internet and runs a man-in-the-middle (MITM) attack.
+ * Attacker controls DNS and sends imap.gmail.com to his own
+ * imap.attacker.com. He has either a valid, CA-issued
+ * cert for imap.attacker.com, or a self-signed cert.
+ * Of course, attacker.com could also be legit-sounding gmailservers.com.
+ *
+ * What makes it dangerous is that we (!) propose the server to the user,
+ * and he cannot judge whether imap.gmailservers.com is correct or not,
+ * and he will likely approve it.
+ */
+
+ if (status.isDomainMismatch) {
+ this._try._gotCertError = Ci.nsICertOverrideService.ERROR_MISMATCH;
+ flags |= Ci.nsICertOverrideService.ERROR_MISMATCH;
+ }
+ else if (status.isUntrusted) { // e.g. self-signed
+ this._try._gotCertError = Ci.nsICertOverrideService.ERROR_UNTRUSTED;
+ flags |= Ci.nsICertOverrideService.ERROR_UNTRUSTED;
+ }
+ else if (status.isNotValidAtThisTime) {
+ this._try._gotCertError = Ci.nsICertOverrideService.ERROR_TIME;
+ flags |= Ci.nsICertOverrideService.ERROR_TIME;
+ }
+ else {
+ this._try._gotCertError = -1; // other
+ }
+
+ /* We will add a temporary cert exception here, so that
+ * we can continue and connect and try.
+ * But we will remove it again as soon as we close the
+ * connection, in _processResult().
+ * _gotCertError will serve as the marker that we
+ * have to clear the override later.
+ *
+ * In verifyConfig(), before we send the password, we *must*
+ * get another cert exception, this time with dialog to the user
+ * so that he gets informed about this and can make a choice.
+ */
+
+ this._try.targetSite = targetSite;
+ Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService)
+ .rememberValidityOverride(host, port, cert, flags,
+ true); // temporary override
+ this._log.warn("!! Overrode bad cert temporarily " + host + " " + port +
+ " flags=" + flags + "\n");
+ return true;
+ },
+}
+
+
+
+//////////////////////////////////////////////////////////////////
+// Socket Util
+
+
+/**
+ * @param hostname {String} The DNS hostname to connect to.
+ * @param port {Integer} The numberic port to connect to on the host.
+ * @param ssl {Integer} SSL, TLS or NONE
+ * @param commands {Array of String}: protocol commands
+ * to send to the server.
+ * @param timeout {Integer} seconds to wait for a server response, then cancel.
+ * @param sslErrorHandler {SSLErrorHandler}
+ * @param resultCallback {function(wiredata)} This function will
+ * be called with the result string array from the server
+ * or null if no communication occurred.
+ * @param errorCallback {function(e)}
+ */
+function SocketUtil(hostname, port, ssl, commands, timeout,
+ sslErrorHandler, resultCallback, errorCallback)
+{
+ assert(commands && commands.length, "need commands");
+
+ var index = 0; // commands[index] is next to send to server
+ var initialized = false;
+ var aborted = false;
+
+ function _error(e)
+ {
+ if (aborted)
+ return;
+ aborted = true;
+ errorCallback(e);
+ }
+
+ function timeoutFunc()
+ {
+ if (!initialized)
+ _error("timeout");
+ }
+
+ // In case DNS takes too long or does not resolve or another blocking
+ // issue occurs before the timeout can be set on the socket, this
+ // ensures that the listener callback will be fired in a timely manner.
+ // XXX There might to be some clean up needed after the timeout is fired
+ // for socket and io resources.
+
+ // The timeout value plus 2 seconds
+ setTimeout(timeoutFunc, (timeout * 1000) + 2000);
+
+ var transportService = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+ // @see NS_NETWORK_SOCKET_CONTRACTID_PREFIX
+ var socketTypeName = ssl == SSL ? "ssl" : (ssl == TLS ? "starttls" : null);
+ var transport = transportService.createTransport([socketTypeName],
+ ssl == NONE ? 0 : 1,
+ hostname, port, null);
+
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, timeout);
+ transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, timeout);
+ try {
+ transport.securityCallbacks = new BadCertHandler(sslErrorHandler);
+ } catch (e) {
+ _error(e);
+ }
+ var outstream = transport.openOutputStream(0, 0, 0);
+ var stream = transport.openInputStream(0, 0, 0);
+ var instream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ instream.init(stream);
+
+ var dataListener =
+ {
+ data : new Array(),
+ onStartRequest: function(request, context)
+ {
+ try {
+ initialized = true;
+ if (!aborted)
+ {
+ // Send the first request
+ let outputData = commands[index++];
+ outstream.write(outputData, outputData.length);
+ }
+ } catch (e) { _error(e); }
+ },
+ onStopRequest: function(request, context, status)
+ {
+ try {
+ instream.close();
+ outstream.close();
+ resultCallback(this.data.length ? this.data : null);
+ } catch (e) { _error(e); }
+ },
+ onDataAvailable: function(request, context, inputStream, offset, count)
+ {
+ try {
+ if (!aborted)
+ {
+ let inputData = instream.read(count);
+ this.data.push(inputData);
+ if (index < commands.length)
+ {
+ // Send the next request to the server.
+ let outputData = commands[index++];
+ outstream.write(outputData, outputData.length);
+ }
+ }
+ } catch (e) { _error(e); }
+ }
+ };
+
+ try {
+ var pump = Cc["@mozilla.org/network/input-stream-pump;1"]
+ .createInstance(Ci.nsIInputStreamPump);
+
+ pump.init(stream, -1, -1, 0, 0, false);
+ pump.asyncRead(dataListener, null);
+ return new SocketAbortable(transport);
+ } catch (e) { _error(e); }
+ return null;
+}
+
+function SocketAbortable(transport)
+{
+ Abortable.call(this);
+ assert(transport instanceof Ci.nsITransport, "need transport");
+ this._transport = transport;
+}
+SocketAbortable.prototype = Object.create(Abortable.prototype);
+SocketAbortable.prototype.constructor = UserCancelledException;
+SocketAbortable.prototype.cancel = function(ex)
+{
+ try {
+ this._transport.close(Components.results.NS_ERROR_ABORT);
+ } catch (e) {
+ ddump("canceling socket failed: " + e);
+ }
+}
+
diff --git a/mailnews/base/prefs/content/accountcreation/readFromXML.js b/mailnews/base/prefs/content/accountcreation/readFromXML.js
new file mode 100644
index 000000000..c7e796f5f
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/readFromXML.js
@@ -0,0 +1,238 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * Takes an XML snipplet (as JXON) and reads the values into
+ * a new AccountConfig object.
+ * It does so securely (or tries to), by trying to avoid remote execution
+ * and similar holes which can appear when reading too naively.
+ * Of course it cannot tell whether the actual values are correct,
+ * e.g. it can't tell whether the host name is a good server.
+ *
+ * The XML format is documented at
+ * <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>
+ *
+ * @param clientConfigXML {JXON} The <clientConfig> node.
+ * @return AccountConfig object filled with the data from XML
+ */
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+function readFromXML(clientConfigXML)
+{
+ function array_or_undef(value) {
+ return value === undefined ? [] : value;
+ }
+ var exception;
+ if (typeof(clientConfigXML) != "object" ||
+ !("clientConfig" in clientConfigXML) ||
+ !("emailProvider" in clientConfigXML.clientConfig))
+ {
+ dump("client config xml = " + JSON.stringify(clientConfigXML) + "\n");
+ var stringBundle = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties");
+ throw stringBundle.GetStringFromName("no_emailProvider.error");
+ }
+ var xml = clientConfigXML.clientConfig.emailProvider;
+
+ var d = new AccountConfig();
+ d.source = AccountConfig.kSourceXML;
+
+ d.id = sanitize.hostname(xml["@id"]);
+ d.displayName = d.id;
+ try {
+ d.displayName = sanitize.label(xml.displayName);
+ } catch (e) { logException(e); }
+ for (var domain of xml.$domain)
+ {
+ try {
+ d.domains.push(sanitize.hostname(domain));
+ } catch (e) { logException(e); exception = e; }
+ }
+ if (d.domains.length == 0)
+ throw exception ? exception : "need proper <domain> in XML";
+ exception = null;
+
+ // incoming server
+ for (let iX of array_or_undef(xml.$incomingServer)) // input (XML)
+ {
+ let iO = d.createNewIncoming(); // output (object)
+ try {
+ // throws if not supported
+ iO.type = sanitize.enum(iX["@type"], ["pop3", "imap", "nntp"]);
+ iO.hostname = sanitize.hostname(iX.hostname);
+ iO.port = sanitize.integerRange(iX.port, kMinPort, kMaxPort);
+ // We need a username even for Kerberos, need it even internally.
+ iO.username = sanitize.string(iX.username); // may be a %VARIABLE%
+
+ if ("password" in iX) {
+ d.rememberPassword = true;
+ iO.password = sanitize.string(iX.password);
+ }
+
+ for (let iXsocketType of array_or_undef(iX.$socketType))
+ {
+ try {
+ iO.socketType = sanitize.translate(iXsocketType,
+ { plain : 1, SSL: 2, STARTTLS: 3 });
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!iO.socketType)
+ throw exception ? exception : "need proper <socketType> in XML";
+ exception = null;
+
+ for (let iXauth of array_or_undef(iX.$authentication))
+ {
+ try {
+ iO.auth = sanitize.translate(iXauth,
+ { "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
+ // @deprecated TODO remove
+ "plain" : Ci.nsMsgAuthMethod.passwordCleartext,
+ "password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ // @deprecated TODO remove
+ "secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ "GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
+ "NTLM" : Ci.nsMsgAuthMethod.NTLM,
+ "OAuth2" : Ci.nsMsgAuthMethod.OAuth2 });
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!iO.auth)
+ throw exception ? exception : "need proper <authentication> in XML";
+ exception = null;
+
+ // defaults are in accountConfig.js
+ if (iO.type == "pop3" && "pop3" in iX)
+ {
+ try {
+ if ("leaveMessagesOnServer" in iX.pop3)
+ iO.leaveMessagesOnServer =
+ sanitize.boolean(iX.pop3.leaveMessagesOnServer);
+ if ("daysToLeaveMessagesOnServer" in iX.pop3)
+ iO.daysToLeaveMessagesOnServer =
+ sanitize.integer(iX.pop3.daysToLeaveMessagesOnServer);
+ } catch (e) { logException(e); }
+ try {
+ if ("downloadOnBiff" in iX.pop3)
+ iO.downloadOnBiff = sanitize.boolean(iX.pop3.downloadOnBiff);
+ } catch (e) { logException(e); }
+ }
+
+ // processed successfully, now add to result object
+ if (!d.incoming.hostname) // first valid
+ d.incoming = iO;
+ else
+ d.incomingAlternatives.push(iO);
+ } catch (e) { exception = e; }
+ }
+ if (!d.incoming.hostname)
+ // throw exception for last server
+ throw exception ? exception : "Need proper <incomingServer> in XML file";
+ exception = null;
+
+ // outgoing server
+ for (let oX of array_or_undef(xml.$outgoingServer)) // input (XML)
+ {
+ let oO = d.createNewOutgoing(); // output (object)
+ try {
+ if (oX["@type"] != "smtp")
+ {
+ var stringBundle = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties");
+ throw stringBundle.GetStringFromName("outgoing_not_smtp.error");
+ }
+ oO.hostname = sanitize.hostname(oX.hostname);
+ oO.port = sanitize.integerRange(oX.port, kMinPort, kMaxPort);
+
+ for (let oXsocketType of array_or_undef(oX.$socketType))
+ {
+ try {
+ oO.socketType = sanitize.translate(oXsocketType,
+ { plain : 1, SSL: 2, STARTTLS: 3 });
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!oO.socketType)
+ throw exception ? exception : "need proper <socketType> in XML";
+ exception = null;
+
+ for (let oXauth of array_or_undef(oX.$authentication))
+ {
+ try {
+ oO.auth = sanitize.translate(oXauth,
+ { // open relay
+ "none" : Ci.nsMsgAuthMethod.none,
+ // inside ISP or corp network
+ "client-IP-address" : Ci.nsMsgAuthMethod.none,
+ // hope for the best
+ "smtp-after-pop" : Ci.nsMsgAuthMethod.none,
+ "password-cleartext" : Ci.nsMsgAuthMethod.passwordCleartext,
+ // @deprecated TODO remove
+ "plain" : Ci.nsMsgAuthMethod.passwordCleartext,
+ "password-encrypted" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ // @deprecated TODO remove
+ "secure" : Ci.nsMsgAuthMethod.passwordEncrypted,
+ "GSSAPI" : Ci.nsMsgAuthMethod.GSSAPI,
+ "NTLM" : Ci.nsMsgAuthMethod.NTLM,
+ "OAuth2" : Ci.nsMsgAuthMethod.OAuth2,
+ });
+
+ break; // take first that we support
+ } catch (e) { exception = e; }
+ }
+ if (!oO.auth)
+ throw exception ? exception : "need proper <authentication> in XML";
+ exception = null;
+
+ if ("username" in oX ||
+ // if password-based auth, we need a username,
+ // so go there anyways and throw.
+ oO.auth == Ci.nsMsgAuthMethod.passwordCleartext ||
+ oO.auth == Ci.nsMsgAuthMethod.passwordEncrypted)
+ oO.username = sanitize.string(oX.username);
+
+ if ("password" in oX) {
+ d.rememberPassword = true;
+ oO.password = sanitize.string(oX.password);
+ }
+
+ try {
+ // defaults are in accountConfig.js
+ if ("addThisServer" in oX)
+ oO.addThisServer = sanitize.boolean(oX.addThisServer);
+ if ("useGlobalPreferredServer" in oX)
+ oO.useGlobalPreferredServer =
+ sanitize.boolean(oX.useGlobalPreferredServer);
+ } catch (e) { logException(e); }
+
+ // processed successfully, now add to result object
+ if (!d.outgoing.hostname) // first valid
+ d.outgoing = oO;
+ else
+ d.outgoingAlternatives.push(oO);
+ } catch (e) { logException(e); exception = e; }
+ }
+ if (!d.outgoing.hostname)
+ // throw exception for last server
+ throw exception ? exception : "Need proper <outgoingServer> in XML file";
+ exception = null;
+
+ d.inputFields = new Array();
+ for (let inputField of array_or_undef(xml.$inputField))
+ {
+ try {
+ var fieldset =
+ {
+ varname : sanitize.alphanumdash(inputField["@key"]).toUpperCase(),
+ displayName : sanitize.label(inputField["@label"]),
+ exampleValue : sanitize.label(inputField.value)
+ };
+ d.inputFields.push(fieldset);
+ } catch (e) { logException(e); } // for now, don't throw,
+ // because we don't support custom fields yet anyways.
+ }
+
+ return d;
+}
diff --git a/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js b/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
new file mode 100644
index 000000000..0f95f78d1
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/sanitizeDatatypes.js
@@ -0,0 +1,207 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * This is a generic input validation lib. Use it when you process
+ * data from the network.
+ *
+ * Just a few functions which verify, for security purposes, that the
+ * input variables (strings, if nothing else is noted) are of the expected
+ * type and syntax.
+ *
+ * The functions take a string (unless noted otherwise) and return
+ * the expected datatype in JS types. If the value is not as expected,
+ * they throw exceptions.
+ */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+var sanitize =
+{
+ integer : function(unchecked)
+ {
+ if (typeof(unchecked) == "number" && !isNaN(unchecked))
+ return unchecked;
+
+ var r = parseInt(unchecked);
+ if (isNaN(r))
+ throw new MalformedException("no_number.error", unchecked);
+
+ return r;
+ },
+
+ integerRange : function(unchecked, min, max)
+ {
+ var int = this.integer(unchecked);
+ if (int < min)
+ throw new MalformedException("number_too_small.error", unchecked);
+
+ if (int > max)
+ throw new MalformedException("number_too_large.error", unchecked);
+
+ return int;
+ },
+
+ boolean : function(unchecked)
+ {
+ if (typeof(unchecked) == "boolean")
+ return unchecked;
+
+ if (unchecked == "true")
+ return true;
+
+ if (unchecked == "false")
+ return false;
+
+ throw new MalformedException("boolean.error", unchecked);
+ },
+
+ string : function(unchecked)
+ {
+ return String(unchecked);
+ },
+
+ nonemptystring : function(unchecked)
+ {
+ if (!unchecked)
+ throw new MalformedException("string_empty.error", unchecked);
+
+ return this.string(unchecked);
+ },
+
+ /**
+ * Allow only letters, numbers, "-" and "_".
+ *
+ * Empty strings not allowed (good idea?).
+ */
+ alphanumdash : function(unchecked)
+ {
+ var str = this.nonemptystring(unchecked);
+ if (!/^[a-zA-Z0-9\-\_]*$/.test(str))
+ throw new MalformedException("alphanumdash.error", unchecked);
+
+ return str;
+ },
+
+ /**
+ * DNS hostnames like foo.bar.example.com
+ * Allow only letters, numbers, "-" and "."
+ * Empty strings not allowed.
+ * Currently does not support IDN (international domain names).
+ */
+ hostname : function(unchecked)
+ {
+ let str = cleanUpHostName(this.nonemptystring(unchecked));
+
+ // Allow placeholders. TODO move to a new hostnameOrPlaceholder()
+ // The regex is "anything, followed by one or more (placeholders than
+ // anything)". This doesn't catch the non-placeholder case, but that's
+ // handled down below.
+ if (/^[a-zA-Z0-9\-\.]*(%[A-Z0-9]+%[a-zA-Z0-9\-\.]*)+$/.test(str))
+ return str;
+
+ if (!isLegalHostNameOrIP(str))
+ throw new MalformedException("hostname_syntax.error", unchecked);
+
+ return str.toLowerCase();
+ },
+ /**
+ * A non-chrome URL that's safe to request.
+ */
+ url : function (unchecked)
+ {
+ var str = this.string(unchecked);
+ if (!str.startsWith("http") && !str.startsWith("https"))
+ throw new MalformedException("url_scheme.error", unchecked);
+
+ var uri;
+ try {
+ uri = Services.io.newURI(str, null, null);
+ uri = uri.QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ throw new MalformedException("url_parsing.error", unchecked);
+ }
+
+ if (uri.scheme != "http" && uri.scheme != "https")
+ throw new MalformedException("url_scheme.error", unchecked);
+
+ return uri.spec;
+ },
+
+ /**
+ * A value which should be shown to the user in the UI as label
+ */
+ label : function(unchecked)
+ {
+ return this.string(unchecked);
+ },
+
+ /**
+ * Allows only certain values as input, otherwise throw.
+ *
+ * @param unchecked {Any} The value to check
+ * @param allowedValues {Array} List of values that |unchecked| may have.
+ * @param defaultValue {Any} (Optional) If |unchecked| does not match
+ * anything in |mapping|, a |defaultValue| can be returned instead of
+ * throwing an exception. The latter is the default and happens when
+ * no |defaultValue| is passed.
+ * @throws MalformedException
+ */
+ enum : function(unchecked, allowedValues, defaultValue)
+ {
+ for (let allowedValue of allowedValues)
+ {
+ if (allowedValue == unchecked)
+ return allowedValue;
+ }
+ // value is bad
+ if (typeof(defaultValue) == "undefined")
+ throw new MalformedException("allowed_value.error", unchecked);
+ return defaultValue;
+ },
+
+ /**
+ * Like enum, allows only certain (string) values as input, but allows the
+ * caller to specify another value to return instead of the input value. E.g.,
+ * if unchecked == "foo", return 1, if unchecked == "bar", return 2,
+ * otherwise throw. This allows to translate string enums into integer enums.
+ *
+ * @param unchecked {Any} The value to check
+ * @param mapping {Object} Associative array. property name is the input
+ * value, property value is the output value. E.g. the example above
+ * would be: { foo: 1, bar : 2 }.
+ * Use quotes when you need freaky characters: "baz-" : 3.
+ * @param defaultValue {Any} (Optional) If |unchecked| does not match
+ * anything in |mapping|, a |defaultValue| can be returned instead of
+ * throwing an exception. The latter is the default and happens when
+ * no |defaultValue| is passed.
+ * @throws MalformedException
+ */
+ translate : function(unchecked, mapping, defaultValue)
+ {
+ for (var inputValue in mapping)
+ {
+ if (inputValue == unchecked)
+ return mapping[inputValue];
+ }
+ // value is bad
+ if (typeof(defaultValue) == "undefined")
+ throw new MalformedException("allowed_value.error", unchecked);
+ return defaultValue;
+ }
+};
+
+function MalformedException(msgID, uncheckedBadValue)
+{
+ var stringBundle = getStringBundle(
+ "chrome://messenger/locale/accountCreationUtil.properties");
+ var msg = stringBundle.GetStringFromName(msgID);
+ if (kDebug)
+ msg += " (bad value: " + new String(uncheckedBadValue) + ")";
+ Exception.call(this, msg);
+}
+MalformedException.prototype = Object.create(Exception.prototype);
+MalformedException.prototype.constructor = MalformedException;
+
diff --git a/mailnews/base/prefs/content/accountcreation/util.js b/mailnews/base/prefs/content/accountcreation/util.js
new file mode 100644
index 000000000..d867bfbe9
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/util.js
@@ -0,0 +1,304 @@
+/* -*- Mode: Java; 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/. */
+/**
+ * Some common, generic functions
+ */
+
+try {
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+} catch (e) { ddump(e); } // if already declared, as in xpcshell-tests
+try {
+ var Cu = Components.utils;
+} catch (e) { ddump(e); }
+
+Cu.import("resource:///modules/errUtils.js");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function assert(test, errorMsg)
+{
+ if (!test)
+ throw new NotReached(errorMsg ? errorMsg :
+ "Programming bug. Assertion failed, see log.");
+}
+
+function makeCallback(obj, func)
+{
+ return function()
+ {
+ return func.apply(obj, arguments);
+ }
+}
+
+
+/**
+ * Runs the given function sometime later
+ *
+ * Currently implemented using setTimeout(), but
+ * can later be replaced with an nsITimer impl,
+ * when code wants to use it in a module.
+ */
+function runAsync(func)
+{
+ setTimeout(func, 0);
+}
+
+
+/**
+ * @param uriStr {String}
+ * @result {nsIURI}
+ */
+function makeNSIURI(uriStr)
+{
+ return Services.io.newURI(uriStr, null, null);
+}
+
+
+/**
+ * Reads UTF8 data from a URL.
+ *
+ * @param uri {nsIURI} what you want to read
+ * @return {Array of String} the contents of the file, one string per line
+ */
+function readURLasUTF8(uri)
+{
+ assert(uri instanceof Ci.nsIURI, "uri must be an nsIURI");
+ try {
+ let chan = Services.io.newChannelFromURI2(uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let is = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ is.init(chan.open(), "UTF-8", 1024,
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+
+ let content = "";
+ let strOut = new Object();
+ try {
+ while (is.readString(1024, strOut) != 0)
+ content += strOut.value;
+ // catch in outer try/catch
+ } finally {
+ is.close();
+ }
+
+ return content;
+ } catch (e) {
+ // TODO this has a numeric error message. We need to ship translations
+ // into human language.
+ throw e;
+ }
+}
+
+/**
+ * Takes a string (which is typically the content of a file,
+ * e.g. the result returned from readURLUTF8() ), and splits
+ * it into lines, and returns an array with one string per line
+ *
+ * Linebreaks are not contained in the result,,
+ * and all of \r\n, (Windows) \r (Mac) and \n (Unix) counts as linebreak.
+ *
+ * @param content {String} one long string with the whole file
+ * @return {Array of String} one string per line (no linebreaks)
+ */
+function splitLines(content)
+{
+ content = content.replace("\r\n", "\n");
+ content = content.replace("\r", "\n");
+ return content.split("\n");
+}
+
+/**
+ * @param bundleURI {String} chrome URL to properties file
+ * @return nsIStringBundle
+ */
+function getStringBundle(bundleURI)
+{
+ try {
+ return Services.strings.createBundle(bundleURI);
+ } catch (e) {
+ throw new Exception("Failed to get stringbundle URI <" + bundleURI +
+ ">. Error: " + e);
+ }
+}
+
+
+function Exception(msg)
+{
+ this._message = msg;
+
+ // get stack
+ try {
+ not.found.here += 1; // force a native exception ...
+ } catch (e) {
+ this.stack = e.stack; // ... to get the current stack
+ }
+}
+Exception.prototype =
+{
+ get message()
+ {
+ return this._message;
+ },
+ toString : function()
+ {
+ return this._message;
+ }
+}
+
+function NotReached(msg)
+{
+ Exception.call(this, msg); // call super constructor
+ logException(this);
+}
+// Make NotReached extend Exception.
+NotReached.prototype = Object.create(Exception.prototype);
+NotReached.prototype.constructor = NotReached;
+
+/**
+ * A handle for an async function which you can cancel.
+ * The async function will return an object of this type (a subtype)
+ * and you can call cancel() when you feel like killing the function.
+ */
+function Abortable()
+{
+}
+Abortable.prototype =
+{
+ cancel : function()
+ {
+ }
+}
+
+/**
+ * Utility implementation, for allowing to abort a setTimeout.
+ * Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
+ * @param setTimeoutID {Integer} Return value of setTimeout()
+ */
+function TimeoutAbortable(setTimeoutID)
+{
+ Abortable.call(this, setTimeoutID); // call super constructor
+ this._id = setTimeoutID;
+}
+TimeoutAbortable.prototype = Object.create(Abortable.prototype);
+TimeoutAbortable.prototype.constructor = TimeoutAbortable;
+TimeoutAbortable.prototype.cancel = function() { clearTimeout(this._id); }
+
+/**
+ * Utility implementation, for allowing to abort a setTimeout.
+ * Use like: return new TimeoutAbortable(setTimeout(function(){ ... }, 0));
+ * @param setIntervalID {Integer} Return value of setInterval()
+ */
+function IntervalAbortable(setIntervalID)
+{
+ Abortable.call(this, setIntervalID); // call super constructor
+ this._id = setIntervalID;
+}
+IntervalAbortable.prototype = Object.create(Abortable.prototype);
+IntervalAbortable.prototype.constructor = IntervalAbortable;
+IntervalAbortable.prototype.cancel = function() { clearInterval(this._id); }
+
+// Allows you to make several network calls, but return
+// only one Abortable object.
+function SuccessiveAbortable()
+{
+ Abortable.call(this); // call super constructor
+ this._current = null;
+}
+SuccessiveAbortable.prototype = {
+ __proto__: Abortable.prototype,
+ get current() { return this._current; },
+ set current(abortable)
+ {
+ assert(abortable instanceof Abortable || abortable == null,
+ "need an Abortable object (or null)");
+ this._current = abortable;
+ },
+ cancel: function()
+ {
+ if (this._current)
+ this._current.cancel();
+ }
+}
+
+function deepCopy(org)
+{
+ if (typeof(org) == "undefined")
+ return undefined;
+ if (org == null)
+ return null;
+ if (typeof(org) == "string")
+ return org;
+ if (typeof(org) == "number")
+ return org;
+ if (typeof(org) == "boolean")
+ return org == true;
+ if (typeof(org) == "function")
+ return org;
+ if (typeof(org) != "object")
+ throw "can't copy objects of type " + typeof(org) + " yet";
+
+ //TODO still instanceof org != instanceof copy
+ //var result = new org.constructor();
+ var result = new Object();
+ if (typeof(org.length) != "undefined")
+ var result = new Array();
+ for (var prop in org)
+ result[prop] = deepCopy(org[prop]);
+ return result;
+}
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+function ddump(text)
+{
+ gEmailWizardLogger.info(text);
+}
+
+function debugObject(obj, name, maxDepth, curDepth)
+{
+ if (curDepth == undefined)
+ curDepth = 0;
+ if (maxDepth != undefined && curDepth > maxDepth)
+ return "";
+
+ var result = "";
+ var i = 0;
+ for (let prop in obj)
+ {
+ i++;
+ try {
+ if (typeof(obj[prop]) == "object")
+ {
+ if (obj[prop] && obj[prop].length != undefined)
+ result += name + "." + prop + "=[probably array, length " +
+ obj[prop].length + "]\n";
+ else
+ result += name + "." + prop + "=[" + typeof(obj[prop]) + "]\n";
+ result += debugObject(obj[prop], name + "." + prop,
+ maxDepth, curDepth + 1);
+ }
+ else if (typeof(obj[prop]) == "function")
+ result += name + "." + prop + "=[function]\n";
+ else
+ result += name + "." + prop + "=" + obj[prop] + "\n";
+ } catch (e) {
+ result += name + "." + prop + "-> Exception(" + e + ")\n";
+ }
+ }
+ if (!i)
+ result += name + " is empty\n";
+ return result;
+}
+
+function alertPrompt(alertTitle, alertMsg)
+{
+ Services.prompt.alert(window, alertTitle, alertMsg);
+}
diff --git a/mailnews/base/prefs/content/accountcreation/verifyConfig.js b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
new file mode 100644
index 000000000..a2afbdad8
--- /dev/null
+++ b/mailnews/base/prefs/content/accountcreation/verifyConfig.js
@@ -0,0 +1,347 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * This checks a given config, by trying a real connection and login,
+ * with username and password.
+ *
+ * TODO
+ * - give specific errors, bug 555448
+ * - return a working |Abortable| to allow cancel
+ *
+ * @param accountConfig {AccountConfig} The guessed account config.
+ * username, password, realname, emailaddress etc. are not filled out,
+ * but placeholders to be filled out via replaceVariables().
+ * @param alter {boolean}
+ * Try other usernames and login schemes, until login works.
+ * Warning: Modifies |accountConfig|.
+ *
+ * This function is async.
+ * @param successCallback function(accountConfig)
+ * Called when we could guess the config.
+ * For accountConfig, see below.
+ * @param errorCallback function(ex)
+ * Called when we could guess not the config, either
+ * because we have not found anything or
+ * because there was an error (e.g. no network connection).
+ * The ex.message will contain a user-presentable message.
+ */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/OAuth2Providers.jsm");
+
+if (typeof gEmailWizardLogger == "undefined") {
+ Cu.import("resource:///modules/gloda/log4moz.js");
+ var gEmailWizardLogger = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+
+function verifyConfig(config, alter, msgWindow, successCallback, errorCallback)
+{
+ ddump(debugObject(config, "config", 3));
+ assert(config instanceof AccountConfig,
+ "BUG: Arg 'config' needs to be an AccountConfig object");
+ assert(typeof(alter) == "boolean");
+ assert(typeof(successCallback) == "function");
+ assert(typeof(errorCallback) == "function");
+
+ if (MailServices.accounts.findRealServer(config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type,
+ ["pop3", "imap", "nntp"]),
+ config.incoming.port)) {
+ errorCallback("Incoming server exists");
+ return;
+ }
+
+ // incoming server
+ let inServer =
+ MailServices.accounts.createIncomingServer(config.incoming.username,
+ config.incoming.hostname,
+ sanitize.enum(config.incoming.type,
+ ["pop3", "imap", "nntp"]));
+ inServer.port = config.incoming.port;
+ inServer.password = config.incoming.password;
+ if (config.incoming.socketType == 1) // plain
+ inServer.socketType = Ci.nsMsgSocketType.plain;
+ else if (config.incoming.socketType == 2) // SSL
+ inServer.socketType = Ci.nsMsgSocketType.SSL;
+ else if (config.incoming.socketType == 3) // STARTTLS
+ inServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ gEmailWizardLogger.info("Setting incoming server authMethod to " +
+ config.incoming.auth);
+ inServer.authMethod = config.incoming.auth;
+
+ try {
+ // Lookup issuer if needed.
+ if (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2 ||
+ config.outgoing.auth == Ci.nsMsgAuthMethod.OAuth2) {
+ if (!config.oauthSettings)
+ config.oauthSettings = {};
+ if (!config.oauthSettings.issuer || !config.oauthSettings.scope) {
+ // lookup issuer or scope from hostname
+ let hostname = (config.incoming.auth == Ci.nsMsgAuthMethod.OAuth2) ?
+ config.incoming.hostname : config.outgoing.hostname;
+ let hostDetails = OAuth2Providers.getHostnameDetails(hostname);
+ if (hostDetails)
+ [config.oauthSettings.issuer, config.oauthSettings.scope] = hostDetails;
+ if (!config.oauthSettings.issuer || !config.oauthSettings.scope)
+ throw "Could not get issuer for oauth2 authentication";
+ }
+ gEmailWizardLogger.info("Saving oauth parameters for issuer " +
+ config.oauthSettings.issuer);
+ inServer.setCharValue("oauth2.scope", config.oauthSettings.scope);
+ inServer.setCharValue("oauth2.issuer", config.oauthSettings.issuer);
+ gEmailWizardLogger.info("OAuth2 issuer, scope is " +
+ config.oauthSettings.issuer + ", " + config.oauthSettings.scope);
+ }
+
+ if (inServer.password ||
+ inServer.authMethod == Ci.nsMsgAuthMethod.OAuth2)
+ verifyLogon(config, inServer, alter, msgWindow,
+ successCallback, errorCallback);
+ else {
+ // Avoid pref pollution, clear out server prefs.
+ MailServices.accounts.removeIncomingServer(inServer, true);
+ successCallback(config);
+ }
+ return;
+ }
+ catch (e) {
+ gEmailWizardLogger.error("ERROR: verify logon shouldn't have failed");
+ }
+ // Avoid pref pollution, clear out server prefs.
+ MailServices.accounts.removeIncomingServer(inServer, true);
+ errorCallback(e);
+}
+
+function verifyLogon(config, inServer, alter, msgWindow, successCallback,
+ errorCallback)
+{
+ gEmailWizardLogger.info("verifyLogon for server at " + inServer.hostName);
+ // hack - save away the old callbacks.
+ let saveCallbacks = msgWindow.notificationCallbacks;
+ // set our own callbacks - this works because verifyLogon will
+ // synchronously create the transport and use the notification callbacks.
+ let listener = new urlListener(config, inServer, alter, msgWindow,
+ successCallback, errorCallback);
+ // our listener listens both for the url and cert errors.
+ msgWindow.notificationCallbacks = listener;
+ // try to work around bug where backend is clearing password.
+ try {
+ inServer.password = config.incoming.password;
+ let uri = inServer.verifyLogon(listener, msgWindow);
+ // clear msgWindow so url won't prompt for passwords.
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = null;
+ }
+ catch (e) { gEmailWizardLogger.error("verifyLogon failed: " + e); throw e;}
+ finally {
+ // restore them
+ msgWindow.notificationCallbacks = saveCallbacks;
+ }
+}
+
+/**
+ * The url listener also implements nsIBadCertListener2. Its job is to prevent
+ * "bad cert" security dialogs from being shown to the user. Currently it puts
+ * up the cert override dialog, though we'd like to give the user more detailed
+ * information in the future.
+ */
+
+function urlListener(config, server, alter, msgWindow, successCallback,
+ errorCallback)
+{
+ this.mConfig = config;
+ this.mServer = server;
+ this.mAlter = alter;
+ this.mSuccessCallback = successCallback;
+ this.mErrorCallback = errorCallback;
+ this.mMsgWindow = msgWindow;
+ this.mCertError = false;
+ this._log = Log4Moz.getConfiguredLogger("mail.wizard");
+}
+urlListener.prototype =
+{
+ OnStartRunningUrl: function(aUrl)
+ {
+ this._log.info("Starting to test username");
+ this._log.info(" username=" + (this.mConfig.incoming.username !=
+ this.mConfig.identity.emailAddress) +
+ ", have savedUsername=" +
+ (this.mConfig.usernameSaved ? "true" : "false"));
+ this._log.info(" authMethod=" + this.mServer.authMethod);
+ },
+
+ OnStopRunningUrl: function(aUrl, aExitCode)
+ {
+ this._log.info("Finished verifyConfig resulted in " + aExitCode);
+ if (Components.isSuccessCode(aExitCode))
+ {
+ this._cleanup();
+ this.mSuccessCallback(this.mConfig);
+ }
+ // Logon failed, and we aren't supposed to try other variations.
+ else if (!this.mAlter)
+ {
+ this._cleanup();
+ var errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ }
+ // Try other variations, unless there's a cert error, in which
+ // case we'll see what the user chooses.
+ else if (!this.mCertError)
+ {
+ this.tryNextLogon()
+ }
+ },
+
+ tryNextLogon: function()
+ {
+ this._log.info("tryNextLogon()");
+ this._log.info(" username=" + (this.mConfig.incoming.username !=
+ this.mConfig.identity.emailAddress) +
+ ", have savedUsername=" +
+ (this.mConfig.usernameSaved ? "true" : "false"));
+ this._log.info(" authMethod=" + this.mServer.authMethod);
+ // check if we tried full email address as username
+ if (this.mConfig.incoming.username != this.mConfig.identity.emailAddress)
+ {
+ this._log.info(" Changing username to email address.");
+ this.mConfig.usernameSaved = this.mConfig.incoming.username;
+ this.mConfig.incoming.username = this.mConfig.identity.emailAddress;
+ this.mConfig.outgoing.username = this.mConfig.identity.emailAddress;
+ this.mServer.username = this.mConfig.incoming.username;
+ this.mServer.password = this.mConfig.incoming.password;
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ return;
+ }
+
+ if (this.mConfig.usernameSaved)
+ {
+ this._log.info(" Re-setting username.");
+ // If we tried the full email address as the username, then let's go
+ // back to trying just the username before trying the other cases.
+ this.mConfig.incoming.username = this.mConfig.usernameSaved;
+ this.mConfig.outgoing.username = this.mConfig.usernameSaved;
+ this.mConfig.usernameSaved = null;
+ this.mServer.username = this.mConfig.incoming.username;
+ this.mServer.password = this.mConfig.incoming.password;
+ }
+
+ // sec auth seems to have failed, and we've tried both
+ // varieties of user name, sadly.
+ // So fall back to non-secure auth, and
+ // again try the user name and email address as username
+ assert(this.mConfig.incoming.auth == this.mServer.authMethod);
+ this._log.info(" Using SSL: " +
+ (this.mServer.socketType == Ci.nsMsgSocketType.SSL ||
+ this.mServer.socketType == Ci.nsMsgSocketType.alwaysSTARTTLS));
+ if (this.mConfig.incoming.authAlternatives &&
+ this.mConfig.incoming.authAlternatives.length)
+ // We may be dropping back to insecure auth methods here,
+ // which is not good. But then again, we already warned the user,
+ // if it is a config without SSL.
+ {
+ this._log.info(" auth alternatives = " +
+ this.mConfig.incoming.authAlternatives.join(","));
+ this._log.info(" Decreasing auth.");
+ this._log.info(" Have password: " +
+ (this.mServer.password ? "true" : "false"));
+ let brokenAuth = this.mConfig.incoming.auth;
+ // take the next best method (compare chooseBestAuthMethod() in guess)
+ this.mConfig.incoming.auth =
+ this.mConfig.incoming.authAlternatives.shift();
+ this.mServer.authMethod = this.mConfig.incoming.auth;
+ // Assume that SMTP server has same methods working as incoming.
+ // Broken assumption, but we currently have no SMTP verification.
+ // TODO implement real SMTP verification
+ if (this.mConfig.outgoing.auth == brokenAuth &&
+ this.mConfig.outgoing.authAlternatives.indexOf(
+ this.mConfig.incoming.auth) != -1)
+ this.mConfig.outgoing.auth = this.mConfig.incoming.auth;
+ this._log.info(" outgoing auth: " + this.mConfig.outgoing.auth);
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ return;
+ }
+
+ // Tried all variations we can. Give up.
+ this._log.info("Giving up.");
+ this._cleanup();
+ let errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ return;
+ },
+
+ _cleanup : function()
+ {
+ try {
+ // Avoid pref pollution, clear out server prefs.
+ if (this.mServer) {
+ MailServices.accounts.removeIncomingServer(this.mServer, true);
+ this.mServer = null;
+ }
+ } catch (e) { this._log.error(e); }
+ },
+
+ // Suppress any certificate errors
+ notifyCertProblem: function(socketInfo, status, targetSite) {
+ this.mCertError = true;
+ this._log.error("cert error");
+ let self = this;
+ setTimeout(function () {
+ try {
+ self.informUserOfCertError(socketInfo, status, targetSite);
+ } catch (e) { logException(e); }
+ }, 0);
+ return true;
+ },
+
+ informUserOfCertError : function(socketInfo, status, targetSite) {
+ var params = {
+ exceptionAdded : false,
+ sslStatus : status,
+ prefetchCert : true,
+ location : targetSite,
+ };
+ window.openDialog("chrome://pippki/content/exceptionDialog.xul",
+ "","chrome,centerscreen,modal", params);
+ this._log.info("cert exception dialog closed");
+ this._log.info("cert exceptionAdded = " + params.exceptionAdded);
+ if (!params.exceptionAdded) {
+ this._cleanup();
+ let errorMsg = getStringBundle(
+ "chrome://messenger/locale/accountCreationModel.properties")
+ .GetStringFromName("cannot_login.error");
+ this.mErrorCallback(new Exception(errorMsg));
+ }
+ else {
+ // Retry the logon now that we've added the cert exception.
+ verifyLogon(this.mConfig, this.mServer, this.mAlter, this.mMsgWindow,
+ this.mSuccessCallback, this.mErrorCallback);
+ }
+ },
+
+ // nsIInterfaceRequestor
+ getInterface: function(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsISupports
+ QueryInterface: function(iid) {
+ if (!iid.equals(Components.interfaces.nsIBadCertListener2) &&
+ !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
+ !iid.equals(Components.interfaces.nsIUrlListener) &&
+ !iid.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ }
+}
diff --git a/mailnews/base/prefs/content/am-addressing.js b/mailnews/base/prefs/content/am-addressing.js
new file mode 100644
index 000000000..9f2584767
--- /dev/null
+++ b/mailnews/base/prefs/content/am-addressing.js
@@ -0,0 +1,79 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function onLoad()
+{
+ parent.onPanelLoaded('am-addressing.xul');
+}
+
+function onInit(aPageId, aServerId)
+{
+ onInitCompositionAndAddressing();
+}
+
+function onInitCompositionAndAddressing()
+{
+ LDAPenabling();
+ quoteEnabling();
+}
+
+function onEditDirectories()
+{
+ window.openDialog("chrome://messenger/content/addressbook/pref-editdirectories.xul",
+ "editDirectories", "chrome,modal=yes,resizable=no", null);
+}
+
+function onPreInit(account, accountValues)
+{
+}
+
+function LDAPenabling()
+{
+ onCheckItem("identity.directoryServer", ["directories"]);
+ onCheckItem("editButton", ["directories"]);
+}
+
+function quoteEnabling()
+{
+ var quotebox = document.getElementById("thenBox");
+ var placebox = document.getElementById("placeBox");
+ var quotecheck = document.getElementById("identity.autoQuote");
+
+ if (quotecheck.checked && !quotecheck.disabled &&
+ (document.getElementById("identity.replyOnTop").value == 1)) {
+ placebox.firstChild.removeAttribute("disabled");
+ placebox.lastChild.removeAttribute("disabled");
+ }
+ else {
+ placebox.firstChild.setAttribute("disabled", "true");
+ placebox.lastChild.setAttribute("disabled", "true");
+ }
+ if (quotecheck.checked && !quotecheck.disabled) {
+ quotebox.firstChild.removeAttribute("disabled");
+ quotebox.lastChild.removeAttribute("disabled");
+ }
+ else {
+ quotebox.firstChild.setAttribute("disabled", "true");
+ quotebox.lastChild.setAttribute("disabled", "true");
+ }
+}
+
+/**
+ * Open the Preferences dialog on the tab with Addressing options.
+ */
+function showGlobalAddressingPrefs()
+{
+ openPrefsFromAccountManager("paneCompose", "addressingTab", null, "addressing_pane");
+}
+
+/**
+ * Open the Preferences dialog on the tab with Composing options.
+ */
+function showGlobalComposingPrefs()
+{
+ openPrefsFromAccountManager("paneCompose", "generalTab", null, "composing_messages_pane");
+}
diff --git a/mailnews/base/prefs/content/am-addressing.xul b/mailnews/base/prefs/content/am-addressing.xul
new file mode 100644
index 000000000..91eae4aac
--- /dev/null
+++ b/mailnews/base/prefs/content/am-addressing.xul
@@ -0,0 +1,18 @@
+<?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/. -->
+
+<?xul-overlay href="chrome://messenger/content/am-addressingOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-addressing.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&addressing.label;"
+ onload="onLoad();">
+ <vbox flex="1" style="overflow: auto;">
+ <dialogheader title="&addressing.label;"/>
+ <vbox id="compositionAndAddressing"/>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-addressingOverlay.xul b/mailnews/base/prefs/content/am-addressingOverlay.xul
new file mode 100644
index 000000000..a5f3c1582
--- /dev/null
+++ b/mailnews/base/prefs/content/am-addressingOverlay.xul
@@ -0,0 +1,135 @@
+<?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/content/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-addressing.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-addressing.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+
+ <vbox flex="1" id="compositionAndAddressing">
+ <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
+
+ <groupbox>
+ <caption label="&compositionGroupTitle.label;"/>
+ <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;"
+ oncommand="quoteEnabling();" accesskey="&autoQuote.accesskey;"
+ pref="true" preftype="bool" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.auto_quote"/>
+ </hbox>
+ <hbox class="indent" align="center" id="thenBox">
+ <label value="&then.label;" accesskey="&then.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="start">
+ <button id="globalComposingPrefsLink"
+ label="&globalComposingPrefs.label;"
+ accesskey="&globalComposingPrefs.accesskey;"
+ oncommand="showGlobalComposingPrefs();"/>
+ </hbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&addressingGroupTitle.label;"/>
+#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 id="identity.directoryServer"
+ wsm_persist="true" preftype="string"
+ prefstring="mail.identity.%identitykey%.directoryServer"
+ style="min-width: 16em;" aria-labelledby="directories">
+ <menupopup class="addrbooksPopup"
+ none="&directoriesNone.label;"
+ remoteonly="true" value="dirPrefId"/>
+ </menulist>
+ <button id="editButton" label="&editDirectories.label;"
+ accesskey="&editDirectories.accesskey;"
+ oncommand="onEditDirectories();"/>
+ </hbox>
+ </radiogroup>
+
+ <separator class="thin"/>
+
+ <hbox pack="start">
+ <button id="globalAddressingPrefsLink"
+ label="&globalAddressingPrefs.label;"
+ accesskey="&globalAddressingPrefs.accesskey;"
+ oncommand="showGlobalAddressingPrefs();"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+</overlay>
diff --git a/mailnews/base/prefs/content/am-archiveoptions.js b/mailnews/base/prefs/content/am-archiveoptions.js
new file mode 100644
index 000000000..ae09ae156
--- /dev/null
+++ b/mailnews/base/prefs/content/am-archiveoptions.js
@@ -0,0 +1,69 @@
+/* -*- Mode: Java; 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/. */
+
+var gIdentity = null;
+
+/**
+ * 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];
+
+ let granularity = document.getElementById("archiveGranularity");
+ granularity.selectedIndex = gIdentity.archiveGranularity;
+ granularity.addEventListener("command", updateArchiveExample, false);
+
+ let kfs = document.getElementById("archiveKeepFolderStructure");
+ kfs.checked = gIdentity.archiveKeepFolderStructure;
+ kfs.addEventListener("command", updateArchiveExample, false);
+
+ 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/mailnews/base/prefs/content/am-archiveoptions.xul b/mailnews/base/prefs/content/am-archiveoptions.xul
new file mode 100644
index 000000000..9d7ecb57c
--- /dev/null
+++ b/mailnews/base/prefs/content/am-archiveoptions.xul
@@ -0,0 +1,99 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderPane.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/am-archiveoptions.dtd" >
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="archive-options"
+ title="&dialogTitle.label;"
+ width="350" height="350"
+ persist="width height"
+ onload="onLoadArchiveOptions();"
+ ondialogaccept="onAcceptArchiveOptions();">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-archiveoptions.js"/>
+
+ <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;"/>
+
+ <groupbox flex="1">
+ <caption label="&archiveExample.label;"/>
+ <tree id="archiveTree" hidecolumnpicker="true" disabled="true" flex="1">
+ <treecols>
+ <treecol primary="true" hideheader="true" flex="1"
+ id="folderNameCol"/>
+ </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="2010"/>
+ </treerow>
+ <treechildren>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2010-11"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2010-12"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ <treeitem class="year">
+ <treerow>
+ <treecell label="2011"/>
+ </treerow>
+ <treechildren>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2011-01"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ <treeitem class="month">
+ <treerow>
+ <treecell label="2011-02"/>
+ </treerow>
+ <treechildren/>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </treeitem>
+ </treechildren>
+ </tree>
+ </groupbox>
+ </vbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/am-copies.js b/mailnews/base/prefs/content/am-copies.js
new file mode 100644
index 000000000..5fc3836a6
--- /dev/null
+++ b/mailnews/base/prefs/content/am-copies.js
@@ -0,0 +1,471 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/MailUtils.js");
+
+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");
+
+ setupCcTextbox(true);
+ setupBccTextbox(true);
+ 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. Second argument (false) signifies
+ // that 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.getFolderForURI(uri, false);
+ 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 aGroup the prefix for the menulist we're handling (e.g. "drafts")
+ * @param aType "Account" for the account picker or "Folder" for the folder
+ * picker
+ * @param 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()
+{
+ var broadcaster = document.getElementById("broadcaster_doFcc");
+
+ var checked = document.getElementById("identity.doFcc").checked;
+ if (checked) {
+ broadcaster.removeAttribute("disabled");
+ 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;
+ }
+ }
+ else
+ broadcaster.setAttribute("disabled", "true");
+}
+
+// Disable CC textbox if CC checkbox is not checked
+function setupCcTextbox(init)
+{
+ var ccChecked = document.getElementById("identity.doCc").checked;
+ var ccTextbox = document.getElementById("identity.doCcList");
+
+ ccTextbox.disabled = !ccChecked;
+
+ if (ccChecked) {
+ if (ccTextbox.value == "") {
+ ccTextbox.value = document.getElementById("identity.email").value;
+ if (!init)
+ ccTextbox.select();
+ }
+ } else if ((ccTextbox.value == document.getElementById("identity.email").value) ||
+ (init && ccTextbox.getAttribute("value") == ""))
+ ccTextbox.value = "";
+}
+
+// Disable BCC textbox if BCC checkbox is not checked
+function setupBccTextbox(init)
+{
+ var bccChecked = document.getElementById("identity.doBcc").checked;
+ var bccTextbox = document.getElementById("identity.doBccList");
+
+ bccTextbox.disabled = !bccChecked;
+
+ if (bccChecked) {
+ if (bccTextbox.value == "") {
+ bccTextbox.value = document.getElementById("identity.email").value;
+ if (!init)
+ bccTextbox.select();
+ }
+ } else if ((bccTextbox.value == document.getElementById("identity.email").value) ||
+ (init && bccTextbox.getAttribute("value") == ""))
+ bccTextbox.value = "";
+}
+
+// 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");
+ return;
+ }
+}
+
+// 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 archiveFolder the currently-selected folder to store archives in
+ */
+function updateArchiveHierarchyButton(archiveFolder) {
+ let isGmailImap = (archiveFolder.server.type == "imap" &&
+ archiveFolder.server.QueryInterface(
+ Components.interfaces.nsIImapIncomingServer)
+ .isGMailServer);
+ document.getElementById("archiveHierarchyButton").disabled = isGmailImap;
+}
+
+/**
+ * Enable or disable (as appropriate) the controls for setting archive options
+ */
+function setupArchiveItems() {
+ let broadcaster = document.getElementById("broadcaster_archiveEnabled");
+ let checked = document.getElementById("identity.archiveEnabled").checked;
+ let archiveFolder;
+
+ if (checked) {
+ broadcaster.removeAttribute("disabled");
+ 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");
+ return;
+ }
+ }
+ else
+ broadcaster.setAttribute("disabled", "true");
+}
+
+/**
+ * Open a dialog to edit the folder hierarchy used when archiving messages.
+ */
+function ChangeArchiveHierarchy() {
+ let identity = parent.gIdentity || parent.getCurrentAccount().defaultIdentity;
+
+ top.window.openDialog("chrome://messenger/content/am-archiveoptions.xul",
+ "", "centerscreen,chrome,modal,titlebar,resizable=yes",
+ identity);
+ return true;
+}
diff --git a/mailnews/base/prefs/content/am-copies.xul b/mailnews/base/prefs/content/am-copies.xul
new file mode 100644
index 000000000..dce174a22
--- /dev/null
+++ b/mailnews/base/prefs/content/am-copies.xul
@@ -0,0 +1,21 @@
+<?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/. -->
+
+
+<?xul-overlay href="chrome://messenger/content/am-copiesOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-copies.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&copyAndFolderTitle.label;"
+ onload="parent.onPanelLoaded('am-copies.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+
+ <dialogheader title="&copyAndFolderTitle.label;"/>
+ <vbox id="copiesAndFolders"/>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-copiesOverlay.xul b/mailnews/base/prefs/content/am-copiesOverlay.xul
new file mode 100644
index 000000000..4b4cdbcfb
--- /dev/null
+++ b/mailnews/base/prefs/content/am-copiesOverlay.xul
@@ -0,0 +1,311 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % copiesDTD SYSTEM "chrome://messenger/locale/am-copies.dtd">
+%copiesDTD;
+]>
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-copies.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+
+ <vbox flex="1" id="copiesAndFolders">
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+ </stringbundleset>
+
+ <broadcaster id="broadcaster_doFcc"/>
+ <broadcaster id="broadcaster_archiveEnabled"/>
+
+ <label hidden="true" wsm_persist="true" id="identity.fccFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.draftFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.draft_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.archiveFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archive_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.stationeryFolder"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.stationary_folder"/>
+ <label hidden="true" wsm_persist="true" id="identity.email"/>
+ <label hidden="true" wsm_persist="true" id="identity.fccFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_folder_picker_mode"/>
+ <label hidden="true" wsm_persist="true" id="identity.draftsFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.drafts_folder_picker_mode"/>
+ <label hidden="true" wsm_persist="true" id="identity.archivesFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archives_folder_picker_mode"/>
+ <label hidden="true" wsm_persist="true" id="identity.tmplFolderPickerMode"
+ pref="true" preftype="int" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.tmpl_folder_picker_mode"/>
+ <groupbox id="copiesGroup">
+ <caption label="&sendingPrefix.label;"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.doFcc" label="&fccMailFolder.label;"
+ accesskey="&fccMailFolder.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc"
+ oncommand="setupFccItems();"/>
+ </hbox>
+ <radiogroup id="doFcc" aria-labelledby="identity.doFcc">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="fcc_selectAccount"
+ value="0" label="&sentFolderOn.label;"
+ accesskey="&sentFolderOn.accesskey;"
+ oncommand="setPickersState('msgFccAccountPicker', 'msgFccFolderPicker', event)"
+ observes="broadcaster_doFcc"/>
+ <menulist id="msgFccAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="fcc_selectAccount"
+ observes="broadcaster_doFcc">
+ <menupopup id="msgFccAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('fcc', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="fcc_selectFolder"
+ value="1" label="&sentInOtherFolder.label;"
+ accesskey="&sentInOtherFolder.accesskey;"
+ oncommand="setPickersState('msgFccFolderPicker', 'msgFccAccountPicker', event)"
+ observes="broadcaster_doFcc"/>
+ <menulist id="msgFccFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="fcc_selectFolder"
+ displayformat="verbose"
+ observes="broadcaster_doFcc">
+ <menupopup id="msgFccFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('fcc', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+
+ <hbox align="center" class="fccReplyFollowsParent" hidable="true" hidefor="nntp,rss">
+ <checkbox wsm_persist="true" id="identity.fccReplyFollowsParent"
+ label="&fccReplyFollowsParent.label;"
+ accesskey="&fccReplyFollowsParent.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.fcc_reply_follows_parent"
+ observes="broadcaster_doFcc"/>
+ </hbox>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+
+ <rows>
+ <row align="center">
+ <checkbox wsm_persist="true" id="identity.doCc" label="&ccAddress.label;"
+ accesskey="&ccAddress.accesskey;"
+ control="identity.doCcList"
+ oncommand="setupCcTextbox();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.doCc"/>
+ <textbox wsm_persist="true" id="identity.doCcList" flex="1"
+ aria-labelledby="identity.doCc"
+ prefstring="mail.identity.%identitykey%.doCcList" class="uri-element"
+ placeholder="&ccAddressList.placeholder;"/>
+ </row>
+ <row align="center">
+ <checkbox wsm_persist="true" id="identity.doBcc" label="&bccAddress.label;"
+ accesskey="&bccAddress.accesskey;"
+ control="identity.doBccList"
+ oncommand="setupBccTextbox();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.doBcc"/>
+ <textbox wsm_persist="true" id="identity.doBccList" flex="1"
+ aria-labelledby="identity.doBcc"
+ prefstring="mail.identity.%identitykey%.doBccList" class="uri-element"
+ placeholder="&bccAddressList.placeholder;"/>
+ </row>
+ </rows>
+ </grid>
+
+ </groupbox>
+
+ <groupbox id="archivesGroup">
+ <caption label="&archivesTitle.label;"/>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.archiveEnabled"
+ label="&keepArchives.label;"
+ accesskey="&keepArchives.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.archive_enabled"
+ oncommand="setupArchiveItems();"/>
+ <spacer flex="1"/>
+ <button id="archiveHierarchyButton"
+ label="&archiveHierarchyButton.label;"
+ accesskey="&archiveHierarchyButton.accesskey;"
+ oncommand="ChangeArchiveHierarchy();"
+ observes="broadcaster_archiveEnabled"/>
+ </hbox>
+
+ <radiogroup id="messageArchives">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="archive_selectAccount"
+ value="0" label="&archivesFolderOn.label;"
+ accesskey="&archivesFolderOn.accesskey;"
+ oncommand="setPickersState('msgArchivesAccountPicker', 'msgArchivesFolderPicker', event)"
+ observes="broadcaster_archiveEnabled"/>
+ <menulist id="msgArchivesAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="archive_selectAccount"
+ observes="broadcaster_archiveEnabled">
+ <menupopup id="msgArchivesAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('archive', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="archive_selectFolder"
+ value="1" label="&archiveInOtherFolder.label;"
+ accesskey="&archiveInOtherFolder.accesskey;"
+ oncommand="setPickersState('msgArchivesFolderPicker', 'msgArchivesAccountPicker', event)"
+ observes="broadcaster_archiveEnabled"/>
+ <menulist id="msgArchivesFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="archive_selectFolder"
+ displayformat="verbose"
+ observes="broadcaster_archiveEnabled">
+ <menupopup id="msgArchivesFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('archive', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </groupbox>
+
+ <groupbox id="foldersGroup">
+ <caption label="&specialFolders.label;"/>
+
+ <hbox align="center">
+ <label value="&keepDrafts2.label;" control="messageDrafts"/>
+ </hbox>
+
+ <radiogroup id="messageDrafts">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="draft_selectAccount"
+ oncommand="setPickersState('msgDraftsAccountPicker', 'msgDraftsFolderPicker', event)"
+ value="0" label="&draftsFolderOn.label;"
+ accesskey="&draftsFolderOn.accesskey;"/>
+ <menulist id="msgDraftsAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="draft_selectAccount">
+ <menupopup id="msgDraftAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('draft', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="draft_selectFolder"
+ oncommand="setPickersState('msgDraftsFolderPicker', 'msgDraftsAccountPicker', event)"
+ value="1" label="&draftInOtherFolder.label;"
+ accesskey="&draftInOtherFolder.accesskey;"/>
+ <menulist id="msgDraftsFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="draft_selectFolder"
+ displayformat="verbose">
+ <menupopup id="msgDraftFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('draft', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+
+ <hbox align="center">
+ <label value="&keepTemplates.label;" control="messageTemplates"/>
+ </hbox>
+
+ <radiogroup id="messageTemplates">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="tmpl_selectAccount"
+ oncommand="setPickersState('msgStationeryAccountPicker', 'msgStationeryFolderPicker', event)"
+ value="0" label="&templatesFolderOn.label;"
+ accesskey="&templatesFolderOn.accesskey;"/>
+ <menulist id="msgStationeryAccountPicker"
+ class="folderMenuItem"
+ aria-labelledby="tmpl_selectAccount">
+ <menupopup id="msgFccAccountPopup" type="folder" mode="filing"
+ expandFolders="false"
+ oncommand="noteSelectionChange('tmpl', 'Account', event)"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="tmpl_selectFolder"
+ oncommand="setPickersState('msgStationeryFolderPicker', 'msgStationeryAccountPicker', event)"
+ value="1" label="&templateInOtherFolder.label;"
+ accesskey="&templateInOtherFolder.accesskey;"/>
+ <menulist id="msgStationeryFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="tmpl_selectFolder"
+ displayformat="verbose">
+ <menupopup id="msgTemplFolderPopup" type="folder" mode="filing"
+ class="menulist-menupopup"
+ showFileHereLabel="true"
+ oncommand="noteSelectionChange('tmpl', 'Folder', event)"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.showSaveMsgDlg" label="&saveMessageDlg.label;"
+ accesskey="&saveMessageDlg.accesskey;"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.showSaveMsgDlg"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+</overlay>
diff --git a/mailnews/base/prefs/content/am-help.js b/mailnews/base/prefs/content/am-help.js
new file mode 100644
index 000000000..da9d0b076
--- /dev/null
+++ b/mailnews/base/prefs/content/am-help.js
@@ -0,0 +1,76 @@
+/* -*- Mode: Java; 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/. */
+
+/**
+ * Key value pairs to derive the tag based on the page loaded.
+ * Each key is the page loaded when user clicks on one of the items on
+ * the accounttree of the AccountManager window.
+ * Value is a tag that is preset which will be used to display
+ * context sensitive help.
+ */
+var pageTagPairs = {
+ "chrome://messenger/content/am-main.xul": "mail_account_identity",
+ "chrome://messenger/content/am-server.xul": "mail",
+ "chrome://messenger/content/am-copies.xul": "mail_copies",
+ "chrome://messenger/content/am-addressing.xul": "mail_addressing_settings",
+ "chrome://messenger/content/am-junk.xul": "mail-account-junk",
+ "chrome://messenger/content/am-offline.xul": "mail-offline-accounts",
+ "chrome://messenger/content/am-smtp.xul": "mail_smtp",
+ "chrome://messenger/content/am-smime.xul": "mail_security_settings",
+ "chrome://messenger/content/am-serverwithnoidentities.xul": "mail_local_folders_settings",
+ "chrome://messenger/content/am-mdn.xul": "mail-account-receipts",
+}
+
+function doHelpButton()
+{
+ // Get the URI of the page loaded in the AccountManager's content frame.
+ var pageSourceURI = contentFrame.location.href;
+ // Get the help tag corresponding to the page loaded.
+ var helpTag = pageTagPairs[pageSourceURI];
+
+ // If the help tag is generic or offline, check if there is a need to set tags per server type
+ if ((helpTag == "mail") || (helpTag == "mail-offline-accounts")) {
+ // Get server type, as we may need to set help tags per server type for some pages
+ var serverType = GetServerType();
+
+ /**
+ * Check the page to be loaded. Following pages needed to be presented with the
+ * help content that is based on server type. For any pages with such requirement
+ * do add comments here about the page and a new case statement for pageSourceURI
+ * switch.
+ * - server settings ("chrome://messenger/content/am-server.xul")
+ * - offline/diskspace settings ("chrome://messenger/content/am-offline.xul")
+ */
+ switch (pageSourceURI) {
+ case "chrome://messenger/content/am-server.xul":
+ helpTag = "mail_server_" + serverType;
+ break;
+
+ case "chrome://messenger/content/am-offline.xul":
+ helpTag = "mail_offline_" + serverType;
+ break;
+
+ default :
+ break;
+ }
+ }
+
+ if ( helpTag )
+ openHelp(helpTag);
+ else
+ openHelp('mail');
+}
+
+/**
+ * Get server type of the seleted item
+ */
+function GetServerType()
+{
+ var serverType = null;
+ var currentAccount = parent.getCurrentAccount();
+ if (currentAccount)
+ serverType = currentAccount.incomingServer.type;
+ return serverType;
+}
diff --git a/mailnews/base/prefs/content/am-identities-list.js b/mailnews/base/prefs/content/am-identities-list.js
new file mode 100644
index 000000000..2cabb400d
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identities-list.js
@@ -0,0 +1,180 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gIdentityListBox; // the root <listbox> node
+var gAddButton;
+var gEditButton;
+var gSetDefaultButton;
+var gDeleteButton;
+
+var gAccount = null; // the account we are showing the identities for
+
+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 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.
+ let identities = gAccount.identities;
+ for (let identity in fixIterator(identities,
+ Components.interfaces.nsIMsgIdentity))
+ {
+ if (identity.valid)
+ {
+ let listitem = document.createElement("listitem");
+ listitem.setAttribute("label", identity.identityName);
+ 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 identity the identity (if any) to load in the dialog
+ */
+function openIdentityEditor(identity)
+{
+ let args = { identity: identity, account: gAccount, result: false };
+
+ let indexToSelect = identity ? gIdentityListBox.selectedIndex :
+ gIdentityListBox.itemCount;
+
+ window.openDialog("am-identity-edit.xul", "",
+ "chrome,modal,resizable,centerscreen", args);
+
+ if (args.result)
+ refreshIdentityList(indexToSelect);
+}
+
+function getSelectedIdentity()
+{
+ if (gIdentityListBox.selectedItems.length != 1)
+ return null;
+
+ var identityKey = gIdentityListBox.selectedItems[0].getAttribute("key");
+ let identities = gAccount.identities;
+ for (let identity in fixIterator(identities,
+ Components.interfaces.nsIMsgIdentity))
+ {
+ if (identity.valid && identity.key == identityKey)
+ return identity;
+ }
+
+ return null; // no identity found
+}
+
+function onEdit(event)
+{
+ var id = (event.target.localName == 'listbox') ? null : 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);
+}
+
+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;
+ return true;
+}
diff --git a/mailnews/base/prefs/content/am-identities-list.xul b/mailnews/base/prefs/content/am-identities-list.xul
new file mode 100644
index 000000000..2acbc45d4
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identities-list.xul
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<!-- 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 dialog SYSTEM "chrome://messenger/locale/am-identities-list.dtd">
+
+<dialog
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoad();"
+ buttons="accept"
+ buttonlabelaccept="&identitiesListClose.label;"
+ buttonaccesskeyaccept="&identitiesListClose.accesskey;"
+ ondialogaccept="return onOk();"
+ ondialogcancel="return onOk();"
+ style="width: 40em;">
+
+<stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+<script type="application/javascript"
+ src="chrome://messenger/content/am-identities-list.js"/>
+
+<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"/>
+
+<hbox flex="1">
+ <listbox id="identitiesList"
+ ondblclick="onEdit(event);"
+ onselect="updateButtons();"
+ seltype="single"
+ flex="1"
+ style="height: 0px;"/>
+
+ <vbox>
+ <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;"/>
+ </vbox>
+</hbox>
+
+</dialog>
diff --git a/mailnews/base/prefs/content/am-identity-edit.js b/mailnews/base/prefs/content/am-identity-edit.js
new file mode 100644
index 000000000..cd6750779
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identity-edit.js
@@ -0,0 +1,405 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.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
+
+function onLoadIdentityProperties()
+{
+ // extract the account
+ gIdentity = window.arguments[0].identity;
+ gAccount = window.arguments[0].account;
+ let prefBundle = document.getElementById("bundle_prefs");
+
+ // Make the dialog the same height and 90% of the width of the main Account
+ // manager page when the Account manager is not maximized.
+ let accountDialog = Services.wm.getMostRecentWindow("mailnews:accountmanager")
+ .document;
+ if (accountDialog.documentElement.getAttribute("sizemode") != "maximized") {
+ document.getElementById("identityDialog").style.width =
+ accountDialog.getElementById("accountManager").clientWidth * 0.9 + "px";
+ document.getElementById("identityDialog").style.height =
+ accountDialog.getElementById("accountManager").clientHeight + "px";
+ }
+
+ 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);
+}
+
+// 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;
+ initSmtpServer(identity.smtpServerKey);
+
+ // This field does not exist for the default identity shown in the am-main.xul 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);
+ }
+
+ 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()
+{
+ if (!validEmailAddress())
+ return false;
+
+ // 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 defaul 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);
+
+ window.arguments[0].result = true;
+
+ return 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.smtpServerKey = document.getElementById('identity.smtpServerKey').value;
+
+ var 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 = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.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 = Components.interfaces.nsIFilePicker;
+
+ var fp = Components.classes["@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;
+
+ var ret = fp.show();
+ if (ret == nsIFilePicker.returnOK) {
+ var folderField = document.getElementById("identity.signature");
+ folderField.value = fp.file.path;
+ }
+}
+
+function GetSigFolder()
+{
+ var sigFolder = null;
+ try
+ {
+ var account = parent.getCurrentAccount();
+ var identity = account.defaultIdentity;
+ var signatureFile = identity.signature;
+
+ if (signatureFile)
+ {
+ signatureFile = signatureFile.QueryInterface(Components.interfaces.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", "true");
+ htmlSigFormat.setAttribute("disabled", "true");
+ }
+ else
+ {
+ htmlSigText.removeAttribute("disabled");
+ htmlSigFormat.removeAttribute("disabled");
+ }
+
+ if (checked && !getAccountValueIsLocked(signature))
+ signature.removeAttribute("disabled");
+ else
+ signature.setAttribute("disabled", "true");
+
+ if (checked && !getAccountValueIsLocked(browse))
+ browse.removeAttribute("disabled");
+ else
+ browse.setAttribute("disabled", "true");
+}
+
+function editVCardCallback(escapedVCardStr)
+{
+ var escapedVCard = document.getElementById("identity.escapedVCard");
+ escapedVCard.value = escapedVCardStr;
+}
+
+function editVCard()
+{
+ var escapedVCard = document.getElementById("identity.escapedVCard");
+
+ // read vCard hidden value from UI
+ window.openDialog("chrome://messenger/content/addressbook/abNewCardDialog.xul",
+ "",
+ "chrome,modal,resizable=no,centerscreen",
+ {escapedVCardStr:escapedVCard.value, okCallback:editVCardCallback,
+ titleProperty:"editVCardTitle", hideABPicker:true});
+}
+
+function getAccountForFolderPickerState()
+{
+ return gAccount;
+}
+
+/**
+ * Build the SMTP server list for display.
+ */
+function loadSMTPServerList()
+{
+ var smtpServerList = document.getElementById("identity.smtpServerKey");
+ let servers = MailServices.smtp.servers;
+ let defaultServer = MailServices.smtp.defaultServer;
+
+ var smtpPopup = smtpServerList.menupopup;
+ while (smtpPopup.lastChild.nodeName != "menuseparator")
+ smtpPopup.lastChild.remove();
+
+ while (servers.hasMoreElements())
+ {
+ var server = servers.getNext();
+
+ if (server instanceof Components.interfaces.nsISmtpServer)
+ {
+ var 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);
+ }
+ }
+}
diff --git a/mailnews/base/prefs/content/am-identity-edit.xul b/mailnews/base/prefs/content/am-identity-edit.xul
new file mode 100644
index 000000000..448892382
--- /dev/null
+++ b/mailnews/base/prefs/content/am-identity-edit.xul
@@ -0,0 +1,154 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
+
+<?xul-overlay href="chrome://messenger/content/am-copiesOverlay.xul"?>
+<?xul-overlay href="chrome://messenger/content/am-addressingOverlay.xul"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % identityEditDTD SYSTEM "chrome://messenger/locale/am-identity-edit.dtd" >
+%identityEditDTD;
+<!ENTITY % identityDTD SYSTEM "chrome://messenger/locale/am-main.dtd" >
+%identityDTD;
+]>
+
+<dialog id="identityDialog"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="onLoadIdentityProperties();"
+ ondialogaccept="return onOk();"
+ style="&identityDialog.style;">
+
+ <stringbundle id="bundle_prefs"
+ src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-prefs.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-identity-edit.js"/>
+
+ <broadcaster id="broadcaster_attachSignature"/>
+
+ <description flex="1">&identityListDesc.label;</description>
+ <separator class="thin"/>
+
+ <tabbox flex="1" style="overflow: auto;">
+ <tabs id="identitySettings">
+ <tab label="&settingsTab.label;"/>
+ <tab label="&copiesFoldersTab.label;"/>
+ <tab label="&addressingTab.label;"/>
+ </tabs>
+
+ <tabpanels id="identityTabsPanels" flex="1">
+ <!-- Identity Settings Tab -->
+ <vbox flex="1" name="settings">
+ <groupbox>
+ <caption label="&publicData.label;"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&name.label;" control="identity.fullName" accesskey="&name.accesskey;"/>
+ <textbox id="identity.fullName" size="30"/>
+ </row>
+ <row align="center">
+ <label value="&email.label;" control="identity.email" accesskey="&email.accesskey;"/>
+ <textbox id="identity.email" class="uri-element"/>
+ </row>
+ <row align="center">
+ <label value="&replyTo.label;" control="identity.replyTo" accesskey="&replyTo.accesskey;"/>
+ <textbox id="identity.replyTo" class="uri-element" placeholder="&replyTo.placeholder;"/>
+ </row>
+ <row align="center">
+ <label value="&organization.label;" control="identity.organization" accesskey="&organization.accesskey;"/>
+ <textbox id="identity.organization"/>
+ </row>
+ <separator class="thin"/>
+ <row align="center">
+ <label value="&signatureText.label;" control="identity.htmlSigText" accesskey="&signatureText.accesskey;"/>
+ <checkbox id="identity.htmlSigFormat" label="&signatureHtml.label;" accesskey="&signatureHtml.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+
+ <hbox class="indent" flex="1">
+ <textbox id="identity.htmlSigText" flex="1" multiline="true" wrap="off" 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">
+ <textbox id="identity.signature" datatype="nsIFile" flex="1" name="identity.signature"
+ aria-labelledby="identity.attachSignature"
+ observes="broadcaster_attachSignature" class="uri-element"/>
+ <button class="push" name="browse" label="&choose.label;"
+ accesskey="&choose.accesskey;"
+ oncommand="selectFile()"
+ observes="broadcaster_attachSignature"
+ id="identity.sigbrowsebutton"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="identity.attachVCard" label="&attachVCard.label;" flex="1"
+ accesskey="&attachVCard.accesskey;"/>
+ <button class="push" name="editVCard" label="&editVCard.label;"
+ accesskey="&editVCard.accesskey;"
+ oncommand="editVCard()"/>
+ <label hidden="true" id="identity.escapedVCard"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&privateData.label;"/>
+
+ <hbox align="center">
+ <label value="&smtpName.label;"
+ control="identity.smtpServerKey"
+ accesskey="&smtpName.accesskey;"/>
+ <menulist id="identity.smtpServerKey" flex="1">
+ <menupopup id="smtpPopup">
+ <menuitem value=""
+ label="&smtpDefaultServer.label;"
+ id="useDefaultItem"/>
+ <menuseparator/>
+ <!-- list will be inserted here -->
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <label value="&identityAlias.label;"
+ accesskey="&identityAlias.accesskey;"
+ control="identity.label"/>
+ <textbox id="identity.label" flex="1"/>
+ </hbox>
+ </groupbox>
+
+ <spacer flex="1"/>
+ </vbox>
+
+ <!-- Copies & Folders Tab -->
+ <vbox flex="1" name="copiesAndFolders" id="copiesAndFolders"/>
+
+ <!-- Composition & Addressing Tab -->
+ <vbox flex="1" name="composeAddressing" id="compositionAndAddressing"/>
+
+ </tabpanels>
+ </tabbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/am-junk.js b/mailnews/base/prefs/content/am-junk.js
new file mode 100644
index 000000000..77daac2ca
--- /dev/null
+++ b/mailnews/base/prefs/content/am-junk.js
@@ -0,0 +1,296 @@
+/* -*- Mode: Java; 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/.
+ */
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+
+var gDeferredToAccount = "";
+
+function onInit(aPageId, aServerId)
+{
+ // manually adjust several pref UI elements
+ document.getElementById('server.spamLevel.visible').setAttribute("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.getFolderForURI(aServerId, false).server.spamSettings,
+ moveOnSpamValue);
+
+ spamActionTargetAccountElement.value = spamActionTargetAccount;
+ spamActionTargetFolderElement.value = spamActionTargetFolder;
+ moveOnSpamCheckbox.checked = moveOnSpamValue;
+
+ let server = MailUtils.getFolderForURI(spamActionTargetAccount, false);
+ document.getElementById("actionAccountPopup").selectFolder(server);
+
+ let folder = MailUtils.getFolderForURI(spamActionTargetFolder, true);
+ 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
+ for (let i = wList.itemCount - 1; i >= 0; i--) {
+ wList.removeItemAt(i);
+ }
+
+ // Populate the listbox with address books
+ let abItems = [];
+ for (let ab in fixIterator(MailServices.ab.directories,
+ Components.interfaces.nsIAbDirectory)) {
+ // 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 item = wList.appendItem(abItem.label, abItem.URI);
+ item.setAttribute("type", "checkbox");
+ item.setAttribute("class", "listitem-iconic");
+
+ // Due to bug 448582, we have to use setAttribute to set the
+ // checked value of the listitem.
+ item.setAttribute("checked", currentArray.includes(abItem.URI));
+ }
+
+ // 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 (getAccountValue(account, accountValues, "server", "type", null, false) == "pop3")
+ gDeferredToAccount = 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 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++) {
+ wList.getItemAtIndex(i).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 (var 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.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,
+ Components.interfaces.nsISimpleEnumerator);
+
+ let menuEntries = [];
+ while (ispDirectories.hasMoreElements())
+ {
+ let ispDirectory = ispDirectories.getNext()
+ .QueryInterface(Components.interfaces.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 aDir directory to look for .sfd files
+ * @param 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";
+ let entries = aDir.directoryEntries
+ .QueryInterface(Components.interfaces.nsIDirectoryEnumerator);
+
+ while (entries.hasMoreElements()) {
+ let entry = entries.nextFile;
+ // 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.indexOf(fileName) == -1)
+ newEntries.push(fileName);
+ }
+ }
+ return newEntries;
+}
+
+/**
+ * Open the Preferences dialog on the Junk settings tab.
+ */
+function showGlobalJunkPrefs()
+{
+ openPrefsFromAccountManager("paneSecurity", "junkTab", null, "junk_pane");
+}
diff --git a/mailnews/base/prefs/content/am-junk.xul b/mailnews/base/prefs/content/am-junk.xul
new file mode 100644
index 000000000..96ee9eaf6
--- /dev/null
+++ b/mailnews/base/prefs/content/am-junk.xul
@@ -0,0 +1,232 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % junkMailDTD SYSTEM "chrome://messenger/locale/am-junk.dtd">
+%junkMailDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:nc="http://home.netscape.com/NC-rdf#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ title="&junkSettings.label;"
+ onload="parent.onPanelLoaded('am-junk.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-junk.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+
+ <dialogheader title="&junkSettings.label;"/>
+
+ <groupbox>
+ <caption label="&junkClassification.label;"/>
+
+ <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="string"
+ 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>
+ <listbox id="whiteListAbURI" rows="5"/>
+ </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 width="1">&ispHeadersWarning.label;</description>
+
+ </groupbox>
+
+ <groupbox>
+ <caption label="&junkActions.label;"/>
+
+ <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">
+
+ <grid class="specialFolderPickerGrid indent">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <radio id="moveTargetMode0"
+ value="0"
+ label="&junkFolderOn.label;"
+ accesskey="&junkFolderOn.accesskey;"/>
+ <menulist id="actionTargetAccount"
+ class="folderMenuItem"
+ aria-labelledby="moveTargetMode0">
+ <menupopup id="actionAccountPopup"
+ type="folder"
+ class="menulist-menupopup"
+ expandFolders="false"
+ mode="filing"
+ oncommand="onActionTargetChange(event, 'server.spamActionTargetAccount');"/>
+ </menulist>
+ </row>
+ <row>
+ <radio id="moveTargetMode1"
+ value="1"
+ label="&otherFolder.label;"
+ accesskey="&otherFolder.accesskey;"/>
+ <menulist id="actionTargetFolder"
+ class="folderMenuItem"
+ aria-labelledby="moveTargetMode1"
+ displayformat="verbose">
+ <menupopup id="actionFolderPopup"
+ type="folder"
+ mode="junk"
+ showFileHereLabel="true"
+ oncommand="onActionTargetChange(event, 'server.spamActionTargetFolder');"/>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ </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;"/>
+ <textbox size="3"
+ type="number"
+ min="1"
+ id="server.purgeSpamInterval"
+ 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>
+
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox pack="start">
+ <button id="globalJunkPrefsLink"
+ label="&globalJunkPrefs.label;"
+ accesskey="&globalJunkPrefs.accesskey;"
+ oncommand="showGlobalJunkPrefs();"/>
+ </hbox>
+
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-main.js b/mailnews/base/prefs/content/am-main.js
new file mode 100644
index 000000000..6725239e7
--- /dev/null
+++ b/mailnews/base/prefs/content/am-main.js
@@ -0,0 +1,55 @@
+/* -*- Mode: Java; 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/. */
+
+function onInit(aPageId, aServerId)
+{
+ var accountName = document.getElementById("server.prettyName");
+ var title = document.getElementById("am-main-title");
+ var defaultTitle = title.getAttribute("defaultTitle");
+ var titleValue;
+
+ if(accountName.value)
+ titleValue = defaultTitle+" - <"+accountName.value+">";
+ else
+ titleValue = defaultTitle;
+
+ title.setAttribute("title",titleValue);
+ document.title = titleValue;
+
+ setupSignatureItems();
+}
+
+function onPreInit(account, accountValues)
+{
+ loadSMTPServerList();
+}
+
+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();
+
+ var account = parent.getCurrentAccount();
+ if (!account)
+ return;
+
+ var accountName = document.getElementById("server.prettyName").value;
+
+ var args = { account: account, accountName: 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 = account.defaultIdentity;
+ saveIdentitySettings(identity);
+
+ window.openDialog("am-identities-list.xul", "", "chrome,modal,resizable=no,centerscreen", args);
+
+ if (args.result) {
+ // now re-initialize the default identity settings in case they changed
+ identity = account.defaultIdentity; // refetch the default identity in case it changed
+ initIdentityValues(identity);
+ }
+}
diff --git a/mailnews/base/prefs/content/am-main.xul b/mailnews/base/prefs/content/am-main.xul
new file mode 100644
index 000000000..53e87bec1
--- /dev/null
+++ b/mailnews/base/prefs/content/am-main.xul
@@ -0,0 +1,147 @@
+<?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 page SYSTEM "chrome://messenger/locale/am-main.dtd" >
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="parent.onPanelLoaded('am-main.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+ <stringbundle id="bundle_messenger" src="chrome://messenger/locale/messenger.properties"/>
+
+ <script type="application/javascript" src="chrome://messenger/content/am-identity-edit.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-main.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+
+ <broadcaster id="broadcaster_attachSignature"/>
+
+ <dialogheader id="am-main-title" defaultTitle="&accountTitle.label;"/>
+
+ <hbox align="center">
+ <label value="&accountName.label;" control="server.prettyName"
+ accesskey="&accountName.accesskey;"/>
+ <textbox wsm_persist="true" size="30" id="server.prettyName"
+ prefstring="mail.server.%serverkey%.name"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <groupbox style="width: 20em !important;" flex="1">
+ <caption label="&identityTitle.label;"/>
+ <description>&identityDesc.label;</description>
+ <separator class="thin"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&name.label;" control="identity.fullName"
+ accesskey="&name.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.fullName" size="30"
+ prefstring="mail.identity.%identitykey%.fullName"/>
+ </row>
+ <row align="center">
+ <label value="&email.label;" control="identity.email"
+ accesskey="&email.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.email"
+ prefstring="mail.identity.%identitykey%.useremail" class="uri-element"/>
+ </row>
+ <row align="center">
+ <label value="&replyTo.label;" control="identity.replyTo"
+ accesskey="&replyTo.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.replyTo"
+ prefstring="mail.identity.%identitykey%.reply_to" class="uri-element"
+ placeholder="&replyTo.placeholder;"/>
+ </row>
+ <row align="center">
+ <label value="&organization.label;" control="identity.organization"
+ accesskey="&organization.accesskey;"/>
+ <textbox wsm_persist="true" id="identity.organization"
+ prefstring="mail.identity.%identitykey%.organization"/>
+ </row>
+ <separator class="thin"/>
+ <row align="center">
+ <label value="&signatureText.label;" control="identity.htmlSigText" accesskey="&signatureText.accesskey;"/>
+ <checkbox wsm_persist="true" id="identity.htmlSigFormat" label="&signatureHtml.label;"
+ prefattribute="value" accesskey="&signatureHtml.accesskey;"
+ prefstring="mail.identity.%identitykey%.htmlSigFormat"/>
+ </row>
+ </rows>
+ </grid>
+
+ <hbox class="indent" flex="1" style="min-height: 50px;">
+ <textbox wsm_persist="true" id="identity.htmlSigText" multiline="true" wrap="off" rows="4" flex="1"
+ prefstring="mail.identity.%identitykey%.htmlSigText" class="signatureBox"/>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox wsm_persist="true" id="identity.attachSignature" label="&signatureFile.label;" flex="1"
+ accesskey="&signatureFile.accesskey;"
+ oncommand="setupSignatureItems();"
+ prefattribute="value"
+ prefstring="mail.identity.%identitykey%.attach_signature"/>
+ </hbox>
+
+ <hbox align="center" class="indent">
+ <textbox wsm_persist="true" id="identity.signature" datatype="nsIFile" flex="1" name="identity.signature"
+ aria-labelledby="identity.attachSignature"
+ observes="broadcaster_attachSignature"
+ prefstring="mail.identity.%identitykey%.sig_file" class="uri-element"/>
+ <button class="push" name="browse" label="&choose.label;"
+ accesskey="&choose.accesskey;"
+ oncommand="selectFile()"
+ observes="broadcaster_attachSignature"
+ 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()"/>
+ <label hidden="true" wsm_persist="true" id="identity.escapedVCard"
+ pref="true" preftype="string" prefattribute="value"
+ prefstring="mail.identity.%identitykey%.escapedVCard"/>
+ </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>
+ </hbox>
+ </groupbox>
+
+ <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>
+
+ <spacer flex="1"/>
+ </vbox>
+
+</page>
diff --git a/mailnews/base/prefs/content/am-offline.js b/mailnews/base/prefs/content/am-offline.js
new file mode 100644
index 000000000..3f45db967
--- /dev/null
+++ b/mailnews/base/prefs/content/am-offline.js
@@ -0,0 +1,351 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+var gIncomingServer;
+var gServerType;
+var gImapIncomingServer;
+var gPref = null;
+var gLockedPref = null;
+var gOfflineMap = null; // map of folder URLs to offline flags
+
+function onInit(aPageId, aServerId)
+{
+ onLockPreference();
+
+ // init values here
+ initServerSettings();
+ initRetentionSettings();
+ initDownloadSettings();
+ initOfflineSettings();
+
+ onCheckItem("offline.notDownloadMin", "offline.notDownload");
+ onCheckItem("nntp.downloadMsgMin", "nntp.downloadMsg");
+ onCheckItem("nntp.removeBodyMin", "nntp.removeBody");
+ onCheckKeepMsg();
+}
+
+function initOfflineSettings()
+{
+ gOfflineMap = collectOfflineFolders();
+}
+
+function initServerSettings()
+{
+ document.getElementById("offline.notDownload").checked = gIncomingServer.limitOfflineMessageSize;
+ document.getElementById("autosync.notDownload").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(Components.interfaces.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 = 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");
+ var headertitle = document.getElementById("headertitle");
+ headertitle.setAttribute('title',prefBundle.getString(titleStringID));
+ document.title = prefBundle.getString(titleStringID);
+
+ if (gServerType == "pop3") {
+ var pop3Server = gIncomingServer.QueryInterface(Components.interfaces.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()
+{
+
+ top.window.openDialog("chrome://messenger/content/msgSelectOffline.xul", "", "centerscreen,chrome,modal,titlebar,resizable=yes");
+ return true;
+
+}
+
+/**
+ * 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("autosync.notDownload").checked;
+ onCheckItem("offline.notDownloadMin", "offline.notDownload");
+}
+
+function onCancel()
+{
+ // restore the offline flags for all folders
+ restoreOfflineFolders(gOfflineMap);
+ return true;
+}
+
+function onSave()
+{
+ var downloadSettings =
+ Components.classes["@mozilla.org/msgDatabase/downloadSettings;1"]
+ .createInstance(Components.interfaces.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 )
+{
+ if (!gLockedPref)
+ gLockedPref = new Array;
+
+ for (var 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 isDownloadLocked = false;
+ var isGetNewLocked = false;
+ 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:"autosync.notDownload"},
+ { 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 );
+}
+
+function onCheckItem(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;
+ let allFolders = gIncomingServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder)) {
+ if (offline)
+ folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ else
+ folder.clearFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ }
+}
+
+function collectOfflineFolders()
+{
+ let offlineFolderMap = {};
+ let allFolders = gIncomingServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder))
+ offlineFolderMap[folder.folderURL] = folder.getFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+
+ return offlineFolderMap;
+}
+
+function restoreOfflineFolders(offlineFolderMap)
+{
+ let allFolders = gIncomingServer.rootFolder.descendants;
+ for (let folder in fixIterator(allFolders, Components.interfaces.nsIMsgFolder)) {
+ if (offlineFolderMap[folder.folderURL])
+ folder.setFlag(Components.interfaces.nsMsgFolderFlags.Offline);
+ else
+ folder.clearFlag(Components.interfaces.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 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/mailnews/base/prefs/content/am-offline.xul b/mailnews/base/prefs/content/am-offline.xul
new file mode 100644
index 000000000..0c312dd74
--- /dev/null
+++ b/mailnews/base/prefs/content/am-offline.xul
@@ -0,0 +1,158 @@
+<?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 page SYSTEM "chrome://messenger/locale/am-offline.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="parent.onPanelLoaded('am-offline.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <stringbundle id="bundle_prefs" src="chrome://messenger/locale/prefs.properties"/>
+
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/retention.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-offline.js"/>
+
+ <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"/>
+
+ <dialogheader id="headertitle"/>
+
+ <groupbox id="offline.titlebox" hidefor="movemail,pop3,none,rss">
+ <caption label="&syncGroupTitle.label;"/>
+
+ <checkbox hidefor="movemail,pop3,nntp,none"
+ id="offline.folders" label="&allFoldersOffline.label;"
+ oncommand="toggleOffline()"
+ accesskey="&allFoldersOffline.accesskey;"/>
+
+ <separator class="thin" hidefor="movemail,pop3,nntp,none"/>
+
+ <hbox hidefor="movemail,pop3,nntp,none" align="right">
+ <button label="&offlineImapAdvancedOffline.label;" accesskey="&offlineImapAdvancedOffline.accesskey;"
+ oncommand="onClickSelect()" id="selectImapFoldersButton" class="selectForOfflineUseButton"/>
+ </hbox>
+
+ <hbox hidefor="movemail,pop3,imap,none" align="right">
+ <button label="&offlineSelectNntp.label;" accesskey="&offlineSelectNntp.accesskey;"
+ oncommand="onClickSelect()" id="selectNewsgroupsButton" class="selectForOfflineUseButton"/>
+ </hbox>
+
+ </groupbox>
+
+ <groupbox id="diskspace.titlebox">
+ <caption label="&diskspaceGroupTitle.label;" hidefor="movemail,pop3,none,rss"/>
+
+ <description hidefor="pop3,nntp,movemail,none,rss">&doNotDownloadImap.label;</description>
+ <description hidefor="pop3,imap,movemail,none,rss">&doNotDownloadNntp.label;</description>
+ <description hidefor="imap,nntp,none,rss">&doNotDownloadPop3Movemail.label;</description>
+
+ <!-- IMAP Autosync Preference -->
+ <radiogroup hidefor="pop3,nntp,movemail,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();"/>
+ <textbox id="autosyncValue" type="number" size="4" min="1"
+ class="autosync" 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="movemail,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', 'offline.notDownload');"/>
+ <checkbox wsm_persist="true" id="autosync.notDownload" hidefor="pop3,nntp,movemail"
+ label="&autosyncNotDownload.label;"
+ accesskey="&autosyncNotDownload.accesskey;"
+ oncommand="onAutosyncNotDownload();"/>
+ <textbox wsm_persist="true" id="offline.notDownloadMin"
+ type="number" min="1" increment="10" size="4" value="50"
+ aria-labelledby="offline.notDownload offline.notDownloadMin kbLabel"/>
+ <label value="&kb.label;" control="offline.notDownloadMin" id="kbLabel"/>
+ </hbox>
+
+ <hbox align="center" class="indent" hidefor="movemail,pop3,imap,none,rss">
+ <checkbox wsm_persist="true" id="nntp.downloadMsg"
+ label="&nntpDownloadMsg.label;"
+ accesskey="&nntpDownloadMsg.accesskey;"
+ oncommand="onCheckItem('nntp.downloadMsgMin', 'nntp.downloadMsg');"/>
+ <textbox wsm_persist="true" id="nntp.downloadMsgMin"
+ type="number" min="1" size="2" value="30"
+ 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="movemail,pop3,nntp,none,rss" class="desc" control="retention.keepMsg">&retentionCleanupImap.label;</label>
+ <label id="retentionDescriptionPop" hidefor="movemail,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();"/>
+ <textbox id="retention.keepNewMsgMin"
+ type="number" min="1" increment="10" size="4" 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();"/>
+ <textbox id="retention.keepOldMsgMin"
+ type="number" min="1" size="4" 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="movemail,pop3,imap,none,rss">
+ <checkbox id="nntp.removeBody" accesskey="&nntpRemoveMsgBody.accesskey;"
+ label="&nntpRemoveMsgBody.label;" oncommand="onCheckItem('nntp.removeBodyMin','nntp.removeBody');"/>
+ <textbox id="nntp.removeBodyMin" size="2" value="30"
+ type="number" min="1"
+ aria-labelledby="nntp.removeBody nntp.removeBodyMin daysOldMsg"/>
+ <label value="&daysOld.label;" control="nntp.removeBodyMin" id="daysOldMsg"/>
+ </hbox>
+ </vbox>
+ </groupbox>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-prefs.js b/mailnews/base/prefs/content/am-prefs.js
new file mode 100644
index 000000000..6f88af016
--- /dev/null
+++ b/mailnews/base/prefs/content/am-prefs.js
@@ -0,0 +1,114 @@
+/* -*- 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. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Prefs in MailNews require dynamic portions to indicate
+ * which of multiple servers or identities. This function
+ * takes a string and a xul element.
+ *
+ * @param aStr The string is a prefstring with a token %tokenname%.
+ * @param aElement The xul 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 /* if (token) */ {
+ 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 aElement a xul element with the pref related attributes
+ * (pref, preftype, prefstring)
+ * @return 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 aChangeElementId Slave element which should be enabled
+ * if all the checkElementIDs are checked.
+ * Otherwise it gets disabled.
+ * @param 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
+ Components.utils.reportError("Unknown type of control element: " + notifyElement.id);
+
+ if (!notifyElementState) {
+ disabled = true;
+ break;
+ }
+ }
+
+ if (!disabled && getAccountValueIsLocked(elementToControl))
+ disabled = true;
+
+ elementToControl.disabled = disabled;
+}
diff --git a/mailnews/base/prefs/content/am-server-advanced.js b/mailnews/base/prefs/content/am-server-advanced.js
new file mode 100644
index 000000000..9232f525a
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server-advanced.js
@@ -0,0 +1,157 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// 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 (var 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()
+{
+ // 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 true;
+
+ var confirmDeferAccount =
+ gPrefsBundle.getString("confirmDeferAccountWarning");
+
+ var confirmTitle = gPrefsBundle.getString("confirmDeferAccountTitle");
+
+ if (!Services.prompt.confirm(window, confirmTitle, confirmDeferAccount))
+ return false;
+ }
+ 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 (var 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;
+ }
+ }
+
+ return true;
+}
+
+
+// Set radio element choices and picker states
+function updateInboxAccount(enablePicker)
+{
+ document.getElementById("deferredServerFolderPicker").disabled = !enablePicker;
+ document.getElementById("deferGetNewMail").disabled = !enablePicker;
+}
diff --git a/mailnews/base/prefs/content/am-server-advanced.xul b/mailnews/base/prefs/content/am-server-advanced.xul
new file mode 100644
index 000000000..82b85b975
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server-advanced.xul
@@ -0,0 +1,146 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://messenger/locale/am-server-advanced.dtd">
+
+<dialog
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&serverAdvanced.label;"
+ buttons="accept,cancel"
+ onload="onLoad();"
+ ondialogaccept="return onOk();">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-server-advanced.js"/>
+ <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 value="&serverDirectory.label;"
+ accesskey="&serverDirectory.accesskey;"
+ control="serverDirectory"/>
+ <textbox amsa_persist="true" id="serverDirectory"/>
+ </hbox>
+
+ <checkbox amsa_persist="true" id="usingSubscription"
+ label="&usingSubscription.label;"
+ accesskey="&usingSubscription.accesskey;"/>
+
+ <checkbox amsa_persist="true" id="dualUseFolders"
+ label="&dualUseFolders.label;"
+ accesskey="&dualUseFolders.accesskey;"/>
+
+ <separator class="groove"/>
+ <row>
+ <hbox align="center">
+ <label control="maximumConnectionsNumber"
+ value="&maximumConnectionsNumber.label;"
+ accesskey="&maximumConnectionsNumber.accesskey;"/>
+ <textbox amsa_persist="true" size="3" type="number"
+ min="1" max="1000" id="maximumConnectionsNumber"/>
+ </hbox>
+ </row>
+
+ <separator class="groove"/>
+ <description>&namespaceDesc.label;</description>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <separator class="indent"/>
+ <label control="personalNamespace" value="&personalNamespace.label;"
+ accesskey="&personalNamespace.accesskey;"/>
+ <textbox amsa_persist="true" id="personalNamespace" />
+ </row>
+ <row align="center">
+ <separator class="indent"/>
+ <label control="publicNamespace" value="&publicNamespace.label;"
+ accesskey="&publicNamespace.accesskey;"/>
+ <textbox amsa_persist="true" id="publicNamespace"/>
+ </row>
+ <row align="center">
+ <separator class="indent"/>
+ <label control="otherUsersNamespace" value="&otherUsersNamespace.label;"
+ accesskey="&otherUsersNamespace.accesskey;"/>
+ <textbox amsa_persist="true" id="otherUsersNamespace"/>
+ </row>
+ </rows>
+ </grid>
+ <grid>
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <separator class="indent"/>
+ <checkbox amsa_persist="true" id="overrideNamespaces"
+ label="&overrideNamespaces.label;"
+ accesskey="&overrideNamespaces.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </vbox>
+
+
+ <!-- POP3 Panel -->
+ <vbox id="pop3Panel">
+ <label flex="1" control="folderStorage">&pop3DeferringDesc.label;</label>
+ <hbox align="center">
+ <radiogroup id="folderStorage"
+ orient="horizontal"
+ amsa_persist="true"
+ onselect="updateInboxAccount(this.selectedIndex == 1);">
+ <rows>
+ <row>
+ <radio id="deferToCurrentAccount"
+ value="currentAccount"
+ label="&accountInbox.label;"
+ accesskey="&accountInbox.accesskey;"/>
+ </row>
+ <row>
+ <vbox>
+ <hbox>
+ <radio id="deferToOtherAccount"
+ value="otherAccount"
+ label="&deferToServer.label;"
+ accesskey="&deferToServer.accesskey;">
+ </radio>
+ <menulist id="deferredServerFolderPicker"
+ class="folderMenuItem"
+ aria-labelledby="deferToServer">
+ <menupopup id="deferredServerPopup"
+ type="folder"
+ 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>
+ </row>
+ </rows>
+ </radiogroup>
+ </hbox>
+
+ </vbox>
+</dialog>
diff --git a/mailnews/base/prefs/content/am-server-top.xul b/mailnews/base/prefs/content/am-server-top.xul
new file mode 100644
index 000000000..890fa7a79
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server-top.xul
@@ -0,0 +1,13 @@
+<?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 page SYSTEM "chrome://messenger/locale/am-server-top.dtd" >
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+</page>
diff --git a/mailnews/base/prefs/content/am-server.js b/mailnews/base/prefs/content/am-server.js
new file mode 100644
index 000000000..e3c2d2b09
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server.js
@@ -0,0 +1,400 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+Components.utils.import("resource:///modules/MailUtils.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gServer;
+
+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);
+
+ // TLS Cert (External) and OAuth2 are only supported on IMAP.
+ document.getElementById("authMethod-oauth2").hidden = (serverType != "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 already been used
+ storeTypeElement.setAttribute("disabled",
+ gServer.getBoolValue("canChangeStoreType") ? "false" : "true");
+}
+
+function onPreInit(account, accountValues)
+{
+ var type = parent.getAccountValue(account, accountValues, "server", "type", null, false);
+ hideShowControls(type);
+
+ Services.obs.notifyObservers(null, "charsetmenu-selected", "other");
+
+ gServer = account.incomingServer;
+
+ if(!account.incomingServer.canEmptyTrashOnExit)
+ {
+ document.getElementById("server.emptyTrashOnExit").setAttribute("hidden", "true");
+ document.getElementById("imap.deleteModel.box").setAttribute("hidden", "true");
+ }
+}
+
+function initServerType()
+{
+ var serverType = document.getElementById("server.type").getAttribute("value");
+ var propertyName = "serverType-" + serverType;
+
+ var messengerBundle = document.getElementById("bundle_messenger");
+ var verboseName = messengerBundle.getString(propertyName);
+ setDivText("servertype.verbose", 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 selectSelect()
+
+ // 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");
+ // string prefs
+ 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");
+ }
+
+ window.openDialog("chrome://messenger/content/am-server-advanced.xul",
+ "_blank", "chrome,modal,titlebar", serverSettings);
+
+ 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);
+ // string prefs
+ 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(Components.interfaces.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 in fixIterator(MailServices.accounts.accounts,
+ Components.interfaces.nsIMsgAccount)) {
+ 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", "string", 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);
+ }
+ }
+ }
+}
+
+function secureSelect(aLoading)
+{
+ var socketType = document.getElementById("server.socketType").value;
+ var serverType = document.getElementById("server.type").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;
+
+ const Ci = Components.interfaces;
+ 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 (var 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 = Components.interfaces.nsIFilePicker;
+ const nsIFile = Components.interfaces.nsIFile;
+
+ var newsrcTextBox = document.getElementById("nntp.newsrcFilePath");
+ var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ fp.init(window,
+ document.getElementById("browseForNewsrc").getAttribute("filepickertitle"),
+ nsIFilePicker.modeSave);
+
+ var currentNewsrcFile;
+ try {
+ currentNewsrcFile = Components.classes["@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);
+
+ if (fp.show() != nsIFilePicker.returnCancel)
+ newsrcTextBox.value = fp.file.path;
+}
+
+function setupImapDeleteUI(aServerId)
+{
+ // read delete_model preference
+ var deleteModel = document.getElementById("imap.deleteModel").getAttribute("value");
+ selectImapDeleteModel(deleteModel);
+
+ // read trash_folder_name preference
+ var trashFolderName = getTrashFolderName();
+
+ // set folderPicker menulist
+ var trashPopup = document.getElementById("msgTrashFolderPopup");
+ trashPopup._teardown();
+ trashPopup._parentFolder = MailUtils.getFolderForURI(aServerId);
+ trashPopup._ensureInitialized();
+
+ // TODO: There is something wrong here, selectFolder() fails even if the
+ // folder does exist. Try to fix in bug 802609.
+ let trashFolder = MailUtils.getFolderForURI(aServerId + "/" + trashFolderName, false);
+ try {
+ trashPopup.selectFolder(trashFolder);
+ } catch(ex) {
+ trashPopup.parentNode.setAttribute("label", trashFolder.prettyName);
+ }
+ 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)
+{
+ var folder = aEvent.target._folder;
+ var folderPath = getFolderPathFromRoot(folder);
+
+ // Set the value to be persisted.
+ document.getElementById("imap.trashFolderName")
+ .setAttribute("value", folderPath);
+
+ // Update the widget to show/do correct things even for subfolders.
+ var trashFolderPicker = document.getElementById("msgTrashFolderPicker");
+ trashFolderPicker.menupopup.selectFolder(folder);
+}
+
+/** Generate the relative folder path from the root. */
+function getFolderPathFromRoot(folder)
+{
+ var path = folder.name;
+ var parentFolder = folder.parent;
+ while (parentFolder && parentFolder != folder.rootFolder) {
+ path = parentFolder.name + "/" + path;
+ parentFolder = parentFolder.parent;
+ }
+ // IMAP Inbox URI's start with INBOX, not Inbox.
+ return path.replace(/^Inbox/, "INBOX");
+}
+
+// Get trash_folder_name from prefs
+function getTrashFolderName()
+{
+ var trashFolderName = document.getElementById("imap.trashFolderName").getAttribute("value");
+ // if the preference hasn't been set, set it to a sane default
+ if (!trashFolderName) {
+ trashFolderName = "Trash";
+ document.getElementById("imap.trashFolderName").setAttribute("value",trashFolderName);
+ }
+ return trashFolderName;
+}
diff --git a/mailnews/base/prefs/content/am-server.xul b/mailnews/base/prefs/content/am-server.xul
new file mode 100644
index 000000000..f52aef1cc
--- /dev/null
+++ b/mailnews/base/prefs/content/am-server.xul
@@ -0,0 +1,468 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/folderMenus.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/content/charsetList.css" type="text/css"?>
+
+<!DOCTYPE page [
+<!ENTITY % trashDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">
+%trashDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&serverSettings.label;"
+ onload="parent.onPanelLoaded('am-server.xul');">
+
+ <vbox flex="1" style="overflow: auto;">
+ <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-server.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/am-prefs.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+ <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"/>
+
+ <dialogheader title="&serverSettings.label;"/>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&serverType.label;"/>
+ <label id="servertype.verbose"/>
+ </row>
+ <row align="center">
+ <hbox>
+ <label value="&serverName.label;" accesskey="&serverName.accesskey;"
+ control="server.realHostName"/>
+ </hbox>
+ <hbox align="center">
+ <label id="fixedServerName" collapsed="true" use="server.realHostName"/>
+ <textbox wsm_persist="true"
+ size="20"
+ flex="1"
+ id="server.realHostName"
+ prefstring="mail.server.%serverkey%.realhostname"
+ class="uri-element"/>
+ </hbox>
+ <hbox align="center">
+ <label hidefor="movemail" value="&port.label;"
+ accesskey="&port.accesskey;" control="server.port"/>
+ <label id="fixedServerPort" hidefor="movemail"
+ collapsed="true" use="server.port"/>
+ <textbox wsm_persist="true" size="3" id="server.port"
+ preftype="int" hidefor="movemail"
+ prefstring="mail.server.%serverkey%.port"
+ type="number" min="1" max="65535" increment="1"/>
+ <label value="&serverPortDefault.label;" hidefor="movemail"/>
+ <label id="defaultPort" hidefor="movemail"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox align="center" hidefor="nntp">
+ <label value="&userName.label;"
+ accesskey="&userName.accesskey;"
+ control="server.realUsername"/>
+ </hbox>
+ <hbox align="center" hidefor="nntp">
+ <label id="fixedUserName" collapsed="true" use="server.realUsername"/>
+ <textbox wsm_persist="true"
+ size="20"
+ flex="1"
+ id="server.realUsername"
+ prefstring="mail.server.%serverkey%.realusername"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <separator class="thin"/>
+
+ <groupbox hidefor="movemail">
+ <caption label="&securitySettings.label;"/>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&connectionSecurity.label;"
+ accesskey="&connectionSecurity.accesskey;"
+ control="server.socketType"/>
+ <menulist wsm_persist="true" id="server.socketType"
+ oncommand="secureSelect();">
+ <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>
+ </row>
+ <row align="center" hidefor="nntp,movemail">
+ <label value="&authMethod.label;"
+ accesskey="&authMethod.accesskey;"
+ control="server.authMethod"/>
+ <!-- same in smtpEditOverlay.xul/js -->
+ <menulist id="server.authMethod"
+ wsm_persist="true"
+ preftype="int" type="number"
+ prefstring="mail.server.%serverkey%.authMethod">
+ <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>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox>
+ <caption label="&serverSettings.label;"/>
+ <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"/>
+ <textbox wsm_persist="true"
+ id="server.biffMinutes"
+ type="number"
+ size="3"
+ min="1"
+ increment="1"
+ 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,movemail">
+ <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 and Movemail (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,movemail">
+ <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"/>
+ <textbox wsm_persist="true" id="pop3.numDaysToLeaveOnServer" size="3"
+ aria-labelledby="pop3.deleteByAgeFromServer pop3.numDaysToLeaveOnServer daysEnd"
+ preftype="int" type="number" min="1" increment="1"
+ 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,movemail" 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"/>
+
+ <hbox align="center" hidefor="pop3,nntp,movemail">
+ <label value="&deleteMessagePrefix.label;" align="left"
+ control="imap.deleteModel"/>
+ </hbox>
+ <vbox>
+ <hbox align="center"
+ id="imap.deleteModel.box"
+ hidefor="pop3,nntp,movemail"
+ flex="1">
+ <radiogroup id="imap.deleteModel"
+ wsm_persist="true"
+ oncommand="selectImapDeleteModel(this.value);"
+ prefstring="mail.server.%serverkey%.delete_model">
+ <grid class="specialFolderPickerGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <radio id="modelMoveToTrash"
+ value="1"
+ label="&modelMoveToTrash.label;"
+ accesskey="&modelMoveToTrash.accesskey;"/>
+ <menulist id="msgTrashFolderPicker" flex="1"
+ class="folderMenuItem"
+ maxwidth="300" crop="center"
+ aria-labelledby="modelMoveToTrash"
+ displayformat="verbose">
+ <menupopup id="msgTrashFolderPopup"
+ type="folder"
+ mode="filing"
+ showFileHereLabel="true"
+ oncommand="folderPickerChange(event);"/>
+ </menulist>
+ </row>
+ <row align="center">
+ <radio id="modelMarkDeleted"
+ value="0"
+ label="&modelMarkDeleted.label;"
+ accesskey="&modelMarkDeleted.accesskey;"/>
+ </row>
+ <row align="center">
+ <radio id="modelDeleteImmediately"
+ value="2"
+ label="&modelDeleteImmediately.label;"
+ accesskey="&modelDeleteImmediately.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ </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,movemail"/>
+ </hbox>
+ </vbox>
+
+ <!-- Advanced IMAP settings -->
+ <hbox flex="1" hidefor="pop3,nntp,movemail" 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,movemail" align="center">
+ <checkbox wsm_persist="true" id="nntp.notifyOn"
+ label="&maxMessagesStart.label;"
+ accesskey="&maxMessagesStart.accesskey;"
+ oncommand="onCheckItem('nntp.maxArticles', [this.id]);"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.notify.on"/>
+ <textbox wsm_persist="true" id="nntp.maxArticles"
+ type="number" size="4" min="1" increment="10"
+ 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,movemail" wsm_persist="true" id="nntp.pushAuth"
+ label="&alwaysAuthenticate.label;"
+ accesskey="&alwaysAuthenticate.accesskey;"
+ prefattribute="value"
+ prefstring="mail.server.%serverkey%.always_authenticate"/>
+
+ <!-- take out for now - bug 45079
+ <hbox flex="1" hidefor="pop3,imap,movemail">
+ <groupbox>
+ <caption class="header" label="&abbreviate.label;"/>
+
+ <radiogroup wsm_persist="true" id="nntp.abbreviate">
+ <radio value="true"
+ label="&abbreviateOn.label;"/>
+ <radio value="false"
+ label="&abbreviateOff.label;"/>
+ </radiogroup>
+ </groupbox>
+ </hbox>
+ -->
+
+ </groupbox>
+
+ <groupbox>
+ <caption label="&messageStorage.label;"/>
+
+ <hbox align="end">
+ <vbox align="start" flex="1" id="exitHandlingBox=">
+ <checkbox hidefor="pop3,nntp,movemail"
+ 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,movemail"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&storeType.label;"
+ accesskey="&storeType.accesskey;"
+ control="server.storeTypeMenulist"/>
+ <menulist id="server.storeTypeMenulist">
+ <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 hidefor="imap,pop3,movemail">
+ <label value="&newsrcFilePath.label;" control="nntp.newsrcFilePath"/>
+ <hbox align="center">
+ <textbox readonly="true"
+ wsm_persist="true"
+ flex="1"
+ id="nntp.newsrcFilePath"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.newsrc.file"
+ class="uri-element"/>
+ <button id="browseForNewsrc"
+ label="&browseNewsrc.label;"
+ filepickertitle="&newsrcPicker.label;"
+ accesskey="&browseNewsrc.accesskey;"
+ oncommand="BrowseForNewsrc();"/>
+ </hbox>
+ </vbox>
+
+ <separator class="thin"/>
+
+ <vbox>
+ <label value="&localPath.label;" control="server.localPath"/>
+ <hbox align="center">
+ <textbox readonly="true"
+ wsm_persist="true"
+ flex="1"
+ id="server.localPath"
+ datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory"
+ class="uri-element"/>
+ <button id="browseForLocalFolder"
+ label="&browseFolder.label;"
+ filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;"
+ oncommand="BrowseForLocalFolders();"/>
+ </hbox>
+ </vbox>
+
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox hidefor="imap,pop3,movemail" align="center" valign="middle" iscontrolcontainer="true">
+ <label value="&serverDefaultCharset2.label;" control="nntp.charset"/>
+ <menulist hidable="true"
+ type="charset"
+ hidefor="imap,pop3,movemail"
+ wsm_persist="true"
+ id="nntp.charset"
+ preftype="string"
+ prefstring="mail.server.%serverkey%.charset"/>
+ </hbox>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-serverwithnoidentities.js b/mailnews/base/prefs/content/am-serverwithnoidentities.js
new file mode 100644
index 000000000..696dc71b0
--- /dev/null
+++ b/mailnews/base/prefs/content/am-serverwithnoidentities.js
@@ -0,0 +1,34 @@
+/* -*- Mode: Java; 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/. */
+
+var gServer;
+
+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 already been used
+ storeTypeElement.setAttribute("disabled",
+ gServer.getBoolValue("canChangeStoreType") ? "false" : "true");
+}
+
+function onPreInit(account, accountValues) {
+ gServer = account.incomingServer;
+}
+
+function onSave()
+{
+ let storeContractID = document.getElementById("server.storeTypeMenulist")
+ .selectedItem
+ .value;
+ document.getElementById("server.storeContractID")
+ .setAttribute("value", storeContractID);
+}
+
diff --git a/mailnews/base/prefs/content/am-serverwithnoidentities.xul b/mailnews/base/prefs/content/am-serverwithnoidentities.xul
new file mode 100644
index 000000000..e33216234
--- /dev/null
+++ b/mailnews/base/prefs/content/am-serverwithnoidentities.xul
@@ -0,0 +1,77 @@
+<?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 page [
+<!ENTITY % accountNoIdentDTD SYSTEM "chrome://messenger/locale/am-serverwithnoidentities.dtd" >%accountNoIdentDTD;
+<!ENTITY % accountServerTopDTD SYSTEM "chrome://messenger/locale/am-server-top.dtd">%accountServerTopDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&accountTitle.label;"
+ onload="parent.onPanelLoaded('am-serverwithnoidentities.xul');">
+
+ <script type="application/javascript" src="chrome://messenger/content/am-serverwithnoidentities.js"/>
+ <script type="application/javascript" src="chrome://messenger/content/amUtils.js"/>
+
+ <dialogheader title="&accountTitle.label;"/>
+
+ <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 align="center">
+ <label value="&accountName.label;" control="server.prettyName"
+ accesskey="&accountName.accesskey;"/>
+ <textbox wsm_persist="true" size="30" id="server.prettyName"
+ prefstring="mail.server.%serverkey%.name"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&messageStorage.label;"/>
+
+ <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">
+ <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"/>
+
+ <label value="&localPath.label;" control="server.localPath"/>
+ <hbox align="center">
+ <textbox readonly="true" wsm_persist="true" flex="1" id="server.localPath" datatype="nsIFile"
+ prefstring="mail.server.%serverkey%.directory" class="uri-element"/>
+ <button id="browseForLocalFolder" label="&browseFolder.label;" filepickertitle="&localFolderPicker.label;"
+ accesskey="&browseFolder.accesskey;" oncommand="BrowseForLocalFolders()"/>
+ </hbox>
+ </groupbox>
+</page>
diff --git a/mailnews/base/prefs/content/am-smtp.js b/mailnews/base/prefs/content/am-smtp.js
new file mode 100644
index 000000000..f5e9ab38c
--- /dev/null
+++ b/mailnews/base/prefs/content/am-smtp.js
@@ -0,0 +1,256 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gSmtpServerListWindow =
+{
+ mBundle: null,
+ mServerList: null,
+ mAddButton: null,
+ mEditButton: null,
+ mDeleteButton: null,
+ mSetDefaultServerButton: null,
+
+ onLoad: function()
+ {
+ parent.onPanelLoaded('am-smtp.xul');
+
+ 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(this.getSelectedServer());
+ },
+
+ onSelectionChanged: function(aEvent)
+ {
+ if (this.mServerList.selectedItems.length <= 0)
+ return;
+
+ var server = this.getSelectedServer();
+ this.updateButtons(server);
+ this.updateServerInfoBox(server);
+ },
+
+ onDeleteServer: function (aEvent)
+ {
+ var server = this.getSelectedServer();
+ if (server)
+ {
+ // 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)
+ {
+ MailServices.smtp.deleteServer(server);
+ parent.replaceWithDefaultSmtpServer(server.key);
+ this.refreshServerList("", true);
+ }
+ }
+ },
+
+ onAddServer: function (aEvent)
+ {
+ this.openServerEditor(null);
+ },
+
+ onEditServer: function (aEvent)
+ {
+ if (this.mServerList.selectedItems.length <= 0)
+ return;
+ this.openServerEditor(this.getSelectedServer());
+ },
+
+ onSetDefaultServer: function(aEvent)
+ {
+ if (this.mServerList.selectedItems.length <= 0)
+ return;
+
+ MailServices.smtp.defaultServer = this.getSelectedServer();
+ this.refreshServerList(MailServices.smtp.defaultServer.key, true);
+ },
+
+ updateButtons: function(aServer)
+ {
+ // can't delete default server
+ if (MailServices.smtp.defaultServer == aServer)
+ {
+ this.mSetDefaultServerButton.setAttribute("disabled", "true");
+ this.mDeleteButton.setAttribute("disabled", "true");
+ }
+ else
+ {
+ this.mSetDefaultServerButton.removeAttribute("disabled");
+ this.mDeleteButton.removeAttribute("disabled");
+ }
+
+ if (this.mServerList.selectedItems.length == 0)
+ this.mEditButton.setAttribute("disabled", "true");
+ else
+ this.mEditButton.removeAttribute("disabled");
+ },
+
+ updateServerInfoBox: function(aServer)
+ {
+ var noneSelected = this.mBundle.getString("smtpServerList-NotSpecified");
+
+ document.getElementById('nameValue').value = aServer.hostname;
+ document.getElementById('descriptionValue').value = aServer.description || noneSelected;
+ document.getElementById('portValue').value = aServer.port;
+ document.getElementById('userNameValue').value = aServer.username || noneSelected;
+ document.getElementById('useSecureConnectionValue').value =
+ this.mBundle.getString("smtpServer-ConnectionSecurityType-" +
+ aServer.socketType);
+
+ const AuthMethod = Components.interfaces.nsMsgAuthMethod;
+ const SocketType = Components.interfaces.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
+ Components.utils.reportError("Warning: unknown value for smtpserver... authMethod: " +
+ aServer.authMethod);
+ }
+ if (authStr)
+ document.getElementById("authMethodValue").value =
+ this.mBundle.getString(authStr);
+ },
+
+ refreshServerList: function(aServerKeyToSelect, aFocusList)
+ {
+ // remove all children
+ while (this.mServerList.hasChildNodes())
+ this.mServerList.lastChild.remove();
+
+ this.fillSmtpServers(this.mServerList,
+ MailServices.smtp.servers,
+ MailServices.smtp.defaultServer);
+
+ 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();
+ },
+
+ fillSmtpServers: function(aListBox, aServers, aDefaultServer)
+ {
+ if (!aListBox || !aServers)
+ return;
+
+ while (aServers.hasMoreElements())
+ {
+ var server = aServers.getNext();
+
+ if (server instanceof Components.interfaces.nsISmtpServer)
+ {
+ var isDefault = (aDefaultServer.key == server.key);
+
+ var listitem = this.createSmtpListItem(server, isDefault);
+ aListBox.appendChild(listitem);
+ }
+ }
+ },
+
+ createSmtpListItem: function(aServer, aIsDefault)
+ {
+ var listitem = document.createElement("listitem");
+ 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");
+ }
+
+ listitem.setAttribute("label", serverName);
+ listitem.setAttribute("key", aServer.key);
+ listitem.setAttribute("class", "smtpServerListItem");
+
+ // give it some unique id
+ listitem.id = "smtpServer." + aServer.key;
+ return listitem;
+ },
+
+ openServerEditor: function(aServer)
+ {
+ var args = {server: aServer,
+ result: false,
+ addSmtpServer: ""};
+
+ window.openDialog("chrome://messenger/content/SmtpServerEdit.xul",
+ "smtpEdit", "chrome,titlebar,modal,centerscreen", args);
+
+ // now re-select the server which was just added
+ if (args.result)
+ this.refreshServerList(aServer ? aServer.key : args.addSmtpServer, true);
+
+ return args.result;
+ },
+
+ setSelectedServer: function(aServer)
+ {
+ if (!aServer)
+ return;
+
+ setTimeout(function(aServerList) {
+ aServerList.ensureElementIsVisible(aServer);
+ aServerList.selectItem(aServer);
+ }, 0, this.mServerList);
+ },
+
+ getSelectedServer: function()
+ {
+ if (this.mServerList.selectedItems.length == 0)
+ return null;
+
+ let serverKey = this.mServerList.selectedItems[0].getAttribute("key");
+ return MailServices.smtp.getServerByKey(serverKey);
+ }
+};
diff --git a/mailnews/base/prefs/content/am-smtp.xul b/mailnews/base/prefs/content/am-smtp.xul
new file mode 100644
index 000000000..3c18bfe11
--- /dev/null
+++ b/mailnews/base/prefs/content/am-smtp.xul
@@ -0,0 +1,112 @@
+<?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 page SYSTEM "chrome://messenger/locale/am-advanced.dtd">
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&smtpServer.label;"
+ onload="gSmtpServerListWindow.onLoad();">
+ <script type="application/javascript"
+ src="chrome://messenger/content/amUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/am-smtp.js"/>
+
+ <stringbundle id="bundle_messenger"
+ src="chrome://messenger/locale/messenger.properties"/>
+
+ <vbox flex="1" style="overflow: auto;">
+ <dialogheader title="&smtpServer.label;"/>
+
+ <label control="smtpList">&smtpDescription.label;</label>
+ <separator class="thin"/>
+
+ <hbox flex="1">
+ <listbox id="smtpList"
+ onselect="gSmtpServerListWindow.onSelectionChanged(event);"
+ ondblclick="gSmtpServerListWindow.onEditServer(event);"
+ flex="1"/>
+
+ <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>
+ <hbox id="smtpServerInfoBox">
+ <stack flex="1" class="inset">
+ <spacer id="backgroundBox"/>
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox pack="end">
+ <label id="descriptionLabel"
+ value="&serverDescription.label;"
+ control="descriptionValue"/>
+ </hbox>
+ <textbox id="descriptionValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="nameLabel" value="&serverName.label;" control="nameValue"/>
+ </hbox>
+ <textbox id="nameValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="portLabel" value="&serverPort.label;" control="portValue"/>
+ </hbox>
+ <textbox id="portValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="userNameLabel" value="&userName.label;" control="userNameValue"/>
+ </hbox>
+ <textbox id="userNameValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="authMethodLabel" value="&authMethod.label;" control="authMethodValue"/>
+ </hbox>
+ <textbox id="authMethodValue" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label id="connectionSecurityLabel" value="&connectionSecurity.label;"
+ control="useSecureConnectionValue"/>
+ </hbox>
+ <textbox id="useSecureConnectionValue" readonly="true" class="plain"/>
+ </row>
+ </rows>
+ </grid>
+ </stack>
+ </hbox>
+ <separator flex="1"/>
+ </vbox>
+</page>
diff --git a/mailnews/base/prefs/content/amUtils.js b/mailnews/base/prefs/content/amUtils.js
new file mode 100644
index 000000000..2b101f9f1
--- /dev/null
+++ b/mailnews/base/prefs/content/amUtils.js
@@ -0,0 +1,205 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource:///modules/MailUtils.js");
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
+
+function BrowseForLocalFolders()
+{
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ const nsIFile = Components.interfaces.nsIFile;
+
+ var currentFolderTextBox = document.getElementById("server.localPath");
+ var fp = Components.classes["@mozilla.org/filepicker;1"]
+ .createInstance(nsIFilePicker);
+
+ fp.init(window,
+ document.getElementById("browseForLocalFolder")
+ .getAttribute("filepickertitle"),
+ nsIFilePicker.modeGetFolder);
+
+ var currentFolder = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(nsIFile);
+ currentFolder.initWithPath(currentFolderTextBox.value);
+ fp.displayDirectory = currentFolder;
+
+ if (fp.show() != nsIFilePicker.returnOK)
+ 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;
+}
+
+/**
+ * Return server/folder name formatted with server name if needed.
+ *
+ * @param aTargetFolder nsIMsgFolder to format name for
+ * 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 aTargetURI the URI specification to check
+ * @param aIsServer true if the URI specifies only a server (without folder)
+ *
+ * @return the value of aTargetURI if it is valid (usable), otherwise null
+ */
+function checkJunkTargetFolder(aTargetURI, aIsServer)
+{
+ try {
+ // Does the target account exist?
+ let targetServer = MailUtils.getFolderForURI(aTargetURI + (aIsServer ? "/Junk" : ""),
+ !aIsServer).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 aTargetURI the URI of a server or folder to try first
+ * @param aIsServer true if the URI specifies only a server (without folder)
+ *
+ * @return the server/folder URI of a usable target for storing Junk
+ */
+function chooseJunkTargetFolder(aTargetURI, aIsServer)
+{
+ let server = null;
+
+ if (aTargetURI) {
+ server = MailUtils.getFolderForURI(aTargetURI, false).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 aSpamActionTargetAccount The value of the server.*.spamActionTargetAccount pref value (URI).
+ * @param aSpamActionTargetFolder The value of the server.*.spamActionTargetFolder pref value (URI).
+ * @param aProposedTarget The URI of a new target to try.
+ * @param aMoveTargetModeValue The value of the server.*.moveTargetMode pref value (0/1).
+ * @param aServerSpamSettings The nsISpamSettings object of any server
+ * (used just for the MOVE_TARGET_MODE_* constants).
+ * @param aMoveOnSpam The server.*.moveOnSpam pref value (bool).
+ *
+ * @return 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 aTBPaneId Thunderbird pref paneID to open.
+ * @param aTBTabId Thunderbird tabID to open.
+ * @param aTBOtherArgs Other arguments to send to the pref tab.
+ * @param aSMPaneId Seamonkey pref pane to open.
+ */
+function openPrefsFromAccountManager(aTBPaneId, aTBTabId, 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, aTBTabId, 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 aAccountName The account name string to look for.
+ * @param aAccountKey Optional. 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 in fixIterator(MailServices.accounts.accounts,
+ Components.interfaces.nsIMsgAccount))
+ {
+ if (account.key != aAccountKey && account.incomingServer &&
+ aAccountName == account.incomingServer.prettyName) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/mailnews/base/prefs/content/aw-accname.js b/mailnews/base/prefs/content/aw-accname.js
new file mode 100644
index 000000000..6fce2e878
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-accname.js
@@ -0,0 +1,73 @@
+/* -*- Mode: Java; 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/. */
+
+var gPrefsBundle;
+
+function acctNamePageValidate()
+{
+ var accountname = document.getElementById("prettyName").value;
+ var canAdvance = accountname ? true : false;
+
+ // Check if this accountname already exists. If so, return false so that
+ // user can enter a different unique account name.
+ if (canAdvance && accountNameExists(accountname))
+ canAdvance = false;
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function acctNamePageUnload() {
+ var pageData = parent.GetPageData();
+
+ // fix for bug #255473
+ // allow for multiple RSS accounts.
+ // if our isp.rdf file defines "wizardAutoGenerateUniqueHostname"
+ // we generate a unique hostname until we have one that doesn't exist
+ // for RSS accounts, in rss.rdf, userName, hostName and serverType
+ // default to the same thing, so we need to do this to allow for
+ // multiple RSS accounts. Note, they can all have the same pretty name.
+ if (gCurrentAccountData &&
+ gCurrentAccountData.wizardAutoGenerateUniqueHostname)
+ {
+ var serverType = parent.getCurrentServerType(pageData);
+ var userName = parent.getCurrentUserName(pageData);
+ var hostName = parent.getCurrentHostname(pageData);
+ var hostNamePref = hostName;
+ var i = 2;
+ while (parent.AccountExists(userName, hostName, serverType))
+ {
+ // If "Feeds" exists, try "Feeds-2", then "Feeds-3", etc.
+ hostName = hostNamePref + "-" + i;
+ i++;
+ }
+ setPageData(pageData, "server", "hostname", hostName);
+ }
+
+ var accountname = document.getElementById("prettyName").value;
+ setPageData(pageData, "accname", "prettyName", accountname);
+ // Set this to true so we know the user has set the name.
+ setPageData(pageData, "accname", "userset", true);
+ return true;
+}
+
+function acctNamePageInit()
+{
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ var accountNameInput = document.getElementById("prettyName");
+ if (accountNameInput.value=="") {
+ var pageData = parent.GetPageData();
+ var type = parent.getCurrentServerType(pageData);
+ var accountName;
+
+ if (gCurrentAccountData && gCurrentAccountData.wizardAccountName)
+ accountName = gCurrentAccountData.wizardAccountName;
+ else if (type == "nntp")
+ accountName = pageData.newsserver.hostname.value;
+ else
+ accountName = pageData.identity.email.value;
+ accountNameInput.value = accountName;
+ }
+ acctNamePageValidate();
+}
diff --git a/mailnews/base/prefs/content/aw-accounttype.js b/mailnews/base/prefs/content/aw-accounttype.js
new file mode 100644
index 000000000..64b828594
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-accounttype.js
@@ -0,0 +1,114 @@
+/* -*- Mode: Java; 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/. */
+
+function setAccountTypeData()
+{
+ var rg = document.getElementById("acctyperadio");
+ var selectedItemId = rg.selectedItem.id;
+ var mail = selectedItemId == "mailaccount";
+ var news = selectedItemId == "newsaccount";
+
+ var pageData = parent.GetPageData();
+ setPageData(pageData, "accounttype", "mailaccount", mail);
+ setPageData(pageData, "accounttype", "newsaccount", news);
+
+ // Other account type, e.g. Movemail
+ setPageData(pageData, "accounttype", "otheraccount", !(news || mail));
+}
+
+function acctTypePageUnload() {
+ gCurrentAccountData = null;
+ setAccountTypeData();
+ initializeIspData();
+ setupWizardPanels();
+ return true;
+}
+
+function setupWizardPanels() {
+ if (gCurrentAccountData && gCurrentAccountData.useOverlayPanels) {
+ if ("testingIspServices" in this) {
+ if ("SetPageMappings" in this && testingIspServices()) {
+ SetPageMappings(document.documentElement.currentPage.id, "done");
+ }
+ }
+ }
+
+ var pageData = parent.GetPageData();
+
+ // We default this to false, even though we could set it to true if we
+ // are going to display the page. However as the accname page will set
+ // it to true for us, we'll just default it to false and not work it out
+ // twice.
+ setPageData(pageData, "accname", "userset", false);
+
+ // If we need to skip wizardpanels, set the wizard to jump to the
+ // summary page i.e., last page. Otherwise, set the flow based
+ // on type of account (mail or news) user is creating.
+ var skipPanels = "";
+ try {
+ if (gCurrentAccountData.wizardSkipPanels)
+ skipPanels = gCurrentAccountData.wizardSkipPanels.toString().toLowerCase();
+ } catch(ex) {}
+
+ // "done" is the only required panel for all accounts. We used to require an identity panel but not anymore.
+ // initialize wizardPanels with the optional mail/news panels
+ var wizardPanels, i;
+ var isMailAccount = pageData.accounttype.mailaccount;
+ var isNewsAccount = pageData.accounttype.newsaccount;
+ if (skipPanels == "true") // Support old syntax of true/false for wizardSkipPanels
+ wizardPanels = new Array("identitypage");
+ else if (isMailAccount && isMailAccount.value)
+ wizardPanels = new Array("identitypage", "incomingpage", "outgoingpage", "accnamepage");
+ else if (isNewsAccount && isNewsAccount.value)
+ wizardPanels = new Array("identitypage", "newsserver", "accnamepage");
+ else { // An account created by an extension and XUL overlays
+ var button = document.getElementById("acctyperadio").selectedItem;
+ wizardPanels = button.value.split(/ *, */);
+ }
+
+ // Create a hash table of the panels to skip
+ var skipArray = skipPanels.split(",");
+ var skipHash = new Array();
+ for (i = 0; i < skipArray.length; i++)
+ skipHash[skipArray[i]] = skipArray[i];
+
+ // Remove skipped panels
+ i = 0;
+ while (i < wizardPanels.length) {
+ if (wizardPanels[i] in skipHash)
+ wizardPanels.splice(i, 1);
+ else
+ i++;
+ }
+
+ wizardPanels.push("done");
+
+ // Set up order of panels
+ for (i = 0; i < (wizardPanels.length-1); i++)
+ setNextPage(wizardPanels[i], wizardPanels[i+1]);
+
+ // make the account type page go to the very first of our approved wizard panels...this is usually going to
+ // be accounttype --> identitypage unless we were configured to skip the identity page
+ setNextPage("accounttype",wizardPanels[0]);
+}
+
+function initializeIspData()
+{
+ let mailAccount = document.getElementById("mailaccount");
+ if (!mailAccount || !mailAccount.selected) {
+ parent.SetCurrentAccountData(null);
+ }
+
+ // now reflect the datasource up into the parent
+ var accountSelection = document.getElementById("acctyperadio");
+
+ var ispName = accountSelection.selectedItem.id;
+
+ dump("initializing ISP data for " + ispName + "\n");
+
+ if (!ispName || ispName == "") return;
+
+ parent.PrefillAccountForIsp(ispName);
+}
diff --git a/mailnews/base/prefs/content/aw-done.js b/mailnews/base/prefs/content/aw-done.js
new file mode 100644
index 000000000..79fb7d000
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-done.js
@@ -0,0 +1,215 @@
+/* -*- 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gPrefsBundle;
+
+function donePageInit() {
+ var pageData = parent.GetPageData();
+ var currentAccountData = gCurrentAccountData;
+
+ if ("testingIspServices" in this) {
+ if (testingIspServices()) {
+ if ("setOtherISPServices" in this) {
+ setOtherISPServices();
+ }
+
+ if (currentAccountData && currentAccountData.useOverlayPanels && currentAccountData.createNewAccount) {
+ var backButton = document.documentElement.getButton("back");
+ backButton.setAttribute("disabled", true);
+
+ var cancelButton = document.documentElement.getButton("cancel");
+ cancelButton.setAttribute("disabled", true);
+
+ setPageData(pageData, "identity", "email", gEmailAddress);
+ setPageData(pageData, "identity", "fullName", gUserFullName);
+ setPageData(pageData, "login", "username", gScreenName);
+ }
+ }
+ }
+
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ var showMailServerDetails = true;
+
+ if (currentAccountData) {
+ // find out if we need to hide server details
+ showMailServerDetails = currentAccountData.showServerDetailsOnWizardSummary;
+ // Change the username field description to email field label in aw-identity
+ if (currentAccountData.emailIDFieldTitle)
+ setUserNameDescField(currentAccountData.emailIDFieldTitle);
+ }
+
+ // Find out if we need to hide details for incoming servers
+ var hideIncoming = (gCurrentAccountData && gCurrentAccountData.wizardHideIncoming);
+
+ var email = "";
+ if (pageData.identity && pageData.identity.email) {
+ // fixup the email
+ email = pageData.identity.email.value;
+ if (email.split('@').length < 2 &&
+ currentAccountData &&
+ currentAccountData.domain)
+ email += "@" + currentAccountData.domain;
+ }
+ setDivTextFromForm("identity.email", email);
+
+ var userName="";
+ if (pageData.login && pageData.login.username)
+ userName = pageData.login.username.value;
+ if (!userName && email) {
+ if (currentAccountData && currentAccountData.incomingServerUserNameRequiresDomain == undefined)
+ currentAccountData.incomingServerUserNameRequiresDomain = false;
+ userName = getUsernameFromEmail(email, currentAccountData &&
+ currentAccountData.incomingServerUserNameRequiresDomain);
+ }
+ // Hide the "username" field if we don't want to show information
+ // on the incoming server.
+ setDivTextFromForm("server.username", hideIncoming ? null : userName);
+
+ var smtpUserName="";
+ if (pageData.login && pageData.login.smtpusername)
+ smtpUserName = pageData.login.smtpusername.value;
+ if (!smtpUserName && email)
+ smtpUserName = getUsernameFromEmail(email, currentAccountData &&
+ currentAccountData.smtpUserNameRequiresDomain);
+ setDivTextFromForm("smtpServer.username", smtpUserName);
+
+ if (pageData.accname && pageData.accname.prettyName) {
+ // If currentAccountData has a pretty name (e.g. news link or from
+ // isp data) only set the user name with the pretty name if the
+ // pretty name originally came from ispdata
+ if ((currentAccountData &&
+ currentAccountData.incomingServer.prettyName) &&
+ (pageData.ispdata &&
+ pageData.ispdata.supplied &&
+ pageData.ispdata.supplied.value))
+ {
+ // Get the polished account name - if the user has modified the
+ // account name then use that, otherwise use the userName.
+ pageData.accname.prettyName.value =
+ gPrefsBundle.getFormattedString("accountName",
+ [currentAccountData.incomingServer.prettyName,
+ (pageData.accname.userset && pageData.accname.userset.value) ? pageData.accname.prettyName.value : userName]);
+ }
+ // else just use the default supplied name
+
+ setDivTextFromForm("account.name", pageData.accname.prettyName.value);
+ } else {
+ setDivTextFromForm("account.name", "");
+ }
+
+ // Show mail servers (incoming&outgoing) details
+ // based on current account data. ISP can set
+ // rdf value of literal showServerDetailsOnWizardSummary
+ // to false to hide server details
+ if (showMailServerDetails && !serverIsNntp(pageData)) {
+ var incomingServerName="";
+ if (pageData.server && pageData.server.hostname) {
+ incomingServerName = pageData.server.hostname.value;
+ if (!incomingServerName &&
+ currentAccountData &&
+ currentAccountData.incomingServer.hostname)
+ incomingServerName = currentAccountData.incomingServer.hostName;
+ }
+ // Hide the incoming server name field if the user specified
+ // wizardHideIncoming in the ISP defaults file
+ setDivTextFromForm("server.name", hideIncoming ? null : incomingServerName);
+ setDivTextFromForm("server.port", pageData.server.port ? pageData.server.port.value : null);
+ var incomingServerType="";
+ if (pageData.server && pageData.server.servertype) {
+ incomingServerType = pageData.server.servertype.value;
+ if (!incomingServerType &&
+ currentAccountData &&
+ currentAccountData.incomingServer.type)
+ incomingServerType = currentAccountData.incomingServer.type;
+ }
+ setDivTextFromForm("server.type", incomingServerType.toUpperCase());
+
+ var smtpServerName="";
+ if (pageData.server && pageData.server.smtphostname) {
+ let smtpServer = MailServices.smtp.defaultServer;
+ smtpServerName = pageData.server.smtphostname.value;
+ if (!smtpServerName && smtpServer && smtpServer.hostname)
+ smtpServerName = smtpServer.hostname;
+ }
+ setDivTextFromForm("smtpServer.name", smtpServerName);
+ }
+ else {
+ setDivTextFromForm("server.name", null);
+ setDivTextFromForm("server.type", null);
+ setDivTextFromForm("server.port", null);
+ setDivTextFromForm("smtpServer.name", null);
+ }
+
+ if (serverIsNntp(pageData)) {
+ var newsServerName="";
+ if (pageData.newsserver && pageData.newsserver.hostname)
+ newsServerName = pageData.newsserver.hostname.value;
+ if (newsServerName) {
+ // No need to show username for news account
+ setDivTextFromForm("server.username", null);
+ }
+ setDivTextFromForm("newsServer.name", newsServerName);
+ setDivTextFromForm("server.port", null);
+ }
+ else {
+ setDivTextFromForm("newsServer.name", null);
+ }
+
+ var isPop = false;
+ if (pageData.server && pageData.server.servertype) {
+ isPop = (pageData.server.servertype.value == "pop3");
+ }
+
+ hideShowDownloadMsgsUI(isPop);
+}
+
+function hideShowDownloadMsgsUI(isPop)
+{
+ // only show the "download messages now" UI
+ // if this is a pop account, we are online, and this was opened
+ // from the 3 pane
+ var downloadMsgs = document.getElementById("downloadMsgs");
+ if (isPop) {
+ if (!Services.io.offline) {
+ if (window.opener.location.href == "chrome://messenger/content/messenger.xul") {
+ downloadMsgs.hidden = false;
+ return;
+ }
+ }
+ }
+
+ // else hide it
+ downloadMsgs.hidden = true;
+}
+
+function setDivTextFromForm(divid, value) {
+
+ // collapse the row if the div has no value
+ var div = document.getElementById(divid);
+ if (!value) {
+ div.setAttribute("collapsed","true");
+ return;
+ }
+ else {
+ div.removeAttribute("collapsed");
+ }
+
+ // otherwise fill in the .text element
+ div = document.getElementById(divid+".text");
+ if (!div) return;
+
+ // set the value
+ div.setAttribute("value", value);
+}
+
+function setUserNameDescField(name)
+{
+ if (name) {
+ var userNameField = document.getElementById("server.username.label");
+ userNameField.setAttribute("value", name);
+ }
+}
diff --git a/mailnews/base/prefs/content/aw-identity.js b/mailnews/base/prefs/content/aw-identity.js
new file mode 100644
index 000000000..ed99c4400
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-identity.js
@@ -0,0 +1,212 @@
+/* -*- 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/. */
+
+var gCurrentDomain;
+var gPrefsBundle;
+
+function identityPageValidate()
+{
+ var canAdvance = false;
+ var name = document.getElementById("fullName").value;
+ let email = document.getElementById("email").value.trim();
+
+ if (name && email) {
+ canAdvance = gCurrentDomain ? emailNameIsLegal(email) : emailNameAndDomainAreLegal(email);
+
+ if (gCurrentDomain && canAdvance) {
+ // For prefilled ISP data we must check if the account already exists as
+ // there is no second chance. The email is the username since the user
+ // fills in [______]@example.com.
+ var pageData = parent.GetPageData();
+ var serverType = parent.getCurrentServerType(pageData);
+ var hostName = parent.getCurrentHostname(pageData);
+ var usernameWithDomain = email + "@" + gCurrentDomain;
+ if (parent.AccountExists(email, hostName, serverType) ||
+ parent.AccountExists(usernameWithDomain, hostName, serverType))
+ canAdvance = false;
+ }
+ }
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function identityPageUnload()
+{
+ var pageData = parent.GetPageData();
+ var name = document.getElementById("fullName").value;
+ let email = document.getElementById("email").value.trim();
+ setPageData(pageData, "identity", "fullName", name);
+ setPageData(pageData, "identity", "email", email);
+
+ return true;
+}
+
+// This is for the case when the code appends the domain
+// unnecessarily.
+// This simply gets rid of "@domain" from "foo@domain"
+
+function fixPreFilledEmail()
+{
+ var emailElement = document.getElementById("email");
+ var email = emailElement.value;
+ var emailArray = email.split('@');
+
+ if (gCurrentDomain) {
+ // check if user entered an @ sign even though we have a domain
+ if (emailArray.length >= 2) {
+ email = emailArray[0];
+ emailElement.value = email;
+ }
+ }
+}
+
+/**
+ * This function checks for common illegal characters.
+ * It shouldn't be too strict, since we do more extensive tests later.
+ */
+function emailNameIsLegal(aString)
+{
+ return aString && !/[^!-?A-~]/.test(aString);
+}
+
+function emailNameAndDomainAreLegal(aString)
+{
+ return /^[!-?A-~]+\@[A-Za-z0-9.-]+$/.test(aString);
+}
+
+function identityPageInit()
+{
+ gCurrentDomain = null;
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ clearEmailTextItems();
+ setEmailDescriptionText();
+ checkForDomain();
+ checkForFullName();
+ checkForEmail();
+ fixPreFilledEmail();
+ identityPageValidate();
+}
+
+function clearEmailTextItems()
+{
+ var emailDescText = document.getElementById("emailDescText");
+
+ if (emailDescText.hasChildNodes())
+ emailDescText.firstChild.remove();
+
+ var postEmailText = document.getElementById("postEmailText");
+ postEmailText.setAttribute("value", "");
+ postEmailText.hidden = true;
+}
+
+// Use email example data that ISP has provided. ISP data, if avaialble
+// for the choice user has made, will be read into CurrentAccountData.
+// Default example data from properties will be used when the info is missing.
+function setEmailDescriptionText()
+{
+ var emailDescText = document.getElementById("emailDescText");
+ var emailFieldLabel = document.getElementById("emailFieldLabel");
+ var currentAccountData = parent.gCurrentAccountData;
+
+ var displayText = null;
+ var emailFieldLabelData = null;
+ var setDefaultEmailDescStrings = true;
+
+ // Set the default field label
+ emailFieldLabel.setAttribute("value", gPrefsBundle.getString("emailFieldText"));
+
+ // Get values for customized data from current account
+ if (currentAccountData)
+ {
+ var emailProvider = currentAccountData.emailProviderName;
+ var sampleEmail = currentAccountData.sampleEmail;
+ var sampleUserName = currentAccountData.sampleUserName;
+ var emailIDDesc = currentAccountData.emailIDDescription;
+ var emailIDTitle = currentAccountData.emailIDFieldTitle;
+
+ if (emailProvider &&
+ sampleEmail &&
+ sampleUserName &&
+ emailIDDesc &&
+ emailIDTitle)
+ {
+ // Get email description data
+ displayText = gPrefsBundle.getFormattedString("customizedEmailText",
+ [emailProvider,
+ emailIDDesc,
+ sampleEmail,
+ sampleUserName]);
+
+ // Set emailfield label
+ emailFieldLabelData = emailIDTitle;
+ emailFieldLabel.setAttribute("value", emailFieldLabelData);
+
+ // Need to display customized data. Turn off default settings.
+ setDefaultEmailDescStrings = false;
+ }
+ }
+
+ if (setDefaultEmailDescStrings)
+ {
+ // Check for obtained values and set with default values if needed
+ var username = gPrefsBundle.getString("exampleEmailUserName");
+ var domain = gPrefsBundle.getString("exampleEmailDomain");
+
+ displayText = gPrefsBundle.getFormattedString("defaultEmailText",
+ [username, domain]);
+ }
+
+ // Create a text nodes with text to be displayed
+ var emailDescTextNode = document.createTextNode(displayText);
+
+ // Display the dynamically generated text for email description
+ emailDescText.appendChild(emailDescTextNode);
+}
+
+// retrieve the current domain from the parent wizard window,
+// and update the UI to add the @domain static text
+function checkForDomain()
+{
+ var accountData = parent.gCurrentAccountData;
+ if (!accountData || !accountData.domain)
+ return;
+
+ // save in global variable
+ gCurrentDomain = accountData.domain;
+ var postEmailText = document.getElementById("postEmailText");
+ postEmailText.setAttribute("value", "@" + gCurrentDomain);
+ postEmailText.hidden = false;
+}
+
+function checkForFullName() {
+ var name = document.getElementById("fullName");
+ if (name.value=="") {
+ try {
+ var userInfo = Components.classes["@mozilla.org/userinfo;1"].getService(Components.interfaces.nsIUserInfo);
+ name.value = userInfo.fullname;
+ }
+ catch (ex) {
+ // dump ("checkForFullName failed: " + ex + "\n");
+ }
+ }
+}
+
+function checkForEmail()
+{
+ var email = document.getElementById("email");
+ var pageData = parent.GetPageData();
+ if (pageData && pageData.identity && pageData.identity.email) {
+ email.value = pageData.identity.email.value;
+ }
+ if (email.value=="") {
+ try {
+ var userInfo = Components.classes["@mozilla.org/userinfo;1"].getService(Components.interfaces.nsIUserInfo);
+ email.value = userInfo.emailAddress;
+ }
+ catch (ex) {
+ // dump ("checkForEmail failed: " + ex + "\n");
+ }
+ }
+}
diff --git a/mailnews/base/prefs/content/aw-incoming.js b/mailnews/base/prefs/content/aw-incoming.js
new file mode 100644
index 000000000..fcc063fed
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-incoming.js
@@ -0,0 +1,176 @@
+/* -*- 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/. */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+
+var gOnMailServersPage;
+var gOnNewsServerPage;
+var gHideIncoming;
+var gProtocolInfo = null;
+
+function incomingPageValidate()
+{
+ var canAdvance = true;
+ var hostName;
+
+ if (gOnMailServersPage) {
+ hostName = document.getElementById("incomingServer").value;
+ if (!gHideIncoming && !isLegalHostNameOrIP(cleanUpHostName(hostName)))
+ canAdvance = false;
+ }
+ if (gOnNewsServerPage) {
+ hostName = document.getElementById("newsServer").value;
+ if (!isLegalHostNameOrIP(cleanUpHostName(hostName)))
+ canAdvance = false;
+ }
+
+ if (canAdvance) {
+ var pageData = parent.GetPageData();
+ var serverType = parent.getCurrentServerType(pageData);
+ var username = document.getElementById("username").value;
+ if (gProtocolInfo && gProtocolInfo.requiresUsername && !username ||
+ parent.AccountExists(username, hostName, serverType))
+ canAdvance = false;
+ }
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function incomingPageUnload()
+{
+ var pageData = parent.GetPageData();
+
+ if (gOnMailServersPage) {
+ // If we have hidden the incoming server dialogs, we don't want
+ // to set the server to an empty value here
+ if (!gHideIncoming) {
+ var incomingServerName = document.getElementById("incomingServer");
+ setPageData(pageData, "server", "hostname", cleanUpHostName(incomingServerName.value));
+ }
+ var serverport = document.getElementById("serverPort").value;
+ setPageData(pageData, "server", "port", serverport);
+ var username = document.getElementById("username").value;
+ setPageData(pageData, "login", "username", username);
+ }
+ else if (gOnNewsServerPage) {
+ var newsServerName = document.getElementById("newsServer");
+ setPageData(pageData, "newsserver", "hostname", cleanUpHostName(newsServerName.value));
+ }
+
+ return true;
+}
+
+function incomingPageInit() {
+ gOnMailServersPage = (document.documentElement.currentPage.id == "incomingpage");
+ gOnNewsServerPage = (document.documentElement.currentPage.id == "newsserver");
+ if (gOnNewsServerPage)
+ {
+ var newsServer = document.getElementById("newsServer");
+ var pageData = parent.GetPageData();
+ try
+ {
+ newsServer.value = pageData.newsserver.hostname.value;
+ }
+ catch (ex){}
+ }
+
+ gHideIncoming = false;
+ if (gCurrentAccountData && gCurrentAccountData.wizardHideIncoming)
+ gHideIncoming = true;
+
+ var incomingServerbox = document.getElementById("incomingServerbox");
+ var serverTypeBox = document.getElementById("serverTypeBox");
+ if (incomingServerbox && serverTypeBox) {
+ if (gHideIncoming) {
+ incomingServerbox.setAttribute("hidden", "true");
+ serverTypeBox.setAttribute("hidden", "true");
+ }
+ else {
+ incomingServerbox.removeAttribute("hidden");
+ serverTypeBox.removeAttribute("hidden");
+ }
+ }
+
+ // Server type selection (pop3 or imap) is for mail accounts only
+ var pageData = parent.GetPageData();
+ var isMailAccount = pageData.accounttype.mailaccount.value;
+ var isOtherAccount = pageData.accounttype.otheraccount.value;
+ if (isMailAccount && !gHideIncoming) {
+ var serverTypeRadioGroup = document.getElementById("servertype");
+ /*
+ * Check to see if the radiogroup has any value. If there is no
+ * value, this must be the first time user visting this page in the
+ * account setup process. So, the default is set to pop3. If there
+ * is a value (it's used automatically), user has already visited
+ * page and server type selection is done. Once user visits the page,
+ * the server type value from then on will persist (whether the selection
+ * came from the default or the user action).
+ */
+ if (!serverTypeRadioGroup.value) {
+ /*
+ * if server type was set to imap in isp data, then
+ * we preset the server type radio group accordingly,
+ * otherwise, use pop3 as the default.
+ */
+ var serverTypeRadioItem = document.getElementById(pageData.server &&
+ pageData.server.servertype && pageData.server.servertype.value == "imap" ?
+ "imap" : "pop3");
+ serverTypeRadioGroup.selectedItem = serverTypeRadioItem; // Set pop3 server type as default selection
+ }
+ var leaveMessages = document.getElementById("leaveMessagesOnServer");
+ var deferStorage = document.getElementById("deferStorage");
+ setServerType();
+ setServerPrefs(leaveMessages);
+ setServerPrefs(deferStorage);
+ }
+ else if (isOtherAccount) {
+ document.getElementById("deferStorageBox").hidden = true;
+ }
+
+ if (pageData.server && pageData.server.hostname) {
+ var incomingServerTextBox = document.getElementById("incomingServer");
+ if (incomingServerTextBox && incomingServerTextBox.value == "")
+ incomingServerTextBox.value = pageData.server.hostname.value;
+ }
+
+ // pageData.server is not a real nsMsgIncomingServer so it does not have
+ // protocolInfo property implemented.
+ let type = parent.getCurrentServerType(pageData);
+ gProtocolInfo = Components.classes["@mozilla.org/messenger/protocol/info;1?type=" + type]
+ .getService(Components.interfaces.nsIMsgProtocolInfo);
+ var loginNameInput = document.getElementById("username");
+
+ if (loginNameInput.value == "") {
+ if (gProtocolInfo.requiresUsername) {
+ // since we require a username, use the uid from the email address
+ loginNameInput.value = parent.getUsernameFromEmail(pageData.identity.email.value, gCurrentAccountData &&
+ gCurrentAccountData.incomingServerUserNameRequiresDomain);
+ }
+ }
+ incomingPageValidate();
+}
+
+function setServerType()
+{
+ var pageData = parent.GetPageData();
+ var serverType = document.getElementById("servertype").value;
+ var deferStorageBox = document.getElementById("deferStorageBox");
+ var leaveMessages = document.getElementById("leaveMsgsOnSrvrBox");
+ var port = serverType == "pop3" ? 110 : 143;
+
+ document.getElementById("serverPort").value = port;
+ document.getElementById("defaultPortValue").value = port;
+
+ deferStorageBox.hidden = serverType == "imap";
+ leaveMessages.hidden = serverType == "imap";
+ setPageData(pageData, "server", "servertype", serverType);
+ setPageData(pageData, "server", "port", port);
+ incomingPageValidate();
+}
+
+function setServerPrefs(aThis)
+{
+ setPageData(parent.GetPageData(), "server", aThis.id, aThis.checked);
+}
diff --git a/mailnews/base/prefs/content/aw-outgoing.js b/mailnews/base/prefs/content/aw-outgoing.js
new file mode 100644
index 000000000..8caffc274
--- /dev/null
+++ b/mailnews/base/prefs/content/aw-outgoing.js
@@ -0,0 +1,151 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/hostnameUtils.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+var gProtocolInfo = null;
+var gPrefsBundle;
+
+function outgoingPageValidate() {
+ let canAdvance = true;
+
+ let smtpServer = document.getElementById("smtphostname").value;
+ let usingDefaultSMTP = document.getElementById("noSmtp").hidden;
+ if (!usingDefaultSMTP && !isLegalHostNameOrIP(cleanUpHostName(smtpServer)))
+ canAdvance = false;
+
+ document.documentElement.canAdvance = canAdvance;
+}
+
+function outgoingPageUnload() {
+ var pageData = parent.GetPageData();
+ var username = document.getElementById("username").value;
+ let smtpserver = document.getElementById("smtphostname").value;
+ setPageData(pageData, "server", "smtphostname", cleanUpHostName(smtpserver));
+
+ // If SMTP username box is blank it is because the
+ // incoming and outgoing server names were the same,
+ // so set to be same as incoming username
+ var smtpusername = document.getElementById("smtpusername").value || username;
+
+ setPageData(pageData, "login", "smtpusername", smtpusername);
+
+ return true;
+}
+
+function outgoingPageInit() {
+ gPrefsBundle = document.getElementById("bundle_prefs");
+ var pageData = parent.GetPageData();
+
+ var smtpServer = null;
+ var smtpCreateNewServer = gCurrentAccountData && gCurrentAccountData.smtpCreateNewServer;
+
+ // Don't use the default smtp server if smtp server creation was explicitly
+ // requested in isp rdf.
+ // If we're reusing the default smtp we should not set the smtp hostname.
+ if (MailServices.smtp.defaultServer && !smtpCreateNewServer) {
+ smtpServer = MailServices.smtp.defaultServer;
+ setPageData(pageData, "identity", "smtpServerKey", "");
+ }
+
+ var noSmtpBox = document.getElementById("noSmtp");
+ var haveSmtpBox = document.getElementById("haveSmtp");
+
+ var boxToHide;
+ var boxToShow;
+
+ if (pageData.server && pageData.server.smtphostname && smtpCreateNewServer) {
+ var smtpTextBox = document.getElementById("smtphostname");
+ if (smtpTextBox && smtpTextBox.value == "")
+ smtpTextBox.value = pageData.server.smtphostname.value;
+ }
+
+ if (smtpServer && smtpServer.hostname) {
+ // we have a hostname, so modify and show the static text and
+ // store the value of the default smtp server in the textbox.
+ modifyStaticText(smtpServer.hostname, "1")
+ boxToShow = haveSmtpBox;
+ boxToHide = noSmtpBox;
+ }
+ else {
+ // no default hostname yet
+ boxToShow = noSmtpBox;
+ boxToHide = haveSmtpBox;
+ }
+
+ if (boxToHide)
+ boxToHide.setAttribute("hidden", "true");
+
+ if (boxToShow)
+ boxToShow.removeAttribute("hidden");
+
+ var smtpNameInput = document.getElementById("smtpusername");
+ smtpServer = MailServices.smtp.defaultServer;
+ if (smtpServer && smtpServer.hostname && smtpServer.username) {
+ // we have a default SMTP server, so modify and show the static text
+ // and store the username for the default server in the textbox.
+ modifyStaticText(smtpServer.username, "2")
+ hideShowLoginSettings(2, 1, 3);
+ smtpNameInput.value = smtpServer.username;
+ }
+ else {
+ // no default SMTP server yet, so need to compare
+ // incoming and outgoing server names
+ var smtpServerName = pageData.server.smtphostname.value;
+ var incomingServerName = pageData.server.hostname.value;
+ if (smtpServerName == incomingServerName) {
+ // incoming and outgoing server names are the same, so show
+ // the static text and make sure textbox blank for later tests.
+ modifyStaticText(smtpServerName, "3")
+ hideShowLoginSettings(3, 1, 2);
+ smtpNameInput.value = "";
+ }
+ else {
+ // incoming and outgoing server names are different, so set smtp
+ // username's textbox to be the same as incoming's one, unless already set.
+ hideShowLoginSettings(1, 2, 3);
+ smtpNameInput.value = smtpNameInput.value || loginNameInput.value;
+ }
+ }
+ outgoingPageValidate();
+}
+
+function modifyStaticText(smtpMod, smtpBox)
+{
+ // modify the value in the smtp display if we already have a
+ // smtp server so that the single string displays the hostname
+ // or username for the smtp server.
+ var smtpStatic = document.getElementById("smtpStaticText"+smtpBox);
+ if (smtpStatic && smtpStatic.hasChildNodes())
+ smtpStatic.childNodes[0].nodeValue = smtpStatic.getAttribute("prefix") +
+ smtpMod + smtpStatic.getAttribute("suffix");
+}
+
+function hideShowLoginSettings(aEle, bEle, cEle)
+{
+ document.getElementById("loginSet" + aEle).hidden = false;
+ document.getElementById("loginSet" + bEle).hidden = true;
+ document.getElementById("loginSet" + cEle).hidden = true;
+}
+
+var savedPassword="";
+
+function onSavePassword(target) {
+ dump("savePassword changed! (" + target.checked + ")\n");
+ var passwordField = document.getElementById("server.password");
+ if (!passwordField) return;
+
+ if (target.checked) {
+ passwordField.removeAttribute("disabled");
+ passwordField.value = savedPassword;
+ }
+ else {
+ passwordField.setAttribute("disabled", "true");
+ savedPassword = passwordField.value;
+ passwordField.value = "";
+ }
+
+}
diff --git a/mailnews/base/prefs/content/ispUtils.js b/mailnews/base/prefs/content/ispUtils.js
new file mode 100644
index 000000000..7735d8a47
--- /dev/null
+++ b/mailnews/base/prefs/content/ispUtils.js
@@ -0,0 +1,166 @@
+/* -*- Mode: Java; 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/. */
+
+// using the rdf service extensively here
+var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
+
+// all the RDF resources we'll be retrieving
+var NC = "http://home.netscape.com/NC-rdf#";
+var Server = rdf.GetResource(NC + "Server");
+var SmtpServer = rdf.GetResource(NC + "SmtpServer");
+var ServerHost = rdf.GetResource(NC + "ServerHost");
+var ServerType = rdf.GetResource(NC + "ServerType");
+var PrefixIsUsername = rdf.GetResource(NC + "PrefixIsUsername");
+var UseAuthenticatedSmtp= rdf.GetResource(NC + "UseAuthenticatedSmtp");
+
+// this is possibly expensive, not sure what to do here
+var ispDefaults;
+
+var nsIRDFResource = Components.interfaces.nsIRDFResource;
+var nsIRDFLiteral = Components.interfaces.nsIRDFLiteral;
+
+var ispRoot = rdf.GetResource("NC:ispinfo");
+
+// given an ISP's domain URI, look up all relevant information about it
+function getIspDefaultsForUri(domainURI)
+{
+ if (!ispDefaults)
+ ispDefaults = rdf.GetDataSource("rdf:ispdefaults");
+
+ var domainRes = rdf.GetResource(domainURI);
+
+ var result = dataSourceToObject(ispDefaults, domainRes);
+
+ if (!result) return null;
+
+ // The domainURI should be in the format domain:example.com. (Where
+ // example.com is the domain name to use for all email addresses). If
+ // it does not match this pattern, then it is possible no domain
+ // has been specified, so we should leave it uninitialized.
+ if (domainURI.startsWith("domain:")) {
+ // add this extra attribute which is the domain itself
+ var domainData = domainURI.split(':');
+ if (domainData.length > 1) {
+ // To faciltate distributing two different account types for one ISP,
+ // it's possible to add parameters to the domain URI
+ // - e.g. domain:example.com?type=imap.
+ // This is necessary so RDF doesn't think they're the same.
+
+ // Save the domain, but only the part up to the (possible) question mark.
+ result.domain = domainData[1].replace(/\?.*/, "");
+ }
+ }
+ return result;
+}
+
+// construct an ISP's domain URI based on it's domain
+// (i.e. turns example.com -> domain:example.com)
+function getIspDefaultsForDomain(domain) {
+ let domainURI = "domain:" + domain;
+ return getIspDefaultsForUri(domainURI);
+}
+
+// Given an email address (like "joe@example.com") look up
+function getIspDefaultsForEmail(email) {
+
+ var emailData = getEmailInfo(email);
+
+ var ispData = null;
+ if (emailData)
+ ispData = getIspDefaultsForDomain(emailData.domain);
+
+ prefillIspData(ispData, email);
+
+ return ispData;
+}
+
+// given an email address, split it into username and domain
+// return in an associative array
+function getEmailInfo(email) {
+ if (!email) return null;
+
+ var result = new Object;
+
+ var emailData = email.split('@');
+
+ if (emailData.length != 2) {
+ dump("bad e-mail address!\n");
+ return null;
+ }
+
+ // all the variables we'll be returning
+ result.username = emailData[0];
+ result.domain = emailData[1];
+
+ return result;
+}
+
+function prefillIspData(ispData, email, fullName) {
+ if (!ispData) return;
+
+ // make sure these objects exist
+ if (!ispData.identity) ispData.identity = new Object;
+ if (!ispData.incomingServer) ispData.incomingServer = new Object;
+
+ // fill in e-mail if it's not already there
+ if (email && !ispData.identity.email)
+ ispData.identity.email = email;
+
+ var emailData = getEmailInfo(email);
+ if (emailData) {
+
+ // fill in the username (assuming the ISP doesn't prevent it)
+ if (!ispData.incomingServer.userName &&
+ !ispData.incomingServer.noDefaultUsername)
+ ispData.incomingServer.username = emailData.username;
+ }
+}
+
+// this function will extract an entire datasource into a giant
+// associative array for easy retrieval from JS
+var NClength = NC.length;
+function dataSourceToObject(datasource, root)
+{
+ var result = null;
+ var arcs = datasource.ArcLabelsOut(root);
+
+ while (arcs.hasMoreElements()) {
+ var arc = arcs.getNext().QueryInterface(nsIRDFResource);
+
+ var arcName = arc.Value;
+ arcName = arcName.substring(NClength, arcName.length);
+
+ if (!result) result = new Object;
+
+ var target = datasource.GetTarget(root, arc, true);
+
+ var value;
+ var targetHasChildren = false;
+ try {
+ target = target.QueryInterface(nsIRDFResource);
+ targetHasChildren = true;
+ } catch (ex) {
+ target = target.QueryInterface(nsIRDFLiteral);
+ }
+
+ if (targetHasChildren)
+ value = dataSourceToObject(datasource, target);
+ else {
+ value = target.Value;
+
+ // fixup booleans/numbers/etc
+ if (value == "true") value = true;
+ else if (value == "false") value = false;
+ else {
+ var num = Number(value);
+ if (!isNaN(num)) value = num;
+ }
+ }
+
+ // add this value
+ result[arcName] = value;
+ }
+ return result;
+}
diff --git a/mailnews/base/prefs/content/removeAccount.js b/mailnews/base/prefs/content/removeAccount.js
new file mode 100644
index 000000000..350a0a065
--- /dev/null
+++ b/mailnews/base/prefs/content/removeAccount.js
@@ -0,0 +1,156 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource:///modules/mailServices.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gServer;
+var gDialog;
+
+function onLoad(event) {
+ gServer = window.arguments[0].account.incomingServer;
+ gDialog = document.documentElement;
+
+ 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", Components.interfaces.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 Components.interfaces.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();
+ window.sizeToContent();
+}
+
+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",
+ "nsILocalFile", "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;
+ }
+
+ window.sizeToContent();
+ gDialog.getButton("disclosure").disabled = true;
+ gDialog.getButton("disclosure").blur();
+}
+
+function removeAccount() {
+ let removeAccount = document.getElementById("removeAccount").checked;
+ let removeData = document.getElementById("removeData").checked;
+ try {
+ // Remove the requested account data.
+ if (removeAccount) {
+ // Remove account
+ // Need to save these before the account and its server is removed.
+ let serverUri = gServer.type + "://" + gServer.hostName;
+ let serverUsername = gServer.username;
+
+ MailServices.accounts.removeAccount(window.arguments[0].account, removeData);
+ window.arguments[0].result = true;
+
+ // Remove password information.
+ let logins = Services.logins.findLogins({}, serverUri, null, serverUri);
+ for (let i = 0; i < logins.length; i++) {
+ if (logins[i].username == serverUsername) {
+ Services.logins.removeLogin(logins[i]);
+ break;
+ }
+ }
+ } else if (removeData) {
+ // Remove files only.
+ // TODO: bug 1302193
+ window.arguments[0].result = false;
+ }
+
+ document.getElementById("status").selectedPanel =
+ document.getElementById("success");
+ } catch (ex) {
+ document.getElementById("status").selectedPanel =
+ document.getElementById("failure");
+ Components.utils.reportError("Failure to remove account: " + ex);
+ window.arguments[0].result = false;
+ }
+}
+
+function onAccept() {
+ // If Cancel is disabled, we already tried to remove the account
+ // and can only close the dialog.
+ if (gDialog.getButton("cancel").disabled)
+ return true;
+
+ 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("infoPane").selectedIndex = 1;
+ window.sizeToContent();
+
+ removeAccount();
+
+ gDialog.getButton("accept").disabled = false;
+ return false;
+}
diff --git a/mailnews/base/prefs/content/removeAccount.xul b/mailnews/base/prefs/content/removeAccount.xul
new file mode 100644
index 000000000..371262a11
--- /dev/null
+++ b/mailnews/base/prefs/content/removeAccount.xul
@@ -0,0 +1,87 @@
+<?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/messenger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/dialogs.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % removalDTD SYSTEM "chrome://messenger/locale/removeAccount.dtd">
+%removalDTD;
+]>
+
+<dialog id="removeAccountDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&dialogTitle;"
+ width="600"
+ buttons="accept,disclosure,cancel"
+ buttonlabelaccept="&removeButton.label;"
+ buttonaccesskeyaccept="&removeButton.accesskey;"
+ defaultButton="cancel"
+ onload="onLoad();"
+ ondialogdisclosure="showInfo();"
+ ondialogaccept="return onAccept();">
+ <stringbundle id="bundle_removeAccount"
+ src="chrome://messenger/locale/removeAccount.properties"/>
+ <script type="application/javascript"
+ src="chrome://messenger/content/removeAccount.js"/>
+ <deck id="infoPane">
+ <vbox flex="1">
+ <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="&removeData.label;"
+ checked="false"
+ accesskey="&removeData.accesskey;"
+ accesskeyChat="&removeData.accesskey;"
+ oncommand="enableRemove();"/>
+ <vbox 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">
+ &removeDataLocalAccount.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 align="center">
+ <spacer flex="1"/>
+ <deck id="status">
+ <vbox align="center">
+ <label>&progressPending;</label>
+ <progressmeter mode="undetermined"/>
+ </vbox>
+ <label id="success">&progressSuccess;</label>
+ <label id="failure">&progressFailure;</label>
+ </deck>
+ <spacer flex="1"/>
+ </vbox>
+ </deck>
+</dialog>
diff --git a/mailnews/base/prefs/content/smtpEditOverlay.js b/mailnews/base/prefs/content/smtpEditOverlay.js
new file mode 100644
index 000000000..54590e1b2
--- /dev/null
+++ b/mailnews/base/prefs/content/smtpEditOverlay.js
@@ -0,0 +1,182 @@
+/* -*- Mode: Java; 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/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource:///modules/mailServices.js");
+
+// be real hacky with document.getElementById until document.controls works
+// with the new XUL widgets
+
+var gSmtpUsername;
+var gSmtpDescription;
+var gSmtpUsernameLabel;
+var gSmtpHostname;
+var gSmtpPort;
+var gSmtpAuthMethod;
+var gSmtpSocketType;
+var gPort;
+var gDefaultPort;
+var Ci = Components.interfaces;
+
+function initSmtpSettings(server) {
+ gSmtpUsername = document.getElementById("smtp.username");
+ gSmtpDescription = document.getElementById("smtp.description");
+ gSmtpUsernameLabel = document.getElementById("smtp.username.label");
+ 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");
+
+ sizeToContent();
+
+ sslChanged(false);
+ authMethodChanged(false);
+
+ if (MailServices.smtp.defaultServer)
+ onLockPreference();
+
+ // 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
+ Components.utils.reportError("Error while getting locked prefs: " + e);
+ }
+}
+
+/**
+ * Does the work of disabling an element given the array which contains xul id/prefstring pairs.
+ *
+ * @param prefstrArray array of XUL elements to check
+ *
+ * TODO: try to merge this with disableIfLocked function in am-offline.js (bug 755885)
+ */
+function disableIfLocked(prefstrArray)
+{
+ let finalPrefString = "mail.smtpserver." +
+ MailServices.smtp.defaultServer.key + ".";
+ let smtpPrefBranch = Services.prefs.getBranch(finalPrefString);
+
+ for (let prefstring in prefstrArray)
+ if (smtpPrefBranch.prefIsLocked(prefstring))
+ prefstrArray[prefstring].disabled = true;
+}
+
+function saveSmtpSettings(server)
+{
+ //dump("Saving to " + server + "\n");
+ 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 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/mailnews/base/prefs/content/smtpEditOverlay.xul b/mailnews/base/prefs/content/smtpEditOverlay.xul
new file mode 100644
index 000000000..f78916d2a
--- /dev/null
+++ b/mailnews/base/prefs/content/smtpEditOverlay.xul
@@ -0,0 +1,124 @@
+<?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 overlay SYSTEM "chrome://messenger/locale/smtpEditOverlay.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src="chrome://messenger/content/smtpEditOverlay.js"/>
+
+ <vbox id="smtpServerEditor">
+ <groupbox>
+ <caption label="&settings.caption;"/>
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&serverDescription.label;"
+ accesskey="&serverDescription.accesskey;"
+ control="smtp.description"/>
+ <textbox id="smtp.description"
+ flex="1"
+ preftype="string"
+ prefstring="mail.smtpserver.%serverkey%.description"/>
+ </row>
+ <row align="center">
+ <label value="&serverName.label;"
+ accesskey="&serverName.accesskey;"
+ control="smtp.hostname"/>
+ <textbox id="smtp.hostname"
+ flex="1"
+ preftype="string"
+ class="uri-element"
+ prefstring="mail.smtpserver.%serverkey%.hostname"/>
+ </row>
+ <row align="center">
+ <label value="&serverPort.label;"
+ accesskey="&serverPort.accesskey;"
+ control="smtp.port"/>
+ <hbox align="center">
+ <textbox id="smtp.port"
+ type="number"
+ min="0"
+ max="65535"
+ size="5"
+ preftype="int"
+ prefstring="mail.smtpserver.%serverkey%.port"/>
+ <label value="&serverPortDefault.label;"/>
+ <label id="smtp.defaultPort"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <caption label="&security.caption;"/>
+
+ <grid flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&connectionSecurity.label;"
+ accesskey="&connectionSecurity.accesskey;"
+ control="smtp.socketType"/>
+ <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>
+ </row>
+ <row align="center">
+ <label value="&authMethod.label;"
+ accesskey="&authMethod.accesskey;"
+ control="server.authMethod"/>
+ <!-- same in am-server.xul/js -->
+ <menulist id="smtp.authMethod"
+ oncommand="authMethodChanged(true);"
+ wsm_persist="true"
+ preftype="int" type="number"
+ 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>
+ </row>
+ <row id="smtp.username.box" align="center">
+ <label id="smtp.username.label" value="&userName.label;"
+ accesskey="&userName.accesskey;"
+ control="smtp.username"/>
+ <textbox id="smtp.username" flex="1"
+ preftype="string"
+ prefstring="mail.smtpserver.%serverkey%.username"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </vbox>
+</overlay>