chat/content/imtooltip.xml
author Rob Lemley <rob@thunderbird.net>
Mon, 26 Aug 2019 21:20:54 -0400
changeset 35928 0bd1a3c3bee51d0643e1f3e9511876d27df182c1
parent 33059 56312084fa4a75f0b80a4f645b947a7f4328910b
permissions -rw-r--r--
Bug 1507754 - Check source repositories and changesets during configure. r=darktrojan a=jorgk PACKAGERS: if you update application.ini, platform.ini, or source-repo.h in some way during your process, you might need to change something. Make sure that the source repositories for both Mozilla and Comm can be found during mach configure and abort if they cannot. For Taskcluster builds, there are various environment variables that can be relied upon. Local builds present a challenge. Chances are those variables are not set. I came up with a set of checks and keep trying until something works. For comm-* code: - Look for MOZ_SOURCE_REPO and MOZ_SOURCE_CHANGESET environment vars. This is counter-intuitive, but it's the current status-quo for Taskcluster builds. Those variables are set to the comm values. - Next, try use the Mercurial source checkout itself. Uses the same technique as Mozilla code does in build/variables.py. - Last, try to use a file named "sourcestamp.txt". That file is part of our source tar files that get built for releases. - Finally, if those MOZ_SOURCE environment variables were not set, set them. This is needed because old-configure will look for them and set buildconfig variables with them when it runs later during the configure process. - Additionally, set MOZ_COMM_SOURCE_REPO and MOZ_COMM_SOURCE_CHANGESET in buildconfig. Code in the comm- tree should prefer those values over the generic MOZ_SOURCE_* values that the Mozilla code will look at. For the Gecko/Mozilla source repository information, it's almost the same process. - Check for GECKO_SOURCE_REPO and GECKO_SOURCE_REV environment variables first. Taskcluster sets these based on comm/.gecko_rev.yml. - Next, try comm/.gecko_rev.yml itself. PyYAML is not required as the file is pretty simple to parse. Release builds are pinned to a specific revision hash, so we can use that. Builds from comm-central pin to "default" though, so next try running "hg id" in $topsrcdir to get the revision hash. - If for some reason there's no .gecko_rev.yml and it's not a Mercurial checkout, try the sourcestamp.txt file. - Set MOZ_GECKO_SOURCE_REPO and MOZ_GECKO_SOURCE_CHANGESET in buildconfig. mach configure should fail if any one of those values cannot be determined. The error message will suggest setting the environment variables; ideally that is not necessary.

<?xml version="1.0"?>
<!-- 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/. -->

<!DOCTYPE bindings>

<bindings id="imTooltipBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="tooltip" extends="chrome://global/content/bindings/popup.xml#panel">
    <content noautohide="true" orient="vertical">
      <children>
        <xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
      </children>
      <xul:vbox id="largeTooltip">
        <xul:hbox align="start" crop="end" flex="1">
          <xul:vbox flex="1">
            <xul:stack>
              <!-- The box around the user icon is a workaround for bug 955673. -->
              <xul:box class="userIconHolder" xbl:inherits="userIcon">
                <xul:image class="userIcon" anonid="userIcon"
                           xbl:inherits="src=userIcon"/>
              </xul:box>
              <xul:image class="statusTypeIcon" xbl:inherits="status,left"/>
            </xul:stack>
            <xul:spacer flex="1"/>
          </xul:vbox>
          <xul:stack class="displayNameMessageBox" flex="1">
            <xul:vbox flex="1">
              <xul:hbox align="start" flex="1">
                <xul:description class="tooltipDisplayName" flex="1" crop="end"
                                 xbl:inherits="value=displayname"/>
                <xul:image class="tooltipProtoIcon"
                           xbl:inherits="src=iconPrpl,status"/>
              </xul:hbox>
              <xul:spacer flex="1"/>
            </xul:vbox>
            <xul:description class="tooltipMessage" anonid="tooltipMessage"
                             xbl:inherits="noTopic"/>
          </xul:stack>
        </xul:hbox>
        <xul:grid>
          <xul:columns>
            <xul:column/>
            <xul:column flex="1"/>
          </xul:columns>
          <xul:rows class="tooltipRows" anonid="tooltiprows"/>
        </xul:grid>
        <xul:vbox class="tooltipBuddies" anonid="buddies"/>
      </xul:vbox>
    </content>
    <implementation implements="nsIObserver">
     <property name="bundle">
       <getter>
         <![CDATA[
          if (!this._bundle) {
            this._bundle =
              Services.strings.createBundle("chrome://chat/locale/imtooltip.properties");
          }
          return this._bundle;
         ]]>
       </getter>
     </property>

     <field name="_buddy">null</field>
     <property name="buddy" onget="return this._buddy;">
       <setter>
         <![CDATA[
           if (val == this._buddy)
             return val;

           if (!val)
             this._buddy.buddy.removeObserver(this);
           else
             val.buddy.addObserver(this);

           return (this._buddy = val);
         ]]>
       </setter>
     </property>

     <field name="_contact">null</field>
     <property name="contact" onget="return this._contact;">
       <setter>
         <![CDATA[
           if (val == this._contact)
             return val;

           if (!val)
             this._contact.removeObserver(this);
           else
             val.addObserver(this);

           return (this._contact = val);
         ]]>
       </setter>
     </property>

     <property name="rows">
       <getter>
         <![CDATA[
           if (!("_rows" in this)) {
             this._rows =
               document.getAnonymousElementByAttribute(this, "anonid",
                                                       "tooltiprows");
           }
           return this._rows;
         ]]>
       </getter>
     </property>

     <method name="setBuddyIcon">
       <parameter name="aSrc"/>
       <body>
       <![CDATA[
         var img = document.getAnonymousElementByAttribute(this, "anonid",
                                                           "userIcon");
         if (aSrc)
           this.setAttribute("userIcon", aSrc);
         else
           this.removeAttribute("userIcon");
       ]]>
       </body>
     </method>

     <method name="setMessage">
       <parameter name="aMessage"/>
       <body>
       <![CDATA[
         // Setting the textContent directly allows text wrapping.
         var msg = document.getAnonymousElementByAttribute(this, "anonid",
                                                           "tooltipMessage");
         msg.textContent = aMessage;
       ]]>
       </body>
     </method>

     <method name="reset">
       <body>
       <![CDATA[
         while (this.rows.hasChildNodes())
           this.rows.lastChild.remove();
       ]]>
       </body>
     </method>

     <method name="addRow">
       <parameter name="aLabel"/>
       <parameter name="aValue"/>
       <body>
       <![CDATA[
         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.
           row = document.createXULElement("row");
           let label = document.createXULElement("label");
           label.className = "header";
           label.setAttribute("value", aLabel);
           row.appendChild(label);
           description = document.createXULElement("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[
         var row = document.createXULElement("row");
         var separator = document.createXULElement("separator");
         separator.className = "thin";
         row.appendChild(separator);
         this.rows.appendChild(row);
       ]]>
       </body>
     </method>

     <method name="requestBuddyInfo">
       <parameter name="aAccount"/>
       <parameter name="aObservedName"/>
       <body>
       <![CDATA[
         // Libpurple prpls don't necessarily return data in response to
         // requestBuddyInfo that is suitable for displaying inside a
         // tooltip (e.g. too many objects, or <img> and <a> tags),
         // so we only use it for JS prpls.
         // This is a terrible, terrible hack to work around the fact that
         // ClassInfo.implementationLanguage has gone.
         if (!aAccount.prplAccount.wrappedJSObject)
           return;
         this.observedUserInfo = aObservedName;
         Services.obs.addObserver(this, "user-info-received");
         aAccount.requestBuddyInfo(aObservedName);
       ]]>
       </body>
     </method>

     <method name="updateTooltipFromBuddy">
       <parameter name="aBuddy"/>
       <body>
       <![CDATA[
         this.buddy = aBuddy;

         this.reset();
         let name = aBuddy.userName;
         let displayName = aBuddy.displayName;
         this.setAttribute("displayname", displayName);
         let account = aBuddy.account;
         this.setAttribute("iconPrpl", account.protocol.iconBaseURI + "icon.png");
         this.setBuddyIcon(aBuddy.buddyIconFilename);

         let statusType = aBuddy.statusType;
         this.setAttribute("status", Status.toAttribute(statusType));
         this.setMessage(Status.toLabel(statusType, aBuddy.statusText));

         if (displayName != name)
           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.updateTooltipInfo(tooltipInfo);
         return true;
       ]]>
       </body>
     </method>

     <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:
             case Ci.prplITooltipInfo.sectionHeader:
               this.addRow(elt.label, elt.value);
               break;
             case Ci.prplITooltipInfo.sectionBreak:
               this.addSeparator();
               break;
             case Ci.prplITooltipInfo.status:
               let statusType = parseInt(elt.label);
               this.setAttribute("status", Status.toAttribute(statusType));
               this.setMessage(Status.toLabel(statusType, elt.value));
               break;
             case Ci.prplITooltipInfo.icon:
               this.setBuddyIcon(elt.value);
               break;
           }
         }
       ]]>
       </body>
     </method>

     <method name="updateTooltipFromContact">
       <parameter name="aContact"/>
       <body>
       <![CDATA[
         this.contact = aContact;

         this.updateTooltipFromBuddy(aContact.preferredBuddy.preferredAccountBuddy);

         this.addRow(this.bundle.GetStringFromName("contact.tags"),
                     aContact.getTags().map(t => t.name).join(", "));

         let buddies = this.contact.getBuddies();
         if (buddies.length <= 1)
           return true;

         let buddiesElt = document.getAnonymousElementByAttribute(this, "anonid",
                                                                  "buddies");
         let sep = document.createXULElement("separator");
         sep.setAttribute("class", "groove tooltipSeparator");
         buddiesElt.appendChild(sep);

         for (let buddy of buddies) {
           let buddyElt = document.createXULElement("buddy");
           buddiesElt.appendChild(buddyElt);
           buddyElt.getBoundingClientRect(); // force binding attachment
           buddyElt.build(buddy, this);
         }

         return true;
       ]]>
       </body>
     </method>

     <method name="updateTooltipFromConversation">
       <parameter name="aConv"/>
       <body>
       <![CDATA[
         if (!aConv.isChat && aConv.buddy)
           return this.updateTooltipFromBuddy(aConv.buddy);

         this.reset();
         this.setAttribute("displayname", aConv.name);
         let account = aConv.account;
         this.setAttribute("iconPrpl", account.protocol.iconBaseURI + "icon.png");
         this.setBuddyIcon(null);
         if (aConv.isChat) {
           this.setAttribute("status", "chat");
           if (!aConv.account.connected || aConv.left)
             this.setAttribute("left", true);
           let topic = aConv.topic;
           if (!topic) {
             this.setAttribute("noTopic", true);
             topic = aConv.noTopicString;
           }
           this.setMessage(topic);
         }
         else {
           this.setAttribute("status", "unknown");
           this.setMessage(Status.toLabel("unknown"));
           // Last ditch attempt to get some tooltip info. This call relies on
           // the account's requestBuddyInfo implementation working correctly
           // with aConv.normalizedName.
           this.requestBuddyInfo(account, aConv.normalizedName);
         }
         this.addRow(this.bundle.GetStringFromName("buddy.account"), account.name);
         return true;
       ]]>
       </body>
     </method>

     <method name="updateTooltipFromParticipant">
       <parameter name="aNick"/>
       <parameter name="aConv"/>
       <parameter name="aParticipant"/>
       <body>
       <![CDATA[
         if (!aConv.target)
           return false;     // We're viewing a log.
         if (!aParticipant)
           aParticipant = aConv.target.getParticipant(aNick);

         let account = aConv.account;
         let normalizedNick = aConv.target.getNormalizedChatBuddyName(aNick);
         // To try to ensure that we aren't misidentifying a nick with a
         // contact, we require at least that the normalizedChatBuddyName of
         // the nick is normalized like a normalizedName for contacts.
         if (normalizedNick == account.normalize(normalizedNick)) {
           let accountBuddy =
             Services.contacts.getAccountBuddyByNameAndAccount(normalizedNick,
                                                               account);
           if (accountBuddy)
             return this.updateTooltipFromBuddy(accountBuddy);
         }

         this.reset();
         this.setAttribute("displayname", aNick);
         this.setAttribute("iconPrpl",
                           account.protocol.iconBaseURI + "icon.png");
         this.setAttribute("status", "unknown");
         this.setMessage(Status.toLabel("unknown"));
         this.setBuddyIcon(aParticipant ? aParticipant.buddyIconFilename : null);
         this.requestBuddyInfo(account, normalizedNick);
         return true;
       ]]>
       </body>
     </method>

     <!-- nsIObserver implementation -->
     <method name="observe">
       <parameter name="aSubject"/>
       <parameter name="aTopic"/>
       <parameter name="aData"/>
       <body>
       <![CDATA[
         if (aSubject == this.buddy &&
             (aTopic == "account-buddy-status-changed" ||
              aTopic == "account-buddy-status-detail-changed" ||
              aTopic == "account-buddy-display-name-changed" ||
              aTopic == "account-buddy-icon-changed"))
           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.updateTooltipInfo(aSubject.QueryInterface(Ci.nsISimpleEnumerator));
         }
       ]]>
       </body>
     </method>
    </implementation>
    <handlers>
     <handler event="popupshowing">
       <![CDATA[
         // No tooltip above the context menu.
         if (document.popupNode)
           return false;

         let elt = document.tooltipNode;
         // No tooltip for elements that have already been removed.
         if (!elt.parentNode)
           return false;

         // Reset tooltip.
         let largeTooltip = document.getAnonymousElementByAttribute(this, "id",
                                                                    "largeTooltip");
         largeTooltip.hidden = false;
         this.removeAttribute("label");

         let localName = elt.localName;

#ifdef MOZ_THUNDERBIRD
         if (localName == "imconv" && elt.conv)
           return updateTooltipFromConversation(elt.conv);

         if (localName == "imcontact")
           return updateTooltipFromBuddy(elt.contact.preferredBuddy.preferredAccountBuddy);

         if (localName == "richlistitem") {
           let contactlistbox = document.getElementById("contactlistbox");
           let conv = contactlistbox.selectedItem.conv;
           return updateTooltipFromParticipant(elt.chatBuddy.name, conv,
                                               elt.chatBuddy);
         }
#else
         if (localName == "tab") {
           if (!elt.linkedConversation || !elt.linkedConversation.conv)
             return false;
           return updateTooltipFromConversation(elt.linkedConversation.conv);
         }

         if (localName == "conv")
           return updateTooltipFromConversation(elt.conv);

         if (localName == "buddy")
           return updateTooltipFromBuddy(elt.buddy.preferredAccountBuddy);

         if (localName == "contact")
           return updateTooltipFromContact(elt.contact);

         if (localName == "richlistitem") {
           let conv = document.getBindingParent(elt).conv;
           return updateTooltipFromParticipant(elt.chatBuddy.name, conv,
                                               elt.chatBuddy);
         }
#endif

         let classList = elt.classList;
         if (classList.contains("ib-nick") ||
             classList.contains("ib-sender") ||
             classList.contains("ib-person")) {
           let conv = getBrowser()._conv;
           if (conv.isChat)
             return updateTooltipFromParticipant(elt.textContent, conv);
           else if (elt.textContent == conv.name)
             return updateTooltipFromConversation(conv);
         }

         // Are we over a message?
         for (let node = elt; node; node = node.parentNode) {
           if (!node._originalMsg)
             continue;
           // It's a message, so add a date/time tooltip.
           let date = new Date(node._originalMsg.time * 1000);
           let text;
           if ((new Date()).toDateString() == date.toDateString()) {
             const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
               timeStyle: "medium"
             });
             text = dateTimeFormatter.format(date);
           }
           else {
             const dateTimeFormatter = new Services.intl.DateTimeFormat(undefined, {
               dateStyle: "short", timeStyle: "medium"
             });
             text = dateTimeFormatter.format(date);
           }
           // Setting the attribute on this node means that if the element
           // we are pointing at carries a title set by the prpl,
           // that title won't be overridden.
           node.setAttribute("title", text);
           break;
         }

         // Use the default content tooltip.
         largeTooltip.hidden = true;
         return false;
       ]]>
     </handler>
     <handler event="popuphiding">
       <![CDATA[
       this.buddy = null;
       this.contact = null;
       this.removeAttribute("noTopic");
       this.removeAttribute("left");
       if ("observedUserInfo" in this && this.observedUserInfo) {
         Services.obs.removeObserver(this, "user-info-received");
         delete this.observedUserInfo;
       }

       let buddies = document.getAnonymousElementByAttribute(this, "anonid",
                                                             "buddies");
       while (buddies.hasChildNodes())
         buddies.lastChild.remove();

       // Since Mozilla 22, tooltips don't seem to shrink automatically after
       // being shown once at a large size. Not sure if it's temporary or not.
       this.removeAttribute("width");
       this.removeAttribute("height");
       ]]>
     </handler>
    </handlers>
  </binding>
</bindings>