Dactyloidae/mailnews/base/prefs/content/AccountManager.js
2019-11-17 00:12:35 +08:00

1604 lines
54 KiB
JavaScript

/* -*- 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);
}
/**
* 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 = MailServices.accounts.defaultAccount;
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(MailServices.accounts.defaultAccount, 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) {
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 != MailServices.accounts.defaultAccount &&
server.canBeDefaultServer && account.identities.length > 0)
canSetDefault = true;
canDelete = server.protocolInfo.canDelete || server.canDelete;
}
setEnabled(addAccountItem, true);
setEnabled(setDefaultItem, canSetDefault);
setEnabled(removeItem, canDelete);
}
/**
* Disable buttons/menu items if their control preference is locked.
* 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;
}
/**
* 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;
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(MailServices.accounts.defaultAccount, 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");
}
};