releases-comm-central/im/content/accounts.js

717 строки
26 KiB
JavaScript

/* 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/. */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource:///modules/imServices.jsm");
Cu.import("resource://gre/modules/DownloadUtils.jsm");
// This is the list of notifications that the account manager window observes
const events = [
"prpl-quit",
"account-list-updated",
"account-added",
"account-updated",
"account-removed",
"account-connected",
"account-connecting",
"account-disconnected",
"account-disconnecting",
"account-connect-progress",
"account-connect-error",
"autologin-processed",
"status-changed",
"network:offline-status-changed"
];
var gAccountManager = {
// Sets the delay after connect() or disconnect() during which
// it is impossible to perform disconnect() and connect()
_disabledDelay: 500,
disableTimerID: 0,
_connectedLabelInterval: 0,
load: function am_load() {
// Wait until the password service is ready before offering anything.
Services.logins.initializationPromise.then(() => {
this.accountList = document.getElementById("accountlist");
let defaultID;
for (let acc in this.getAccounts()) {
var elt = document.createElement("richlistitem");
this.accountList.appendChild(elt);
elt.build(acc);
if (!defaultID && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
defaultID = acc.id;
}
for each (let event in events)
Services.obs.addObserver(this, event, false);
if (!this.accountList.getRowCount())
// This is horrible, but it works. Otherwise (at least on mac)
// the wizard is not centered relatively to the account manager
setTimeout(function() { gAccountManager.new(); }, 0);
else {
// we have accounts, show the list
document.getElementById("accountsDesk").selectedIndex = 1;
// ensure an account is selected
if (defaultID)
this.selectAccount(defaultID);
else
this.accountList.selectedIndex = 0;
}
this.setAutoLoginNotification();
this.accountList.addEventListener("keypress", this.onKeyPress, true);
window.addEventListener("unload", this.unload.bind(this));
this._connectedLabelInterval = setInterval(this.updateConnectedLabels, 60000);
}, () => {
this.close();
});
},
unload: function am_unload() {
clearInterval(this._connectedLabelInterval);
for each (let event in events)
Services.obs.removeObserver(this, event);
},
_updateAccountList: function am__updateAccountList() {
let accountList = this.accountList;
let i = 0;
for (let acc in this.getAccounts()) {
let oldItem = accountList.getItemAtIndex(i);
if (oldItem.id != acc.id) {
let accElt = document.getElementById(acc.id);
accountList.insertBefore(accElt, oldItem);
accElt.restoreItems();
}
++i;
}
if (accountList.itemCount == 0) {
// Focus the "New Account" button if there are no accounts left.
document.getElementById("newaccount").focus();
// Return early, otherwise we'll run into an 'undefined property' strict
// warning when trying to focus the buttons. Fixes bug 408.
return;
}
// The selected item is still selected
accountList.selectedItem.buttons.setFocus();
accountList.ensureSelectedElementIsVisible();
// We need to refresh the disabled menu items
this.disableCommandItems();
},
observe: function am_observe(aObject, aTopic, aData) {
if (aTopic == "prpl-quit") {
// libpurple is being uninitialized. We don't need the account
// manager window anymore, close it.
this.close();
return;
}
else if (aTopic == "autologin-processed") {
var notification = document.getElementById("accountsNotificationBox")
.getNotificationWithValue("autoLoginStatus");
if (notification)
notification.close();
return;
}
else if (aTopic == "network:offline-status-changed") {
this.setOffline(aData == "offline");
return;
}
else if (aTopic == "status-changed") {
this.setOffline(aObject.statusType == Ci.imIStatusInfo.STATUS_OFFLINE);
return;
}
else if (aTopic == "account-list-updated") {
this._updateAccountList();
return;
}
// The following notification handlers need an account.
aObject.QueryInterface(Ci.imIAccount);
if (aTopic == "account-added") {
document.getElementById("accountsDesk").selectedIndex = 1;
var elt = document.createElement("richlistitem");
this.accountList.appendChild(elt);
elt.build(aObject);
if (this.accountList.getRowCount() == 1)
this.accountList.selectedIndex = 0;
}
else if (aTopic == "account-removed") {
var elt = document.getElementById(aObject.id);
elt.destroy();
if (!elt.selected) {
elt.remove();
return;
}
// The currently selected element is removed,
// ensure another element gets selected (if the list is not empty)
var selectedIndex = this.accountList.selectedIndex;
// Prevent errors if the timer is active and the account deleted
clearTimeout(this.disableTimerID);
this.disableTimerID = 0;
elt.remove();
var count = this.accountList.getRowCount();
if (!count) {
document.getElementById("accountsDesk").selectedIndex = 0;
return;
}
if (selectedIndex == count)
--selectedIndex;
this.accountList.selectedIndex = selectedIndex;
}
else if (aTopic == "account-updated") {
document.getElementById(aObject.id).build(aObject);
this.disableCommandItems();
}
else if (aTopic == "account-connect-progress")
document.getElementById(aObject.id).updateConnectionState();
else if (aTopic == "account-connect-error")
document.getElementById(aObject.id).updateConnectionError();
else {
const stateEvents = {
"account-connected": "connected",
"account-connecting": "connecting",
"account-disconnected": "disconnected",
"account-disconnecting": "disconnecting"
};
if (aTopic in stateEvents) {
let elt = document.getElementById(aObject.id);
if (!elt)
return; // probably disconnecting a removed account.
if (aTopic == "account-connecting") {
elt.removeAttribute("error");
elt.updateConnectionState();
}
else {
if (aTopic == "account-connected")
elt.refreshConnectedLabel();
}
elt.setAttribute("state", stateEvents[aTopic]);
}
}
},
cancelReconnection: function am_cancelReconnection() {
this.accountList.selectedItem.cancelReconnection();
},
connect: function am_connect() {
let account = this.accountList.selectedItem.account;
if (account.disconnected) {
this.temporarilyDisableButtons();
account.connect();
}
},
disconnect: function am_disconnect() {
let account = this.accountList.selectedItem.account;
if (account.connected || account.connecting) {
this.temporarilyDisableButtons();
account.disconnect();
}
},
addException: function am_addException() {
let account = this.accountList.selectedItem.account;
let prplAccount = account.prplAccount;
if (!account.disconnected || !prplAccount.connectionTarget)
return;
// Open the Gecko SSL exception dialog.
let params = {
exceptionAdded: false,
sslStatus: prplAccount.sslStatus,
prefetchCert: true,
location: prplAccount.connectionTarget
};
window.openDialog("chrome://pippki/content/exceptionDialog.xul", "",
"chrome,centerscreen,modal", params);
// Reconnect the account if an exception was added.
if (params.exceptionAdded)
account.connect();
},
copyDebugLog: function am_copyDebugLog() {
let account = this.accountList.selectedItem.account;
let text = account.getDebugMessages().map(function(dbgMsg) {
let m = dbgMsg.message;
const dateServ = Cc["@mozilla.org/intl/scriptabledateformat;1"]
.getService(Ci.nsIScriptableDateFormat);
let time = new Date(m.timeStamp);
time = dateServ.FormatDateTime("", dateServ.dateFormatShort,
dateServ.timeFormatSeconds,
time.getFullYear(), time.getMonth() + 1,
time.getDate(), time.getHours(),
time.getMinutes(), time.getSeconds());
let level = dbgMsg.logLevel;
if (!level)
return "(" + m.errorMessage + ")";
if (level == dbgMsg.LEVEL_ERROR)
level = "ERROR";
else if (level == dbgMsg.LEVEL_WARNING)
level = "WARN.";
else if (level == dbgMsg.LEVEL_LOG)
level = "LOG ";
else
level = "DEBUG"
return "[" + time + "] " + level + " (@ " + m.sourceLine +
" " + m.sourceName + ":" + m.lineNumber + ")\n" +
m.errorMessage;
}).join("\n");
Cc["@mozilla.org/widget/clipboardhelper;1"]
.getService(Ci.nsIClipboardHelper).copyString(text);
},
showDebugLog: function am_showDebugLog() {
if (!("Core" in window))
Cu.import("resource:///modules/ibCore.jsm");
Core.showDebugLog(this.accountList.selectedItem.account.id);
},
updateConnectedLabels: function am_updateConnectedLabels() {
for (let i = 0; i < gAccountManager.accountList.itemCount; ++i) {
let item = gAccountManager.accountList.getItemAtIndex(i);
if (item.account.connected)
item.refreshConnectedLabel();
}
},
/* This function disables the connect/disconnect buttons for
* `this._disabledDelay` ms before calling disableCommandItems to restore
* the state of the buttons.
*/
temporarilyDisableButtons: function am_temporarilyDisableButtons() {
document.getElementById("cmd_disconnect").setAttribute("disabled", "true");
document.getElementById("cmd_connect").setAttribute("disabled", "true");
clearTimeout(this.disableTimerID);
this.accountList.focus();
this.disableTimerID = setTimeout(function(aItem) {
gAccountManager.disableTimerID = 0;
gAccountManager.disableCommandItems();
aItem.buttons.setFocus();
}, this._disabledDelay, this.accountList.selectedItem);
},
delete: function am_delete() {
var selectedItem = this.accountList.selectedItem;
if (!selectedItem)
return;
var showPrompt =
Services.prefs.getBoolPref("messenger.accounts.promptOnDelete");
if (showPrompt) {
var prompts = Services.prompt;
var bundle = document.getElementById("accountsBundle");
var promptTitle = bundle.getString("account.deletePrompt.title");
var promptMessage = bundle.getString("account.deletePrompt.message");
var promptCheckbox = bundle.getString("account.deletePrompt.checkbox");
var deleteButton = bundle.getString("account.deletePrompt.button");
var checkbox = {};
var flags = prompts.BUTTON_TITLE_IS_STRING * prompts.BUTTON_POS_0 +
prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1 +
prompts.BUTTON_POS_1_DEFAULT;
if (prompts.confirmEx(window, promptTitle, promptMessage, flags,
deleteButton, null, null, promptCheckbox, checkbox))
return;
if (checkbox.value)
Services.prefs.setBoolPref("messenger.accounts.promptOnDelete", false);
}
Services.accounts.deleteAccount(selectedItem.id);
},
new: function am_new() {
this.openDialog("chrome://instantbird/content/accountWizard.xul");
},
edit: function am_edit() {
this.openDialog("chrome://instantbird/content/account.xul",
this.accountList.selectedItem.account);
},
autologin: function am_autologin() {
var elt = this.accountList.selectedItem;
elt.autoLogin = !elt.autoLogin;
},
close: function am_close() {
// If a modal dialog is opened, we can't close this window now
if (this.modalDialog)
setTimeout(function() { window.close();}, 0);
else
window.close();
},
/* This function disables or enables the currently selected button and
the corresponding context menu item */
disableCommandItems: function am_disableCommandItems() {
let accountList = this.accountList;
let selectedItem = accountList.selectedItem;
// When opening the account manager, if accounts have errors, we
// can be called during build(), before any item is selected.
// In this case, just return early.
if (!selectedItem)
return;
// If the timer that disables the button (for a short time) already exists,
// we don't want to interfere and set the button as enabled.
if (this.disableTimerID)
return;
let account = selectedItem.account;
let isCommandDisabled =
(this.isOffline ||
(account.disconnected &&
account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL));
let disabledItems = {
connect: isCommandDisabled,
disconnect: isCommandDisabled,
moveup: accountList.selectedIndex == 0,
movedown: accountList.selectedIndex == accountList.itemCount - 1
};
for each (let [name, state] in Iterator(disabledItems)) {
let elt = document.getElementById("cmd_" + name);
if (state)
elt.setAttribute("disabled", "true");
else
elt.removeAttribute("disabled");
}
},
onContextMenuShowing: function am_onContextMenuShowing() {
let targetElt = document.popupNode;
let isAccount = targetElt instanceof Ci.nsIDOMXULSelectControlItemElement;
document.getElementById("contextAccountsItems").hidden = !isAccount;
if (isAccount) {
let account = targetElt.account;
let hiddenItems = {
connect: !account.disconnected,
disconnect: account.disconnected || account.disconnecting,
cancelReconnection: !targetElt.hasAttribute("reconnectPending"),
accountsItemsSeparator: account.disconnecting
};
for (let name in hiddenItems)
document.getElementById("context_" + name).hidden = hiddenItems[name];
}
},
selectAccount: function am_selectAccount(aAccountId) {
this.accountList.selectedItem = document.getElementById(aAccountId);
},
onAccountSelect: function am_onAccountSelect() {
clearTimeout(this.disableTimerID);
this.disableTimerID = 0;
this.disableCommandItems();
let selectedItem = this.accountList.selectedItem;
selectedItem.clientHeight; // force synchronous binding attachment
selectedItem.buttons.setFocus();
// Without the following, the account manager is scrolled to the first
// (dis)connect button when being opened.
this.accountList.ensureSelectedElementIsVisible();
},
onKeyPress: function am_onKeyPress(event) {
if (!this.selectedItem)
return;
if (event.shiftKey &&
(event.keyCode == event.DOM_VK_DOWN || event.keyCode == event.DOM_VK_UP)) {
let offset = event.keyCode == event.DOM_VK_DOWN ? 1 : -1;
gAccountManager.moveCurrentItem(offset);
event.stopPropagation();
event.preventDefault();
return;
}
// As we stop propagation, the default action applies to the richlistbox
// so that the selected account is changed with this default action
if (event.keyCode == event.DOM_VK_DOWN) {
if (this.selectedIndex < this.itemCount - 1)
this.ensureIndexIsVisible(this.selectedIndex + 1);
event.stopPropagation();
return;
}
if (event.keyCode == event.DOM_VK_UP) {
if (this.selectedIndex > 0)
this.ensureIndexIsVisible(this.selectedIndex - 1);
event.stopPropagation();
return;
}
if (event.keyCode == event.DOM_VK_RETURN) {
let target = event.originalTarget;
if (target.localName != "checkbox" &&
(target.localName != "button" ||
/^(dis)?connect$/.test(target.getAttribute("anonid"))))
this.selectedItem.buttons.proceedDefaultAction();
return;
}
if (event.keyCode == event.DOM_VK_DELETE)
document.getElementById("cmd_delete").doCommand();
},
moveCurrentItem: function am_moveCurrentItem(aOffset) {
let accountList = this.accountList;
if (!aOffset || !accountList.selectedItem)
return;
// Create the new preference value from the richlistbox list
let items = accountList.children;
let selectedID = accountList.selectedItem.id;
let array = [];
for (let i in items)
if (items[i].id != selectedID)
array.push(items[i].id);
let newIndex = accountList.selectedIndex + aOffset;
if (newIndex < 0)
newIndex = 0;
else if (newIndex >= accountList.itemCount)
newIndex = accountList.itemCount - 1;
array.splice(newIndex, 0, selectedID);
Services.prefs.setCharPref("messenger.accounts", array.join(","));
},
getAccounts: function am_getAccounts() {
let accounts = Services.accounts.getAccounts();
while (accounts.hasMoreElements())
yield accounts.getNext();
},
openDialog: function am_openDialog(aUrl, aArgs) {
this.modalDialog = true;
window.openDialog(aUrl, "", "chrome,modal,titlebar,centerscreen", aArgs);
this.modalDialog = false;
},
setAutoLoginNotification: function am_setAutoLoginNotification() {
var as = Services.accounts;
var autoLoginStatus = as.autoLoginStatus;
let isOffline = false;
let crashCount = 0;
for (let acc in this.getAccounts())
if (acc.autoLogin && acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
++crashCount;
if (autoLoginStatus == as.AUTOLOGIN_ENABLED && crashCount == 0) {
let status = Services.core.globalUserStatus.statusType;
this.setOffline(isOffline || status == Ci.imIStatusInfo.STATUS_OFFLINE);
return;
}
var bundle = document.getElementById("accountsBundle");
var box = document.getElementById("accountsNotificationBox");
var priority = box.PRIORITY_INFO_HIGH;
var connectNowButton = {
accessKey: bundle.getString("accountsManager.notification.button.accessKey"),
callback: this.processAutoLogin,
label: bundle.getString("accountsManager.notification.button.label")
};
var label;
switch (autoLoginStatus) {
case as.AUTOLOGIN_USER_DISABLED:
label = bundle.getString("accountsManager.notification.userDisabled.label");
break;
case as.AUTOLOGIN_SAFE_MODE:
label = bundle.getString("accountsManager.notification.safeMode.label");
break;
case as.AUTOLOGIN_START_OFFLINE:
label = bundle.getString("accountsManager.notification.startOffline.label");
isOffline = true;
break;
case as.AUTOLOGIN_CRASH:
label = bundle.getString("accountsManager.notification.crash.label");
priority = box.PRIORITY_WARNING_MEDIUM;
break;
/* One or more accounts made the application crash during their connection.
If none, this function has already returned */
case as.AUTOLOGIN_ENABLED:
if (!("PluralForm" in window))
Cu.import("resource://gre/modules/PluralForm.jsm");
label = bundle.getString("accountsManager.notification.singleCrash.label");
label = PluralForm.get(crashCount, label).replace("#1", crashCount);
priority = box.PRIORITY_WARNING_MEDIUM;
connectNowButton.callback = this.processCrashedAccountsLogin;
break;
default:
label = bundle.getString("accountsManager.notification.other.label");
}
let status = Services.core.globalUserStatus.statusType;
this.setOffline(isOffline || status == Ci.imIStatusInfo.STATUS_OFFLINE);
box.appendNotification(label, "autologinStatus", null, priority, [connectNowButton]);
},
processAutoLogin: function am_processAutoLogin() {
var ioService = Services.io;
if (ioService.offline) {
ioService.manageOfflineStatus = false;
ioService.offline = false;
}
Services.accounts.processAutoLogin();
gAccountManager.accountList.selectedItem.buttons.setFocus();
},
processCrashedAccountsLogin: function am_processCrashedAccountsLogin() {
for (let acc in gAccountManager.getAccounts())
if (acc.disconnected && acc.autoLogin &&
acc.firstConnectionState == acc.FIRST_CONNECTION_CRASHED)
acc.connect();
let notification = document.getElementById("accountsNotificationBox")
.getNotificationWithValue("autoLoginStatus");
if (notification)
notification.close();
gAccountManager.accountList.selectedItem.buttons.setFocus();
},
setOffline: function am_setOffline(aState) {
this.isOffline = aState;
if (aState)
this.accountList.setAttribute("offline", "true");
else
this.accountList.removeAttribute("offline");
this.disableCommandItems();
}
};
let gAMDragAndDrop = {
ACCOUNT_MIME_TYPE: "application/x-moz-richlistitem",
// Size of the scroll zone on the top and on the bottom of the account list
MAGIC_SCROLL_HEIGHT: 20,
// A preference already exists to define scroll speed, let's use it.
get SCROLL_SPEED() {
delete this.SCROLL_SPEED;
try {
this.SCROLL_SPEED =
Services.prefs.getIntPref("toolkit.scrollbox.scrollIncrement");
}
catch (e) {
this.SCROLL_SPEED = 20;
}
return this.SCROLL_SPEED;
},
onDragStart: function amdnd_onDragStart(aEvent, aTransferData, aAction) {
let accountElement = aEvent.explicitOriginalTarget;
// This stops the dragging session.
if (!(accountElement instanceof Ci.nsIDOMXULSelectControlItemElement))
throw "Element is not draggable!";
if (gAccountManager.accountList.itemCount == 1)
throw "Can't drag while there is only one account!";
// Transferdata is never used, but we need to transfer something.
aTransferData.data = new TransferData();
aTransferData.data.addDataForFlavour(this.ACCOUNT_MIME_TYPE, accountElement);
},
onDragOver: function amdnd_onDragOver(aEvent, aFlavour, aSession) {
let accountElement = aEvent.explicitOriginalTarget;
// We are dragging over the account manager, consider it is the same as
// the last element.
if (accountElement == gAccountManager.accountList)
accountElement = gAccountManager.accountList.lastChild;
// Auto scroll the account list if we are dragging at the top/bottom
this.checkForMagicScroll(aEvent.clientY);
// The hovered element has changed, change the border too
if (("_accountElement" in this) && this._accountElement != accountElement)
this.cleanBorders();
if (!aSession.canDrop) {
aEvent.dataTransfer.dropEffect = "none";
return;
}
aEvent.dataTransfer.dropEffect = "move";
if (aEvent.clientY < accountElement.getBoundingClientRect().top +
accountElement.clientHeight / 2) {
// we don't want the previous item to show its default bottom-border
let previousItem = accountElement.previousSibling;
if (previousItem)
previousItem.style.borderBottom = "none";
accountElement.setAttribute("dragover", "up");
}
else {
if (("_accountElement" in this) &&
this._accountElement == accountElement &&
accountElement.getAttribute("dragover") == "up")
this.cleanBorders();
accountElement.setAttribute("dragover", "down");
}
this._accountElement = accountElement;
},
cleanBorders: function amdnd_cleanBorders(aIsEnd) {
if (!this._accountElement)
return;
this._accountElement.removeAttribute("dragover");
// reset the border of the previous element
let previousItem = this._accountElement.previousSibling;
if (previousItem) {
if (aIsEnd && !previousItem.style.borderBottom && previousItem.previousSibling)
previousItem = previousItem.previousSibling;
previousItem.style.borderBottom = "";
}
if (aIsEnd)
delete this._accountElement;
},
canDrop: function amdnd_canDrop(aEvent, aSession) {
let accountElement = aEvent.explicitOriginalTarget;
if (accountElement == gAccountManager.accountList)
accountElement = gAccountManager.accountList.lastChild;
return (accountElement != gAccountManager.accountList.selectedItem);
},
checkForMagicScroll: function amdnd_checkForMagicScroll(aClientY) {
let accountList = gAccountManager.accountList;
let listSize = accountList.getBoundingClientRect();
let direction = 1;
if (aClientY < listSize.top + this.MAGIC_SCROLL_HEIGHT)
direction = -1;
else if (aClientY < listSize.bottom - this.MAGIC_SCROLL_HEIGHT)
// We are not on a scroll zone
return;
accountList._scrollbox.scrollTop += direction * this.SCROLL_SPEED;
},
onDrop: function amdnd_onDrop(aEvent, aTransferData, aSession) {
let accountElement = aEvent.explicitOriginalTarget;
if (accountElement == gAccountManager.accountList)
accountElement = gAccountManager.accountList.lastChild;
if (!aSession.canDrop)
return;
// compute the destination
let accountList = gAccountManager.accountList;
let offset = accountList.getIndexOfItem(accountElement) -
accountList.selectedIndex;
let isDroppingAbove =
aEvent.clientY < accountElement.getBoundingClientRect().top +
accountElement.clientHeight / 2;
if (offset > 0)
offset -= isDroppingAbove;
else
offset += !isDroppingAbove;
gAccountManager.moveCurrentItem(offset);
},
getSupportedFlavours: function amdnd_getSupportedFlavours() {
var flavours = new FlavourSet();
flavours.appendFlavour(this.ACCOUNT_MIME_TYPE,
"nsIDOMXULSelectControlItemElement");
return flavours;
}
};