suite/mailnews/mailWidgets.xml
author Jorg K <jorgk@jorgk.com>
Wed, 19 Sep 2018 10:25:00 +0200
changeset 33196 5fca7138f5f682d588e3a7784673e9214dcf8e26
parent 31486 8853a3eec03cab09359883f74c1659aaf9241035
child 34022 2ef791acbdef73c9f28161a091010613fec526dc
permissions -rw-r--r--
Bug 1492522 - Port Bug 1465219: Replace MenuBoxObject with XULElement subclass in suite/. r=frg

<?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/. -->


<bindings   id="mailBindings"
            xmlns="http://www.mozilla.org/xbl"
            xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
            xmlns:nc="http://home.netscape.com/NC-rdf#"
            xmlns:xbl="http://www.mozilla.org/xbl">

  <!-- dummy widget to force this file to load -->
  <binding id="dummy" extends="xul:box"/>

  <!-- temporary holding place for horizontal list -->

  <binding id="extdescription" extends="chrome://global/content/bindings/listbox.xml#listbox-base">
    <implementation>
      <constructor><![CDATA[
          this.children.filter(aChild => aChild.getAttribute("selected") == "true")
                       .forEach(this.selectedItems.append, this.selectedItems);
      ]]></constructor>

      <!-- ///////////////// public members ///////////////// -->

      <property name="itemCount" readonly="true"
                onget="return this.children.length;"/>

      <method name="getIndexOfItem">
        <parameter name="item"/>
        <body><![CDATA[
          return this.children.indexOf(item);
        ]]></body>
      </method>
      <method name="getItemAtIndex">
        <parameter name="index"/>
        <body><![CDATA[
          return this.children[index] || null;
        ]]></body>
      </method>
      <method name="getRowCount">
        <body><![CDATA[
          return this.children.length;
        ]]></body>
      </method>
      <method name="getNumberOfVisibleRows">
        <body><![CDATA[
          var firstItem = this.children[0] || null;
          if (!firstItem)
            return 0; // nothing to be visible
          var itemsPerRow = Math.floor(this.boxObject.width / firstItem.boxObject.width);
          var itemsPerCol = Math.floor(this.boxObject.height / firstItem.boxObject.height);
          return Math.max(itemsPerRow, 1) * Math.max(itemsPerCol, 1);
        ]]></body>
      </method>
      <method name="getIndexOfFirstVisibleRow">
        <body><![CDATA[
          //XXXzeniko unimplementable without a way to scroll
        ]]></body>
      </method>

      <method name="ensureIndexIsVisible">
        <parameter name="index"/>
        <body><![CDATA[
          this.ensureElementIsVisible(this.getItemAtIndex(index));
        ]]></body>
      </method>
      <method name="ensureElementIsVisible">
        <parameter name="item"/>
        <body><![CDATA[
          //XXXzeniko unimplementable without a way to scroll
        ]]></body>
      </method>
      <method name="scrollToIndex">
        <parameter name="index"/>
        <body><![CDATA[
          //XXXzeniko unimplementable without a way to scroll
        ]]></body>
      </method>

      <method name="appendItem">
        <parameter name="label"/>
        <parameter name="value"/>
        <body><![CDATA[
          // -1 appends due to the way getItemAtIndex is implemented
          return this.insertItemAt(-1, label, value);
        ]]></body>
      </method>
      <method name="insertItemAt">
        <parameter name="index"/>
        <parameter name="label"/>
        <parameter name="value"/>
        <body><![CDATA[
          const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var item = document.createElementNS(XULNS, "descriptionitem");
          item.setAttribute("label", label);
          this.insertBefore(item, this.getItemAtIndex(index));
          return item;
        ]]></body>
      </method>

      <method name="scrollOnePage">
        <parameter name="direction"/>
        <body><![CDATA[
          return direction * this.getNumberOfVisibleRows();
        ]]></body>
      </method>

      <!-- ///////////////// private members ///////////////// -->

      <property name="children" readonly="true"
                onget="return Array.from(this.getElementsByTagName('descriptionitem'));"/>

      <method name="_fireOnSelect">
        <body><![CDATA[
          if (!this._suppressOnSelect && !this.suppressOnSelect) {
            this.dispatchEvent(new Event("select",
              { bubbles: false, cancelable: true }));
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_LEFT" modifiers="control shift any"
               action="this.moveByOffset(-1, !event.ctrlKey, event.shiftKey);"
               phase="target" preventdefault="true"/>
      <handler event="keypress" keycode="VK_RIGHT" modifiers="control shift any"
               action="this.moveByOffset(1, !event.ctrlKey, event.shiftKey);"
               phase="target" preventdefault="true"/>
      <handler event="click" button="0" phase="target"><![CDATA[
        if (this.selType != "multiple" || (!event.ctrlKey && !event.shiftKey && !event.metaKey))
          this.clearSelection();
      ]]></handler>
      <!-- make sure we keep the focus... -->
      <handler event="mousedown" button="0"
               action="if (document.commandDispatcher.focusedElement != this) this.focus();"/>
    </handlers>
  </binding>

  <binding id="descriptionitem" extends="chrome://global/content/bindings/listbox.xml#listitem">
    <content>
      <xul:hbox class="attachmentBox" xbl:inherits="orient" align="start">
        <xul:label class="descriptioncell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled,context" flex="1" dir="ltr" crop="center"/>
      </xul:hbox>
    </content>
  </binding>

  <binding id="descriptionitem-iconic"  extends="chrome://global/content/bindings/listbox.xml#listitem">
    <content>
      <xul:hbox class="attachmentBox" xbl:inherits="orient" align="center">
        <xul:image class="descriptioncell-icon" xbl:inherits="src=image"/>
        <xul:label class="descriptioncell-label" xbl:inherits="value=label,flex=flexlabel,crop,disabled,context" flex="1" dir="ltr" crop="center"/>
      </xul:hbox>
    </content>
  </binding>

  <!-- Message Pane Widgets -->

  <!-- mail-toggle-headerfield: Non-email addrs headers which have a toggle
       associated with them (i.e. the subject).
       Use label to set the header name.
       Use headerValue to set the header value. -->
  <binding id="mail-toggle-headerfield">
    <content>
      <xul:hbox class="headerNameBox" align="start">
        <xul:image class="expandHeaderViewButton" xbl:inherits="onclick=ontwistyclick"/>
        <xul:spacer flex="1"/>
        <xul:label class="headerName" xbl:inherits="value=label" control="headerValue"/>
      </xul:hbox>
      <xul:hbox class="headerValueBox" flex="1" align="start">
        <xul:textbox class="headerValue plain" anonid="headerValue" flex="1" readonly="true"/>
      </xul:hbox>
    </content>

    <implementation>
      <property name="headerValue" onset="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue').value = val;"/>
    </implementation>
  </binding>

  <!-- mail-headerfield: presents standard text header name & value pairs. Don't use this for email addresses.
       use label to set the header name.
       use headerValue to set the header value. -->
  <binding id="mail-headerfield">
    <content>
      <xul:hbox class="headerNameBox" align="start">
        <xul:label class="headerName" xbl:inherits="value=label" control="headerValue" flex="1"/>
      </xul:hbox>
      <xul:hbox class="headerValueBox" flex="1" align="start">
        <xul:textbox class="headerValue plain" anonid="headerValue" flex="1" readonly="true"/>
      </xul:hbox>
    </content>

    <implementation>
      <property name="headerValue" onset="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue').value = val;"/>
    </implementation>
  </binding>

  <binding id="mail-urlfield" extends="chrome://messenger/content/mailWidgets.xml#mail-headerfield">
    <content>
      <xul:hbox class="headerNameBox" align="start">
        <xul:label class="headerName" xbl:inherits="value=label" flex="1"/>
      </xul:hbox>
      <xul:hbox class="headerValueBox" flex="1" align="start">
        <xul:label onclick="if (event.button != 2) openUILink(event.target.value, event);"
                   ondragstart="this.parentNode.setDataTransfer(event);"
                   class="headerValue plain text-link headerValueUrl"
                 anonid="headerValue" flex="1" readonly="true" context="copyUrlPopup"/>
      </xul:hbox>
    </content>

    <implementation>
      <method name="setDataTransfer">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var dt = aEvent.dataTransfer;
          var val = aEvent.target.value;
          dt.setData('text/x-moz-url', val + "\n" + val);
          dt.setData('text/uri-list', val);
          dt.setData('text/plain', val);
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="mail-emailheaderfield">
    <content>
      <xul:hbox class="headerNameBox" align="start">
        <xul:label class="headerName" xbl:inherits="value=label" flex="1"/>
      </xul:hbox>
      <xul:hbox class="headerValueBox" flex="1" align="start">
        <xul:mail-emailaddress class="headerValue" anonid="emailAddressNode"/>
      </xul:hbox>
    </content>

    <implementation>
      <property name="emailAddressNode" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'emailAddressNode');"
        readonly="true"/>
    </implementation>
  </binding>

  <!-- multi-emailHeaderField: presents multiple emailheaderfields with a toggle -->
  <binding id="mail-multi-emailHeaderField">
    <content>
      <xul:hbox class="headerNameBox" align="start" pack="end">
        <xul:image class="addresstwisty" anonid="toggleIcon"
                   collapsed="true" onclick="toggleWrap();"/>
        <xul:label class="headerName" xbl:inherits="value=label"/>
      </xul:hbox>

      <xul:hbox class="headerValueBox" anonid="longEmailAddresses" flex="1" align="start"
                onoverflow="if (event.detail != 1) this.parentNode.toggleIcon.collapsed = false;"
                onunderflow="if (event.detail != 1) this.parentNode.toggleIcon.collapsed = true;">
        <xul:description class="headerValue" anonid="emailAddresses" flex="1"/>
      </xul:hbox>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
          this.mAddresses = new Array;
        ]]>
      </constructor>

      <field name="mAddresses"/>
      <!-- as a perf optimization we are going to keep a cache of email address nodes which we've
           created around for the lifetime of the widget. mSizeOfAddressCache controls how many of these
           elements we keep around -->
      <field name="mSizeOfAddressCache">3</field>

      <!-- addAddressView: a public method used to add an address to this widget.
           aAddresses is an object with 3 properties: displayName, emailAddress and fullAddress
      -->
      <method name="addAddressView">
        <parameter name="aAddress"/>
        <body>
          <![CDATA[
            this.mAddresses.push(aAddress);
          ]]>
        </body>
      </method>

      <!-- updateEmailAddressNode: private method used to set properties on an address node -->
      <method name="updateEmailAddressNode">
        <parameter name="aEmailNode"/>
        <parameter name="aAddress"/>
        <body>
          <![CDATA[
            if (aEmailNode.parentNode.useShortView && aAddress.displayName)
            {
              aEmailNode.setAttribute("label", aAddress.displayName);
              aEmailNode.setAttribute("tooltiptext", aAddress.fullAddress);
            }
            else
            {
              aEmailNode.setAttribute("label", aAddress.fullAddress || aAddress.displayName);
              aEmailNode.removeAttribute("tooltiptext");
            }
            aEmailNode.setAttribute("emailAddress", aAddress.emailAddress);
            aEmailNode.setAttribute("fullAddress", aAddress.fullAddress);
            aEmailNode.setAttribute("displayName", aAddress.displayName);

            // Add aria-label with header field type and header field content
            // for better accessibility.
            // Note: No extra colon and space needed, since it is
            // already provided by this object's label attribute.
            var ariaLabel = this.getAttribute("label") +
                            aEmailNode.getAttribute("label");
            aEmailNode.setAttribute("aria-label", ariaLabel);

            try
            {
              if ("UpdateEmailNodeDetails" in top)
                UpdateEmailNodeDetails(aAddress.emailAddress, aEmailNode);
            }
            catch(ex)
            {
              dump("UpdateEmailNodeDetails failed: " + ex + "\n");
            }
          ]]>
        </body>
      </method>

      <!-- fillCachedAddresses: private method used to fill up any cached pre-existing
           emailAddress fields without creating new email address fields. Returns a remainder
           for the # of addresses which require new addresses being created.
           Invariants: 1) aNumAddressesToShow >= 0 && it is <= mAddresses.length -->
      <method name="fillCachedAddresses">
        <parameter name="aAddressesNode"/>
        <parameter name="aNumAddressesToShow"/>
        <body>
          <![CDATA[
            var numExistingCachedAddresses = aAddressesNode.childNodes.length;
            if (!numExistingCachedAddresses)
              return this.mAddresses.length; // we couldn't pre fill anything
            else if (numExistingCachedAddresses > 1)
              numExistingCachedAddresses = (numExistingCachedAddresses + 1)/ 2;

            var index = 0;
            var numAddressesAdded = 0;
            var emailAddressNode;
            var commaNode;
            while (numAddressesAdded < numExistingCachedAddresses && numAddressesAdded < aNumAddressesToShow)
            {
              if (index && numExistingCachedAddresses > 1)
              {
                commaNode = aAddressesNode.childNodes[index++];
                if (commaNode)
                  commaNode.hidden = false;
              }

              // get the node pointed to by index
              emailAddressNode = aAddressesNode.childNodes[index++];
              this.updateEmailAddressNode(emailAddressNode, this.mAddresses[numAddressesAdded]);
              emailAddressNode.hidden = false;
              numAddressesAdded++;
            }

            // if we have added all of our elements but we still have more cached items in this address node
            // then make sure the extra cached copies are hidden...
            numExistingCachedAddresses = aAddressesNode.childNodes.length;  // reset
            while (index < numExistingCachedAddresses)
            {
              aAddressesNode.childNodes[index++].hidden = true;
            }

            return this.mAddresses.length - numAddressesAdded;
          ]]>
        </body>
      </method>

      <!-- fillAddressesNode: private method used to create email address nodes for either our short
           or long view. aAddressesNode: the div we want to add addresses too.
          aNumAddressesToShow: number of addresses to put into the list -->
      <method name="fillAddressesNode">
        <parameter name="aAddressesNode"/>
        <parameter name="aNumAddressesToShow"/>
        <body>
          <![CDATA[
            var numAddresses = this.mAddresses.length;
            if (aNumAddressesToShow <= 0 || aNumAddressesToShow > numAddresses)  // then show all
              aNumAddressesToShow = numAddresses;

            // before we try to create email address nodes, try to leverage any cached nodes...
            var remainder = this.fillCachedAddresses(aAddressesNode, aNumAddressesToShow);
            var index = numAddresses - remainder;
            while (index < numAddresses && index < aNumAddressesToShow)
            {
              var newAddressNode = document.createElement("mail-emailaddress");

              // Stash the headerName somewhere that UpdateEmailNodeDetails
              // will be able to find it.
              newAddressNode.setAttribute("headerName", this.headerName);

              if (index)
              {
                var textNode = document.createElement("text");
                textNode.setAttribute("value", ", ");
                textNode.setAttribute("class", "emailSeparator");
                aAddressesNode.appendChild(textNode);
              }

              var itemInDocument = aAddressesNode.appendChild(newAddressNode);
              this.updateEmailAddressNode(itemInDocument, this.mAddresses[index]);
              index++;
            }
          ]]>
        </body>
      </method>

      <property name="emailAddresses" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'emailAddresses');"
        readonly="true"/>
      <property name="longEmailAddresses" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'longEmailAddresses');"
        readonly="true"/>
      <property name="toggleIcon" onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'toggleIcon');"
        readonly="true"/>

      <!-- buildView: public method used by callers when they are done adding all the email addresses to the widget
           aNumAddressesToShow: total # of addresses to show in the short view -->
      <method name="buildViews">
        <body>
          <![CDATA[
            this.fillAddressesNode(this.emailAddresses, -1);
          ]]>
        </body>
      </method>

      <!-- Updates the nodes of this field with a call to
           UpdateExtraAddressProcessing. The parameters are optional fields
           that can contain extra information to be passed to
           UpdateExtraAddressProcessing, the implementation of that function
           should be checked to determine what it requires -->
      <method name="updateExtraAddressProcessing">
        <parameter name="aParam1"/>
        <parameter name="aParam2"/>
        <parameter name="aParam3"/>
        <body>
          <![CDATA[
            if (UpdateExtraAddressProcessing) {
              var childNodes = this.emailAddresses.childNodes;
              for (let i = 0; i < this.mAddresses.length; i++) {
                UpdateExtraAddressProcessing(this.mAddresses[i],
                                             childNodes[i * 2],
                                             aParam1, aParam2, aParam3);
              }
            }
          ]]>
        </body>
      </method>

      <method name="toggleWrap">
        <body>
          <![CDATA[
            if (this.toggleIcon.hasAttribute("open")) {
              this.toggleIcon.removeAttribute("open");
              this.longEmailAddresses.setAttribute("singleline", "true");
            } else {
              this.toggleIcon.setAttribute("open", "true");
              this.longEmailAddresses.removeAttribute("singleline");
            }
          ]]>
        </body>
      </method>

      <!-- internal method used to clear both our divs -->
      <method name="clearChildNodes">
        <parameter name="aParentNode"/>
        <body>
          <![CDATA[
            // we want to keep around the first mSizeOfAddressCache email address nodes
            // don't forget that we have comma text nodes in there too so really we want to keep
            // around cache size * 2 - 1.
            var numItemsToPreserve = this.mSizeOfAddressCache * 2 - 1;
            var numItemsInNode = aParentNode.childNodes.length;

            while (numItemsInNode && (numItemsInNode > numItemsToPreserve))
            {
              aParentNode.childNodes[numItemsInNode - 1].remove();
              numItemsInNode = numItemsInNode - 1;
            }
          ]]>
        </body>
      </method>

      <method name="clearHeaderValues">
        <body>
          <![CDATA[
            // clear out our local state
            this.mAddresses = new Array;
            if (this.toggleIcon.hasAttribute("open"))
              // no automatic overflow tracking in this case
              this.toggleIcon.collapsed = true;
            this.toggleIcon.removeAttribute("open");
            this.longEmailAddresses.setAttribute("singleline", "true");
            // remove anything inside of each of our labels....
            this.clearChildNodes(this.emailAddresses);
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="mail-emailaddress">
    <content popup="emailAddressPopup" context="emailAddressPopup">
      <xul:description anonid="emailValue" class="emailDisplayButton plain"
                 xbl:inherits="xbl:text=label,crop,aria-label" flex="1"/>
      <xul:image class="emailDisplayImage" anonid="emailImage"
                 xbl:inherits="src=image"/>
    </content>

    <implementation>
      <property name="label"      onset="this.getPart('emailValue').setAttribute('label',val); return val;"
                                  onget="return this.getPart('emailValue').getAttribute('label');"/>
      <property name="crop"       onset="this.getPart('emailValue').setAttribute('crop',val); return val;"
                                  onget="return this.getPart('emailValue').getAttribute('crop');"/>
      <property name="disabled"   onset="this.getPart('emailValue').setAttribute('disabled',val); return val;"
                                  onget="return this.getPart('emailValue').getAttribute('disabled');"/>
      <property name="src"        onset="this.getPart('emailImage').setAttribute('src',val); return val;"
                                  onget="return this.getPart('emailImage').getAttribute('src');"/>
      <property name="imgalign"   onset="this.getPart('emailImage').setAttribute('imgalign',val); return val;"
                                  onget="return this.getPart('emailImage').getAttribute('imgalign');"/>

      <method name="getPart">
        <parameter name="aPartId"/>
        <body><![CDATA[
          return document.getAnonymousElementByAttribute(this, "anonid", aPartId);
        ]]></body>
      </method>
    </implementation>
  </binding>

  <binding id="mail-messageids-headerfield">
    <content>
      <xul:hbox class="headerNameBox" align="start" pack="end">
        <xul:image class="addresstwisty" anonid="toggleIcon"
                   onclick="toggleWrap();"/>
        <xul:label class="headerName" xbl:inherits="value=label"/>
      </xul:hbox>
      <xul:hbox class="headerValueBox" flex="1" align="start">
        <xul:label class="headerValue" anonid="headerValue" flex="1"/>
      </xul:hbox>
    </content>

    <implementation>
      <constructor>
        <![CDATA[
          this.mMessageIds = [];
          this.showFullMessageIds = false;
        ]]>
      </constructor>

      <property name="headerValue" readonly="true"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue');"/>
      <property name="toggleIcon" readonly="true"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'toggleIcon');"/>

      <field name="mMessageIds"/>

      <!-- addMessageIdView: a public method used to add a message-id to this widget. -->
      <method name="addMessageIdView">
        <parameter name="aMessageId"/>
        <body>
          <![CDATA[
            this.mMessageIds.push(aMessageId);
          ]]>
        </body>
      </method>

      <!-- updateMessageIdNode: private method used to set properties on an MessageId node -->
      <method name="updateMessageIdNode">
        <parameter name="aMessageIdNode"/>
        <parameter name="aIndex"/>
        <parameter name="aMessageId"/>
        <parameter name="aLastId"/>
        <body>
          <![CDATA[
            var showFullMessageIds = this.showFullMessageIds;

            if (showFullMessageIds || aIndex == aLastId)
            {
              aMessageIdNode.setAttribute("label", aMessageId);
              aMessageIdNode.removeAttribute("tooltiptext");
            }
            else
            {
              aMessageIdNode.setAttribute("label", aIndex);
              aMessageIdNode.setAttribute("tooltiptext", aMessageId);
            }

            aMessageIdNode.setAttribute("index", aIndex);
            aMessageIdNode.setAttribute("messageid", aMessageId);
          ]]>
        </body>
      </method>

      <method name="fillMessageIdNodes">
        <body>
          <![CDATA[
            var headerValue    = this.headerValue;
            var messageIdNodes = headerValue.childNodes;
            var numMessageIds  = this.mMessageIds.length;
            var index = 0;

            while (messageIdNodes.length > numMessageIds * 2 - 1)
              headerValue.lastChild.remove();

            this.toggleIcon.hidden = numMessageIds <= 1;

            for (var index = 0; index < numMessageIds; index++)
            {
              if (index * 2 <= messageIdNodes.length - 1)
              {
                this.updateMessageIdNode(messageIdNodes[index * 2], index + 1,
                                         this.mMessageIds[index], numMessageIds);
              }
              else
              {
                var newMessageIdNode = document.createElement("mail-messageid");

                if (index)
                {
                  var textNode = document.createElement("text");
                  textNode.setAttribute("value", ", ");
                  textNode.setAttribute("class", "messageIdSeparator");
                  headerValue.appendChild(textNode);
                }
                var itemInDocument = headerValue.appendChild(newMessageIdNode);
                this.updateMessageIdNode(itemInDocument, index + 1,
                                         this.mMessageIds[index], numMessageIds);
              }
            }
          ]]>
        </body>
      </method>

      <method name="toggleWrap">
        <body>
          <![CDATA[
            var headerValue        = this.headerValue;
            var messageIdNodes     = headerValue.childNodes;
            var showFullMessageIds = !this.showFullMessageIds;
            var messageIds         = this.mMessageIds

            for (var i = 0; i < messageIdNodes.length; i += 2)
            {
              if (showFullMessageIds)
              {
                this.toggleIcon.setAttribute("open", "true");
                messageIdNodes[i].setAttribute("label", messageIds[i / 2]);
                messageIdNodes[i].removeAttribute("tooltiptext");
                headerValue.removeAttribute("singleline");
              } else
              {
                this.toggleIcon.removeAttribute("open");
                messageIdNodes[i].setAttribute("label", i / 2 + 1);
                messageIdNodes[i].setAttribute("tooltiptext", messageIds[i / 2]);
              }
            }

            this.showFullMessageIds = showFullMessageIds;
          ]]>
        </body>
      </method>

      <method name="clearHeaderValues">
        <body>
          <![CDATA[
            // clear out our local state
            this.mMessageIds = new Array;
            if (this.showFullMessageIds)
            {
              this.showFullMessageIds = false;
              this.toggleIcon.removeAttribute("open");
            }
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="mail-messageid">
    <content context="messageIdContext" onclick="MessageIdClick(this, event);">
      <xul:label anonid="messageIdValue" class="messageIdDisplayButton plain"
                 xbl:inherits="value=label"/>
      <xul:image class="messageIdDisplayImage" anonid="messageIdImage"/>
    </content>

    <implementation>
      <property name="label"      onset="this.getPart().setAttribute('label',val); return val;"
                                  onget="return this.getPart('messageIdValue').getAttribute('label');"/>

      <method name="getPart">
        <parameter name="aPartId"/>
        <body><![CDATA[
          return document.getAnonymousElementByAttribute(this, "anonid", 'messageIdValue');
        ]]></body>
      </method>
    </implementation>
  </binding>

  <!-- Header field for showing the tags associated with a message -->
  <binding id="mail-headerfield-tags">
    <content>
      <xul:hbox class="headerNameBox" align="start">
        <xul:label class="headerName" xbl:inherits="value=label" flex="1"/>
      </xul:hbox>
      <xul:hbox class="headerValueBox" flex="1" align="start">
        <xul:label class="headerValue plain" anonid="headerValue" flex="1"/>
      </xul:hbox>
    </content>

    <implementation>
      <property name="headerValue" onset="return this.buildTags(val);"/>
      <method name="buildTags">
        <parameter name="aTags"/>
        <body>
          <![CDATA[
            // aTags contains a list of actual tag names (not the keys), delimited by spaces
            // each tag name is encoded.

            // remove any existing tag items we've appended to the list
            var headerValueNode = document.getAnonymousElementByAttribute(this, 'anonid', 'headerValue');
            for (var i = headerValueNode.childNodes.length - 1; i >= 0; --i)
              headerValueNode.childNodes[i].remove();

            var tagService = Cc["@mozilla.org/messenger/tagservice;1"]
                             .getService(Ci.nsIMsgTagService);

            // tokenize the keywords based on ' '
            var tagsArray = aTags.split(' ');
            for (var index = 0; index < tagsArray.length; index++)
            {
              // for each tag, create a label, give it the font color that corresponds to the
              // color of the tag and append it.
              var tagName;
              try {
                // if we got a bad tag name, getTagForKey will throw an exception, skip it
                // and go to the next one.
                tagName = tagService.getTagForKey(tagsArray[index]);
              } catch (ex) { continue; }

              var color = tagService.getColorForKey(tagsArray[index]);

              // now create a label for the tag name, and set the color
              var label = document.createElement("label");
              label.setAttribute('value', tagName);
              label.style.color = color;
              label.className = "tagvalue blc-" + color.substr(1);
              headerValueNode.appendChild(label);
            }
        ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="search-menulist-abstract" name="searchMenulistAbstract" extends="xul:box">
    <content>
      <xul:menulist class="search-menulist" xbl:inherits="flex,disabled" oncommand="this.parentNode.onSelect(event)">
        <xul:menupopup class="search-menulist-popup"/>
      </xul:menulist>
    </content>

    <implementation>
      <field name="internalScope">null</field>
      <field name="internalValue">-1</field>
      <field readonly="true" name="validityManager">
        <![CDATA[
           Cc['@mozilla.org/mail/search/validityManager;1'].getService(Ci.nsIMsgSearchValidityManager);
        ]]>
      </field>
      <field name="filterService">
        Cc["@mozilla.org/messenger/services/filters;1"]
          .getService(Ci.nsIMsgFilterService);
      </field>
      <property name="searchScope" onget="return this.internalScope;">
        <!-- scope ID - retrieve the table -->
        <setter>
          <![CDATA[
            // if scope isn't changing this is a noop
            if (this.internalScope == val) return val;

            this.internalScope = val;
            this.refreshList();
            var targets = this.targets;
            if (targets) {
              for (var i=0; i< targets.length; i++) {
                targets[i].searchScope = val;
              }
            }
            return val;
          ]]>
        </setter>
      </property>

      <property name="validityTable" readonly="true" onget="return this.validityManager.getTable(this.searchScope)"/>

      <property name="targets" readonly="true">
        <getter>
          <![CDATA[
            var forAttrs =  this.getAttribute("for");
            if (!forAttrs) return null;
            var targetIds = forAttrs.split(",");
            if (targetIds.length == 0) return null;

            var targets = new Array;
            for (let j = 0, i = 0; i < targetIds.length; i++) {
              var target = document.getElementById(targetIds[i]);
              if (target) targets[j++] = target;
            }
            return targets;
          ]]>
        </getter>
      </property>

      <property name="optargets" readonly="true">
        <getter>
          <![CDATA[
            var forAttrs =  this.getAttribute("opfor");
            if (!forAttrs) return null;
            var optargetIds = forAttrs.split(",");
            if (optargetIds.length == 0) return null;

            var optargets = new Array;
            var j=0;
            for (var i=0; i<optargetIds.length;i++) {
              var optarget = document.getElementById(optargetIds[i]);
              if (optarget) optargets[j++] = optarget;
            }
            return optargets;
          ]]>
        </getter>
      </property>

      <property name="value" onget="return this.internalValue;">
        <setter>
          <![CDATA[
            if (this.internalValue == val)
              return val;
            this.internalValue = val;
            var menulist = document.getAnonymousNodes(this)[0];
            menulist.selectedItem = this.validMenuitem;

            // now notify targets of new parent's value
            var targets = this.targets;
            if (targets) {
              for (var i=0; i < targets.length; i++) {
                targets[i].parentValue = val;
              }
            }

            // now notify optargets of new op parent's value
            var optargets = this.optargets;
            if (optargets) {
              for (i=0; i < optargets.length; i++) {
                optargets[i].opParentValue = val;
              }
            }

            return val;
          ]]>
        </setter>
      </property>
      <!-- label forwards to the internal menulist's "label" attribute -->
      <property name="label" onget="return document.getAnonymousNodes(this)[0].selectedItem.getAttribute('label');">
      </property>
      <property name="validMenuitem" readonly="true">
      <!-- Prepare menulist selection, adding a missing hidden menuitem if needed, and
        updating the disabled state of the menulist label. -->
        <getter>
          <![CDATA[
            if (this.value == -1) // -1 means not initialized
              return null;

            let menulist = document.getAnonymousNodes(this)[0];
            let isCustom = isNaN(this.value);
            let typedValue = isCustom ? this.value : parseInt(this.value);

            // custom attribute to style the unavailable menulist item
            menulist.setAttribute("unavailable",
              this.valueIds.indexOf(typedValue) < 0);

            // add a hidden menulist item if value is missing
            let menuitem = menulist.getElementsByAttribute("value", this.value).item(0);
            if (!menuitem)
            { // need to add a hidden menuitem
              menuitem = menulist.appendItem(this.valueLabel, this.value);
              menuitem.hidden = true;
            }
            return menuitem;
          ]]>
        </getter>
      </property>
      <method name="refreshList">
        <parameter name="dontRestore"/> <!-- should we not restore old selection? -->
        <body>
          <![CDATA[
            var menuItemIds = this.valueIds;
            var menuItemStrings = this.valueStrings;

            var menulist = document.getAnonymousNodes(this)[0];
            var popup = menulist.firstChild;

            // save our old "value" so we can restore it later
            var oldData;
            if (!dontRestore)
              oldData = menulist.value;

            // remove the old popup children
            while (popup.hasChildNodes())
              popup.lastChild.remove();

            var newSelection;
            var customizePos=-1;
            for (var i = 0; i < menuItemIds.length; ++i)
            {
              // create the menuitem
              if (Ci.nsMsgSearchAttrib.OtherHeader == menuItemIds[i].toString())
                customizePos = i;
              else
              {
                var menuitem = document.createElement("menuitem");
                menuitem.setAttribute("label", menuItemStrings[i]);
                menuitem.setAttribute("value", menuItemIds[i]);
                popup.appendChild(menuitem);
                // try to restore the selection
                if (!newSelection || oldData == menuItemIds[i].toString())
                  newSelection = menuitem;
              }
            }
            if (customizePos != -1)
            {
              var separator = document.createElement("menuseparator");
              popup.appendChild(separator);
              menuitem = document.createElement("menuitem");
              menuitem.setAttribute("label", menuItemStrings[customizePos]);
              menuitem.setAttribute("value", menuItemIds[customizePos]);
              popup.appendChild(menuitem);
            }
            //
            // If we are either uninitialized, or if we are called because
            // of a change in our parent, update the value to the
            // default stored in newSelection.
            //
            if ((this.value == -1 || dontRestore) && newSelection)
              this.value = newSelection.getAttribute("value");
            menulist.selectedItem = this.validMenuitem;
          ]]>
        </body>
      </method>
      <method name="onSelect">
        <parameter name="event"/>
        <body>
          <![CDATA[
            var menulist = document.getAnonymousNodes(this)[0];
            this.value = menulist.value;
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <!-- searchattribute - Subject, Sender, To, CC, etc. -->
  <binding id="searchattribute" name="searchAttribute"
           extends="chrome://messenger/content/mailWidgets.xml#search-menulist-abstract">
    <implementation>
      <field name="stringBundle">
        <![CDATA[
          Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService)
            .createBundle("chrome://messenger/locale/search-attributes.properties")
        ]]>
      </field>
      <property name="valueLabel" readonly="true">
        <getter>
          <![CDATA[
            if (isNaN(this.value)) // is this a custom term?
            {
              let customTerm = this.filterService.getCustomTerm(this.value);
              if (customTerm)
                return customTerm.name;
              else
              {
                let consoleService = Cc["@mozilla.org/consoleservice;1"]
                    .getService(Ci.nsIConsoleService);
                let scriptError = Cc["@mozilla.org/scripterror;1"]
                    .createInstance(Ci.nsIScriptError);
                scriptError.init("Missing custom search term " + this.value,
                    null, null, 0, 0,
                    Ci.nsIScriptError.errorFlag,
                    "component javascript");
                consoleService.logMessage(scriptError);
                return this.stringBundle.GetStringFromName("MissingCustomTerm");
              }
            }
            return this.stringBundle.GetStringFromName(
              this.validityManager.getAttributeProperty(parseInt(this.value)));
          ]]>
        </getter>
      </property>
      <property name="valueIds" readonly="true">
        <getter>
          <![CDATA[
            var length = new Object;
            let result = this.validityTable.getAvailableAttributes(length);
            // add any available custom search terms
            let customEnum = this.filterService.getCustomTerms();
            while (customEnum && customEnum.hasMoreElements())
            {
              let customTerm =
                 customEnum.getNext()
                           .QueryInterface(Ci.nsIMsgSearchCustomTerm);
              // for custom terms, the array element is a string with the custom id
              // instead of the integer attribute
              if (customTerm.getAvailable(this.searchScope, null))
                result.push(customTerm.id);
            }
            return result;
          ]]>
        </getter>
      </property>
      <property name="valueStrings" readonly="true">
        <getter>
          <![CDATA[
            let strings = new Array;
            let ids = this.valueIds;
            let hdrsArray = null;
            try
            {
              let hdrs = Cc["@mozilla.org/preferences-service;1"]
                           .getService(Ci.nsIPrefBranch)
                           .getCharPref("mailnews.customHeaders");
              hdrs = hdrs.replace(/\s+/g, "");  //remove white spaces before splitting
              hdrsArray = hdrs.match(/[^:]+/g);
            }
            catch(ex)
            {
            }

            let j = 0;
            for (let i = 0; i < ids.length; i++)
            {
              if (isNaN(ids[i])) // Is this a custom search term?
              {
                let customTerm = this.filterService.getCustomTerm(ids[i]);
                if (customTerm)
                  strings[i] = customTerm.name;
                else
                  strings[i] = "";
              }
              else if(ids[i] > Ci.nsMsgSearchAttrib.OtherHeader && hdrsArray)
                strings[i] = hdrsArray[j++];
              else
                strings[i] = this.stringBundle.GetStringFromName(
                               this.validityManager.getAttributeProperty(ids[i]));
            }
            return strings;
          ]]>
        </getter>
      </property>
      <constructor>
      <![CDATA[
        initializeTermFromId(this.id);
      ]]>
      </constructor>
    </implementation>
  </binding>

  <!-- searchoperator - Contains, Is Less than, etc -->
  <binding id="searchoperator" name="searchOperator"
           extends="chrome://messenger/content/mailWidgets.xml#search-menulist-abstract">
    <implementation>
      <field name="searchAttribute">Ci.nsMsgSearchAttrib.Default</field>
      <field name="stringBundle">
        <![CDATA[
          Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService)
            .createBundle("chrome://messenger/locale/search-operators.properties")
        ]]>
      </field>
      <property name="valueLabel" readonly="true">
        <getter>
          <![CDATA[
            return this.stringBundle.GetStringFromName(this.value);
          ]]>
        </getter>
      </property>
      <property name="valueIds" readonly="true">
        <getter>
          <![CDATA[
            var length = new Object;
            let isCustom = isNaN(this.searchAttribute);
            if (isCustom)
            {
              let customTerm = this.filterService.getCustomTerm(this.searchAttribute);
              if (customTerm)
                return customTerm.getAvailableOperators(this.searchScope, length);
              return [Ci.nsMsgSearchOp.Contains];
            }
            return this.validityTable.getAvailableOperators(this.searchAttribute,length);
          ]]>
        </getter>
      </property>
      <property name="valueStrings" readonly="true">
        <getter>
          <![CDATA[
            let strings = new Array;
            let ids = this.valueIds;
            for (let i = 0; i < ids.length; i++)
              strings[i] = this.stringBundle.GetStringFromID(ids[i]);
            return strings;
          ]]>
        </getter>
      </property>
      <property name="parentValue">
        <setter>
          <![CDATA[
            if (this.searchAttribute == val && val != Ci.nsMsgSearchAttrib.OtherHeader) return val;
            this.searchAttribute = val;
            this.refreshList(true); // don't restore the selection, since searchvalue nulls it
            if (val == Ci.nsMsgSearchAttrib.OtherHeader)
            {
              window.openDialog('chrome://messenger/content/CustomHeaders.xul', "", 'modal,centerscreen,resizable,titlebar,chrome', null);
              setTimeout(UpdateAfterCustomHeaderChange, 0); // XXX bug 212625
            }
            else if (val == Ci.nsMsgSearchAttrib.AgeInDays)
            {
              // Bug 187741 We want "Age in Days" to default to "is less than".
              this.value = Ci.nsMsgSearchOp.IsLessThan;
            }
            return val;
          ]]>
        </setter>
        <getter>
          <![CDATA[
            return this.searchAttribute;
          ]]>
        </getter>
      </property>
    </implementation>
  </binding>

  <!-- searchvalue - a widget which dynamically changes its user interface
       depending on what type of data it's supposed to be showing
       currently handles arbitrary text entry, and menulists for
       priority, status, junk status, tags, hasAttachment status,
       and addressbook
  -->
  <binding id="searchvalue" name="searchValue">
    <content>
      <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup">
          <xul:menuitem value="6" stringTag="priorityHighest" class="search-value-menuitem"/>
          <xul:menuitem value="5" stringTag="priorityHigh" class="search-value-menuitem"/>
          <xul:menuitem value="4" stringTag="priorityNormal" class="search-value-menuitem"/>
          <xul:menuitem value="3" stringTag="priorityLow" class="search-value-menuitem"/>
          <xul:menuitem value="2" stringTag="priorityLowest" class="search-value-menuitem"/>
        </xul:menupopup>
      </xul:menulist>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup">
          <xul:menuitem value="2" stringTag="replied" class="search-value-menuitem"/>
          <xul:menuitem value="1" stringTag="read" class="search-value-menuitem"/>
          <xul:menuitem value="65536" stringTag="new" class="search-value-menuitem"/>
          <xul:menuitem value="4096" stringTag="forwarded" class="search-value-menuitem"/>
          <xul:menuitem value="4" stringTag="flagged" class="search-value-menuitem"/>
        </xul:menupopup>
      </xul:menulist>
      <xul:textbox flex="1" class="search-value-textbox" xbl:inherits="disabled"/>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup addrbooksPopup" localonly="true"/>
      </xul:menulist>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup">
        </xul:menupopup>
      </xul:menulist>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup">
          <xul:menuitem value="2" stringTag="junk" class="search-value-menuitem"/>
        </xul:menupopup>
      </xul:menulist>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup">
          <xul:menuitem value="0" stringTag="hasAttachments" class="search-value-menuitem"/>
        </xul:menupopup>
      </xul:menulist>
      <xul:menulist flex="1" class="search-value-menulist" xbl:inherits="disabled">
        <xul:menupopup class="search-value-popup">
          <xul:menuitem value="plugin" stringTag="junkScoreOriginPlugin"
                        class="search-value-menuitem"/>
          <xul:menuitem value="user" stringTag="junkScoreOriginUser"
                        class="search-value-menuitem"/>
          <xul:menuitem value="filter" stringTag="junkScoreOriginFilter"
                        class="search-value-menuitem"/>
          <xul:menuitem value="whitelist" stringTag="junkScoreOriginWhitelist"
                        class="search-value-menuitem"/>
          <xul:menuitem value="imapflag" stringTag="junkScoreOriginImapFlag"
                        class="search-value-menuitem"/>
        </xul:menupopup>
      </xul:menulist>
      <xul:hbox flex="1" class="search-value-custom" xbl:inherits="disabled"/>
    </content>
    <implementation>
      <field name="internalOperator">null</field>
      <field name="internalAttribute">null</field>
      <field name="internalValue">null</field>
      <field name="filterService">
        Cc["@mozilla.org/messenger/services/filters;1"]
          .getService(Ci.nsIMsgFilterService);
      </field>
      <property name="opParentValue" onget="return this.internalOperator;">
        <setter>
          <![CDATA[
            // noop if we're not changing it
            if (this.internalOperator == val) return val;

            // Keywords has the null field IsEmpty
            if (this.searchAttribute == Ci.nsMsgSearchAttrib.Keywords) {
              if (val == Ci.nsMsgSearchOp.IsEmpty ||
                  val == Ci.nsMsgSearchOp.IsntEmpty)
                this.setAttribute("selectedIndex", "-1");
              else
                this.setAttribute("selectedIndex", "5");
            }

            // JunkStatus has the null field IsEmpty
            if (this.searchAttribute == Ci.nsMsgSearchAttrib.JunkStatus) {
              if (val == Ci.nsMsgSearchOp.IsEmpty ||
                  val == Ci.nsMsgSearchOp.IsntEmpty)
                this.setAttribute("selectedIndex", "-1");
              else
                this.setAttribute("selectedIndex", "6");
            }

              // if it's not sender, to, cc, alladdresses, or toorcc, we don't care
              if (this.searchAttribute != Ci.nsMsgSearchAttrib.Sender &&
                this.searchAttribute != Ci.nsMsgSearchAttrib.To &&
                this.searchAttribute != Ci.nsMsgSearchAttrib.ToOrCC &&
                this.searchAttribute != Ci.nsMsgSearchAttrib.AllAddresses &&
                this.searchAttribute != Ci.nsMsgSearchAttrib.CC ) {
              this.internalOperator = val;
              return val;
            }

            var children = document.getAnonymousNodes(this);
            if (val == Ci.nsMsgSearchOp.IsntInAB ||
                val == Ci.nsMsgSearchOp.IsInAB) {
              // if the old internalOperator was
              // IsntInAB or IsInAB, and the new internalOperator is
              // IsntInAB or IsInAB, noop because the search value
              // was an ab type, and it still is.
              // otherwise, switch to the ab picker and select the PAB
              if (this.internalOperator != Ci.nsMsgSearchOp.IsntInAB &&
                  this.internalOperator != Ci.nsMsgSearchOp.IsInAB) {
                var abs = children[4].getElementsByAttribute("value", "moz-abmdbdirectory://abook.mab");
                if (abs.item(0))
                  children[4].selectedItem = abs[0];
                this.setAttribute("selectedIndex", "4");
              }
            }
            else {
              // if the old internalOperator wasn't
              // IsntInAB or IsInAB, and the new internalOperator isn't
              // IsntInAB or IsInAB, noop because the search value
              // wasn't an ab type, and it still isn't.
              // otherwise, switch to the textbox and clear it
              if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
                  this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
                children[0].value = "";
                this.setAttribute("selectedIndex", "0");
              }
            }

            this.internalOperator = val;
            return val;
          ]]>
        </setter>
      </property>
      <!-- parentValue forwards to the attribute -->
      <property name="parentValue" onset="return this.searchAttribute=val;"
                                   onget="return this.searchAttribute;"/>
      <property name="searchAttribute" onget="return this.internalAttribute;">
        <setter>
          <![CDATA[
            // noop if we're not changing it
            if (this.internalAttribute == val) return val;
            this.internalAttribute = val;

            // if the searchAttribute changing, null out the internalOperator
            this.internalOperator = null;

            // we inherit from a deck, so just use it's index attribute
            // to hide/show widgets
            if (isNaN(val)) // Is this a custom attribute?
            {
              this.setAttribute("selectedIndex", "9");
              let customHbox = document.getAnonymousNodes(this)[9];
              if (this.internalValue)
                customHbox.setAttribute("value", this.internalValue.str);
              // the searchAttribute attribute is intended as a selector in
              // CSS for custom search terms to bind a custom value
              customHbox.setAttribute("searchAttribute", val);
            }
            else if (val == Ci.nsMsgSearchAttrib.Priority)
              this.setAttribute("selectedIndex", "1");
            else if (val == Ci.nsMsgSearchAttrib.MsgStatus)
              this.setAttribute("selectedIndex", "2");
            else if (val == Ci.nsMsgSearchAttrib.Date)
              this.setAttribute("selectedIndex", "3");
            else if (val == Ci.nsMsgSearchAttrib.Sender) {
              // since the internalOperator is null
              // this is the same as the initial state
              // the initial state for Sender isn't an ab type search
              // it's a text search, so show the textbox
              this.setAttribute("selectedIndex", "0");
            }
            else if (val == Ci.nsMsgSearchAttrib.Keywords) {
              this.setAttribute("selectedIndex", "5");
            }
            else if (val == Ci.nsMsgSearchAttrib.JunkStatus) {
              this.setAttribute("selectedIndex", "6");
            }
            else if (val == Ci.nsMsgSearchAttrib.HasAttachmentStatus) {
              this.setAttribute("selectedIndex", "7");
            }
            else if (val == Ci.nsMsgSearchAttrib.JunkScoreOrigin) {
              this.setAttribute("selectedIndex", "8");
            }
            else {
              // a normal text field
              this.setAttribute("selectedIndex", "0");
            }
            return val;
          ]]>
        </setter>
      </property>
      <property name="value" onget="return this.internalValue;">
        <setter>
          <![CDATA[
          // val is a nsIMsgSearchValue object
          this.internalValue = val;
          var attrib = this.internalAttribute;
          var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
          var children = document.getAnonymousNodes(this);
          this.searchAttribute = attrib;
          if (isNaN(attrib)) // a custom term
          {
            let customHbox = document.getAnonymousNodes(this)[9];
            customHbox.setAttribute("value", val.str);
            return val;
          }
          if (attrib == nsMsgSearchAttrib.Priority) {
            var matchingPriority =
              children[1].getElementsByAttribute("value", val.priority);
            if (matchingPriority.item(0))
              children[1].selectedItem = matchingPriority[0];
          }
          else if (attrib == nsMsgSearchAttrib.MsgStatus) {
            var matchingStatus =
              children[2].getElementsByAttribute("value", val.status);
            if (matchingStatus.item(0))
              children[2].selectedItem = matchingStatus[0];
          }
          else if (attrib == nsMsgSearchAttrib.AgeInDays)
            children[0].value = val.age;
          else if (attrib == nsMsgSearchAttrib.Date)
            children[3].value = convertPRTimeToString(val.date);
          else if (attrib == nsMsgSearchAttrib.Sender ||
                   attrib == nsMsgSearchAttrib.To ||
                   attrib == nsMsgSearchAttrib.CC ||
                   attrib == nsMsgSearchAttrib.AllAddresses ||
                   attrib == nsMsgSearchAttrib.ToOrCC)
          {
            if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
                this.internalOperator == Ci.nsMsgSearchOp.IsInAB) {
              var abs = children[4].getElementsByAttribute("value", val.str);
              if (abs.item(0))
                children[4].selectedItem = abs[0];
            }
            else
              children[0].value = val.str;
          }
          else if (attrib == nsMsgSearchAttrib.Keywords)
          {
            var keywordVal = children[5].getElementsByAttribute("value", val.str);
            if (keywordVal.item(0))
            {
              children[5].value = val.str;
              children[5].selectedItem = keywordVal[0];
            }
          }
          else if (attrib == nsMsgSearchAttrib.JunkStatus) {
            var junkStatus =
              children[6].getElementsByAttribute("value", val.junkStatus);
            if (junkStatus.item(0))
              children[6].selectedItem = junkStatus[0];
          }
          else if (attrib == nsMsgSearchAttrib.HasAttachmentStatus) {
            var hasAttachmentStatus =
              children[7].getElementsByAttribute("value", val.hasAttachmentStatus);
            if (hasAttachmentStatus.item(0))
              children[7].selectedItem = hasAttachmentStatus[0];
          }
          else if (attrib == nsMsgSearchAttrib.JunkScoreOrigin) {
            var junkScoreOrigin =
              children[8].getElementsByAttribute("value", val.str);
            if (junkScoreOrigin.item(0))
              children[8].selectedItem = junkScoreOrigin[0];
          }
          else if (attrib == nsMsgSearchAttrib.JunkPercent) {
            children[0].value = val.junkPercent;
          }
          else if (attrib == nsMsgSearchAttrib.Size) {
            children[0].value = val.size;
          }
          else
            children[0].value = val.str;
          return val;
          ]]>
        </setter>
      </property>
      <method name="save">
        <body>
          <![CDATA[
            var searchValue = this.value;
            var searchAttribute = this.searchAttribute;
            var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
            var children = document.getAnonymousNodes(this);

            searchValue.attrib = searchAttribute;
            if (searchAttribute == nsMsgSearchAttrib.Priority) {
               searchValue.priority = children[1].selectedItem.value;
            }
            else if (searchAttribute == nsMsgSearchAttrib.MsgStatus)
               searchValue.status = children[2].value;
            else if (searchAttribute == nsMsgSearchAttrib.AgeInDays)
               searchValue.age = children[0].value;
            else if (searchAttribute == nsMsgSearchAttrib.Date)
               searchValue.date = convertStringToPRTime(children[3].value);
            else if (searchAttribute == nsMsgSearchAttrib.Sender ||
                   searchAttribute == nsMsgSearchAttrib.To ||
                   searchAttribute == nsMsgSearchAttrib.CC ||
                   searchAttribute == nsMsgSearchAttrib.AllAddresses ||
                   searchAttribute == nsMsgSearchAttrib.ToOrCC)
            {
              if (this.internalOperator == Ci.nsMsgSearchOp.IsntInAB ||
                  this.internalOperator == Ci.nsMsgSearchOp.IsInAB)
                searchValue.str = children[4].selectedItem.value;
              else
                searchValue.str = children[0].value;
            }
            else if (searchAttribute == nsMsgSearchAttrib.Keywords)
            {
              searchValue.str = children[5].value;
            }
            else if (searchAttribute == nsMsgSearchAttrib.JunkStatus)
               searchValue.junkStatus = children[6].value;
            else if (searchAttribute == nsMsgSearchAttrib.JunkPercent)
               searchValue.junkPercent = children[0].value;
            else if (searchAttribute == nsMsgSearchAttrib.Size)
               searchValue.size = children[0].value;
            else if (searchAttribute == nsMsgSearchAttrib.HasAttachmentStatus)
               searchValue.status = 0x10000000;  // 0x10000000 is MSG_FLAG_ATTACHMENT;
            else if (searchAttribute == nsMsgSearchAttrib.JunkScoreOrigin)
               searchValue.str = children[8].value;
            else if (isNaN(searchAttribute)) //  a custom term
            {
              searchValue.attrib = nsMsgSearchAttrib.Custom;
              searchValue.str = children[9].getAttribute("value");
            }
            else
              searchValue.str = children[0].value;
          ]]>
        </body>
      </method>
      <method name="saveTo">
        <parameter name="searchValue"/>
        <body>
          <![CDATA[
            this.internalValue = searchValue;
            this.save();
          ]]>
        </body>
      </method>
      <method name="fillInTags">
        <body>
          <![CDATA[
            var children = document.getAnonymousNodes(this);
            var popupMenu = children[5].firstChild;
            var tagService = Cc["@mozilla.org/messenger/tagservice;1"]
                               .getService(Ci.nsIMsgTagService);
            var tagArray = tagService.getAllTags({});
            for (var i = 0; i < tagArray.length; ++i)
            {
              var taginfo = tagArray[i];
              var newMenuItem = document.createElement('menuitem');
              newMenuItem.setAttribute('label', taginfo.tag);
              newMenuItem.setAttribute('value', taginfo.key);
              popupMenu.appendChild(newMenuItem);
              if (!i)
                children[5].selectedItem = newMenuItem;
            }
          ]]>
        </body>
      </method>
      <method name="fillStringsForChildren">
        <parameter name="parentNode"/>
        <parameter name="bundle"/>
        <body>
          <![CDATA[
            var children = parentNode.childNodes;
            var len=children.length;
            for (var i=0; i<len; i++) {
              var node = children[i];
              var stringTag = node.getAttribute("stringTag");
              if (stringTag) {
                var attr = (node.tagName == "label") ? "value" : "label";
                node.setAttribute(attr, bundle.GetStringFromName(stringTag));
              }
            }
          ]]>
        </body>
      </method>
      <method name="initialize">
        <parameter name="menulist"/>
        <parameter name="bundle"/>
        <body>
          <![CDATA[
            this.fillStringsForChildren(menulist.firstChild, bundle);
          ]]>
        </body>
      </method>
      <constructor>
      <![CDATA[
        // initialize strings
        var bundle = Cc["@mozilla.org/intl/stringbundle;1"]
                       .getService(Ci.nsIStringBundleService)
                       .createBundle("chrome://messenger/locale/messenger.properties");

        // intialize the priority picker
        this.initialize(document.getAnonymousNodes(this)[1], bundle);

        // initialize the status picker
        this.initialize(document.getAnonymousNodes(this)[2], bundle);

        // initialize the date picker
        var datePicker = document.getAnonymousNodes(this)[3];
        var searchAttribute = this.searchAttribute;
        var nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
        var time;
        if (searchAttribute == nsMsgSearchAttrib.Date)
         time = datePicker.value;
        else
         time = new Date();
        // do .value instead of .setAttribute("value", xxx);
        // to work around for bug #179412
        // (caused by bug #157210)
        //
        // the searchvalue widget has two textboxes
        // one for text, one as a placeholder for a date / calendar widget
        datePicker.value = convertDateToString(time);

        // initialize the address book picker
        this.initialize(document.getAnonymousNodes(this)[4], bundle);

        // initialize the junk status picker
        this.initialize(document.getAnonymousNodes(this)[6], bundle);

        // initialize the has attachment status picker
        this.initialize(document.getAnonymousNodes(this)[7], bundle);

        // initialize the junk score origin picker
        this.initialize(document.getAnonymousNodes(this)[8], bundle);

        // initialize the tag list
        fillInTags();
      ]]>
      </constructor>
    </implementation>
    <handlers>
      <handler event="keypress" keycode="VK_RETURN" modifiers="accel any"
               action="onEnterInSearchTerm(event);" preventdefault="true"/>
    </handlers>
  </binding>

  <!-- Folder picker helper widget -->
  <binding id="locationpopup" extends="chrome://global/content/bindings/popup.xml#popup">
    <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
      <tree class="foldersTree"
            treelines="true"
            flex="1"
            datasources="rdf:null"
            flags="dont-build-content"
            seltype="text"
            hidecolumnpicker="true">
        <treecols>
          <treecol flex="1" primary="true" sort="rdf:http://home.netscape.com/NC-rdf#FolderTreeName?sort=true" sortActive="true" sortDirection="ascending" crop="center" hideheader="true"/>
        </treecols>
        <treechildren class="foldersTreeChildren"/>
        <template>
          <rule nc:IsDeferred="false">
            <treechildren>
              <treeitem uri="rdf:*">
                <treerow sort="rdf:http://home.netscape.com/NC-rdf#FolderTreeName?sort=true">
                  <treecell label="rdf:http://home.netscape.com/NC-rdf#FolderTreeName"
                    properties="folderNameCol specialFolder-rdf:http://home.netscape.com/NC-rdf#SpecialFolder biffState-rdf:http://home.netscape.com/NC-rdf#BiffState isServer-rdf:http://home.netscape.com/NC-rdf#IsServer newMessages-rdf:http://home.netscape.com/NC-rdf#NewMessages hasUnreadMessages-rdf:http://home.netscape.com/NC-rdf#HasUnreadMessages isSecure-rdf:http://home.netscape.com/NC-rdf#IsSecure serverType-rdf:http://home.netscape.com/NC-rdf#ServerType noSelect-rdf:http://home.netscape.com/NC-rdf#NoSelect"/>
                </treerow>
              </treeitem>
            </treechildren>
          </rule>
        </template>
      </tree>
    </xbl:content>
    <implementation>
      <field name="tree" readonly="true">
        document.getAnonymousNodes(this)[0];
      </field>
      <method name="updateHover">
        <parameter name="event"/>
        <body>
          <![CDATA[
            var box = this.tree.treeBoxObject;
            if (event.originalTarget == box.treeBody) {
              var index = box.getRowAt(event.clientX, event.clientY);
              this.tree.view.selection.select(index);
              return index;
            }
            return -1;
          ]]>
        </body>
      </method>
      <method name="fire">
        <body>
          <![CDATA[
            this.hidePopup();
            if (this.tree.currentIndex >= 0) {
              this.setAttribute("uri", this.tree.builderView.getResourceAtIndex(this.tree.currentIndex).Value);
              this.doCommand();
            }
          ]]>
        </body>
      </method>
      <method name="onBlurMenuList">
        <parameter name="event"/>
        <body>
          <![CDATA[
            this.openMenu(false);
          ]]>
        </body>
      </method>
      <field name="onKeyPressMenuList" readonly="true">
        <![CDATA[
          ({
            self: this,
            tree: this.tree,
            parentNode: this.parentNode,
            getLastVisibleRow: function getLastVisibleRow(box) {
              var f = box.getFirstVisibleRow();
              var p = box.getPageLength();
              var l = box.view.rowCount;
              return (l < f + p ? l : f + p) - 1;
            },
            handleEvent: function handleEvent(event) {
              if (event.altKey)
                return;
              var index;
              var box = this.tree.treeBoxObject;
              if (this.parentNode.hasAttribute("open")) {
                event.stopPropagation();
                event.preventDefault();
                switch (event.keyCode) {
                  case event.DOM_VK_ESCAPE:
                    this.self.hidePopup();
                    return;
                  case event.DOM_VK_RETURN:
                    this.self.fire();
                    return;
                }
                index = this.tree.currentIndex;
              } else {
                switch (event.keyCode) {
                  case event.DOM_VK_PAGE_UP:
                  case event.DOM_VK_PAGE_DOWN:
                    return;
                }
                index = this.self.setInitialSelection();
              }
              switch (event.keyCode) {
                case event.DOM_VK_UP:
                  if (index <= 0)
                    return;
                  index--;
                  break;
                case event.DOM_VK_DOWN:
                  index++;
                  if (index == box.view.rowCount)
                    return;
                  break;
                case event.DOM_VK_PAGE_UP:
                  if (index == box.getFirstVisibleRow())
                    box.scrollByPages(-1);
                  index = box.getFirstVisibleRow();
                  break;
                case event.DOM_VK_PAGE_DOWN:
                  if (index == this.getLastVisibleRow(box))
                    box.scrollByPages(1);
                  index = this.getLastVisibleRow(box);
                  break;
                case event.DOM_VK_HOME:
                  index = 0;
                  break;
                case event.DOM_VK_END:
                  index = box.view.rowCount - 1;
                  break;
                default:
                  if (event.charCode > 0 && !event.ctrlKey && !event.metaKey) {
                    event.preventDefault();
                    index = tree.keyNavigate(event);
                    if (index >= 0)
                      break;
                  }
                  return;
              }
              box.view.selection.select(index);
              if (this.parentNode.hasAttribute("open"))
                box.ensureRowIsVisible(index);
              else
                this.self.fire();
            }
          })
        ]]>
      </field>
      <method name="setInitialSelection">
        <body>
          <![CDATA[
            var view = this.tree.view;

            view.selection.currentColumn = this.tree.columns.getFirstColumn();

            view.selection.selectEventsSuppressed = true;
            for (var i = 0; i < view.rowCount; i++) {
              if (view.isContainer(i)) {
                if (view.isContainerEmpty(i) == view.isContainerOpen(i))
                  view.toggleOpenState(i);
                if (view.isContainerOpen(i)) {
                  if (i + 1 == view.rowCount ||
                      view.getLevel(i + 1) <= view.getLevel(i)) {
                    view.toggleOpenState(i);
                  }
                }
              }
            }
            var index = -1;
            var uri = this.parentNode.getAttribute("uri");
            if (uri) {
              var RDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
              index = view.getIndexOfResource(RDF.GetResource(uri));
            }
            view.selection.select(index);
            return index;
          ]]>
        </body>
      </method>
      <constructor>
        <![CDATA[
          this.setAttribute("ignorekeys", "true");
          this.parentNode.addEventListener("keypress", this.onKeyPressMenuList, true);
        ]]>
      </constructor>
      <destructor>
        <![CDATA[
          this.parentNode.removeEventListener("keypress", this.onKeyPressMenuList, true);
        ]]>
      </destructor>
    </implementation>
    <handlers>
      <handler event="mousemove" action="this.updateHover(event);"/>
      <handler event="click" button="0" action="if (this.updateHover(event) >= 0) this.fire();"/>
      <handler event="popupshowing">
        <![CDATA[
          this.parentNode.addEventListener("blur", this.onBlurMenuList);
          var box = this.tree.treeBoxObject;
          box.focused = true;
          var index = this.setInitialSelection();
          var height = box.view.rowCount * box.rowHeight;
          height += this.boxObject.height - box.treeBody.boxObject.height;
          this.height = height;
          if (index >= 0)
            setTimeout(function() { box.ensureRowIsVisible(index); }, 0);
        ]]>
      </handler>
      <handler event="popuphiding">
        <![CDATA[
          this.parentNode.removeEventListener("blur", this.onBlurMenuList);
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="folderSummary-popup" extends="chrome://global/content/bindings/popup.xml#tooltip">
    <content>
      <children>
        <xul:folderSummary/>
      </children>
    </content>
    <handlers>
      <handler event="popupshowing">
        <![CDATA[
          var folderTree = GetFolderTree();
          var { row, col } = folderTree.treeBoxObject.getCellAt(event.clientX, event.clientY);
          if (row == -1)
            return false;

          var msgFolder = GetFolderResource(folderTree, row).QueryInterface(Ci.nsIMsgFolder);
          var server = msgFolder.server;
          var popupValue = null;
          // Use the full newsgroup name as tooltip for abbreviated newsgroups.
          // Else show full cell content as tooltip for cropped cells.
          if (col.id == "folderNameCol" && !msgFolder.isServer &&
              !(msgFolder.flags & Ci.nsMsgFolderFlags.Virtual) &&
              (server instanceof Ci.nsINntpIncomingServer) &&
              server.abbreviate)
          {
            popupValue = msgFolder.name;
          }
          else if (folderTree.treeBoxObject.isCellCropped(row, col))
          {
            popupValue = folderTree.view.getCellText(row, col);
          }

          if (popupValue)
          {
            let tooltip = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "label");
            tooltip.setAttribute("value", popupValue);
            let folderSummary = document.getAnonymousNodes(this)[0];
            document.getAnonymousNodes(folderSummary)[0].appendChild(tooltip);
          }

          var newMessages = document.getAnonymousNodes(this)[0]
                                    .parseFolder(msgFolder, null, {});
          return popupValue || newMessages;
        ]]>
      </handler>

      <handler event="popuphiding">
        document.getAnonymousNodes(this)[0].clear();
      </handler>
    </handlers>
  </binding>

  <binding id="folderSummary">
    <content>
      <xul:vbox/>
    </content>

    <implementation>
      <field name="mMaxMsgHdrsInPopup">8</field>
      <property name="hasMessages" readonly="true" onget="return document.getAnonymousNodes(this)[0].hasChildNodes();"/>
      <method name="parseFolder">
        <parameter name="aFolder"/>
        <parameter name="aUrlListener"/>
        <parameter name="aOutAsync"/>
        <body>
          <![CDATA[
            // skip servers, Trash and Junk folders
            if (!aFolder || aFolder.isServer ||
                aFolder.getFlag(Ci.nsMsgFolderFlags.Junk) ||
                aFolder.getFlag(Ci.nsMsgFolderFlags.Trash))
              return false;
            var pref = Cc["@mozilla.org/preferences-service;1"]
                         .getService(Ci.nsIPrefBranch);
            var showPreviewText = pref.getBoolPref("mail.biff.alert.show_preview");
            var folderArray = new Array;
            if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual)
            {
              var dbFolderInfo = aFolder.msgDatabase.dBFolderInfo;
              var srchFolderUri = dbFolderInfo.getCharProperty("searchFolderUri");
              var srchFolderUriArray = srchFolderUri.split('|');
              var foldersAdded = 0;
              var RDF = Cc['@mozilla.org/rdf/rdf-service;1']
                          .getService(Ci.nsIRDFService);
              for (var i in srchFolderUriArray)
              {
                var realFolder = RDF.GetResource(srchFolderUriArray[i])
                                    .QueryInterface(Ci.nsIMsgFolder);
                if (!realFolder.isServer)
                  folderArray[foldersAdded++] = realFolder;
              }
            }
            else
              folderArray[0] = aFolder;
            var foundNewMsg = false;
            for (var folderIndex = 0; folderIndex < folderArray.length; folderIndex++)
            {
              aFolder = folderArray[folderIndex];
              // now get the database
              var msgDatabase = aFolder.msgDatabase;
              aFolder.msgDatabase = null;
              var msgKeys = {};
              var numMsgKeys = {};
              msgDatabase.getNewList(numMsgKeys, msgKeys);

              if (!numMsgKeys.value)
                continue;

              if (showPreviewText)
              {
                // fetchMsgPreviewText forces the previewText property to get generated
                // for each of the message keys.
                try {
                  aOutAsync.value = aFolder.fetchMsgPreviewText(msgKeys.value, numMsgKeys.value, false, aUrlListener);
                  aFolder.msgDatabase = null;
                }
                catch (ex)
                {
                  // fetchMsgPreviewText throws an error when we call it on a news folder, we should just not show
                  // the tooltip if this method returns an error.
                  aFolder.msgDatabase = null;
                  continue;
                }
              }
              // if fetching the preview text is going to be an asynch operation and the caller
              // is set up to handle that fact, then don't bother filling in any of the fields since
              // we'll have to do this all over again when the fetch for the preview text completes.
              // We don't expect to get called with a urlListener if we're doing a virtual folder.
              if (aOutAsync.value && aUrlListener)
                return false;
              var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                                    .createInstance(Ci.nsIScriptableUnicodeConverter);
              unicodeConverter.charset = "UTF-8";
              foundNewMsg = true;

              var index = 0;
              var hdrParser = Cc["@mozilla.org/messenger/headerparser;1"]
                                .getService(Ci.nsIMsgHeaderParser);
              while (document.getAnonymousNodes(this)[0].childNodes.length < this.mMaxMsgHdrsInPopup && index < numMsgKeys.value)
              {
                var msgPopup = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "folderSummaryMessage");
                var msgHdr = msgDatabase.GetMsgHdrForKey(msgKeys.value[index++]);

                var msgSubject = msgHdr.mime2DecodedSubject;
                const kMsgFlagHasRe = 0x0010; // MSG_FLAG_HAS_RE
                if(msgHdr.flags & kMsgFlagHasRe)
                  msgSubject = (msgSubject) ? "Re: " + msgSubject : "Re: ";

                msgPopup.setAttribute('subject', msgSubject);

                var previewText = msgHdr.getStringProperty('preview');
                // convert the preview text from utf-8 to unicode
                if (previewText)
                {
                  try
                  {
                    var text = unicodeConverter.ConvertToUnicode(previewText);
                    if (text)
                      msgPopup.setAttribute('previewText', text);
                  }
                  catch (ex) { }
                }

                var names = {};
                var emails = {};
                var numAddresses = hdrParser.parseHeadersWithArray(msgHdr.mime2DecodedAuthor, emails, names, {});
                msgPopup.setAttribute('sender', names.value[0] ? names.value[0] : emails.value[0]);
                msgPopup.messageUri = aFolder.getUriForMsg(msgHdr);
                msgPopup.folderUri = aFolder.URI;
                msgPopup.msgKey = msgHdr.messageKey;
                document.getAnonymousNodes(this)[0].appendChild(msgPopup);
              }
              if (document.getAnonymousNodes(this)[0].childNodes.length >= this.mMaxMsgHdrsInPopup)
                return true;
            }
            return foundNewMsg;
          ]]>
        </body>
      </method>

      <method name="clear">
        <body>
          <![CDATA[
            var containingBox = document.getAnonymousNodes(this)[0];
            while (containingBox.hasChildNodes())
              containingBox.lastChild.remove();
          ]]>
        </body>
      </method>
    </implementation>
  </binding>

  <binding id="folderSummary-message">
    <content>
      <xul:vbox class="folderSummaryMessage">
        <xul:hbox class="folderSummary-message-row">
          <xul:label anonid="subject" flex="1" class="folderSummary-subject" xbl:inherits="value=subject" crop="right"/>
          <xul:label anonid="sender"  class="folderSummary-sender" xbl:inherits="value=sender" crop="right"/>
          <xul:spring anonid="spring" flex="100%"/>
        </xul:hbox>
        <xul:description anonid="preview" class="folderSummary-message-row folderSummary-previewText" xbl:inherits="value=previewText" crop="right"></xul:description>
      </xul:vbox>
    </content>
    <implementation>
      <constructor>
        <![CDATA[
          var pref = Cc["@mozilla.org/preferences-service;1"]
                       .getService(Ci.nsIPrefBranch);
          if (!pref.getBoolPref("mail.biff.alert.show_preview"))
            document.getAnonymousElementByAttribute(this, "anonid", "preview").hidden = true;
          var hideSubject = !pref.getBoolPref("mail.biff.alert.show_subject");
          var hideSender = !pref.getBoolPref("mail.biff.alert.show_sender");
          if (hideSubject)
            document.getAnonymousElementByAttribute(this, "anonid", "subject").hidden = true;
          if (hideSender)
            document.getAnonymousElementByAttribute(this, "anonid", "sender").hidden = true;
          if (hideSubject && hideSender)
            document.getAnonymousElementByAttribute(this, "anonid", "spring").hidden = true;
        ]]>
      </constructor>
    </implementation>
    <handlers>
      <handler event="click" button="0">
        <![CDATA[
          var mailSession = Cc["@mozilla.org/messenger/services/session;1"].
                              getService(Ci.nsIMsgMailSession);
          var topmostMsgWindow;
          try {
           topmostMsgWindow = mailSession.topmostMsgWindow;
          } catch (ex) {}

          if (topmostMsgWindow)
          {
            // Bring window to the front
            topmostMsgWindow.domWindow.focus();

            try {
              // SelectFolder throws an exception if the folder is not in the current folder view
              mailSession.topmostMsgWindow.windowCommands.selectFolder(this.folderUri);
              mailSession.topmostMsgWindow.windowCommands.selectMessage(this.messageUri);
            } catch (ex) {}
          }
          else
          {
            // open a new window
            var mailWindowService = Cc["@mozilla.org/messenger/windowservice;1"].
                                      getService(Ci.nsIMessengerWindowService);
            mailWindowService.openMessengerWindowWithUri("mail:3pane", this.folderUri, this.msgKey);
          }

          if (gAlertListener)
            gAlertListener.observe(null, "alertclicksimplecallback", "");
        ]]>
      </handler>
    </handlers>
  </binding>
</bindings>