commit 6e77842b91dae35d0dc05adde4cf96ff0533fc42 Author: Arlo Breault arlolra@gmail.com Date: Wed Nov 26 19:31:11 2014 -0800
Rebase the preparation patch
* And add the tests patch for good measure. --- projects/instantbird/config | 5 +- .../prepare-messages-for-displaying.patch | 40 ++- projects/instantbird/test-transformation-api.patch | 367 ++++++++++++++++++++ 3 files changed, 392 insertions(+), 20 deletions(-)
diff --git a/projects/instantbird/config b/projects/instantbird/config index f49d4f8..ce37906 100644 --- a/projects/instantbird/config +++ b/projects/instantbird/config @@ -56,14 +56,15 @@ input_files: project: mozilla pkg_type: src - filename: preferences.patch - - filename: irc.patch + - filename: prepare-messages-for-displaying.patch + - filename: test-transformation-api.patch - filename: slashme.patch + - filename: irc.patch - filename: xmpp.patch - filename: facebook.patch - filename: accountcreation.patch - filename: links.patch - filename: picture.patch - - filename: prepare-messages-for-displaying.patch - filename: spi-cacert.der - filename: ccc-jabber-cacert.der - filename: fix-mingw-build.nsprpatch diff --git a/projects/instantbird/prepare-messages-for-displaying.patch b/projects/instantbird/prepare-messages-for-displaying.patch index 1796a0c..4828728 100644 --- a/projects/instantbird/prepare-messages-for-displaying.patch +++ b/projects/instantbird/prepare-messages-for-displaying.patch @@ -1,9 +1,9 @@ # HG changeset patch # User Arlo Breault arlolra@gmail.com -# Date 1411421387 25200 -# Mon Sep 22 14:29:47 2014 -0700 -# Node ID fbe43529c92d26a1c7674fc964ae5e1aadc9ac2f -# Parent 2b6f0a6859958c9b57b3975001ee7aac257d9ae8 +# Date 1417058407 28800 +# Wed Nov 26 19:20:07 2014 -0800 +# Node ID 0108590a22b0943081fe0f26375834da2f84b0b6 +# Parent e4c698528d69e8d4e2116bbf2e28f91fd5b5f972 Prepare messages for displaying
* Adds a converse method to prepareForSending. @@ -56,7 +56,7 @@ diff --git a/chat/components/public/prplIConversation.idl b/chat/components/publ * This is the XPCOM purple conversation component, a proxy for PurpleConversation. */
-@@ -47,16 +48,19 @@ interface prplIConversation: nsISupports +@@ -47,16 +48,21 @@ interface prplIConversation: nsISupports void sendMsg(in AUTF8String aMsg);
/* Preprocess messages before they are sent (eg. split long messages). @@ -65,7 +65,9 @@ diff --git a/chat/components/public/prplIConversation.idl b/chat/components/publ [optional] out unsigned long aMsgCount, [retval, array, size_is(aMsgCount)] out wstring aMsgs);
-+ /* Postprocess messages before they are displayed (eg. escaping). */ ++ /* Postprocess messages before they are displayed (eg. escaping). The ++ implementation can set aMsg.displayMessage, otherwise the originalMessage ++ is used. */ + void prepareForDisplaying(in imIMessage aMsg); + /* Send information about the current typing state to the server. @@ -79,7 +81,7 @@ diff --git a/chat/components/public/prplIConversation.idl b/chat/components/publ 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 -@@ -399,16 +399,17 @@ UIConversation.prototype = { +@@ -403,16 +403,17 @@ UIConversation.prototype = { this._observers = this._observers.filter(function(o) o !== aObserver); }, notifyObservers: function(aSubject, aTopic, aData) { @@ -97,7 +99,7 @@ diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConv if (!this.isChat || aSubject.containsNick) ++this._unreadTargetedMessageCount; } -@@ -426,16 +427,20 @@ UIConversation.prototype = { +@@ -430,16 +431,21 @@ UIConversation.prototype = { if (aSubject.incoming && !aSubject.system && (!this.isChat || aSubject.containsNick)) { this.notifyObservers(aSubject, "new-directed-incoming-message", aData); @@ -106,8 +108,9 @@ diff --git a/chat/components/src/imConversations.js b/chat/components/src/imConv } },
-+ // Called above when the conversation service itself is set as the -+ // conversation for a message. This is case for some system messages. ++ // Used above when notifying of new-texts originating in the ++ // UIConversation. The happens when this.systemMessage() is called. The ++ // conversation for the message is set as the UIConversation. + prepareForDisplaying: function(aMsg) {}, + // prplIConvIM @@ -142,7 +145,7 @@ diff --git a/chat/modules/jsProtoHelper.jsm b/chat/modules/jsProtoHelper.jsm 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,16 +129,20 @@ const GenericIRCConversation = { +@@ -126,16 +126,20 @@ const GenericIRCConversation = { getMaxMessageLength: function() { // Build the shortest possible message that could be sent to other users. let baseMessage = ":" + this._account._nickname + this._account.prefix + @@ -163,13 +166,13 @@ diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js
// Attempt to smartly split a string into multiple lines (based on the // maximum number of characters the message can contain). -@@ -278,24 +282,16 @@ ircChannel.prototype = { +@@ -274,24 +278,16 @@ 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, + // True while we are rejoining a channel previously parted by the user. + _rejoined: false, banMasks: [],
- // Overwrite the writeMessage function to apply CTCP formatting before @@ -188,7 +191,7 @@ diff --git a/chat/protocols/irc/irc.js b/chat/protocols/irc/irc.js // Otherwise, fall back to the default part message, if it exists. let msg = aMessage || this._account.getString("partmsg"); if (msg) -@@ -599,24 +595,16 @@ function ircConversation(aAccount, aName +@@ -595,24 +591,16 @@ function ircConversation(aAccount, aName // Always request the info as it may be out of date. this._waitingForNick = true; this.requestBuddyInfo(aName); @@ -238,7 +241,7 @@ diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm * owner -> founder */ const kRoles = ["outcast", "visitor", "participant", "member", "moderator", -@@ -255,16 +260,22 @@ const XMPPConversationPrototype = { +@@ -255,16 +260,23 @@ const XMPPConversationPrototype = { who = this._account._connection._jid.jid; if (!who) who = this._account.name; @@ -247,7 +250,8 @@ diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm delete this._typingState; },
-+ /* Perform entity escaping before displaying the message. */ ++ /* Perform entity escaping before displaying the message. We assume incoming ++ messages have already been escaped, and will otherwise be filtered. */ + prepareForDisplaying: function(aMsg) { + if (aMsg.outgoing && !aMsg.system) + aMsg.displayMessage = TXTToHTML(aMsg.displayMessage); @@ -261,7 +265,7 @@ diff --git a/chat/protocols/xmpp/xmpp.jsm b/chat/protocols/xmpp/xmpp.jsm if (aStanza.attributes["type"] == "error") { aMsg = _("conversation.error.notDelivered", aMsg); flags.system = true; -@@ -889,19 +900,17 @@ const XMPPAccountPrototype = { +@@ -889,19 +901,17 @@ const XMPPAccountPrototype = { // Prefer HTML (in <html><body>) and use plain text (<body>) as fallback. let htmlBody = aStanza.getElement(["html", "body"]); if (htmlBody) diff --git a/projects/instantbird/test-transformation-api.patch b/projects/instantbird/test-transformation-api.patch new file mode 100644 index 0000000..f43fcbc --- /dev/null +++ b/projects/instantbird/test-transformation-api.patch @@ -0,0 +1,367 @@ +# HG changeset patch +# User Arlo Breault arlolra@gmail.com +# Date 1414370632 25200 +# Sun Oct 26 17:43:52 2014 -0700 +# Node ID 49fc79e39b9c292d182a46aa800135bd65b6c38f +# Parent 030063bcb41284997f862b9b2d12e93bfc0b121a +Add tests for new messaging pipeline + + * Tests for bugs 1071166, 1088772, and 983347. + +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 +@@ -70,29 +70,31 @@ interface imIConversationsService: nsISu + }; + + // Because of limitations in libpurple (write_conv is called without context), + // there's an implicit contract that whatever message string the conversation + // service passes to a protocol, it'll get back as the originalMessage when + // "new-text" is notified. This is required for the OTR extensions to work. + + // A cancellable outgoing message. Before handing a message off to a protocol, +-// the conversation service notifies observers (typically add-ons) of an +-// outgoing message, which can be transformed or cancelled. ++// the conversation service notifies observers of `preparing-message` and ++// `sending-message` (typically add-ons) of an outgoing message, which can be ++// transformed or cancelled. + [scriptable, uuid(4391ba5c-9566-41a9-bb9b-fd0a0a490c2c)] + interface imIOutgoingMessage: nsISupports { + attribute AUTF8String message; + attribute boolean cancelled; + readonly attribute prplIConversation conversation; + }; + + // A cancellable message to be displayed. When the conversation service is +-// notified of a new-text (ie. an incoming or outgoing message to be displayed), +-// it in turn notifies observers (again, typically add-ons), which have the +-// opportunity to swap or cancel the message. ++// notified of a `new-text` (ie. an incoming or outgoing message to be ++// displayed), it in turn notifies observers of `received-message` ++// (again, typically add-ons), which have the opportunity to swap or cancel ++// the message. + [scriptable, uuid(3f88cc5c-6940-4eb5-a576-c65770f49ce9)] + interface imIMessage: prplIMessage { + attribute boolean cancelled; + // Holds the sender color for Chats. + // Empty string by default, it is set by the conversation binding. + attribute AUTF8String color; + + // What eventually gets shown to the user. +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 +@@ -59,16 +59,17 @@ imMessage.prototype = { + 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, ++ get originalMessage() this.prplMessage.originalMessage, + getActions: function(aCount) this.prplMessage.getActions(aCount || {}) + }; + + function UIConversation(aPrplConversation) + { + this._prplConv = {}; + this.id = ++gLastUIConvId; + this._observers = []; +@@ -120,17 +121,21 @@ UIConversation.prototype = { + _currentTargetId: 0, + changeTargetTo: function(aPrplConversation) { + let id = aPrplConversation.id; + if (this._currentTargetId == id) + return; + + if (!(id in this._prplConv)) { + this._prplConv[id] = aPrplConversation; +- aPrplConversation.addObserver(this.observeConv.bind(this, id)); ++ // Pass an object here, instead of just a function, because coercion ++ // to an nsIObserver won't happen in the JS tests. ++ aPrplConversation.addObserver({ ++ observe: this.observeConv.bind(this, id) ++ }); + } + + let shouldNotify = this._currentTargetId; + this._currentTargetId = id; + if (!this.isChat) { + let buddy = this.buddy; + if (buddy) + ({statusType: this.statusType, statusText: this.statusText}) = buddy; +@@ -356,17 +361,17 @@ UIConversation.prototype = { + + // Protocols 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. + let messages = this.target.prepareForSending(om); + + // Protocols can return null if they don't need to make any changes. + // (nb. passing null with retval array results in an empty array) +- if (!messages.length) ++ if (!messages || !messages.length) + messages = [om.message]; + + for (let msg of messages) { + // Add-ons (eg. OTR) have an opportunity to tweak or cancel the message + // at this point. + om = new OutgoingMessage(msg, this.target); + this.notifyObservers(om, "sending-message"); + if (om.cancelled) +diff --git a/chat/components/src/test/test_conversations.js b/chat/components/src/test/test_conversations.js +new file mode 100644 +--- /dev/null ++++ b/chat/components/src/test/test_conversations.js +@@ -0,0 +1,235 @@ ++/* Any copyright is dedicated to the Public Domain. ++ * http://creativecommons.org/publicdomain/zero/1.0/ */ ++ ++const Cu = Components.utils; ++Cu.import("resource:///modules/imServices.jsm"); ++Cu.import("resource:///modules/jsProtoHelper.jsm"); ++ ++let imConversations = {}; ++Services.scriptloader.loadSubScript( ++ "resource:///components/imConversations.js", imConversations ++); ++ ++// Fake prplConversation ++let _id = 0; ++function Conversation(aName) { ++ this._name = aName; ++ this._observers = []; ++ this._date = Date.now() * 1000; ++ this.id = ++_id; ++} ++Conversation.prototype = { ++ __proto__: GenericConvIMPrototype, ++ _account: { ++ imAccount: { ++ protocol: {name: "Fake Protocol"}, ++ alias: "", ++ name: "Fake Account" ++ }, ++ ERROR: function(e) {throw e;} ++ } ++}; ++ ++// Ensure that when iMsg.message is set to a message (including the empty ++// string), it returns that message. If not, it should return the original ++// message. This prevents regressions due to JS coercions. ++let test_null_message = function() { ++ let originalMessage = "Hi!"; ++ let pMsg = new Message("buddy", originalMessage, { ++ outgoing: true, _alias: "buddy", time: Date.now() ++ }); ++ let iMsg = new imConversations.imMessage(pMsg); ++ equal(iMsg.message, originalMessage); ++ // Setting the message should prevent a fallback to the original. ++ iMsg.message = ""; ++ equal(iMsg.message, ""); ++ equal(iMsg.originalMessage, originalMessage); ++}; ++ ++// ROT13, used as an example transformation. ++function rot13(aString) { ++ return aString.replace(/[a-zA-Z]/g, function(c) { ++ return String.fromCharCode(c.charCodeAt(0) + (c.toLowerCase() < "n" ? 1 : -1) * 13); ++ }); ++} ++ ++// A test that exercises the message transformation pipeline. ++// ++// From the sending users perspective, this looks like: ++// -> UIConv sendMsg ++// -> UIConv notifyObservers `preparing-message` ++// -> protocol prepareForSending ++// -> UIConv notifyObservers `sending-message` ++// -> protocol sendMsg ++// -> protocol writeMessage ++// -> protocol notifyObservers `new-text` ++// -> UIConv notifyObservers `received-message` ++// -> protocol prepareForDisplaying ++// -> UIConv notifyObservers `new-text` ++// ++// From the receiving users perspective, they get: ++// -> protocol writeMessage ++// -> protocol notifyObservers `new-text` ++// -> UIConv notifyObservers `received-message` ++// -> protocol prepareForDisplaying ++// -> UIConv notifyObservers `new-text` ++// ++// The test walks the sending path, which covers both. ++let test_message_transformation = function() { ++ let conv = new Conversation(); ++ conv.sendMsg = function(aMsg) { ++ this.writeMessage("user", aMsg, {outgoing: true}); ++ }; ++ ++ let uiConv = new imConversations.UIConversation(conv); ++ let message = "Hello!"; ++ let receivedMsg = false, newTxt = false; ++ ++ uiConv.addObserver({ ++ observe: function(aObject, aTopic, aMsg) { ++ switch(aTopic) { ++ case "sending-message": ++ ok(!newTxt); ++ ok(!receivedMsg); ++ ok(aObject instanceof imConversations.OutgoingMessage); ++ aObject.message = rot13(aObject.message); ++ break; ++ case "received-message": ++ ok(!newTxt); ++ ok(!receivedMsg); ++ ok(aObject.outgoing); ++ ok(aObject instanceof imConversations.imMessage); ++ equal(aObject.displayMessage, rot13(message)); ++ aObject.displayMessage = rot13(aObject.displayMessage); ++ receivedMsg = true; ++ break; ++ case "new-text": ++ ok(!newTxt); ++ ok(receivedMsg); ++ ok(aObject.outgoing); ++ ok(aObject instanceof imConversations.imMessage); ++ equal(aObject.displayMessage, message); ++ newTxt = true; ++ break; ++ } ++ } ++ }); ++ uiConv.sendMsg(message); ++ ++ ok(newTxt); ++}; ++ ++// A test that cancels a message before it can be sent. ++let test_cancel_send_message = function() { ++ let conv = new Conversation(); ++ conv.sendMsg = function(aMsg) { ++ ok(false); ++ }; ++ ++ let uiConv = new imConversations.UIConversation(conv); ++ uiConv.addObserver({ ++ observe: function(aObject, aTopic, aMsg) { ++ if (aTopic === "sending-message") { ++ ok(aObject instanceof imConversations.OutgoingMessage); ++ aObject.cancelled = true; ++ } ++ } ++ }); ++ uiConv.sendMsg("Hi!"); ++}; ++ ++// A test that cancels a message before it gets displayed. ++let test_cancel_display_message = function() { ++ let conv = new Conversation(); ++ conv.sendMsg = function(aMsg) { ++ this.writeMessage("user", aMsg, {outgoing: true}); ++ }; ++ ++ let uiConv = new imConversations.UIConversation(conv); ++ uiConv.addObserver({ ++ observe: function(aObject, aTopic, aMsg) { ++ switch(aTopic) { ++ case "received-message": ++ ok(aObject instanceof imConversations.imMessage); ++ aObject.cancelled = true; ++ break; ++ case "new-text": ++ ok(false); ++ break; ++ } ++ } ++ }); ++ uiConv.sendMsg("Hi!"); ++}; ++ ++// A test that ensures protocols get a chance to prepare a message before ++// sending and displaying. ++let test_prpl_message_prep = function() { ++ let conv = new Conversation(); ++ conv.sendMsg = function(aMsg) { ++ this.writeMessage("user", aMsg, {outgoing: true}); ++ }; ++ ++ let msg = "Hi!"; ++ let prefix = "test> "; ++ ++ let prepared = false; ++ conv.prepareForSending = function(aMsg) { ++ ok(aMsg instanceof imConversations.OutgoingMessage); ++ equal(aMsg.message, msg); ++ aMsg.message = prefix + aMsg.message; ++ prepared = true; ++ }; ++ ++ conv.prepareForDisplaying = function(aMsg) { ++ ok(aMsg instanceof imConversations.imMessage); ++ equal(aMsg.displayMessage, prefix + msg); ++ aMsg.displayMessage = aMsg.displayMessage.slice(prefix.length); ++ }; ++ ++ let receivedMsg = false; ++ let uiConv = new imConversations.UIConversation(conv); ++ uiConv.addObserver({ ++ observe: function(aObject, aTopic, aMsg) { ++ if (aTopic === "new-text") { ++ equal(aObject.displayMessage, msg); ++ receivedMsg = true; ++ } ++ } ++ }); ++ uiConv.sendMsg(msg); ++ ok(prepared); ++ ok(receivedMsg); ++}; ++ ++// A test that ensures protocols can split messages before they are sent. ++let test_split_message_before_sending = function() { ++ let msgCount = 0; ++ let prepared = false; ++ ++ let conv = new Conversation(); ++ conv.sendMsg = function(aMsg) { ++ ++msgCount; ++ }; ++ conv.prepareForSending = function(aMsg) { ++ ok(aMsg instanceof imConversations.OutgoingMessage); ++ prepared = true; ++ return aMsg.message.split("\n"); ++ }; ++ ++ let uiConv = new imConversations.UIConversation(conv); ++ uiConv.sendMsg("This is a looo\nooong message."); ++ ++ ok(prepared); ++ equal(msgCount, 2); ++}; ++ ++function run_test() { ++ test_null_message(); ++ test_message_transformation(); ++ test_cancel_send_message(); ++ test_cancel_display_message(); ++ test_prpl_message_prep(); ++ test_split_message_before_sending(); ++ run_next_test(); ++} +diff --git a/chat/components/src/test/xpcshell.ini b/chat/components/src/test/xpcshell.ini +--- a/chat/components/src/test/xpcshell.ini ++++ b/chat/components/src/test/xpcshell.ini +@@ -3,9 +3,10 @@ + ; file, You can obtain one at http://mozilla.org/MPL/2.0/. + + [DEFAULT] + head = + tail = + + [test_accounts.js] + [test_commands.js] ++[test_conversations.js] + [test_logger.js]