@@ -1,133 +1,252 @@
diff --git a/chat/components/public/imIConversationsService.idl b/chat/components/public/imIConversationsService.idl
--- a/chat/components/public/imIConversationsService.idl
+++ b/chat/components/public/imIConversationsService.idl
@@ -1,17 +1,18 @@
/* 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/. */
#include "nsISupports.idl"
#include "prplIConversation.idl"
+ #include "prplIMessage.idl"
#include "imIContactsService.idl"
- interface prplIMessage;
+ interface imIMessage;
[scriptable, uuid(81b8d9a9-4715-4109-b522-84b9d31493a3)]
interface imIConversation: prplIConversation {
// Will be null for MUCs and IMs from people not in the contacts list.
readonly attribute imIContact contact;
// Write a system message into the conversation.
// Note: this will not be logged.
@@ -38,17 +39,17 @@ interface imIConversation: prplIConversa
// conversation. If the conversation is a left MUC or an IM
// conversation without unread message, the implementation will call
// close().
// The returned value indicates if the conversation was closed.
boolean checkClose();
// Get an array of all messages of the conversation.
void getMessages([optional] out unsigned long messageCount,
- [retval, array, size_is(messageCount)] out prplIMessage messages);
+ [retval, array, size_is(messageCount)] out imIMessage messages);
};
[scriptable, uuid(984e182c-d395-4fba-ba6e-cc80c71f57bf)]
interface imIConversationsService: nsISupports {
void initConversations();
void unInitConversations();
// Register a conversation. This will create a unique id for the
@@ -62,8 +63,32 @@ interface imIConversationsService: nsISu
imIConversation getUIConversationByContactId(in long aId);
nsISimpleEnumerator getConversations();
prplIConversation getConversationById(in unsigned long aId);
prplIConversation getConversationByNameAndAccount(in AUTF8String aName,
in imIAccount aAccount,
in boolean aIsChat);
};
+
+ [scriptable, uuid(4391ba5c-9566-41a9-bb9b-fd0a0a490c2c)]
+ interface imIOutgoingMessage: nsISupports {
+ attribute AUTF8String message;
+ attribute boolean cancelled;
+ readonly attribute imIConversation conversation;
+ };
+
+ [scriptable, uuid(0684ed1e-97b7-4eba-bf58-dd9dd81c668c)]
+ interface imISendableMessage: nsISupports {
+ void getSendableMessages([optional] out unsigned long messageCount,
+ [retval, array, size_is(messageCount)] out string messages);
+ void setSendableMessages(in unsigned long messageCount,
+ [array, size_is(messageCount)] in string messages);
+ readonly attribute AUTF8String originalMessage;
+ attribute boolean cancelled;
+ readonly attribute prplIConversation target;
+ };
+
+ [scriptable, uuid(bd2f77d4-1fad-432d-a914-6bb5ed5c13d0)]
+ interface imIMessage: prplIMessage {
+ attribute boolean cancelled;
+ attribute AUTF8String decodedMessage;
+ };
diff --git a/chat/components/public/imILogger.idl b/chat/components/public/imILogger.idl
--- a/chat/components/public/imILogger.idl
+++ b/chat/components/public/imILogger.idl
@@ -5,18 +5,18 @@
#include "nsISupports.idl"
#include "nsISimpleEnumerator.idl"
#include "nsIFile.idl"
interface imIAccount;
interface imIAccountBuddy;
interface imIBuddy;
interface imIContact;
+ interface imIMessage;
interface prplIConversation;
- interface prplIMessage;
[scriptable, uuid(5bc06f3b-33a1-412b-a4d8-4fc7ba4c962b)]
interface imILogConversation: nsISupports {
readonly attribute AUTF8String title;
readonly attribute AUTF8String name;
readonly attribute PRTime startDate;
// Simplified account implementation:
@@ -26,17 +26,17 @@ interface imILogConversation: nsISupport
// - protocol will only contain a "name" attribute, with the prpl's normalized name.
// Other methods/attributes aren't implemented.
readonly attribute imIAccount account;
readonly attribute boolean isChat; // always false (compatibility with prplIConversation).
readonly attribute imIAccountBuddy buddy; // always null (compatibility with prplIConvIM).
void getMessages([optional] out unsigned long messageCount,
- [retval, array, size_is(messageCount)] out prplIMessage messages);
+ [retval, array, size_is(messageCount)] out imIMessage messages);
// Callers that process the messages asynchronously should use the enumerator
// instead of the array version of the getMessages* methods to avoid paying
// up front the cost of xpconnect wrapping all message objects.
nsISimpleEnumerator getMessagesEnumerator([optional] out unsigned long messageCount);
};
[scriptable, uuid(164ff6c3-ca64-4880-b8f3-67eb1817955f)]
diff --git a/chat/components/public/prplIConversation.idl b/chat/components/public/prplIConversation.idl
--- a/chat/components/public/prplIConversation.idl
+++ b/chat/components/public/prplIConversation.idl
@@ -6,16 +6,17 @@
@@ -4,16 +4,17 @@
#include "nsISupports.idl"
#include "nsISimpleEnumerator.idl"
#include "nsIObserver.idl"
interface imIAccountBuddy;
interface imIAccount;
+ interface imIOutgoingMessage;
interface nsIURI;
interface nsIDOMDocument;
+ interface prplIMessage;
/*
* This is the XPCOM purple conversation component, a proxy for PurpleConversation.
*/
[scriptable, uuid(e40dc3e5-c9ff-457b-a6cc-655cce81042c)]
interface prplIConversation: nsISupports {
@@ -39,16 +40,20 @@ interface prplIConversation: nsISupports
readonly attribute PRTime startDate;
/* Unique identifier of the conversation */
/* Setable only once by purpleCoreService while calling addConversation. */
attribute unsigned long id;
/* Send a message in the conversation */
void sendMsg(in AUTF8String aMsg);
+ void prepareForSending(in imIOutgoingMessage message,
+ [optional] out unsigned long messageCount,
+ [retval, array, size_is(messageCount)] out string messages);
+
/* Send information about the current typing state to the server.
aString should contain the content currently in the text field. The
protocol should return the number of characters that can still be typed. */
long sendTyping(in AUTF8String aString);
const long NO_TYPING_LIMIT = 2147483647; // max int = 2 ^ 31 - 1
@@ -53,22 +54,38 @@ interface prplIConversation: nsISupports
/* Un-initialize the conversation. Will be called by
purpleCoreService::RemoveConversation when the conversation is
closed or by purpleCoreService::Quit while exiting. */
void unInit();
/* When the conversation is closed from the UI. */
void close();
- /* Method to add or remove an observer */
- void addObserver(in nsIObserver aObserver);
+ /* Methods to add or remove an observer. Priority determines the order
+ * in which they are invoked. Any integer value is usable as a priority.
+ * 0 is the default priority.
+ * < 0 is lower priority.
+ * > 0 is higher priority.
+ */
+ void addObserver(in nsIObserver aObserver, [optional] in long aPriority);
void removeObserver(in nsIObserver aObserver);
- /* Observers will be all receive new-text notifications.
- aSubject will contain the message (prplIMessage) */
+ /* Observers will be notified of messaging events.
+ * aSubject will contain the message (prplIMessage)
+ *
+ * Fired notifications:
+ * new-text
+ * Tells the UI to display the message in the conversation.
+ * sending-message
+ * Where the message can be modified, or cancelled, by observers.
+ * receiving-message
+ * Observers can modify the message, or stop its processing.
+ */
+ void notifyObservers(in prplIMessage aSubject, in string aTopic,
+ [optional] in wstring aData);
};
[scriptable, uuid(0c072a80-103a-4992-b249-8e442b5f0d46)]
interface prplIConvIM: prplIConversation {
/* The buddy at the remote end of the conversation */
readonly attribute imIAccountBuddy buddy;
diff --git a/chat/components/public/prplIMessage.idl b/chat/components/public/prplIMessage.idl
--- a/chat/components/public/prplIMessage.idl
+++ b/chat/components/public/prplIMessage.idl
@@ -21,17 +21,19 @@ interface prplIMessageAction: nsIRunnabl
[scriptable, uuid(d9f0ca7f-ee59-4657-a3dd-f458c204ca45)]
interface prplIMessage: nsISupports {
/* The uniqueness of the message id is only guaranteed across
messages of a conversation, not across all messages created
during the execution of the application. */
readonly attribute unsigned long id;
readonly attribute AUTF8String who;
readonly attribute AUTF8String alias;
- readonly attribute AUTF8String originalMessage;
+ /* This isn't readonly so that observers can change what's
+ displayed to the UI. */
+ attribute AUTF8String originalMessage;
attribute AUTF8String message;
readonly attribute AUTF8String iconURL;
readonly attribute PRTime time;
readonly attribute prplIConversation conversation;
/* Holds the sender color for Chats.
Empty string by default, it is set by the conversation binding. */
attribute AUTF8String color;
@@ -64,15 +66,19 @@ interface prplIMessage: nsISupports {
readonly attribute boolean noFormat;
/* PURPLE_MESSAGE_IMAGES = 0x1000, /**< Message contains images */
readonly attribute boolean containsImages;
/* PURPLE_MESSAGE_NOTIFY = 0x2000, /**< Message is a notification */
readonly attribute boolean notification;
/* PURPLE_MESSAGE_NO_LINKIFY = 0x4000 /**< Message should not be auto-linkified */
readonly attribute boolean noLinkification;
+ /* This is a way for conversation observers to indicate
+ that message processing should abort this message. */
+ attribute boolean cancel;
+
/* An array of actions the user may perform on this message.
The first action will be the 'default' and may be performed
automatically when the message is double clicked.
'Reply' is usually a good default action. */
void getActions([optional] out unsigned long actionCount,
[retval, array, size_is(actionCount)] out prplIMessageAction actions);
};
diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConversations.js
--- a/chat/components/src/imConversations.js
+++ b/chat/components/src/imConversations.js
@@ -16,16 +16,17 @@ XPCOMUtils.defineLazyGetter(this, "bundl
@@ -10,16 +10,85 @@ Cu.import("resource:///modules/jsProtoHe
var gLastUIConvId = 0;
var gLastPrplConvId = 0;
XPCOMUtils.defineLazyGetter(this, "bundle", function()
Services.strings.createBundle("chrome://chat/locale/conversations.properties")
);
+ function OutgoingMessage(message, conversation) {
+ this.message = message;
+ this.conversation = conversation;
+ }
+ OutgoingMessage.prototype = {
+ __proto__: ClassInfo("imIOutgoingMessage", "Outgoing Message"),
+ cancelled: false
+ };
+
+ function SendableMessage(message, target) {
+ this._originalMessage = message;
+ this.target = target;
+ }
+ SendableMessage.prototype = {
+ __proto__: ClassInfo("imISendableMessage", "Sendable Message"),
+ cancelled: false,
+ get originalMessage() this._originalMessage,
+ _sendableMessages: null,
+ getSendableMessages: function(aMessageCount) {
+ let sms = this._sendableMessages || [this.originalMessage];
+ if (aMessageCount)
+ aMessageCount.value = sms.length;
+ return sms;
+ },
+ setSendableMessages: function(aMessageCount, aMessages) {
+ this._sendableMessages = aMessages;
+ }
+ };
+
+ function imMessage(prplMessage) {
+ this.prplMessage = prplMessage;
+ }
+ imMessage.prototype = {
+ __proto__: ClassInfo(["imIMessage", "prplIMessage"], "IM Message"),
+ cancelled: false,
+ _decodedMessage: null,
+
+ get decodedMessage() {
+ return this._decodedMessage || this.prplMessage.originalMessage;
+ },
+ set decodedMessage(msg) { this._decodedMessage = msg; },
+
+ get message() this.prplMessage.message,
+ set message(msg) { this.prplMessage.message = msg; },
+
+ // from prplIMessage
+ get who() this.prplMessage.who,
+ get time() this.prplMessage.time,
+ get id() this.prplMessage.id,
+ get alias() this.prplMessage.alias,
+ get iconURL() this.prplMessage.iconURL,
+ get conversation() this.prplMessage.conversation,
+ set conversation(aConv) { this.prplMessage.conversation = aConv; },
+ get color() this.prplMessage.color,
+ get outgoing() this.prplMessage.outgoing,
+ get incoming() this.prplMessage.incoming,
+ get system() this.prplMessage.system,
+ get autoResponse() this.prplMessage.autoResponse,
+ get containsNick() this.prplMessage.containsNick,
+ get noLog() this.prplMessage.noLog,
+ get error() this.prplMessage.error,
+ get delayed() this.prplMessage.delayed,
+ get noFormat() this.prplMessage.noFormat,
+ get containsImages() this.prplMessage.containsImages,
+ get notification() this.prplMessage.notification,
+ get noLinkification() this.prplMessage.noLinkification,
+ getActions: function(aCount) this.prplMessage.getActions(aCount)
+ };
+
function UIConversation(aPrplConversation)
{
this._prplConv = {};
this.id = ++gLastUIConvId;
this._observers = [];
this._messages = [];
+ this._msgBuffer = [];
this.changeTargetTo(aPrplConversation);
let iface = Ci["prplIConv" + (aPrplConversation.isChat ? "Chat" : "IM")];
this._interfaces = this._interfaces.concat(iface);
// XPConnect will create a wrapper around 'this' after here,
// so the list of exposed interfaces shouldn't change anymore.
this.updateContactObserver();
Services.obs.notifyObservers(this, "new-ui-conversation", null);
}
@@ -261,16 +262,21 @@ UIConversation.prototype = {
@@ -261,16 +330,24 @@ UIConversation.prototype = {
},
observeConv: function(aTargetId, aSubject, aTopic, aData) {
@@ -137,9 +256,12 @@ diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConv
this._prplConv[aTargetId].typingState == Ci.prplIConvIM.TYPING)))
this.target = this._prplConv[aTargetId];
+
+ // Process new texts and possibly cancel the message at this point.
+ if (aTopic == "new-text" && !this.processText(aSubject))
+ return;
+ if (aTopic == "new-text") {
+ aSubject = new imMessage(aSubject);
+ this.notifyObservers(aSubject, "received-message");
+ if (aSubject.cancelled)
+ return;
+ }
+
this.notifyObservers(aSubject, aTopic, aData);
if (aTopic == "new-text") {
@@ -149,7 +271,7 @@ diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConv
this.notifyObservers(aSubject, "new-directed-incoming-message", aData);
Services.obs.notifyObservers(aSubject, "new-directed-incoming-message", aData);
}
@@ -284,17 +290,81 @@ UIConversation.prototype = {
@@ -284,17 +361,42 @@ UIConversation.prototype = {
// prplIConversation
get isChat() this.target.isChat,
@@ -159,71 +281,32 @@ diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConv
get title() this.target.title,
get startDate() this.target.startDate,
- sendMsg: function (aMsg) { this.target.sendMsg(aMsg); },
+
+ sendMsg: function(aMsg) {
+ // Create a new message with aMsg to satisfy the observer interface.
+ let nMsg = new Message(this.target.name, aMsg, { outgoing: true });
+
+ // Notify observers that now is the time to modify the message
+ // before sending. After modifications,
+ // "message" will contain the outgoing text
+ // "originalMessage" will be displayed to the user
+ this.target.notifyObservers(nMsg, "sending-message", null);
+
+ // If an observer cancelled the message, abort here.
+ if (nMsg.cancel)
+ // add-ons (eg. pastebin) have an opportunity to cancel the message at this
+ // point, or change the text content of the message.
+ // If an add-on wants to split a message, it should truncate the first
+ // message, and insert new messages using the conversation's sendMsg method.
+ let om = new OutgoingMessage(aMsg, this);
+ this.notifyObservers(om, "preparing-message");
+ if (om.cancelled)
+ return;
+
+ // Because of the libpurple context restriction, we're forced
+ // to buffer messages here. When "new-text" is fired, we'll
+ // grab this message to display the intended content.
+ this.bufferMsg(this.target, nMsg);
+
+ // Send just the outgoing text.
+ this.target.sendMsg(nMsg.message);
+ },
+
+ bufferMsg: function(aConv, aMsg) {
+ this._msgBuffer.push({ conv: aConv, msg: aMsg });
+ },
+ // prpls have an opportunity here to preprocess messages before they are
+ // sent, eg. split long messages. If a message is split here, the split
+ // will be visible in the UI.
+ // prpls can return null if they don't need to make any change
+ let messages = this.target.prepareForSending(om) || [om.message];
+
+ // If we don't find the message, it was injected directly
+ // and was intended to be ignored.
+ pluckMsg: function(aConv, aMsg) {
+ let mb = this._msgBuffer;
+ return mb.some(function(a, i) {
+ if (a.conv === aConv && a.msg.message === aMsg.message) {
+ // This is likely the message we stored.
+ // Replace the text to be displayed with its contents.
+ aMsg.originalMessage = a.msg.originalMessage;
+ // And remove it from the buffer.
+ mb.splice(i, 1);
+ // Indicate the message was located.
+ return true;
+ }
+ });
+ },
+
+ processText: function(aMsg) {
+ if (!aMsg.system) {
+ if (aMsg.outgoing) {
+ // If the message isn't located, suppress it.
+ return this.pluckMsg(this.target, aMsg);
+ } else if (aMsg.incoming) {
+ // Notify observers that now is the time to modify the incoming
+ // message. After modifications,
+ // "message" will still contain the incoming text
+ // "originalMessage" will be displayed to the user
+ this.target.notifyObservers(aMsg, "receiving-message", null);
+
+ // If an observer cancelled the message, abort here.
+ if (aMsg.cancel)
+ return false;
+ }
+ for (let msg of messages) {
+ // add-ons (eg. OTR) have an opportunity to tweak or cancel the message
+ // point.
+ let sm = new SendableMessage(msg, this.target);
+ this.notifyObservers(sm, "sending-message");
+ if (sm.cancelled)
+ continue;
+ sm.getSendableMessages().forEach((m) => this.target.sendMsg(m));
+ }
+ return true;
+ },
+
unInit: function() {
for each (let conv in this._prplConv)
gConversationsService.forgetConversation(conv);
@@ -232,55 +315,181 @@ diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConv
delete this._observedContact;
}
this._prplConv = {}; // Prevent .close from failing.
diff --git a/chat/components/src/logger.js b/chat/components/src/logger.js
--- a/chat/components/src/logger.js
+++ b/chat/components/src/logger.js
@@ -162,32 +162,32 @@ ConversationLog.prototype = {
logMessage: function cl_logMessage(aMessage) {
if (!this._log)
this._init();
if (this.format == "json") {
let msg = {
date: new Date(aMessage.time * 1000),
who: aMessage.who,
- text: aMessage.originalMessage,
+ text: aMessage.decodedMessage,
flags: ["outgoing", "incoming", "system", "autoResponse",
"containsNick", "error", "delayed",
"noFormat", "containsImages", "notification",
"noLinkification"].filter(function(f) aMessage[f])
};
let alias = aMessage.alias;
if (alias && alias != msg.who)
msg.alias = alias;
this._log.writeString(JSON.stringify(msg) + "\n");
return;
}
let date = new Date(aMessage.time * 1000);
let line = "(" + date.toLocaleTimeString() + ") ";
- let msg = this._serialize(aMessage.originalMessage);
+ let msg = this._serialize(aMessage.decodedMessage);
if (aMessage.system)
line += msg;
else {
let sender = aMessage.alias || aMessage.who;
if (aMessage.autoResponse)
line += sender + " <AUTO-REPLY>: " + msg;
else {
if (msg.startsWith("/me "))
@@ -317,17 +317,21 @@ function LogMessage(aData, aConversation
this._init(aData.who, aData.text);
this._conversation = aConversation;
this.time = Math.round(new Date(aData.date) / 1000);
if ("alias" in aData)
this._alias = aData.alias;
for each (let flag in aData.flags)
this[flag] = true;
}
- LogMessage.prototype = GenericMessagePrototype;
+ LogMessage.prototype = {
+ __proto__: GenericMessagePrototype,
+ _interfaces: [Ci.imIMessage, Ci.prplIMessage],
+ get decodedMessage() this.originalMessage
+ };
function LogConversation(aLineInputStreams)
{
// If aLineInputStreams isn't an Array, we'll assume that it's a lone
// InputStream, and wrap it in an Array.
if (!Array.isArray(aLineInputStreams))
aLineInputStreams = [aLineInputStreams];
diff --git a/chat/content/convbrowser.xml b/chat/content/convbrowser.xml
--- a/chat/content/convbrowser.xml
+++ b/chat/content/convbrowser.xml
@@ -464,17 +464,17 @@
let csFlags = cs.kStructPhrase;
// Automatically find and link freetext URLs
if (!aMsg.noLinkification)
csFlags |= cs.kURLs;
if (aFirstUnread)
this.setUnreadRuler();
- let msg = aMsg.originalMessage;
+ let msg = aMsg.decodedMessage;
// The slash of a leading '/me' should not be used to
// format as italic, so we remove the '/me' text before
// scanning the HTML, and we add it back later.
let meRegExp = /^((<[^>]+>)*)\/me /;
let me = false;
if (meRegExp.test(msg)) {
me = true;
diff --git a/chat/modules/imThemes.jsm b/chat/modules/imThemes.jsm
--- a/chat/modules/imThemes.jsm
+++ b/chat/modules/imThemes.jsm
@@ -365,17 +365,17 @@ const statusMessageReplacements = {
else {
msgClass.push("message");
if (aMsg.incoming)
msgClass.push("incoming");
else if (aMsg.outgoing)
msgClass.push("outgoing");
- if (/^(<[^>]+>)*\/me /.test(aMsg.originalMessage))
+ if (/^(<[^>]+>)*\/me /.test(aMsg.decodedMessage))
msgClass.push("action");
if (aMsg.autoResponse)
msgClass.push("autoreply");
}
if (aMsg.containsNick)
msgClass.push("nick");
@@ -490,21 +490,21 @@ function getHTMLForMessage(aMsg, aTheme,
else {
html = aMsg.incoming ? "incoming" : "outgoing";
if (aIsNext)
html += "Next";
html += aIsContext ? "Context" : "Content";
html = aTheme.html[html];
replacements = messageReplacements;
let meRegExp = /^((<[^>]+>)*)\/me /;
- // We must test originalMessage here as aMsg.message loses its /me
+ // We must test decodedMessage here as aMsg.message loses its /me
// in the following, so if getHTMLForMessage is called a second time for
// the same aMsg (e.g. because it follows the unread ruler), the test
// would fail otherwise.
- if (meRegExp.test(aMsg.originalMessage)) {
+ if (meRegExp.test(aMsg.decodedMessage)) {
aMsg.message = aMsg.message.replace(meRegExp, "$1");
let actionMessageTemplate = "* %message% *";
if (hasMetadataKey(aTheme, "ActionMessageTemplate"))
actionMessageTemplate = getMetadata(aTheme, "ActionMessageTemplate");
html = html.replace(/%message%/g, actionMessageTemplate);
}
}
@@ -894,17 +894,17 @@ SelectedMessage.prototype = {
let html, replacements;
if (msg.system) {
replacements = statusReplacements;
html = getLocalizedPrefWithDefault("systemMessagesTemplate",
"%time% - %message%");
}
else {
replacements = messageReplacements;
- if (/^(<[^>]+>)*\/me /.test(msg.originalMessage)) {
+ if (/^(<[^>]+>)*\/me /.test(msg.decodedMessage)) {
html = getLocalizedPrefWithDefault("actionMessagesTemplate",
"%time% * %sender% %message%");
}
else {
html = getLocalizedPrefWithDefault("contentMessagesTemplate",
"%time% - %sender%: %message%");
}
}
diff --git a/chat/modules/jsProtoHelper.jsm b/chat/modules/jsProtoHelper.jsm
--- a/chat/modules/jsProtoHelper.jsm
+++ b/chat/modules/jsProtoHelper.jsm
@@ -450,27 +450,35 @@ const GenericConversationPrototype = {
_id: 0,
get id() this._id,
set id(aId) {
if (this._id)
throw Cr.NS_ERROR_ALREADY_INITIALIZED;
this._id = aId;
},
- addObserver: function(aObserver) {
- if (this._observers.indexOf(aObserver) == -1)
- this._observers.push(aObserver);
+ // Observers are run in descending order.
+ addObserver: function(aObserver, aPriority) {
+ let wrap = {
+ observer: aObserver,
+ priority: aPriority || 0
+ };
+ for (var i = 0; i < this._observers.length; i++)
+ if (wrap.priority > this._observers[i].priority) break;
+ this._observers.splice(i, 0, wrap);
},
removeObserver: function(aObserver) {
- this._observers = this._observers.filter(function(o) o !== aObserver);
+ this._observers = this._observers.filter(function(w) {
+ return w.observer !== aObserver;
+ });
},
notifyObservers: function(aSubject, aTopic, aData) {
- for each (let observer in this._observers) {
+ for each (let wrap in this._observers) {
@@ -467,17 +467,18 @@ const GenericConversationPrototype = {
try {
- observer.observe(aSubject, aTopic, aData);
+ wrap.observer.observe(aSubject, aTopic, aData);
observer.observe(aSubject, aTopic, aData);
} catch(e) {
this.ERROR(e);
}
}
},
sendMsg: function (aMsg) {
- sendMsg: function (aMsg) {
+ prepareForSending: function(aOm, aCount) null,
+ sendMsg: function(aMsg) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
sendTyping: function(aString) Ci.prplIConversation.NO_TYPING_LIMIT,
close: function() {
Services.obs.notifyObservers(this, "closing-conversation", null);
Services.conversations.removeConversation(this);
},
diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js
--- a/chat/protocols/irc/irc.js
+++ b/chat/protocols/irc/irc.js
@@ -129,22 +129,41 @@ const GenericIRCConversation = {
@@ -129,19 +129,19 @@ const GenericIRCConversation = {
getMaxMessageLength: function() {
// Build the shortest possible message that could be sent to other users.
let baseMessage = ":" + this._account._nickname + this._account.prefix +
@@ -290,42 +499,19 @@ diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js
this._account.countBytes(baseMessage);
},
- sendMsg: function(aMessage) {
- // Split the message by line breaks and send each one individually.
+ prepareForSending: function(aOm, aCount) {
// Split the message by line breaks and send each one individually.
- let messages = aMessage.split(/[\r\n]+/);
+
+ // Apply CTCP formatting before display
+ applyCTCP: function(aSubject, aTopic, aData) {
+ if (aTopic !== "sending-message")
+ return;
+
+ aSubject.originalMessage = ctcpFormatToHTML(aSubject.originalMessage);
+ },
+
+ splitMsg: function(aSubject, aTopic, aData) {
+ if (aTopic !== "sending-message")
+ return;
+ let messages = aOm.message.split(/[\r\n]+/);
let maxLength = this.getMaxMessageLength();
+ // Ensure the message is an appropriate length.
+ if (aSubject.message.length <= maxLength)
+ return;
+
+ // Cancel the message and split it.
+ aSubject.cancel = true;
+
+ // Split the message by line breaks and send each one individually.
+ let messages = aSubject.message.split(/[\r\n]+/);
+
// Attempt to smartly split a string into multiple lines (based on the
// maximum number of characters the message can contain).
for (let i = 0; i < messages.length; ++i) {
let message = messages[i];
let length = this._account.countBytes(message);
// The message is short enough.
if (length <= maxLength)
continue;
@@ -154,35 +173,42 @@ const GenericIRCConversation = {
@@ -154,34 +154,37 @@ const GenericIRCConversation = {
// Remove the current message and insert the two new ones. If no space was
// found, cut the first message to the maximum length and start the second
@@ -334,107 +520,50 @@ diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js
message.substr((index + 1) || maxLength));
}
+ // Get the UIConversation to re-send the messages.
+ let UIConv = Services.conversations.getUIConversation(this);
+
// Send each message and display it in the conversation.
for (let message of messages) {
if (!message.length)
return;
-
- // Send each message and display it in the conversation.
- for (let message of messages) {
- if (!message.length)
- return;
+ if (aCount)
+ aCount.value = messages.length;
- if (!this._account.sendMessage("PRIVMSG", [this.name, message])) {
- this.writeMessage(this._account._currentServerName,
- _("error.sendMessageFailed"),
- {error: true, system: true});
- break;
- }
-
+ return messages;
+ },
+ sendMsg: function(aMessage) {
+ if (!aMessage.length)
+ return;
- // Since the server doesn't send us a message back, just assume the
- // message was received and immediately show it.
- this.writeMessage(this._account._nickname, message, {outgoing: true});
-
- this._pendingMessage = true;
+ UIConv.sendMsg(message);
}
},
+
+ sendMsg: function(aMessage) {
+ if (!this._account.sendMessage("PRIVMSG", [this.name, aMessage])) {
+ this.writeMessage(this._account._currentServerName,
+ _("error.sendMessageFailed"),
+ {error: true, system: true});
+ return;
+ }
+
- this._pendingMessage = true;
- }
+ // Since the server doesn't send us a message back, just assume the
+ // message was received and immediately show it.
+ this.writeMessage(this._account._nickname, aMessage, {outgoing: true});
+
+ this._pendingMessage = true;
+ },
+
},
// IRC doesn't support typing notifications, but it does have a maximum
// message length.
sendTyping: function(aString) {
let longestLineLength =
Math.max.apply(null, aString.split("\n").map(this._account.countBytes,
this._account));
return this.getMaxMessageLength() - longestLineLength;
},
@@ -275,22 +301,20 @@ ircChannel.prototype = {
_receivedInitialMode: false,
// For IRC you're not in a channel until the JOIN command is received, open
// all channels (initially) as left.
_left: true,
// True until successfully joined for the first time.
_firstJoin: false,
banMasks: [],
- // Overwrite the writeMessage function to apply CTCP formatting before
- // display.
- writeMessage: function(aWho, aText, aProperties) {
- GenericConvChatPrototype.writeMessage.call(this, aWho,
- ctcpFormatToHTML(aText),
- aProperties);
+ _init: function() {
+ GenericConvChatPrototype._init.call(this, aAccount, aName, aNick);
+ this.addObserver({ observe: this.splitMsg.bind(this) }, 999);
+ this.addObserver({ observe: this.applyCTCP.bind(this) });
},
// Stores the prplIChatRoomFieldValues required to join this channel
// to enable later reconnections. If absent, the MUC will not be reconnected
// automatically after disconnections.
_chatRoomFields: null,
// Section 3.2.2 of RFC 2812.
@@ -601,22 +625,20 @@ function ircConversation(aAccount, aName
// Always request the info as it may be out of date.
this._waitingForNick = true;
this.requestBuddyInfo(aName);
}
ircConversation.prototype = {
__proto__: GenericConvIMPrototype,
get buddy() this._account.buddies.get(this.name),
- // Overwrite the writeMessage function to apply CTCP formatting before
- // display.
- writeMessage: function(aWho, aText, aProperties) {
- GenericConvIMPrototype.writeMessage.call(this, aWho,
- ctcpFormatToHTML(aText),
- aProperties);
+ _init: function(aAccount, aName) {
+ GenericConvIMPrototype._init.call(this, aAccount, aName);
+ this.addObserver({ observe: this.splitMsg.bind(this) }, 999);
+ this.addObserver({ observe: this.applyCTCP.bind(this) });
},
unInit: function() {
this.unInitIRCConversation();
GenericConvIMPrototype.unInit.call(this);
},
updateNick: function(aNewNick) {
diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -460,30 +589,26 @@ diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm
* owner -> founder
*/
const kRoles = ["outcast", "visitor", "participant", "member", "moderator",
@@ -177,16 +182,22 @@ const XMPPConversationPrototype = {
_typingTimer: null,
supportChatStateNotifications: true,
_typingState: "active",
@@ -233,32 +238,35 @@ const XMPPConversationPrototype = {
_targetResource: "",
get to() {
let to = this.buddy.userName;
if (this._targetResource)
to += "/" + this._targetResource;
return to;
},
_init : function(aAccount, aBuddy ) {
this.buddy = aBuddy;
GenericConvIMPrototype._init.call(this, aAccount, aBuddy.normalizedName) ;
+ this.addObserver(this) ;
+ prepareForSending : function(aOm, aCount ) {
+ if (aCount)
+ aCount.value = 1 ;
+ return [TXTToHTML(aOm.message)] ;
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic === "sending-message")
+ aSubject.originalMessage = TXTToHTML(aSubject.originalMessage);
},
get title() this.buddy.contactDisplayName,
get normalizedName() this.buddy.normalizedName,
get shouldSendTypingNotifications()
this._supportChatStateNotifications &&
Services.prefs.getBoolPref("purple.conversations.im.send_typing"),
@@ -245,20 +256,17 @@ const XMPPConversationPrototype = {
/* Called when the user enters a chat message */
- sendMsg: function (aMsg) {
+ sendMsg: function(aMsg) {
this._cancelTypingTimer();
let cs = this.shouldSendTypingNotifications ? "active" : null;
let s = Stanza.message(this.to, aMsg, cs);
this._account.sendStanza(s);
let who;
@@ -505,67 +630,3 @@ diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm
let from = aStanza.attributes["from"];
this._targetResource = this._account._parseJID(from).resource;
let flags = {};
@@ -876,19 +884,17 @@ const XMPPAccountPrototype = {
// Prefer HTML (in <html><body>) and use plain text (<body>) as fallback.
let htmlBody = aStanza.getElement(["html", "body"]);
if (htmlBody)
body = htmlBody.innerXML;
else {
// Even if the message is in plain text, the prplIMessage
// should contain a string that's correctly escaped for
// insertion in an HTML document.
- body = Cc["@mozilla.org/txttohtmlconv;1"]
- .getService(Ci.mozITXTToHTMLConv)
- .scanTXT(b.innerText, Ci.mozITXTToHTMLConv.kEntities);
+ body = TXTToHTML(b.innerText);
}
}
if (body) {
let date;
let delay = aStanza.getElement(["delay"]);
if (delay && delay.uri == Stanza.NS.delay) {
if (delay.attributes["stamp"])
date = new Date(delay.attributes["stamp"]);
diff --git a/im/config/mozconfigs/macosx/mozconfig b/im/config/mozconfigs/macosx/mozconfig
--- a/im/config/mozconfigs/macosx/mozconfig
+++ b/im/config/mozconfigs/macosx/mozconfig
@@ -17,17 +17,17 @@ export CXXFLAGS="-gdwarf-2 -W -Wno-unuse
# For NSS symbols
export MOZ_DEBUG_SYMBOLS=1
ac_add_options --enable-debug-symbols="-gdwarf-2"
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-sj10"
ac_add_options --enable-application=im
- ac_add_options --enable-extensions=purple
+ #ac_add_options --enable-extensions=purple
ac_add_options --enable-update-channel=nightly
ac_add_options --enable-update-packaging
ac_add_options --enable-optimize
ac_add_options --disable-debug
if [ `date +%m%d` = 1031 -o `date +%m%d` = 1101 ]; then
ac_add_options --with-branding=instantbird/branding/halloween
fi
diff --git a/im/config/mozconfigs/macosx/mozconfig-release b/im/config/mozconfigs/macosx/mozconfig-release
--- a/im/config/mozconfigs/macosx/mozconfig-release
+++ b/im/config/mozconfigs/macosx/mozconfig-release
@@ -17,16 +17,16 @@ export CXXFLAGS="-gdwarf-2 -W -Wno-unuse
# For NSS symbols
export MOZ_DEBUG_SYMBOLS=1
ac_add_options --enable-debug-symbols="-gdwarf-2"
# Enable parallel compiling
mk_add_options MOZ_MAKE_FLAGS="-sj10"
ac_add_options --enable-application=im
- ac_add_options --enable-extensions=purple
+ #ac_add_options --enable-extensions=purple
ac_add_options --enable-update-channel=release
ac_add_options --enable-update-packaging
ac_add_options --enable-optimize
ac_add_options --disable-debug
ac_add_options --enable-official-branding
ac_add_options --with-l10n-base=..