Bug 1193643 - Allow requestBuddyInfo to return cached/available data immediately to speed up tooltips. r=clokep
authoraleth <aleth@instantbird.org>
Sat, 03 Oct 2015 00:04:34 +0200
changeset 18475 f1c3d32341ddb64e95b1a5b8e285f97497dfa652
parent 18474 601943b5a517a4847936ce60848e73bfabd14a62
child 18476 34aa79c759051f4a26485e10364d04d1577e40c2
push id11308
push useraleth@instantbird.org
push dateFri, 02 Oct 2015 22:40:38 +0000
treeherdercomm-central@258f0a389e63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersclokep
bugs1193643
Bug 1193643 - Allow requestBuddyInfo to return cached/available data immediately to speed up tooltips. r=clokep
chat/components/public/imIAccount.idl
chat/content/imtooltip.xml
chat/protocols/irc/irc.js
chat/protocols/irc/ircBase.jsm
chat/protocols/irc/ircCommands.jsm
chat/protocols/irc/ircWatchMonitor.jsm
chat/protocols/xmpp/xmpp.jsm
--- a/chat/components/public/imIAccount.idl
+++ b/chat/components/public/imIAccount.idl
@@ -100,20 +100,22 @@ interface prplIAccount: nsISupports {
 
   // Used when the user wants to add a buddy to the buddy list
   void addBuddy(in imITag aTag, in AUTF8String aName);
 
   // Used while loading the buddy list at startup.
   prplIAccountBuddy loadBuddy(in imIBuddy aBuddy, in imITag aTag);
 
   /* Request more info on a buddy (typically a chat buddy).
-   * The result (if any) will be provided by a user-info-received
-   * notification dispatched through the observer service:
+   * The result (if any) will be provided by user-info-received
+   * notifications dispatched through the observer service:
    *  - aSubject will be an nsISimpleEnumerator of prplITooltipInfo.
    *  - aData will be aBuddyName.
+   * If multiple user-info-received are sent, subsequent notifications
+   * will update any previous data.
    */
   void requestBuddyInfo(in AUTF8String aBuddyName);
 
   readonly attribute boolean canJoinChat;
   nsISimpleEnumerator getChatRoomFields();
   prplIChatRoomFieldValues getChatRoomDefaultFieldValues([optional] in AUTF8String aDefaultChatName);
   /* Request information on available chat rooms, to be returned via the
    * callback.
--- a/chat/content/imtooltip.xml
+++ b/chat/content/imtooltip.xml
@@ -151,29 +151,39 @@
        </body>
      </method>
 
      <method name="addRow">
        <parameter name="aLabel"/>
        <parameter name="aValue"/>
        <body>
        <![CDATA[
-         const XULNS =
-           "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-         var row = document.createElementNS(XULNS, "row");
-         var label = document.createElementNS(XULNS, "label");
-         label.className = "header";
-         label.setAttribute("value", aLabel);
-         row.appendChild(label);
-         label = document.createElementNS(XULNS, "description");
-         label.textContent = aValue;
-         row.appendChild(label);
-         row.setAttribute("align", "baseline");
-         this.rows.appendChild(row);
+         let description;
+         let row = [...this.rows.querySelectorAll("row")].find(row => {
+           return row.querySelector("label").getAttribute("value") == aLabel;
+         });
+         if (!row) {
+           // Create a new row for this label.
+           const XULNS =
+             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+           row = document.createElementNS(XULNS, "row");
+           let label = document.createElementNS(XULNS, "label");
+           label.className = "header";
+           label.setAttribute("value", aLabel);
+           row.appendChild(label);
+           description = document.createElementNS(XULNS, "description");
+           row.appendChild(description);
+           row.setAttribute("align", "baseline");
+           this.rows.appendChild(row);
+         }
+         else {
+           // Row with this label already exists - just update.
+           description = row.querySelector("description");
+         }
+         description.textContent = aValue;
        ]]>
        </body>
      </method>
 
      <method name="addSeparator">
        <body>
        <![CDATA[
          const XULNS =
@@ -230,23 +240,23 @@
            this.addRow(this.bundle.GetStringFromName("buddy.username"), name);
 
          this.addRow(this.bundle.GetStringFromName("buddy.account"), account.name);
 
          this.requestBuddyInfo(account, aBuddy.normalizedName);
 
          var tooltipInfo = aBuddy.getTooltipInfo();
          if (tooltipInfo)
-           this.appendTooltipInfo(tooltipInfo);
+           this.updateTooltipInfo(tooltipInfo);
          return true;
        ]]>
        </body>
      </method>
 
-     <method name="appendTooltipInfo">
+     <method name="updateTooltipInfo">
        <parameter name="aTooltipInfo"/>
        <body>
        <![CDATA[
          while (aTooltipInfo.hasMoreElements()) {
            var elt =
              aTooltipInfo.getNext().QueryInterface(Ci.prplITooltipInfo);
            switch (elt.type) {
              case Ci.prplITooltipInfo.pair:
@@ -383,19 +393,17 @@
            this.updateTooltipFromBuddy(this.buddy);
          else if (aTopic == "contact-preferred-buddy-changed" &&
                   aSubject.id == this.contact.id) {
            let buddy = this.contact.preferredBuddy;
            this.updateTooltipFromBuddy(buddy.preferredAccountBuddy);
          }
          else if (aTopic == "user-info-received" &&
                   aData == this.observedUserInfo) {
-           this.appendTooltipInfo(aSubject.QueryInterface(Ci.nsISimpleEnumerator));
-           Services.obs.removeObserver(this, "user-info-received");
-           delete this.observedUserInfo;
+           this.updateTooltipInfo(aSubject.QueryInterface(Ci.nsISimpleEnumerator));
          }
        ]]>
        </body>
      </method>
     </implementation>
     <handlers>
      <handler event="popupshowing">
        <![CDATA[
--- a/chat/protocols/irc/irc.js
+++ b/chat/protocols/irc/irc.js
@@ -231,21 +231,21 @@ const GenericIRCConversation = {
   // message length.
   sendTyping: function(aString) {
     let longestLineLength =
       Math.max.apply(null, aString.split("\n").map(this._account.countBytes,
                                                    this._account));
     return this.getMaxMessageLength() - longestLineLength;
   },
 
-  requestBuddyInfo: function(aNick) {
+  requestCurrentWhois: function(aNick) {
     if (!this._observedNicks.length)
       Services.obs.addObserver(this, "user-info-received", false);
     this._observedNicks.push(this.normalizeNick(aNick));
-    this._account.requestBuddyInfo(aNick);
+    this._account.requestCurrentWhois(aNick);
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic != "user-info-received")
       return;
 
     let nick = this.normalizeNick(aData);
     let nickIndex = this._observedNicks.indexOf(nick);
@@ -589,17 +589,17 @@ function ircConversation(aAccount, aName
     aName = aAccount.whoisInformation.get(nick)["nick"];
 
   this._init(aAccount, aName);
   this._observedNicks = [];
 
   // Fetch correctly capitalized name.
   // Always request the info as it may be out of date.
   this._waitingForNick = true;
-  this.requestBuddyInfo(aName);
+  this.requestCurrentWhois(aName);
 }
 ircConversation.prototype = {
   __proto__: GenericConvIMPrototype,
   get buddy() { return this._account.buddies.get(this.name); },
 
   unInit: function() {
     this.unInitIRCConversation();
     GenericConvIMPrototype.unInit.call(this);
@@ -1076,23 +1076,40 @@ ircAccount.prototype = {
       executeSoon(() => this._handleCommandBuffer(aCommand));
     buffer.set(aParam, aKey);
   },
 
   // The whois information: nicks are used as keys and refer to a map of field
   // to value.
   whoisInformation: null,
   // Request WHOIS information on a buddy when the user requests more
-  // information.
+  // information. If we already have some WHOIS information stored for this
+  // nick, a notification with this (potentially out-of-date) information
+  // is sent out immediately. It is followed by another notification when
+  // the current WHOIS data is returned by the server.
+  // If you are only interested in the current WHOIS, requestCurrentWhois
+  // should be used instead.
   requestBuddyInfo: function(aBuddyName) {
     if (!this.connected)
       return;
 
-    this.removeBuddyInfo(aBuddyName);
-    this.sendBufferedCommand("WHOIS", aBuddyName);
+    // Return what we have stored immediately.
+    if (this.whoisInformation.has(aBuddyName))
+      this.notifyWhois(aBuddyName);
+
+    // Request the current whois and update.
+    this.requestCurrentWhois(aBuddyName);
+  },
+  // Request fresh WHOIS information on a nick.
+  requestCurrentWhois: function(aNick) {
+    if (!this.connected)
+      return;
+
+    this.removeBuddyInfo(aNick);
+    this.sendBufferedCommand("WHOIS", aNick);
   },
   notifyWhois: function(aNick) {
     Services.obs.notifyObservers(this.getBuddyInfo(aNick), "user-info-received",
                                  this.normalizeNick(aNick));
   },
   // Request WHOWAS information on a buddy when the user requests more
   // information.
   requestOfflineBuddyInfo: function(aBuddyName) {
--- a/chat/protocols/irc/ircBase.jsm
+++ b/chat/protocols/irc/ircBase.jsm
@@ -369,17 +369,17 @@ var ircBase = {
       this._modes = new Set();
       this._userModeReceived = false;
 
       // Check if our nick has changed.
       if (aMessage.params[0] != this._nickname)
         this.changeBuddyNick(this._nickname, aMessage.params[0]);
 
       // Request our own whois entry so we can set the prefix.
-      this.requestBuddyInfo(this._nickname);
+      this.requestCurrentWhois(this._nickname);
 
       // If our status is Unavailable, tell the server.
       if (this.imAccount.statusInfo.statusType < Ci.imIStatusInfo.STATUS_AVAILABLE)
         this.observe(null, "status-changed");
 
       // Check if any of our buddies are online!
       const kInitialIsOnDelay = 1000;
       this._isOnTimer = setTimeout(this.sendIsOn.bind(this), kInitialIsOnDelay);
--- a/chat/protocols/irc/ircCommands.jsm
+++ b/chat/protocols/irc/ircCommands.jsm
@@ -503,13 +503,13 @@ var commands = [
         return false;
       // If the user does not provide a nick, but is in a private conversation,
       // assume the user is trying to whois the person they are talking to.
       if (!aMsg) {
         if (aConv.isChat)
           return false;
         aMsg = aConv.name;
       }
-      getConv(aConv).requestBuddyInfo(aMsg);
+      getConv(aConv).requestCurrentWhois(aMsg);
       return true;
     }
   }
 ];
--- a/chat/protocols/irc/ircWatchMonitor.jsm
+++ b/chat/protocols/irc/ircWatchMonitor.jsm
@@ -22,17 +22,17 @@ Cu.import("resource:///modules/ircHandle
 Cu.import("resource:///modules/ircUtils.jsm");
 
 function setStatus(aAccount, aNick, aStatus) {
   if (!aAccount.watchEnabled && !aAccount.monitorEnabled)
     return false;
 
   if (aStatus == "AWAY") {
     // We need to request the away message.
-    aAccount.requestBuddyInfo(aNick);
+    aAccount.requestCurrentWhois(aNick);
   }
   else {
     // Clear the WHOIS information.
     aAccount.removeBuddyInfo(aNick);
   }
 
   let buddy = aAccount.buddies.get(aNick);
   if (!buddy)
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -1277,16 +1277,18 @@ const XMPPAccountPrototype = {
           userName = participant.accountJid;
         if (!muc.left) {
           let statusType = participant.statusType;
           let statusText = participant.statusText;
           tooltipInfo.push(new TooltipInfo(statusType, statusText, true));
         }
       }
     }
+    Services.obs.notifyObservers(new nsSimpleEnumerator(tooltipInfo),
+                                 "user-info-received", aJid);
 
     let iq = Stanza.iq("get", null, aJid, Stanza.node("vCard", Stanza.NS.vcard));
     this.sendStanza(iq, aStanza => {
       let vCardInfo = {};
       let vCardNode = aStanza.getElement(["vCard"]);
 
       // In the case of an error response, we just notify the observers with
       // what info we already have.
@@ -1299,16 +1301,17 @@ const XMPPAccountPrototype = {
       if (userName)
         vCardInfo.userName = userName;
 
       // vCard fields we want to display in the tooltip.
       const kTooltipFields = ["userName", "fullName", "nickname", "title",
                               "organization", "email", "birthday", "locality",
                               "country"];
 
+      let tooltipInfo = [];
       for (let field of kTooltipFields) {
         if (vCardInfo.hasOwnProperty(field))
           tooltipInfo.push(new TooltipInfo(_("tooltip." + field), vCardInfo[field]));
       }
       Services.obs.notifyObservers(new nsSimpleEnumerator(tooltipInfo),
                                    "user-info-received", aJid);
     });
   },