releases-comm-central/chat/modules/jsProtoHelper.jsm

924 строки
30 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/. */
this.EXPORTED_SYMBOLS = [
"GenericAccountPrototype",
"GenericAccountBuddyPrototype",
"GenericConvIMPrototype",
"GenericConvChatPrototype",
"GenericConvChatBuddyPrototype",
"GenericConversationPrototype",
"GenericMessagePrototype",
"GenericProtocolPrototype",
"Message",
"TooltipInfo"
];
var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
Cu.import("resource:///modules/imXPCOMUtils.jsm");
Cu.import("resource:///modules/imServices.jsm");
XPCOMUtils.defineLazyGetter(this, "_", () =>
l10nHelper("chrome://chat/locale/conversations.properties")
);
var GenericAccountPrototype = {
__proto__: ClassInfo("prplIAccount", "generic account object"),
get wrappedJSObject() { return this; },
_init: function _init(aProtocol, aImAccount) {
this.protocol = aProtocol;
this.imAccount = aImAccount;
initLogModule(aProtocol.id, this);
},
observe: function(aSubject, aTopic, aData) {},
remove: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
unInit: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
connect: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
disconnect: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
createConversation: function(aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
joinChat: function(aComponents) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
setBool: function(aName, aVal) {},
setInt: function(aName, aVal) {},
setString: function(aName, aVal) {},
get name() { return this.imAccount.name; },
get connected() { return this.imAccount.connected; },
get connecting() { return this.imAccount.connecting; },
get disconnected() { return this.imAccount.disconnected; },
get disconnecting() { return this.imAccount.disconnecting; },
_connectionErrorReason: Ci.prplIAccount.NO_ERROR,
get connectionErrorReason() { return this._connectionErrorReason; },
/*
* Convert a socket's nsISSLStatus into a prplIAccount connection error. Store
* the nsISSLStatus and the connection location on the account so the
* certificate exception dialog can access the information.
*/
handleBadCertificate: function(aSocket, aIsSslError) {
this._connectionTarget = aSocket.host + ":" + aSocket.port;
if (aIsSslError)
return Ci.prplIAccount.ERROR_ENCRYPTION_ERROR;
let sslStatus = this._sslStatus = aSocket.sslStatus;
if (!sslStatus)
return Ci.prplIAccount.ERROR_CERT_NOT_PROVIDED;
if (sslStatus.isUntrusted) {
if (sslStatus.serverCert &&
sslStatus.serverCert.isSelfSigned)
return Ci.prplIAccount.ERROR_CERT_SELF_SIGNED;
return Ci.prplIAccount.ERROR_CERT_UNTRUSTED;
}
if (sslStatus.isNotValidAtThisTime) {
if (sslStatus.serverCert &&
sslStatus.serverCert.validity.notBefore < Date.now() * 1000)
return Ci.prplIAccount.ERROR_CERT_NOT_ACTIVATED;
return Ci.prplIAccount.ERROR_CERT_EXPIRED;
}
if (sslStatus.isDomainMismatch)
return Ci.prplIAccount.ERROR_CERT_HOSTNAME_MISMATCH;
// XXX ERROR_CERT_FINGERPRINT_MISMATCH
return Ci.prplIAccount.ERROR_CERT_OTHER_ERROR;
},
_connectionTarget: "",
get connectionTarget() { return this._connectionTarget; },
_sslStatus: null,
get sslStatus() { return this._sslStatus; },
reportConnected: function() {
this.imAccount.observe(this, "account-connected", null);
},
reportConnecting: function(aConnectionStateMsg) {
// Delete any leftover errors from the previous connection.
delete this._connectionTarget;
delete this._sslStatus;
if (!this.connecting)
this.imAccount.observe(this, "account-connecting", null);
if (aConnectionStateMsg)
this.imAccount.observe(this, "account-connect-progress", aConnectionStateMsg);
},
reportDisconnected: function() {
this.imAccount.observe(this, "account-disconnected", null);
},
reportDisconnecting: function(aConnectionErrorReason, aConnectionErrorMessage) {
this._connectionErrorReason = aConnectionErrorReason;
this.imAccount.observe(this, "account-disconnecting", aConnectionErrorMessage);
this.cancelPendingBuddyRequests();
},
// Called when the user adds a new buddy from the UI.
addBuddy: function(aTag, aName) {
Services.contacts
.accountBuddyAdded(new AccountBuddy(this, null, aTag, aName));
},
// Called during startup for each of the buddies in the local buddy list.
loadBuddy: function(aBuddy, aTag) {
try {
return new AccountBuddy(this, aBuddy, aTag);
} catch (x) {
dump(x + "\n");
return null;
}
},
_pendingBuddyRequests: null,
addBuddyRequest: function(aUserName, aGrantCallback, aDenyCallback) {
if (!this._pendingBuddyRequests)
this._pendingBuddyRequests = [];
let buddyRequest = {
get account() { return this._account.imAccount; },
get userName() { return aUserName; },
_account: this,
// Grant and deny callbacks both receive the auth request object as an
// argument for further use.
grant: function() {
aGrantCallback(this);
this._remove();
},
deny: function() {
aDenyCallback(this);
this._remove();
},
cancel: function() {
Services.obs.notifyObservers(this,
"buddy-authorization-request-canceled",
null);
this._remove();
},
_remove: function() {
this._account.removeBuddyRequest(this);
},
QueryInterface: XPCOMUtils.generateQI([Ci.prplIBuddyRequest])
};
this._pendingBuddyRequests.push(buddyRequest);
Services.obs.notifyObservers(buddyRequest, "buddy-authorization-request",
null);
},
removeBuddyRequest: function(aRequest) {
if (!this._pendingBuddyRequests)
return;
this._pendingBuddyRequests =
this._pendingBuddyRequests.filter(r => r !== aRequest);
},
cancelPendingBuddyRequests: function() {
if (!this._pendingBuddyRequests)
return;
for (let request of this._pendingBuddyRequests)
request.cancel();
delete this._pendingBuddyRequests;
},
requestBuddyInfo: function(aBuddyName) {},
get canJoinChat() { return false; },
getChatRoomFields: function() {
if (!this.chatRoomFields)
return EmptyEnumerator;
let fields = [];
for (let fieldName in this.chatRoomFields)
fields.push(new ChatRoomField(fieldName, this.chatRoomFields[fieldName]));
return new nsSimpleEnumerator(fields);
},
getChatRoomDefaultFieldValues: function(aDefaultChatName) {
if (!this.chatRoomFields)
return EmptyEnumerator;
let defaultFieldValues = [];
for (let fieldName in this.chatRoomFields)
defaultFieldValues[fieldName] = this.chatRoomFields[fieldName].default;
if (aDefaultChatName && "parseDefaultChatName" in this) {
let parsedDefaultChatName = this.parseDefaultChatName(aDefaultChatName);
for (let field in parsedDefaultChatName)
defaultFieldValues[field] = parsedDefaultChatName[field];
}
return new ChatRoomFieldValues(defaultFieldValues);
},
requestRoomInfo: function(aCallback) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
getRoomInfo: function(aName) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
get isRoomInfoStale() { return false; },
getPref: function (aName, aType) {
return this.prefs.prefHasUserValue(aName) ?
this.prefs["get" + aType + "Pref"](aName) :
this.protocol._getOptionDefault(aName);
},
getInt: function(aName) { return this.getPref(aName, "Int"); },
getBool: function(aName) { return this.getPref(aName, "Bool"); },
getString: function(aName) {
return this.prefs.prefHasUserValue(aName) ?
this.prefs.getComplexValue(aName, Ci.nsISupportsString).data :
this.protocol._getOptionDefault(aName);
},
get prefs() {
return this._prefs ||
(this._prefs = Services.prefs.getBranch("messenger.account." +
this.imAccount.id + ".options."));
},
get normalizedName() { return this.normalize(this.name); },
normalize: function(aName) { return aName.toLowerCase(); },
get proxyInfo() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
set proxyInfo(val) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
get HTMLEnabled() { return false; },
get HTMLEscapePlainText() { return false; },
get noBackgroundColors() { return true; },
get autoResponses() { return false; },
get singleFormatting() { return false; },
get noFontSizes() { return false; },
get noUrlDesc() { return false; },
get noImages() { return true; }
};
var GenericAccountBuddyPrototype = {
__proto__: ClassInfo("prplIAccountBuddy", "generic account buddy object"),
get DEBUG() { return this._account.DEBUG; },
get LOG() { return this._account.LOG; },
get WARN() { return this._account.WARN; },
get ERROR() { return this._account.ERROR; },
_init: function(aAccount, aBuddy, aTag, aUserName) {
if (!aBuddy && !aUserName)
throw "aUserName is required when aBuddy is null";
this._tag = aTag;
this._account = aAccount;
this._buddy = aBuddy;
if (aBuddy) {
let displayName = aBuddy.displayName;
if (displayName != aUserName)
this._serverAlias = displayName;
}
this._userName = aUserName;
},
unInit: function() {
delete this._tag;
delete this._account;
delete this._buddy;
},
get account() { return this._account.imAccount; },
set buddy(aBuddy) {
if (this._buddy)
throw Cr.NS_ERROR_ALREADY_INITIALIZED;
this._buddy = aBuddy;
},
get buddy() { return this._buddy; },
get tag() { return this._tag; },
set tag(aNewTag) {
let oldTag = this._tag;
this._tag = aNewTag;
Services.contacts.accountBuddyMoved(this, oldTag, aNewTag);
},
_notifyObservers: function(aTopic, aData) {
try {
this._buddy.observe(this, "account-buddy-" + aTopic, aData);
} catch(e) {
this.ERROR(e);
}
},
_userName: "",
get userName() { return this._userName || this._buddy.userName; },
get normalizedName() { return this._account.normalize(this.userName); },
_serverAlias: "",
get serverAlias() { return this._serverAlias; },
set serverAlias(aNewAlias) {
let old = this.displayName;
this._serverAlias = aNewAlias;
if (old != this.displayName)
this._notifyObservers("display-name-changed", old);
},
remove: function() {
Services.contacts.accountBuddyRemoved(this);
},
// imIStatusInfo implementation
get displayName() { return this.serverAlias || this.userName; },
_buddyIconFileName: "",
get buddyIconFilename() { return this._buddyIconFileName; },
set buddyIconFilename(aNewFileName) {
this._buddyIconFileName = aNewFileName;
this._notifyObservers("icon-changed");
},
_statusType: 0,
get statusType() { return this._statusType; },
get online() { return this._statusType > Ci.imIStatusInfo.STATUS_OFFLINE; },
get available() { return this._statusType == Ci.imIStatusInfo.STATUS_AVAILABLE; },
get idle() { return this._statusType == Ci.imIStatusInfo.STATUS_IDLE; },
get mobile() { return this._statusType == Ci.imIStatusInfo.STATUS_MOBILE; },
_statusText: "",
get statusText() { return this._statusText; },
// This is for use by the protocol plugin, it's not exposed in the
// imIStatusInfo interface.
// All parameters are optional and will be ignored if they are null
// or undefined.
setStatus: function(aStatusType, aStatusText, aAvailabilityDetails) {
// Ignore omitted parameters.
if (aStatusType === undefined || aStatusType === null)
aStatusType = this._statusType;
if (aStatusText === undefined || aStatusText === null)
aStatusText = this._statusText;
if (aAvailabilityDetails === undefined || aAvailabilityDetails === null)
aAvailabilityDetails = this._availabilityDetails;
// Decide which notifications should be fired.
let notifications = [];
if (this._statusType != aStatusType ||
this._availabilityDetails != aAvailabilityDetails)
notifications.push("availability-changed");
if (this._statusType != aStatusType ||
this._statusText != aStatusText) {
notifications.push("status-changed");
if (this.online && aStatusType <= Ci.imIStatusInfo.STATUS_OFFLINE)
notifications.push("signed-off");
if (!this.online && aStatusType > Ci.imIStatusInfo.STATUS_OFFLINE)
notifications.push("signed-on");
}
// Actually change the stored status.
[this._statusType, this._statusText, this._availabilityDetails] =
[aStatusType, aStatusText, aAvailabilityDetails];
// Fire the notifications.
notifications.forEach(function(aTopic) {
this._notifyObservers(aTopic);
}, this);
},
_availabilityDetails: 0,
get availabilityDetails() { return this._availabilityDetails; },
get canSendMessage() { return this.online /*|| this.account.canSendOfflineMessage(this) */; },
getTooltipInfo: () => EmptyEnumerator,
createConversation: function() { throw Cr.NS_ERROR_NOT_IMPLEMENTED; }
};
// aUserName is required only if aBuddy is null, i.e., we are adding a buddy.
function AccountBuddy(aAccount, aBuddy, aTag, aUserName) {
this._init(aAccount, aBuddy, aTag, aUserName);
}
AccountBuddy.prototype = GenericAccountBuddyPrototype;
var GenericMessagePrototype = {
__proto__: ClassInfo("prplIMessage", "generic message object"),
_lastId: 0,
_init: function (aWho, aMessage, aObject) {
this.id = ++GenericMessagePrototype._lastId;
this.time = Math.floor(new Date() / 1000);
this.who = aWho;
this.message = aMessage;
this.originalMessage = aMessage;
if (aObject)
for (let i in aObject)
this[i] = aObject[i];
},
_alias: "",
get alias() { return this._alias || this.who; },
_iconURL: "",
get iconURL() {
// If the protocol plugin has explicitly set an icon for the message, use it.
if (this._iconURL)
return this._iconURL;
// Otherwise, attempt to find a buddy for incoming messages, and forward the call.
if (this.incoming && this._conversation && !this._conversation.isChat) {
let buddy = this._conversation.buddy;
if (buddy)
return buddy.buddyIconFilename;
}
return "";
},
_conversation: null,
get conversation() { return this._conversation; },
set conversation(aConv) {
this._conversation = aConv;
aConv.notifyObservers(this, "new-text", null);
},
outgoing: false,
incoming: false,
system: false,
autoResponse: false,
containsNick: false,
noLog: false,
error: false,
delayed: false,
noFormat: false,
containsImages: false,
notification: false,
noLinkification: false,
getActions: function(aCount) {
if (aCount)
aCount.value = 0;
return [];
}
};
function Message(aWho, aMessage, aObject) {
this._init(aWho, aMessage, aObject);
}
Message.prototype = GenericMessagePrototype;
var GenericConversationPrototype = {
__proto__: ClassInfo("prplIConversation", "generic conversation object"),
get wrappedJSObject() { return this; },
get DEBUG() { return this._account.DEBUG; },
get LOG() { return this._account.LOG; },
get WARN() { return this._account.WARN; },
get ERROR() { return this._account.ERROR; },
_init: function(aAccount, aName) {
this._account = aAccount;
this._name = aName;
this._observers = [];
this._date = new Date() * 1000;
Services.conversations.addConversation(this);
},
_id: 0,
get id() { return this._id; },
set id(aId) {
if (this._id)
throw Cr.NS_ERROR_ALREADY_INITIALIZED;
this._id = aId;
},
addObserver: function(aObserver) {
if (!this._observers.includes(aObserver))
this._observers.push(aObserver);
},
removeObserver: function(aObserver) {
this._observers = this._observers.filter(o => o !== aObserver);
},
notifyObservers: function(aSubject, aTopic, aData) {
for (let observer of this._observers) {
try {
observer.observe(aSubject, aTopic, aData);
} catch(e) {
this.ERROR(e);
}
}
},
prepareForSending: (aOutgoingMessage, aCount) => null,
prepareForDisplaying: function(aImMessage) {
if (aImMessage.displayMessage !== aImMessage.message) {
this.DEBUG("Preparing:\n" + aImMessage.message + "\nDisplaying:\n" +
aImMessage.displayMessage);
}
},
sendMsg: function(aMsg) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
sendTyping: aString => Ci.prplIConversation.NO_TYPING_LIMIT,
close: function() {
Services.obs.notifyObservers(this, "closing-conversation", null);
Services.conversations.removeConversation(this);
},
unInit: function() {
delete this._account;
delete this._observers;
},
writeMessage: function(aWho, aText, aProperties) {
(new Message(aWho, aText, aProperties)).conversation = this;
},
get account() { return this._account.imAccount; },
get name() { return this._name; },
get normalizedName() { return this._account.normalize(this.name); },
get title() { return this.name; },
get startDate() { return this._date; }
};
var GenericConvIMPrototype = {
__proto__: GenericConversationPrototype,
_interfaces: [Ci.prplIConversation, Ci.prplIConvIM],
classDescription: "generic ConvIM object",
updateTyping: function(aState, aName) {
if (aState == this.typingState)
return;
if (aState == Ci.prplIConvIM.NOT_TYPING)
delete this.typingState;
else
this.typingState = aState;
this.notifyObservers(null, "update-typing", aName);
},
get isChat() { return false; },
buddy: null,
typingState: Ci.prplIConvIM.NOT_TYPING
};
var GenericConvChatPrototype = {
__proto__: GenericConversationPrototype,
_interfaces: [Ci.prplIConversation, Ci.prplIConvChat],
classDescription: "generic ConvChat object",
_init: function(aAccount, aName, aNick) {
this._participants = new Map();
this.nick = aNick;
GenericConversationPrototype._init.call(this, aAccount, aName);
},
get isChat() { return true; },
// Stores the prplIChatRoomFieldValues required to join this channel
// to enable later reconnections. If null, the MUC will not be reconnected
// automatically after disconnections.
chatRoomFields: null,
_topic: "",
_topicSetter: null,
get topic() { return this._topic; },
get topicSettable() { return false; },
get topicSetter() { return this._topicSetter; },
setTopic: function(aTopic, aTopicSetter, aQuiet) {
// Only change the topic if the topic and/or topic setter has changed.
if (this._topic == aTopic &&
(!this._topicSetter || this._topicSetter == aTopicSetter))
return;
this._topic = aTopic;
this._topicSetter = aTopicSetter;
this.notifyObservers(null, "chat-update-topic");
if (aQuiet)
return;
// Send the topic as a message.
let message;
if (aTopicSetter) {
if (aTopic)
message = _("topicChanged", aTopicSetter, aTopic);
else
message = _("topicCleared", aTopicSetter);
}
else {
aTopicSetter = null;
if (aTopic)
message = _("topicSet", this.name, aTopic);
else
message = _("topicNotSet", this.name);
}
this.writeMessage(aTopicSetter, message, {system: true});
},
get nick() { return this._nick; },
set nick(aNick) {
this._nick = aNick;
let escapedNick = this._nick.replace(/[[\]{}()*+?.\\^$|]/g, "\\$&");
this._pingRegexp = new RegExp("(?:^|\\W)" + escapedNick + "(?:\\W|$)", "i");
},
_left: false,
get left() { return this._left; },
set left(aLeft) {
if (aLeft == this._left)
return;
this._left = aLeft;
this.notifyObservers(null, "update-conv-chatleft");
},
_joining: false,
get joining() { return this._joining; },
set joining(aJoining) {
if (aJoining == this._joining)
return;
this._joining = aJoining;
this.notifyObservers(null, "update-conv-chatjoining");
},
getParticipant: function(aName) {
return this._participants.has(aName) ? this._participants.get(aName) : null;
},
getParticipants: function() {
// Convert the values of the Map into a nsSimpleEnumerator.
return new nsSimpleEnumerator(
Array.from(this._participants.values())
);
},
getNormalizedChatBuddyName: aChatBuddyName => aChatBuddyName,
// Updates the nick of a participant in conversation to a new one.
updateNick: function(aOldNick, aNewNick, isOwnNick) {
let message;
let isParticipant = this._participants.has(aOldNick);
if (isOwnNick) {
// If this is the user's nick, change it.
this.nick = aNewNick;
message = _("nickSet.you", aNewNick);
// If the account was disconnected, it's OK the user is not a participant.
if (!isParticipant)
return;
}
else if (!isParticipant) {
this.ERROR("Trying to rename nick that doesn't exist! " + aOldNick +
" to " + aNewNick);
return;
}
else
message = _("nickSet", aOldNick, aNewNick);
// Get the original participant and then remove it.
let participant = this._participants.get(aOldNick);
this._participants.delete(aOldNick);
// Update the nickname and add it under the new nick.
participant.name = aNewNick;
this._participants.set(aNewNick, participant);
this.notifyObservers(participant, "chat-buddy-update", aOldNick);
this.writeMessage(aOldNick, message, {system: true});
},
// Removes a participant from conversation.
removeParticipant: function(aNick) {
if (!this._participants.has(aNick))
return;
let stringNickname = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
stringNickname.data = aNick;
this.notifyObservers(new nsSimpleEnumerator([stringNickname]),
"chat-buddy-remove");
this._participants.delete(aNick);
},
// Removes all participant in conversation.
removeAllParticipants: function() {
let stringNicknames = [];
this._participants.forEach(function(aParticipant) {
let stringNickname = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
stringNickname.data = aParticipant.name;
stringNicknames.push(stringNickname);
});
this.notifyObservers(new nsSimpleEnumerator(stringNicknames),
"chat-buddy-remove");
this._participants.clear();
},
writeMessage: function (aWho, aText, aProperties) {
aProperties.containsNick =
"incoming" in aProperties && this._pingRegexp.test(aText);
GenericConversationPrototype.writeMessage.apply(this, arguments);
}
};
var GenericConvChatBuddyPrototype = {
__proto__: ClassInfo("prplIConvChatBuddy", "generic ConvChatBuddy object"),
_name: "",
get name() { return this._name; },
set name(aName) { this._name = aName; },
alias: "",
buddy: false,
buddyIconFilename: "",
get noFlags() {
return !(this.voiced || this.halfOp || this.op ||
this.founder || this.typing);
},
voiced: false,
halfOp: false,
op: false,
founder: false,
typing: false
};
function TooltipInfo(aLabel, aValue, aType = Ci.prplITooltipInfo.pair) {
this.type = aType;
if (aType == Ci.prplITooltipInfo.status) {
this.label = aLabel.toString();
this.value = aValue || "";
}
else if (aType == Ci.prplITooltipInfo.icon)
this.value = aValue;
else if (aLabel === undefined || aType == Ci.prplITooltipInfo.sectionBreak)
this.type = Ci.prplITooltipInfo.sectionBreak;
else {
this.label = aLabel;
if (aValue === undefined)
this.type = Ci.prplITooltipInfo.sectionHeader;
else
this.value = aValue;
}
}
TooltipInfo.prototype = ClassInfo("prplITooltipInfo", "generic tooltip info");
/* aOption is an object containing:
* - label: localized text to display (recommended: use a getter with _)
* - default: the default value for this option. The type of the
* option will be determined based on the type of the default value.
* If the default value is a string, the option will be of type
* list if listValues has been provided. In that case the default
* value should be one of the listed values.
* - [optional] listValues: only if this option can only take a list of
* predefined values. This is an object of the form:
* {value1: localizedLabel, value2: ...}.
* - [optional] masked: boolean, if true the UI shouldn't display the value.
* This could typically be used for password field.
* Warning: The UI currently doesn't support this.
*/
function purplePref(aName, aOption) {
this.name = aName; // Preference name
this.label = aOption.label; // Text to display
if (aOption.default === undefined || aOption.default === null)
throw "A default value for the option is required to determine its type.";
this._defaultValue = aOption.default;
const kTypes = {boolean: "Bool", string: "String", number: "Int"};
let type = kTypes[typeof aOption.default];
if (!type)
throw "Invalid option type";
if (type == "String" && ("listValues" in aOption)) {
type = "List";
this._listValues = aOption.listValues;
}
this.type = Ci.prplIPref["type" + type];
if ("masked" in aOption && aOption.masked)
this.masked = true;
}
purplePref.prototype = {
__proto__: ClassInfo("prplIPref", "generic account option preference"),
masked: false,
// Default value
getBool: function() { return this._defaultValue; },
getInt: function() { return this._defaultValue; },
getString: function() { return this._defaultValue; },
getList: function() {
// Convert a JavaScript object map {"value 1": "label 1", ...}
let keys = Object.keys(this._listValues);
if (!keys.length)
return EmptyEnumerator;
return new nsSimpleEnumerator(
keys.map(key => new purpleKeyValuePair(this._listValues[key], key))
);
},
getListDefault: function() { return this._defaultValue; }
};
function purpleKeyValuePair(aName, aValue) {
this.name = aName;
this.value = aValue;
}
purpleKeyValuePair.prototype =
ClassInfo("prplIKeyValuePair", "generic Key Value Pair");
function UsernameSplit(aValues) {
this._values = aValues;
}
UsernameSplit.prototype = {
__proto__: ClassInfo("prplIUsernameSplit", "username split object"),
get label() { return this._values.label; },
get separator() { return this._values.separator; },
get defaultValue() { return this._values.defaultValue; },
get reverse() { return !!this._values.reverse; } // Ensure boolean
};
function ChatRoomField(aIdentifier, aField) {
this.identifier = aIdentifier;
this.label = aField.label;
this.required = !!aField.required;
let type = "TEXT";
if ((typeof aField.default) == "number") {
type = "INT";
this.min = aField.min;
this.max = aField.max;
}
else if (aField.isPassword)
type = "PASSWORD";
this.type = Ci.prplIChatRoomField["TYPE_" + type];
}
ChatRoomField.prototype =
ClassInfo("prplIChatRoomField", "ChatRoomField object");
function ChatRoomFieldValues(aMap) {
this.values = aMap;
}
ChatRoomFieldValues.prototype = {
__proto__: ClassInfo("prplIChatRoomFieldValues", "ChatRoomFieldValues"),
getValue: function(aIdentifier) {
return this.values.hasOwnProperty(aIdentifier) ? this.values[aIdentifier] : null;
},
setValue: function(aIdentifier, aValue) {
this.values[aIdentifier] = aValue;
}
};
// the name getter and the getAccount method need to be implemented by
// protocol plugins.
var GenericProtocolPrototype = {
__proto__: ClassInfo("prplIProtocol", "Generic protocol object"),
init: function(aId) {
if (aId != this.id)
throw "Creating an instance of " + aId + " but this object implements " + this.id;
},
get id() { return "prpl-" + this.normalizedName; },
// This is more aggressive than the account normalization of just
// toLowerCase() since prpl names must be only letters/numbers.
get normalizedName() { return this.name.replace(/[^a-z0-9]/gi, "").toLowerCase(); },
get iconBaseURI() { return "chrome://chat/skin/prpl-generic/"; },
getAccount: function(aImAccount) { throw Cr.NS_ERROR_NOT_IMPLEMENTED; },
_getOptionDefault: function(aName) {
if (this.options && this.options.hasOwnProperty(aName))
return this.options[aName].default;
throw aName + " has no default value in " + this.id + ".";
},
getOptions: function() {
if (!this.options)
return EmptyEnumerator;
let purplePrefs = [];
for (let [name, option] of Object.entries(this.options))
purplePrefs.push(new purplePref(name, option));
return new nsSimpleEnumerator(purplePrefs);
},
getUsernameSplit: function() {
if (!this.usernameSplits || !this.usernameSplits.length)
return EmptyEnumerator;
return new nsSimpleEnumerator(
this.usernameSplits.map(split => new UsernameSplit(split)));
},
registerCommands: function() {
if (!this.commands)
return;
this.commands.forEach(function(command) {
if (!command.hasOwnProperty("name") || !command.hasOwnProperty("run"))
throw "Every command must have a name and a run function.";
if (!("QueryInterface" in command))
command.QueryInterface = XPCOMUtils.generateQI([Ci.imICommand]);
if (!command.hasOwnProperty("usageContext"))
command.usageContext = Ci.imICommand.CMD_CONTEXT_ALL;
if (!command.hasOwnProperty("priority"))
command.priority = Ci.imICommand.CMD_PRIORITY_PRPL;
Services.cmd.registerCommand(command, this.id);
}, this);
},
// NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED errors are too noisy
get usernameEmptyText() { return ""; },
accountExists: () => false, //FIXME
get uniqueChatName() { return false; },
get chatHasTopic() { return false; },
get noPassword() { return false; },
get newMailNotification() { return false; },
get imagesInIM() { return false; },
get passwordOptional() { return false; },
get usePointSize() { return true; },
get registerNoScreenName() { return false; },
get slashCommandsNative() { return false; },
get usePurpleProxy() { return false; },
get classDescription() { return this.name + " Protocol"; },
get contractID() { return "@mozilla.org/chat/" + this.normalizedName + ";1"; }
};