suite/mailnews/tabmail.xml
author Karsten Düsterloh <mnyromyr@tprac.de>
Fri, 19 Jun 2009 23:33:56 +0200
changeset 2894 5ce9e897332fe5f297ca9cbcd0e7d0c7d7a062c5
parent 2808 mail/base/content/tabmail.xml@61d5f8fd0fe2d3bca890b4b7e69af89c270df8f0
child 2944 5d02eacab64bb60e2d6046164118827275ea8d83
permissions -rw-r--r--
Bug 460960: fork TB's tabmail.xml from rev. 2808; sr-over-IRC=Neil

<?xml version="1.0"?>
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is tab email
#
# The Initial Developer of the Original Code is
#   David Bienvenu <bienvenu@nventure.com>.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#  Scott MacGregor <mscott@mozilla.org>
#  Andrew Sutherland <asutherland@asutherland.org>
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

<!DOCTYPE bindings [
<!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
%messengerDTD;
<!ENTITY % tabMailDTD SYSTEM "chrome://messenger/locale/tabmail.dtd" >
 %tabMailDTD;
 <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
 %globalDTD;
]>

<bindings id="tabmailBindings"
   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">

  <!-- Thunderbird's tab UI mechanism.
    -
    - We expect to be instantiated with the following children:
    - * One "tabpanels" child element whose id must be placed in the
    -   "panelcontainer" attribute on the element we are being bound to. We do
    -   this because it is important to allow overlays to contribute panels.
    -   When we attempted to have the immediate children of the bound element
    -   be propagated through use of the "children" tag, we found that children
    -   contributed by overlays did not propagate.
    - * Any children you want added to the right side of the tab bar.  This is
    -   primarily intended to allow for "open a BLANK tab" buttons, namely
    -   calendar and tasks.  For reasons similar to the tabpanels case, we
    -   expect the instantiating element to provide a child hbox for overlays
    -   to contribute buttons to.
    -
    - From a javascript perspective, there are three types of code that we
    -  expect to interact with:
    - 1) Code that wants to open new tabs.
    - 2) Code that wants to contribute one or more varieties of tabs.
    - 3) Code that wants to monitor to know when the active tab changes.
    -
    - Consumer code should use the following methods:
    - * openTab(aTabModeName, arguments...): Open a tab of the given "mode",
    -   passing the provided arguments.  The tab type author should tell you
    -   the modes they implement and the required/optional arguments.
    - * setTabTitle([aOptionalTabInfo]): Tells us that the title of the current
    -   tab (if no argument is provided) or provided tab needs to be updated.
    -   This will result in a call to the tab mode's logic to update the title.
    -   In the event this is not for the current tab, the caller is responsible
    -   for ensuring that the underlying tab mode is capable of providing a tab
    -   title when it is in the background.  (The is currently not the case for
    -   "folder" and "mail" modes because of their implementation.)
    - * removeCurrentTab(): Close the current tab.
    - * removeTabByNode(aTabElement): Close the tab whose tabmail-tab bound
    -   element is passed in.
    - Changing the currently displayed tab is accomplished by changing
    -  tabmail.tabContainer's selectedIndex or selectedItem property.
    -
    - Tab contributing code should define a tab type object and register it
    -  with us by calling registerTabType.  Each tab type can provide multiple
    -  tab modes.  The rationale behind this organization is that Thunderbird
    -  historically/currently uses a single 3-pane view to display both
    -  three-pane folder browsing and single message browsing across multiple
    -  tabs.  Each tab type has the ability to use a single tab panel for all
    -  of its display needs.  So Thunderbird's "mail" tab type covers both the
    -  "folder" (3-pane folder-based browsing)  and "message" (just a single
    -  message) tab modes.  Likewise, calendar/lightning currently displays both
    -  its calendar and tasks in the same panel.  A tab type can also create a
    -  new tabpanel for each tab as it is created.  In that case, the tab type 
    -  should probably only have a single mode unless there are a number of
    -  similar modes that can gain from code sharing.
    - The tab type definition should include the following attributes:
    - * name: The name of the tab-type, mainly to aid in debugging.
    - * panelId or perTabPanel: If using a single tab panel, the id of the
    -     panel must be provided in panelId.  If using one tab panel per tab,
    -     perTabPanel should be the XUL element name that should be created for
    -     each tab.  If a reason arises, in the future we might support
    -     this being a helper function to create and return the element.
    - * modes: An object whose attributes are mode names (which are
    -     automatically propagated to a 'name' attribute for debugging) and
    -     values are objects with the following attributes...
    - * any of the openTab/closeTab/saveTabState/showTab/onTitleChanged
    -     functions as described on the mode definitions.  These will only be
    -     called if the mode does not provide the functions.  Note that because
    -     the 'this' variable passed to the functions will always reference the
    -     tab type definition (rather than the mode definition), the mode
    -     functions can defer to the tab type functions by calling 
    -     this.functionName().  (This should prove convenient.)
    - Mode definition attributes:
    - * type: The "type" attribute to set on the displayed tab for CSS purposes.
    -     Generally, this would be the same as the mode name, but you can do as
    -     you please.
    - * isDefault: This should only be present and should be true for the tab
    -     mode that is the tab displayed automatically on startup.
    - * maxTabs: The maximum number of this mode that can be opened at a time.
    -     If this limit is reached, any additional calls to openTab for this
    -     mode will simply result in the first existing tab of this mode being
    -     displayed.
    - * shouldSwitchTo(arguments...): Optional function. Called when
    -     openTab is called on the top-level tabmail binding. It is used to
    -     decide if the openTab function should switch to an existing tab or
    -     actually open a new tab.
    -     If the openTab function should switch to an existing tab, return the
    -     index of that tab; otherwise return -1.
    - * openTab(aTab, arguments...): Called when a tab of the given mode is
    -     in the process of being opened.  aTab will have its "mode" attribute
    -     set to the mode definition of the tab mode being opened.  You should
    -     set the "title" attribute on it, and may set any other attributes
    -     you wish for your own use in subsequent functions.  Note that 'this'
    -     points to the tab type definition, not the mode definition as you
    -     might expect.  This allows you to place common logic code on the
    -     tab type for use by multiple modes and to defer to it.  Any arguments
    -     provided to the caller of tabmail.openTab will be passed to your
    -     function as well.
    - * closeTab(aTab): Called when aTab is being closed.  The tab need not be
    -     currently displayed.  You are responsible for properly cleaning up
    -     any state you preserved in aTab.
    - * saveTabState(aTab): Called when aTab is being switched away from so that
    -     you can preserve its state on aTab.  This is primarily for single
    -     tab panel implementations; you may not have much state to save if your
    -     tab has its own tab panel.
    - * showTab(aTab): Called when aTab is being displayed and you should
    -     restore its state (if required).
    - * onTitleChanged(aTab): Called when someone calls tabmail.setTabTitle() to
    -     hint that the tab's title needs to be updated.  This function should
    -     update aTab.title if it can.
    - Mode definition functions to do with menu/toolbar commands:
    - * supportsCommand(aTab, aCommand): Called when a menu or toolbar needs to
    -     be updated. Return true if you support that command in
    -     isCommandEnabled and doCommand, return false otherwise.
    - * isCommandEnabled(aTab, aCommand): Called when a menu or toolbar needs
    -     to be updated. Return true if the command can be executed at the
    -     current time, false otherwise.
    - * doCommand(aTab, aCommand): Called when a menu or toolbar command is to
    -     be executed. Perform the action appropriate to the command.
    - * onEvent(aTab, aEvent): This can be used to handle different events on
    -     the window.
    - * getBrowser(aTab): This function should return the browser element for
    -     your tab if there is one (return null or don't define this function
    -     otherwise). It is used for some toolkit functions that require a
    -     global "getBrowser" function, e.g. ZoomManager.
    -
    - Tab monitoring code is expected to be used for widgets on the screen
    -  outside of the tab box that need to update themselves as the active tab
    -  changes.  This is primarily intended to be used for the ThunderBar; if
    -  you are not the ThunderBar and this sounds appealing to you, please
    -  solicit discussion on your needs on the mozilla.dev.apps.thunderbird
    -  newsgroup.
    - Tab monitoring code (un)registers itself via (un)registerTabMonitor.
    -  The following functions should be provided on the monitor object:
    - * onTabTitleChanged(aTab): Called when the tab's title changes.
    - * onTabSwitched(aTab, aOldTab): Called when a new tab is made active.  If
    -     this is the first tab ever, aOldTab will be null, otherwise aOldTab
    -     will be the previously active tab.
    -->
  <binding id="tabmail">
    <resources>
      <stylesheet src="chrome://messenger/skin/tabmail.css"/>
    </resources>
    <content>
      <xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
                  onselect="if (!('updateCurrentTab' in this.parentNode) || event.target.localName != 'tabs')
                            return; this.parentNode.updateCurrentTab();">
        <xul:hbox class="tab-drop-indicator-bar" collapsed="true">
          <xul:hbox class="tab-drop-indicator" mousethrough="always"/>
        </xul:hbox>
        <xul:hbox class="tabmail-strip" collapsed="false" tooltip="_child" context="_child"
                  anonid="strip"
                  ondraggesture="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
                  ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
                  ondragdrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
                  ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
          <xul:tooltip onpopupshowing="return CreateToolbarTooltip(document, event);"/>
          <xul:menupopup anonid="tabContextMenu"
                onpopupshowing="
                  var tabmail = this.parentNode.parentNode.parentNode;
                  return tabmail.onTabContextMenuShowing(document.popupNode);">
            <xul:menuitem label="&closeTabCmd.label;" accesskey="&closeTabCmd.accesskey;"
                          oncommand="var tabmail = this.parentNode.parentNode.parentNode.parentNode;
                                     tabmail.removeTabByNode(document.popupNode);"/>
          </xul:menupopup>
          <xul:tabs class="tabmail-tabs" flex="1"
                    anonid="tabcontainer"
                    setfocus="false"
                    onclick="this.parentNode.parentNode.parentNode.onTabClick(event);">
            <xul:tab selected="true" validate="never" type="folder"
                     maxwidth="250" width="0" minwidth="100" flex="100"
                     class="tabmail-tab" crop="end"/>
            <children/>
          </xul:tabs>
        </xul:hbox>
        <!-- Remember, user of this binding, you need to provide tabpanels!  -->
        <children includes="tabpanels"/>
      </xul:tabbox>
    </content>

    <implementation implements="nsIController">
      <constructor>
        window.controllers.insertControllerAt(0, this);
      </constructor>
      <destructor>
        window.controllers.removeController(this);
      </destructor>
      <field name="currentTabInfo">
        null
      </field>
      <field name="tabTypes" readonly="true">
        new Object()
      </field>
      <field name="tabModes" readonly="true">
        new Object()
      </field>
      <field name="defaultTabMode">
        null
      </field>
      <field name="tabInfo" readonly="true">
        new Array()
      </field>
      <field name="tabStrip" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "strip");
      </field>
      <field name="tabContainer" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
      </field>
      <field name="panelContainer" readonly="true">
        document.getElementById(this.getAttribute("panelcontainer"));
      </field>
      <field name="tabMonitors" readonly="true">
        new Array()
      </field>
      <method name="registerTabType">
        <parameter name="aTabType"/>
        <body><![CDATA[
          if (aTabType.name in this.tabTypes)
            return;
        
          this.tabTypes[aTabType.name] = aTabType;
          for (let [modeName, modeDetails] in Iterator(aTabType.modes)) {
            modeDetails.name = modeName;
            modeDetails.tabType = aTabType;
            modeDetails.tabs = [];
            this.tabModes[modeName] = modeDetails;
            if (modeDetails.isDefault)
              this.defaultTabMode = modeDetails;
          }
          
          aTabType.panel = document.getElementById(aTabType.panelId);
        ]]></body>
      </method>
      <method name="registerTabMonitor">
        <parameter name="aTabMonitor"/>
        <body><![CDATA[
          if (this.tabMonitors.indexOf(aTabMonitor) == -1)
            this.tabMonitors.push(aTabMonitor);
        ]]></body>
      </method>
      <method name="unregisterTabMonitor">
        <parameter name="aTabMonitor"/>
        <body><![CDATA[
          if (this.tabMonitors.indexOf(aTabMonitor) >= 0)
            this.tabMonitors.splice(this.tabMonitors.indexOf(aTabMonitor), 1);
        ]]></body>
      </method>
      <method name="openFirstTab">
        <body><![CDATA[
          // From the moment of creation, our XBL binding already has a visible
          //  tab.  We need to create a tab information structure for this tab.
          //  In the process we also generate a synthetic tab title changed
          //  event to ensure we have an accurate title.  We assume the tab
          //  contents will set themselves up correctly.
          if (this.tabInfo.length == 0) {
            let firstTab = {mode: this.defaultTabMode, canClose: false};
            firstTab.mode.tabs.push(firstTab);
           
            this.tabInfo[0] = this.currentTabInfo = firstTab;
            this.setTabTitle(firstTab);

            if (this.tabMonitors.length) {
              for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
                tabMonitor.onTabSwitched(tab, null);
            }
          }
        ]]></body>
      </method>
      <method name="openTab">
        <parameter name="aTabModeName"/>
        <body>
            <![CDATA[
          let tabMode = this.tabModes[aTabModeName];
          // if we are already at our limit for this mode, show an existing one
          if (tabMode.tabs.length == tabMode.maxTabs) {
            let desiredTab = tabMode.tabs[0];
            this.tabContainer.selectedIndex = this.tabInfo.indexOf(desiredTab);
            return;
          }

          // If the mode wants us to, we should switch to an existing tab
          // rather than open a new one.
          let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
                                   tabMode.tabType.shouldSwitchTo;

          if (shouldSwitchToFunc) {
            let args = Array.prototype.slice.call(arguments, 1);
            let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, args);
            if (tabIndex >= 0) {
              this.selectTabByIndex(null, tabIndex);
              return;
            }
          }

          // we need to save the state before it gets corrupted
          this.saveCurrentTabState();

          let tab = {mode: tabMode, canClose: true};
          tabMode.tabs.push(tab);

          var t = document.createElementNS(
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
            "tab");
          t.setAttribute("crop", "end");
          t.maxWidth = this.tabContainer.mTabMaxWidth;
          t.minWidth = this.tabContainer.mTabMinWidth;
          t.width = 0;
          t.setAttribute("flex", "100");
          t.setAttribute("validate", "never");
          t.className = "tabmail-tab";
          this.tabContainer.appendChild(t);
          this.tabContainer.appendChild(t);
          if (this.tabContainer.collapsed) {
            this.tabContainer.collapsed = false;
            this.tabContainer.adjustTabstrip();
          }

          let oldTab = this.currentTabInfo;

          // the order of the following statements is important
          this.currentTabInfo = tab;
          this.tabInfo[this.tabContainer.childNodes.length - 1] = tab;
          // this has a side effect of calling updateCurrentTab, but our
          //  setting currentTabInfo above will cause it to take no action.
          this.tabContainer.selectedIndex =
            this.tabContainer.childNodes.length - 1;

          // make sure we are on the right panel
          if (tab.mode.tabType.perTabPanel) {
            // should we create the element for them, or will they do it?
            if (typeof(tab.mode.tabType.perTabPanel) == "string") {
              tab.panel = document.createElementNS(
                "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                tab.mode.tabType.perTabPanel);
            }
            else {
              tab.panel = tab.mode.tabType.perTabPanel(tab);
            }
            this.panelContainer.appendChild(tab.panel);
            this.panelContainer.selectedPanel = tab.panel;
          }
          else
            this.panelContainer.selectedPanel = tab.mode.tabType.panel;
            
          let tabOpenFunc = tab.mode.openTab || tab.mode.tabType.openTab;
          let args = [tab].concat(Array.prototype.slice.call(arguments, 1));
          tabOpenFunc.apply(tab.mode.tabType, args);
          
          if (this.tabMonitors.length) {
            for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
              tabMonitor.onTabSwitched(tab, oldTab);
          }
          
          t.setAttribute("label", tab.title);
          
          let docTitle = tab.title;
#ifndef XP_MACOSX
          docTitle += " - " + gBrandBundle.getString("brandFullName");
#endif
          document.title = docTitle;
          
          // for styling purposes, apply the type to the tab...
          t.setAttribute('type', tab.mode.type);
        ]]></body>
      </method>
      <method name="selectTabByMode">
        <parameter name="aTabModeName"/>
        <body><![CDATA[
          let tabMode = this.tabModes[aTabModeName];
          if (tabMode.tabs.length) {
            let desiredTab = tabMode.tabs[0];
            this.tabContainer.selectedIndex = this.tabInfo.indexOf(desiredTab);
          }
        ]]></body>
      </method>
      <method name="selectTabByIndex">
        <parameter name="aEvent"/>
        <parameter name="aIndex"/>
        <body><![CDATA[
          // count backwards for aIndex < 0
          if (aIndex < 0)
            aIndex += this.tabInfo.length;

          if (aIndex >= 0 &&
              aIndex < this.tabInfo.length &&
              aIndex != this.tabContainer.selectedIndex) {
            this.tabContainer.selectedIndex = aIndex;
          }

          if (aEvent) {
            aEvent.preventDefault();
            aEvent.stopPropagation();
          }
        ]]></body>
      </method>
      <method name="closeTabs">
        <body>
          <![CDATA[
            for (var i = 0; i < this.tabInfo.length; i++) {
              let tab = this.tabInfo[i];
              
              let tabCloseFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
              tabCloseFunc.call(tab.mode.tabType, tab);
            }
          ]]>
        </body>
      </method>
      <method name="removeTabByNode">
        <parameter name="aTabNode"/>
        <body>
          <![CDATA[
            // Find and locate the tab in our list.
            let iTab, numTabs = this.tabContainer.childNodes.length;
            for (iTab = 0; iTab < numTabs; iTab++)
              if (this.tabContainer.childNodes[iTab] == aTabNode)
                break;
            let tab = this.tabInfo[iTab];

            if (!tab.canClose)
              return;
            
            let closeFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
            closeFunc.call(tab.mode.tabType, tab);
             
            this.tabInfo.splice(iTab, 1);
            tab.mode.tabs.splice(tab.mode.tabs.indexOf(tab), 1);
            this.tabContainer.removeChild(aTabNode);
            if (this.tabContainer.selectedIndex == -1)
              this.tabContainer.selectedIndex = (iTab == --numTabs) ?
                iTab - 1 : iTab;
            if (this.currentTabInfo == tab)
              this.updateCurrentTab();
              
            if (tab.panel) {
              this.panelContainer.removeChild(tab.panel);
              delete tab.panel;
            }
            if (numTabs == 1 && this.tabContainer.mAutoHide)
              this.tabContainer.collapsed = true;
          ]]>
        </body>
      </method>
      <!-- getBrowserForSelectedTab is required as some toolkit functions
           require a getBrowser() function. -->
      <method name="getBrowserForSelectedTab">
        <body><![CDATA[
          if (!this.currentTabInfo)
            this.currentTabInfo = this.tabInfo[0];

          let tab = this.currentTabInfo;

          let browserFunc = tab.mode.getBrowser || tab.mode.tabType.getBrowser;
          if (browserFunc)
            return browserFunc.call(tab.mode.tabType, tab);

          return null;
        ]]></body>
      </method>
      <method name="removeCurrentTab">
        <body><![CDATA[
          this.removeTabByNode(
            this.tabContainer.childNodes[this.tabContainer.selectedIndex]);
        ]]></body>
      </method>
      <!--  UpdateCurrentTab - called in response to changing the current tab -->
      <method name="updateCurrentTab">
        <body>
          <![CDATA[
            if (this.currentTabInfo != this.tabInfo[this.tabContainer.selectedIndex])
            {
              if (this.currentTabInfo)
                this.saveCurrentTabState();
              
              let oldTab = this.currentTabInfo;
              let tab = this.currentTabInfo =
                this.tabInfo[this.tabContainer.selectedIndex];

              this.panelContainer.selectedPanel = tab.panel ||
                                                  tab.mode.tabType.panel;

              let showTabFunc = tab.mode.showTab || tab.mode.tabType.showTab;
              showTabFunc.call(tab.mode.tabType, tab);

              if (this.tabMonitors.length) {
                for each (let [i, tabMonitor] in Iterator(this.tabMonitors))
                  tabMonitor.onTabSwitched(tab, oldTab);
              }


              let docTitle = tab.title;
#ifndef XP_MACOSX
              docTitle += " - " + gBrandBundle.getString("brandFullName");
#endif
              document.title = docTitle;

              // Update the toolbar status - we don't need to do menus as they
              // do themselves when we open them.
              UpdateMailToolbar("tabmail");
            }
          ]]>
        </body>
      </method>
      <method name="saveCurrentTabState">
        <body>
          <![CDATA[
            if (!this.currentTabInfo)
              this.currentTabInfo = this.tabInfo[0];
            let tab = this.currentTabInfo;
            
            // save the old tab state before we change the current tab
            let saveTabFunc = tab.mode.saveTabState ||
                              tab.mode.tabType.saveTabState;
            saveTabFunc.call(tab.mode.tabType, tab);
          ]]>
        </body>
      </method>
      <method name="onTabClick">
        <parameter name="event"/>
        <body>
          <![CDATA[
            // a middle mouse button click on a tab is a short cut for closing a tab
            if (event.button != 1 || event.target.localName != 'tab')
              return;
            this.removeTabByNode(event.target);
            event.stopPropagation();
          ]]>
        </body>
      </method>
      <method name="setTabTitle">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            // First find the tab and its index.
            let tab;
            let index;
            if (aTab) {
              tab = aTab;
              for (index = 0; index < this.tabInfo.length; ++index) {
                if (tab == this.tabInfo[index])
                  break;
              }
            }
            else {
              index = this.tabContainer.selectedIndex;
              tab = this.tabInfo[index];
            }

            // on startup, we may not have a tab...
            if (tab)
            {
              let tabNode =
                this.tabContainer.childNodes[index];

              let titleChangeFunc = tab.mode.onTitleChanged ||
                                    tab.mode.tabType.onTitleChanged;
              if (titleChangeFunc)
                titleChangeFunc.call(tab.mode.tabType, tab, tabNode);

              if (this.tabMonitors.length) {
                for each (let [index, tabMonitor] in Iterator(this.tabMonitors))
                  tabMonitor.onTabTitleChanged(tab);
              }

              tabNode.setAttribute("label", tab.title);

              // Update the window title if we're the displayed tab.
              if (index == this.tabContainer.selectedIndex) {
                let docTitle = tab.title;
#ifndef XP_MACOSX
                docTitle += " - " + gBrandBundle.getString("brandFullName");
#endif
                document.title = docTitle;
              }
            }
          ]]>
        </body>
      </method>
      <method name="onTabContextMenuShowing">
        <parameter name="aTabNode"/>
        <body>
          <![CDATA[
            // To determine whether the 'Close Tab' context menu should be
            // visible according to the tab node that was being clicked on.
            // If the tab node is not closable, the context menu should not
            // be shown.
            if (aTabNode.localName != "tab")
              return false;

            for (let iTab = 0; iTab < this.tabContainer.childNodes.length; iTab++) {
              if (this.tabContainer.childNodes[iTab] == aTabNode)
                return this.tabInfo[iTab].canClose;
            }
            return false;
          ]]>
        </body>
      </method>
      <method name="supportsCommand">
        <parameter name="aCommand"/>
        <body>
           <![CDATA[
             let tab = this.currentTabInfo;

             // This can happen if we're starting up and haven't got a tab
             // loaded yet.
             if (!tab)
               return false;

             let supportsCommandFunc = tab.mode.supportsCommand ||
                                       tab.mode.tabType.supportsCommand;
             if (supportsCommandFunc)
               return supportsCommandFunc.call(tab.mode.tabType, tab, aCommand);

             return false;
           ]]>
        </body>
      </method>
      <method name="isCommandEnabled">
        <parameter name="aCommand"/>
        <body>
           <![CDATA[
             let tab = this.currentTabInfo;

             // This can happen if we're starting up and haven't got a tab
             // loaded yet.
             if (!tab)
               return false;

             let isCommandEnabledFunc = tab.mode.isCommandEnabled ||
                                        tab.mode.tabType.isCommandEnabled;
             if (isCommandEnabledFunc)
               return isCommandEnabledFunc.call(tab.mode.tabType, tab, aCommand);

             return false;
           ]]>
        </body>
      </method>
      <method name="doCommand">
        <parameter name="aCommand"/>
        <body>
           <![CDATA[
             let tab = this.currentTabInfo;

             // This can happen if we're starting up and haven't got a tab
             // loaded yet.
             if (!tab)
               return;

             let doCommandFunc = tab.mode.doCommand ||
                                 tab.mode.tabType.doCommand;
             if (doCommandFunc)
               doCommandFunc.call(tab.mode.tabType, tab, aCommand);
           ]]>
        </body>
      </method>
      <method name="onEvent">
        <parameter name="aEvent"/>
        <body>
           <![CDATA[
             let tab = this.currentTabInfo;

             // This can happen if we're starting up and haven't got a tab
             // loaded yet.
             if (!tab)
               return;

             let onEventFunc = tab.mode.onEvent ||
                               tab.mode.tabType.onEvent;
             if (onEventFunc)
               return onEventFunc.call(tab.mode.tabType, tab, aEvent);

             return false;
           ]]>
        </body>
      </method>
    </implementation>
  </binding>
  
 <binding id="tabmail-tab" display="xul:box"
           extends="chrome://global/content/bindings/tabbox.xml#tab">
    <content chromedir="&locale.dir;"
             closetabtext="&closeTab.label;">
      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
        <xul:image class="tab-icon" xbl:inherits="validate,src=image"/>
        <xul:label class="tab-text" xbl:inherits="value=label,accesskey,crop,disabled" flex="1"/>
      </xul:hbox>
      <xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button"/>
    </content>

    <implementation>
      <field name="mOverCloseButton">false</field>
      <field name="mCorrespondingMenuitem">null</field>
    </implementation>

    <handlers>
      <handler event="mouseover">
        var anonid = event.originalTarget.getAttribute("anonid");
        if (anonid == "close-button")
          this.mOverCloseButton = true;
      </handler>
      <handler event="mouseout">
        var anonid = event.originalTarget.getAttribute("anonid");
        if (anonid == "close-button")
          this.mOverCloseButton = false;
      </handler>
      <handler event="mousedown" button="0" phase="capturing">
      <![CDATA[
        if (this.mOverCloseButton)
          event.stopPropagation();
      ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="tabmail-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
    <content>
      <xul:toolbarbutton class="scrollbutton-up" collapsed="true"
                         xbl:inherits="orient"
                         anonid="scrollbutton-up"
                         onmousedown="_startScroll(-1);"
                         onmouseup="_stopScroll();"
                         onmouseout="_stopScroll();"
                         chromedir="&locale.dir;"/>
      <xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
        <children/>
      </xul:scrollbox>
      <xul:stack align="center" pack="end" class="scrollbutton-down-stack">
        <xul:hbox flex="1" class="scrollbutton-down-box" 
                           collapsed="true" anonid="down-box"/>
        <xul:hbox flex="1" class="scrollbutton-down-box-animate" 
                           collapsed="true" anonid="down-box-animate"/>
        <xul:toolbarbutton class="scrollbutton-down" collapsed="true"
                           xbl:inherits="orient"
                           anonid="scrollbutton-down"
                           onmousedown="_startScroll(1);"
                           onmouseup="_stopScroll();"
                           onmouseout="_stopScroll();"
                           chromedir="&locale.dir;"/>
      </xul:stack>
    </content>
    <implementation>
      <field name="_scrollButtonDownBox">
        document.getAnonymousElementByAttribute(this, "anonid", "down-box");
      </field>
      <field name="_scrollButtonDownBoxAnimate">
        document.getAnonymousElementByAttribute(this, "anonid", "down-box-animate");
      </field>
    </implementation>
    <handlers>
      <handler event="underflow"><![CDATA[
        // filter underflow events which were dispatched on nested scrollboxes
        if (event.target != this)
          return;

        // Ignore vertical events.
        if (event.detail == 0) {
          return;
        }

        this._scrollButtonDownBox.collapsed = true;
        this._scrollButtonDownBoxAnimate.collapsed = true;
      ]]></handler>

      <handler event="overflow"><![CDATA[
        // filter underflow events which were dispatched on nested scrollboxes
        if (event.target != this)
          return;

        // Ignore vertical events.
        if (event.detail == 0) {
          return;
        }

        this._scrollButtonDownBox.collapsed = false;
        this._scrollButtonDownBoxAnimate.collapsed = false;
      ]]></handler>

      <handler event="UpdatedScrollButtonsDisabledState"><![CDATA[
        // filter underflow events which were dispatched on nested scrollboxes
        if (event.target != this)
          return;

        // fix for bug #352353
        // unlike the scrollup button on the tab strip (which is a 
        // simple toolbarbutton) the scrolldown button is 
        // a more complicated stack of boxes and a toolbarbutton
        // so that we can animate when a tab is opened offscreen.
        // in order to style the box with the actual background image
        // we need to manually set the disable state to match the
        // disable state of the toolbarbutton.
        this._scrollButtonDownBox
            .setAttribute("disabled", this._scrollButtonDown.disabled);
      ]]></handler>

    </handlers>
  </binding>

  <binding id="tabmail-tabs"
           extends="chrome://global/content/bindings/tabbox.xml#tabs">
    <content>
      <xul:arrowscrollbox anonid="arrowscrollbox" class="tabmail-arrowscrollbox" flex="1"
                          xbl:inherits="smoothscroll" orient="horizontal" style="min-width: 1px;">
        <children includes="tab"/>
      </xul:arrowscrollbox>
      <children/>
      <xul:stack align="center" pack="end" class="tabs-alltabs-stack">
        <xul:hbox flex="1" class="tabs-alltabs-box" anonid="alltabs-box"/>
        <xul:hbox flex="1" class="tabs-alltabs-box-animate" 
                  anonid="alltabs-box-animate"/>
        <xul:toolbarbutton class="tabs-alltabs-button" type="menu"
                           anonid="alltabs-button"
                           tooltipstring="&listAllTabs.label;">
          <xul:menupopup class="tabs-alltabs-popup"
                         anonid="alltabs-popup" 
                         position="after_end"/>
        </xul:toolbarbutton>
      </xul:stack>
      <xul:hbox class="tabs-closebutton-box" align="center" pack="end" anonid="tabstrip-closebutton">
        <xul:toolbarbutton class="close-button tabs-closebutton"/>
      </xul:hbox>
    </content>
    <implementation implements="nsITimerCallback, nsIDOMEventListener">
      <constructor>
        <![CDATA[
          var pb2 =
              Components.classes['@mozilla.org/preferences-service;1'].
              getService(Components.interfaces.nsIPrefBranch2);

          try {
            this.mTabMinWidth = pb2.getIntPref("mail.tabs.tabMinWidth");
          } catch (e) {
          }
          try {
            this.mTabMaxWidth = pb2.getIntPref("mail.tabs.tabMaxWidth");
          } catch (e) {
          }
          try {
            this.mTabClipWidth = pb2.getIntPref("mail.tabs.tabClipWidth");
          } catch (e) {
          }
          try {
            this.mCloseButtons = pb2.getIntPref("mail.tabs.closeButtons");
          } catch (e) {
          }
          try {
            this.mAutoHide = pb2.getBoolPref("mail.tabs.autoHide");
          } catch (e) {
          }

          if (this.mAutoHide)
            this.collapsed = true;

          this.firstChild.minWidth = this.mTabMinWidth;
          this.firstChild.maxWidth = this.mTabMaxWidth;
          this.adjustTabstrip();

          pb2.addObserver("mail.tabs.", this._prefObserver, false);

          window.addEventListener("resize", this, false);

          // Listen to overflow/underflow events on the tabstrip,
          // we cannot put these as xbl handlers on the entire binding because
          // they would also get called for the all-tabs popup scrollbox.
          // Also, we can't rely on event.target becuase these are all
          // anonymous nodes.
          this.mTabstrip.addEventListener("overflow", this, false);
          this.mTabstrip.addEventListener("underflow", this, false);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          var pb2 =
              Components.classes['@mozilla.org/preferences-service;1'].
              getService(Components.interfaces.nsIPrefBranch2);
          pb2.removeObserver("mail.tabs.", this._prefObserver);

          // Release timer to avoid reference cycles.
          if (this._animateTimer) {
            this._animateTimer.cancel();
            this._animateTimer = null;
          }

          this.mTabstrip.removeEventListener("overflow", this, false);
          this.mTabstrip.removeEventListener("underflow", this, false);
        ]]>
      </destructor>

      <field name="mTabstripWidth">0</field>

      <field name="mTabstrip">
        document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
      </field>

      <field name="mTabstripClosebutton">
        document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
      </field>

      <field name="_prefObserver">({
        tabbox: this,
  
        observe: function(subject, topic, data)
        {
          if (topic == "nsPref:changed") {
            subject.QueryInterface(Components.interfaces.nsIPrefBranch);
            switch (data) {
            case "mail.tabs.closeButtons":
              this.tabbox.mCloseButtons = subject.getIntPref("mail.tabs.closeButtons");
              this.tabbox.adjustTabstrip();
              break;
            case "mail.tabs.autoHide":
              this.tabbox.mAutoHide = subject.getBoolPref("mail.tabs.autoHide");
              break;
            }
          }
        },
  
        QueryInterface: function(aIID)
        {
          if (aIID.equals(Components.interfaces.nsIObserver) ||
              aIID.equals(Components.interfaces.nsISupports))
            return this;
          throw Components.results.NS_NOINTERFACE;
        }
        });
      </field>
      <field name="mTabMinWidth">100</field>
      <field name="mTabMaxWidth">250</field>
      <field name="mTabClipWidth">140</field>
      <field name="mCloseButtons">1</field>

      <field name="_mAutoHide">false</field>
      <property name="mAutoHide" onget="return this._mAutoHide;">
        <setter><![CDATA[
          if (val != this._mAutoHide) {
            if (this.childNodes.length == 1)
              this.collapsed = val;
            this._mAutoHide = val;
          }
          return val;
        ]]></setter>
      </property>

      <method name="adjustTabstrip">
        <body><![CDATA[
          // modes for tabstrip
          // 0 - activetab  = close button on active tab only
          // 1 - alltabs    = close buttons on all tabs
          // 2 - noclose    = no close buttons at all
          // 3 - closeatend = close button at the end of the tabstrip
          switch (this.mCloseButtons) {
          case 0:
            this.setAttribute("closebuttons", "activetab");
            break;
          case 1:
            var width = this.firstChild.boxObject.width;
            // 0 width is an invalid value and indicates
            // an item without display, so ignore.
            if (width > this.mTabClipWidth || width == 0)
              this.setAttribute("closebuttons", "alltabs");
            else
              this.setAttribute("closebuttons", "activetab");
            break;
          case 2:
          case 3:
            this.setAttribute("closebuttons", "noclose");
            break;
          }
          this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
        ]]></body>
      </method>
        
      <field name="_mPrefs">null</field>
      <property name="mPrefs" readonly="true">
        <getter>
        <![CDATA[
          if (!this._mPrefs) {
            this._mPrefs =
              Components.classes['@mozilla.org/preferences-service;1'].
              getService(Components.interfaces.nsIPrefBranch2);
          }
          return this._mPrefs;
        ]]>
        </getter>
      </property>
        
      <method name="_handleTabSelect">
        <body><![CDATA[
          this.mTabstrip.ensureElementIsVisible(this.selectedItem);
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "overflow":
              this.setAttribute("overflow", "true");
              this.mTabstrip.scrollBoxObject
                            .ensureElementIsVisible(this.selectedItem);
              break;
            case "underflow":
              this.removeAttribute("overflow");
              break;
            case "resize":
              var width = this.mTabstrip.boxObject.width;
              if (width != this.mTabstripWidth) {
                this.adjustTabstrip();
                // XXX without this line the tab bar won't budge
                this.mTabstrip.scrollByPixels(1);
                this._handleTabSelect();
                this.mTabstripWidth = width;
              }
              break;
          }
        ]]></body>
      </method>

      <field name="mAllTabsPopup">
        document.getAnonymousElementByAttribute(this, 
                                                "anonid", "alltabs-popup");
      </field>

      <field name="mAllTabsBoxAnimate">
        document.getAnonymousElementByAttribute(this, 
                                                "anonid",
                                                "alltabs-box-animate");
      </field>

      <field name="mDownBoxAnimate">
        this.mTabstrip._scrollButtonDownBoxAnimate;
      </field>

      <field name="mAllTabsButton">
        document.getAnonymousElementByAttribute(this, 
                                                "anonid", "alltabs-button");
      </field>

      <field name="_animateTimer">null</field>
      <field name="_animateStep">-1</field>
      <field name="_animateDelay">25</field>
      <field name="_animatePercents">
       [1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
        0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
        0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
        0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
        0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
        0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
      </field>

      <method name="_stopAnimation">
        <body><![CDATA[
          if (this._animateStep != -1) {
            if (this._animateTimer)
              this._animateTimer.cancel();

            this._animateStep = -1;
            this.mAllTabsBoxAnimate.style.opacity = 0.0;
            this.mDownBoxAnimate.style.opacity = 0.0;
          }
        ]]></body>
      </method>
      
      <method name="_notifyBackgroundTab">
        <parameter name="aTab"/>
        <body><![CDATA[
          var tsbo = this.mTabstrip.scrollBoxObject;
          var tsboStart = tsbo.screenX;
          var tsboEnd = tsboStart + tsbo.width;

          var ctbo = aTab.boxObject;
          var ctboStart = ctbo.screenX;
          var ctboEnd = ctboStart + ctbo.width;

          // only start the flash timer if the new tab (which was loaded in
          // the background) is not completely visible
          if (tsboStart > ctboStart || ctboEnd > tsboEnd) {
            this._animateStep = 0;

            if (!this._animateTimer) 
              this._animateTimer =
                Components.classes["@mozilla.org/timer;1"]
                          .createInstance(Components.interfaces.nsITimer);
            else
               this._animateTimer.cancel();

            this._animateTimer.initWithCallback(this,
                         this._animateDelay,
                         Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
          }
        ]]></body>
      </method>
      
      <method name="notify">
        <parameter name="aTimer"/>
        <body><![CDATA[
          if (!document)
            aTimer.cancel();

          var percent = this._animatePercents[this._animateStep];
          this.mAllTabsBoxAnimate.style.opacity = percent;
          this.mDownBoxAnimate.style.opacity = percent;

          if (this._animateStep < (this._animatePercents.length - 1))
            this._animateStep++;
          else
            this._stopAnimation();
        ]]></body>
      </method>
    </implementation>
    <handlers>
      <handler event="TabSelect" action="this._handleTabSelect();"/>
      <handler event="mouseover"><![CDATA[
        if (event.originalTarget == this.mAllTabsButton) {
          this.mAllTabsButton
              .setAttribute("tooltiptext",
                            this.mAllTabsButton.getAttribute("tooltipstring"));
        }
        else
          this.mAllTabsButton.removeAttribute("tooltiptext");
      ]]></handler>
    </handlers>
  </binding>
  
  <!-- alltabs-popup binding
       This binding relies on the structure of the tabbrowser binding.
       Therefore it should only be used as a child of the tabs element.
       This binding is exposed as a pseudo-public-API so themes can customize
       the tabbar appearance without having to be scriptable
       (see globalBindings.xml in Pinstripe for example).
  -->
  <binding id="tabmail-alltabs-popup"
           extends="chrome://global/content/bindings/popup.xml#popup">
    <implementation implements="nsIDOMEventListener">
      <field name="_xulWindow">
        null
      </field>

      <constructor><![CDATA[
        // We cannot cache the XULBrowserWindow object itself since it might
        // be set after this binding is constructed.
        try {
          this._xulWindow = 
            window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                  .getInterface(Components.interfaces.nsIWebNavigation)
                  .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
                  .treeOwner
                  .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                  .getInterface(Components.interfaces.nsIXULWindow);
        }
        catch(ex) { }
      ]]></constructor>

      <method name="_menuItemOnCommand">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var tabcontainer = document.getBindingParent(this);
          tabcontainer.selectedItem = aEvent.target.tab;
        ]]></body>
      </method>

      <method name="_tabOnAttrModified">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var menuItem = aEvent.target.mCorrespondingMenuitem;
          if (menuItem) {
            var attrName = aEvent.attrName;
            switch (attrName) {
              case "label":
              case "crop":
              case "busy":
              case "image":
              case "selected":
                if (aEvent.attrChange == aEvent.REMOVAL)
                  menuItem.removeAttribute(attrName);
                else
                  menuItem.setAttribute(attrName, aEvent.newValue);
            }
          }
        ]]></body>
      </method>

      <method name="_tabOnTabClose">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var menuItem = aEvent.target.mCorrespondingMenuitem;
          if (menuItem)
            this.removeChild(menuItem);
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!aEvent.isTrusted)
            return;

          switch (aEvent.type) {
            case "command":
              this._menuItemOnCommand(aEvent);
              break;
            case "DOMAttrModified":
              this._tabOnAttrModified(aEvent);
              break;
            case "TabClose":
              this._tabOnTabClose(aEvent);
              break;
            case "TabOpen":
              this._createTabMenuItem(aEvent.originalTarget);
              break;
            case "scroll":
              this._updateTabsVisibilityStatus();
              break;
          }
        ]]></body>
      </method>

      <method name="_updateTabsVisibilityStatus">
        <body><![CDATA[
          var tabContainer = document.getBindingParent(this);
          // We don't want menu item decoration unless there is overflow.
          if (tabContainer.getAttribute("overflow") != "true")
            return;

          var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
          for (var i = 0; i < this.childNodes.length; i++) {
            var curTabBO = this.childNodes[i].tab.boxObject;
            if (curTabBO.screenX >= tabstripBO.screenX &&
                curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
              this.childNodes[i].setAttribute("tabIsVisible", "true"); 
            else
              this.childNodes[i].removeAttribute("tabIsVisible");
          }
        ]]></body>
      </method>

      <method name="_createTabMenuItem">
        <parameter name="aTab"/>
        <body><![CDATA[
          var menuItem = document.createElementNS(
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 
            "menuitem");

          menuItem.setAttribute("class", "menuitem-iconic alltabs-item");

          menuItem.setAttribute("label", aTab.label);
          menuItem.setAttribute("crop", aTab.getAttribute("crop"));
          menuItem.setAttribute("image", aTab.getAttribute("image"));

          if (aTab.hasAttribute("busy"))
            menuItem.setAttribute("busy", aTab.getAttribute("busy"));
          if (aTab.selected)
            menuItem.setAttribute("selected", "true");

          // Keep some attributes of the menuitem in sync with its
          // corresponding tab (e.g. the tab label)
          aTab.mCorrespondingMenuitem = menuItem;
          aTab.addEventListener("DOMAttrModified", this, false);
          aTab.addEventListener("TabClose", this, false);
          menuItem.tab = aTab;
          menuItem.addEventListener("command", this, false);

          this.appendChild(menuItem);
          return menuItem;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="popupshowing">
      <![CDATA[
        // set up the menu popup
        var tabcontainer = document.getBindingParent(this);
        var tabs = tabcontainer.childNodes;

        // Listen for changes in the tab bar.
        var tabbrowser = document.getBindingParent(tabcontainer);
        tabbrowser.addEventListener("TabOpen", this, false);
        tabcontainer.mTabstrip.addEventListener("scroll", this, false);

        // if an animation is in progress and the user
        // clicks on the "all tabs" button, stop the animation
        tabcontainer._stopAnimation();

        for (var i = 0; i < tabs.length; i++) {
          this._createTabMenuItem(tabs[i]);
        }
        this._updateTabsVisibilityStatus();
      ]]></handler>

      <handler event="popuphiding">
      <![CDATA[
        // clear out the menu popup and remove the listeners
        while (this.hasChildNodes()) {
          var menuItem = this.lastChild;
          menuItem.removeEventListener("command", this, false);
          menuItem.tab.removeEventListener("DOMAttrModified", this, false);
          menuItem.tab.removeEventListener("TabClose", this, false);
          menuItem.tab.mCorrespondingMenuitem = null;
          this.removeChild(menuItem);
        }
        var tabcontainer = document.getBindingParent(this);
        tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
        document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this, false);
      ]]></handler>
    </handlers>
  </binding>
  <!-- close-tab-button binding
       This binding relies on the structure of the tabbrowser binding.
       Therefore it should only be used as a child of the tab or the tabs
       element (in both cases, when they are anonymous nodes of <tabbrowser>).
       This binding is exposed as a pseudo-public-API so themes can customize
       the tabbar appearance without having to be scriptable
       (see globalBindings.xml in Pinstripe for example).
  -->
  <binding id="tabmail-close-tab-button"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
    <handlers>
      <handler event="click" button="0"><![CDATA[
        var bindingParent = document.getBindingParent(this);
        if (bindingParent) {
          var tabbedBrowser = document.getBindingParent(bindingParent);
          if (bindingParent.localName == "tab") {
            /* The only sequence in which a second click event (i.e. dblclik)
             * can be dispatched on an in-tab close button is when it is shown
             * after the first click (i.e. the first click event was dispatched
             * on the tab). This happens when we show the close button only on
             * the active tab. (bug 352021)
             * The only sequence in which a third click event can be dispatched
             * on an in-tab close button is when the tab was opened with a
             * double click on the tabbar. (bug 378344)
             * In both cases, it is most likely that the close button area has
             * been accidentally clicked, therefore we do not close the tab.
             */
            if (event.detail > 1)
              return;

            tabbedBrowser.removeTabByNode(bindingParent);
            tabbedBrowser._blockDblClick = true;

            /* XXXmano hack (see bug 343628):
             * Since we're removing the event target, if the user
             * double-clicks this button, the dblclick event will be dispatched
             * with the tabbar as its event target (and explicit/originalTarget),
             * which treats that as a mouse gesture for opening a new tab.
             * In this context, we're manually blocking the dblclick event
             * (see onTabBarDblClick).
             */
            var clickedOnce = false;
            function enableDblClick(event) {
              if (event.detail == 1 && !clickedOnce) {
                clickedOnce = true;
                return;
              }
              setTimeout(function() {
                tabbedBrowser._blockDblClick = false;
              }, 0);
              tabbedBrowser.removeEventListener("click", enableDblClick, false);
            }
            tabbedBrowser.addEventListener("click", enableDblClick, false);
          }
          else // "tabs"
            tabbedBrowser.removeCurrentTab();
        }
      ]]></handler>
      <handler event="dblclick" button="0" phase="capturing">
        // for the one-close-button case
        event.stopPropagation();
      </handler>
    </handlers>
  </binding>

</bindings>