summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/content/tabmail.xml
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/mailnews/content/tabmail.xml
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/mailnews/content/tabmail.xml')
-rw-r--r--comm/suite/mailnews/content/tabmail.xml1583
1 files changed, 1583 insertions, 0 deletions
diff --git a/comm/suite/mailnews/content/tabmail.xml b/comm/suite/mailnews/content/tabmail.xml
new file mode 100644
index 0000000000..613bb3a418
--- /dev/null
+++ b/comm/suite/mailnews/content/tabmail.xml
@@ -0,0 +1,1583 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % messengerDTD SYSTEM "chrome://messenger/locale/messenger.dtd" >
+ %messengerDTD;
+ <!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">
+
+ <!-- SeaMonkey's clone of 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, aArgs): Open a tab of the given "mode",
+ - passing the provided arguments as an object. The tab type author
+ - should tell you the modes they implement and the required/optional
+ - arguments.
+ - One of the arguments you can pass is "background": if this is true,
+ - the tab will be loaded in the background.
+ - * 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.
+ - * removeCurrentTab(): Close the current tab.
+ - * removeTab(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, while SeaMonkey integrates both flavours into just
+ - one "3pane" mode. 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 either the XUL element name that should be
+ - created for each tab, or 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(aArgs): 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.
+ - aArgs is a set of named parameters (the ones that are later passed to
+ - openTab).
+ - * openTab(aTabInfo, aArgs): Called when a tab of the given mode is in the
+ - process of being opened. aTabInfo 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, including background.
+ - * canCloseTab(aTabInfo): Optional function.
+ - Return true (false) if the tab is (not) allowed to close.
+ - A tab's default permission is stored in aTabInfo.canClose.
+ - * closeTab(aTabInfo): Called when aTabInfo is being closed. The tab need
+ - not be currently displayed. You are responsible for properly cleaning
+ - up any state you preserved in aTabInfo.
+ - * saveTabState(aTabInfo): Called when aTabInfo is being switched away from
+ - so that you can preserve its state on aTabInfo. 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(aTabInfo): Called when aTabInfo is being displayed and you
+ - should restore its state (if required).
+ - * onTitleChanged(aTabInfo): Called when someone calls
+ - tabmail.setTabTitle() to hint that the tab's title needs to be
+ - updated. This function should update aTabInfo.title if it can.
+ - * getBrowser(aTabInfo): 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.
+ -
+ - Mode definition functions for menu/toolbar commands (see nsIController):
+ - * supportsCommand(aCommand, aTabInfo): 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(aCommand, aTabInfo): 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(aCommand, aTabInfo): Called when a menu or toolbar command is
+ - to be executed. Perform the action appropriate to the command.
+ - * onEvent(aEvent, aTabInfo): This can be used to handle different events
+ - on the window.
+ -
+ - 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(aTabInfo): Called when the tab's title changes.
+ - * onTabSwitched(aTabInfo, aOldTabInfo): Called when a new tab is made
+ - active. If this is the first tab ever, aOldTabInfo will be null,
+ - otherwise aOldTabInfo will be the previously active tab.
+ -->
+ <binding id="tabmail"
+ extends="chrome://navigator/content/tabbrowser.xml#tabbrowser">
+ <resources>
+ <stylesheet src="chrome://navigator/skin/tabbrowser.css"/>
+ </resources>
+ <content>
+ <xul:stringbundle anonid="tmstringbundle" src="chrome://messenger/locale/tabmail.properties"/>
+ <xul:tabbox anonid="tabbox"
+ flex="1"
+ eventnode="document"
+ onselect="if (event.target.localName == 'tabs' &amp;&amp;
+ 'updateCurrentTab' in this.parentNode)
+ 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="tabbrowser-strip tabmail-strip"
+ tooltip="_child"
+ context="_child"
+ anonid="strip"
+ ondragstart="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
+ ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
+ <xul:tooltip onpopupshowing="var tabmail = this.parentNode.parentNode.parentNode;
+ return tabmail.FillTabmailTooltip(document, event);"/>
+ <xul:menupopup anonid="tabContextMenu"
+ onpopupshowing="return document.getBindingParent(this)
+ .onTabContextMenuShowing();">
+ <xul:menuitem label="&closeTabCmd.label;"
+ accesskey="&closeTabCmd.accesskey;"
+ oncommand="var tabmail = document.getBindingParent(this);
+ tabmail.removeTab(tabmail.mContextTab);"/>
+ </xul:menupopup>
+ <xul:tabs class="tabbrowser-tabs tabmail-tabs"
+ flex="1"
+ anonid="tabcontainer"
+ setfocus="false"
+ onclick="this.parentNode.parentNode.parentNode.onTabClick(event);">
+ <xul:tab selected="true"
+ validate="never"
+ type="3pane"
+ maxwidth="250"
+ width="0"
+ minwidth="100"
+ flex="100"
+ class="tabbrowser-tab tabmail-tab icon-holder"
+ crop="end"/>
+ </xul:tabs>
+ <children/>
+ </xul:hbox>
+ <!-- Remember, user of this binding, you need to provide tabpanels! -->
+ <children includes="tabpanels"/>
+ </xul:tabbox>
+ </content>
+
+ <implementation implements="nsIController, nsIObserver">
+ <constructor>
+ <![CDATA[
+ window.controllers.insertControllerAt(0, this);
+ const kAutoHide = "mail.tabs.autoHide";
+ this.mAutoHide = Services.prefs.getBoolPref(kAutoHide);
+ Services.prefs.addObserver(kAutoHide, this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("mail.tabs.autoHide", this);
+ 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="tabs" readonly="true">
+ this.tabContainer.childNodes
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tmstringbundle");
+ </field>
+ <field name="mContextTab">
+ null
+ </field>
+
+ <!-- _mAutoHide/mAutoHide reflect the current autoHide pref value -->
+ <field name="_mAutoHide">false</field>
+ <property name="mAutoHide" onget="return this._mAutoHide;">
+ <setter>
+ <![CDATA[
+ if (val != this._mAutoHide)
+ {
+ if (this.tabContainer.childNodes.length == 1)
+ this.mStripVisible = !val;
+ this._mAutoHide = val;
+ }
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <!-- mStripVisible reflects the actual XUL autoHide state -->
+ <property name="mStripVisible"
+ onget="return !this.tabStrip.collapsed;"
+ onset="return this.tabStrip.collapsed = !val;"/>
+
+ <method name="registerTabType">
+ <parameter name="aTabType"/>
+ <body>
+ <![CDATA[
+ if (aTabType.name in this.tabTypes)
+ return;
+ this.tabTypes[aTabType.name] = aTabType;
+ for (let [modeName, modeDetails] of Object.entries(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>
+
+ <field name="tabMonitors" readonly="true">
+ new Array()
+ </field>
+
+ <method name="registerTabMonitor">
+ <parameter name="aTabMonitor"/>
+ <body>
+ <![CDATA[
+ if (!this.tabMonitors.includes(aTabMonitor))
+ this.tabMonitors.push(aTabMonitor);
+ ]]>
+ </body>
+ </method>
+
+ <method name="unregisterTabMonitor">
+ <parameter name="aTabMonitor"/>
+ <body>
+ <![CDATA[
+ let index = this.tabMonitors.indexOf(aTabMonitor);
+ if (index >= 0)
+ this.tabMonitors.splice(index, 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.
+ // Note: for mail tabs, the title gets only set later when the
+ // folder or message is loaded, as we don't have a gDBView yet!
+ // We assume the tab contents will set themselves up correctly.
+ if (!this.tabInfo.length)
+ {
+ let firstTabInfo = {mode: this.defaultTabMode, canClose: true};
+ let firstTabNode = this.tabContainer.firstChild;
+ firstTabInfo.mode.tabs.push(firstTabInfo);
+ this.tabInfo[0] = this.currentTabInfo = firstTabInfo;
+ this.setTabTitle(firstTabInfo);
+ if (this.tabMonitors.length)
+ {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabSwitched(firstTabInfo, null);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="openTab">
+ <parameter name="aTabModeName"/>
+ <parameter name="aArgs"/>
+ <body>
+ <![CDATA[
+ if (!aTabModeName)
+ aTabModeName = this.currentTabInfo.mode.type;
+
+ 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)
+ {
+ // show the first tab of this mode
+ this.tabContainer.selectedIndex = this.tabInfo.indexOf(tabMode.tabs[0]);
+ return;
+ }
+
+ // Do this so that we don't generate strict warnings.
+ let background = ("background" in aArgs) && aArgs.background;
+
+ // If the mode wants us to, we should switch to an existing tab
+ // rather than open a new one. We shouldn't switch to the tab if
+ // we're opening it in the background, though.
+ let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
+ tabMode.tabType.shouldSwitchTo;
+
+ if (shouldSwitchToFunc)
+ {
+ let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, [aArgs]);
+ if (tabIndex >= 0)
+ {
+ if (!background)
+ this.selectTabByIndex(tabIndex);
+ return;
+ }
+ }
+
+ if (!background)
+ // we need to save the state before it gets corrupted
+ this.saveCurrentTabState();
+
+ let tabInfo = {mode: tabMode, canClose: true};
+ tabMode.tabs.push(tabInfo);
+
+ let 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 = "tabbrowser-tab tabmail-tab icon-holder";
+ // for styling purposes, apply the type to the tab
+ // (this attribute may be overwritten by mode functions)
+ t.setAttribute("type", tabInfo.mode.type);
+ this.tabContainer.appendChild(t);
+ if (!this.mStripVisible)
+ {
+ this.mStripVisible = true;
+ this.tabContainer._updateCloseButtons();
+ }
+
+ let oldPanel = this.panelContainer.selectedPanel;
+
+ // Open new tabs in the background?
+ tabInfo.switchToNewTab = !background;
+
+ // the order of the following statements is important
+ let oldTabInfo = this.currentTabInfo;
+ this.tabInfo[this.tabContainer.childNodes.length - 1] = tabInfo;
+
+ if (!background) {
+ this.currentTabInfo = tabInfo;
+ // 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
+ let selectedPanel;
+ if (tabInfo.mode.tabType.perTabPanel)
+ {
+ // should we create the element for them, or will they do it?
+ if (typeof(tabInfo.mode.tabType.perTabPanel) == "string")
+ {
+ tabInfo.panel = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ tabInfo.mode.tabType.perTabPanel);
+ }
+ else
+ {
+ tabInfo.panel = tabInfo.mode.tabType.perTabPanel(tabInfo);
+ }
+ this.panelContainer.appendChild(tabInfo.panel);
+ selectedPanel = tabInfo.panel;
+ }
+ else
+ {
+ selectedPanel = tabInfo.mode.tabType.panel;
+ }
+ if (!background)
+ this.panelContainer.selectedPanel = selectedPanel;
+
+ oldPanel.removeAttribute("selected");
+ this.panelContainer.selectedPanel.setAttribute("selected", "true");
+
+ let tabOpenFunc = tabInfo.mode.openTab ||
+ tabInfo.mode.tabType.openTab;
+ if (tabOpenFunc)
+ tabOpenFunc.apply(tabInfo.mode.tabType, [tabInfo, aArgs]);
+
+ if (background) {
+ // if the new tab isn't made current,
+ // its title won't change automatically
+ this.setTabTitle(tabInfo);
+ }
+
+ if (!background && this.tabMonitors.length) {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
+ }
+
+ t.setAttribute("label", tabInfo.title);
+
+ if (!background) {
+ let docTitle = tabInfo.title;
+ if (AppConstants.platform != "macosx") {
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
+ }
+ 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="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="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 (let i = 0; i < this.tabInfo.length; i++)
+ {
+ let tabInfo = this.tabInfo[i];
+ let tabCloseFunc = tabInfo.mode.closeTab ||
+ tabInfo.mode.tabType.closeTab;
+ if (tabCloseFunc)
+ tabCloseFunc.call(tabInfo.mode.tabType, tabInfo);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTab">
+ <parameter name="aTabNode"/>
+ <!-- parameter name="aMoreParameters..."/-->
+ <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 tabInfo = this.tabInfo[iTab];
+
+ // ask the tab type implementation if we're allowed to close the tab
+ let canClose = tabInfo.canClose;
+ let canCloseFunc = tabInfo.mode.canCloseTab ||
+ tabInfo.mode.tabType.canCloseTab;
+ if (canCloseFunc)
+ canClose = canCloseFunc.call(tabInfo.mode.tabType, tabInfo);
+ if (!canClose)
+ return;
+
+ let closeFunc = tabInfo.mode.closeTab ||
+ tabInfo.mode.tabType.closeTab;
+ if (closeFunc)
+ closeFunc.call(tabInfo.mode.tabType, tabInfo);
+
+ this.tabInfo.splice(iTab, 1);
+ tabInfo.mode.tabs.splice(tabInfo.mode.tabs.indexOf(tabInfo), 1);
+ aTabNode.remove();
+ --numTabs;
+ if (this.tabContainer.selectedIndex == -1)
+ this.tabContainer.selectedIndex = (iTab == numTabs) ? iTab - 1 : iTab;
+ if (this.currentTabInfo == tabInfo)
+ this.updateCurrentTab();
+
+ if (tabInfo.panel)
+ {
+ tabInfo.panel.remove();
+ delete tabInfo.panel;
+ }
+ if (numTabs == 1 && this.mAutoHide)
+ this.mStripVisible = false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <body>
+ <![CDATA[
+ this.removeTab(this.tabContainer.selectedItem);
+ ]]>
+ </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 oldTabInfo = this.currentTabInfo;
+ let oldPanel = this.panelContainer.selectedPanel;
+ let tabInfo = this.currentTabInfo = this.tabInfo[this.tabContainer.selectedIndex];
+ this.panelContainer.selectedPanel = tabInfo.panel ||
+ tabInfo.mode.tabType.panel;
+
+ // Update the selected attribute on the current and old tab panel.
+ oldPanel.removeAttribute("selected");
+ this.panelContainer.selectedPanel.setAttribute("selected", "true");
+
+ let showTabFunc = tabInfo.mode.showTab ||
+ tabInfo.mode.tabType.showTab;
+ if (showTabFunc)
+ showTabFunc.call(tabInfo.mode.tabType, tabInfo);
+ if (this.tabMonitors.length)
+ {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
+ }
+
+ let docTitle = tabInfo.title;
+ if (AppConstants.platform != "macosx") {
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
+ }
+ 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="saveTabState">
+ <parameter name="aTabInfo"/>
+ <body>
+ <![CDATA[
+ if (!aTabInfo)
+ return;
+ let saveTabFunc = aTabInfo.mode.saveTabState ||
+ aTabInfo.mode.tabType.saveTabState;
+ if (saveTabFunc)
+ saveTabFunc.call(aTabInfo.mode.tabType, aTabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="saveCurrentTabState">
+ <body>
+ <![CDATA[
+ if (!this.currentTabInfo)
+ this.currentTabInfo = this.tabInfo[0];
+ // save the old tab state before we change the current tab
+ this.saveTabState(this.currentTabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTabInfo"/>
+ <body>
+ <![CDATA[
+ // First find the tab and its index.
+ let tabInfo;
+ let index;
+ if (aTabInfo)
+ {
+ tabInfo = aTabInfo;
+ for (index = 0; index < this.tabInfo.length; ++index)
+ {
+ if (tabInfo == this.tabInfo[index])
+ break;
+ }
+ }
+ else
+ {
+ index = this.tabContainer.selectedIndex;
+ tabInfo = this.tabInfo[index];
+ }
+
+ if (tabInfo)
+ {
+ let tabNode = this.tabContainer.childNodes[index];
+ let titleChangeFunc = tabInfo.mode.onTitleChanged ||
+ tabInfo.mode.tabType.onTitleChanged;
+ if (titleChangeFunc)
+ titleChangeFunc.call(tabInfo.mode.tabType, tabInfo, tabNode);
+ if (this.tabMonitors.length)
+ {
+ for (let tabMonitor of this.tabMonitors)
+ tabMonitor.onTabTitleChanged(tabInfo);
+ }
+ tabNode.setAttribute("label", tabInfo.title);
+
+ // Update the window title if we're the displayed tab.
+ if (index == this.tabContainer.selectedIndex)
+ {
+ let docTitle = tabInfo.title;
+ if (AppConstants.platform != "macosx") {
+ docTitle += " - " + gBrandBundle.getString("brandFullName");
+ }
+ 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="FillTabmailTooltip">
+ <parameter name="aDocument"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ aEvent.stopPropagation();
+ let tn = aDocument.tooltipNode;
+ if (tn.localName != "tab")
+ return false; // Not a tab, so cancel the tooltip.
+ if (tn.hasAttribute("label"))
+ {
+ aEvent.target.setAttribute("label", tn.getAttribute("label"));
+ return true;
+ }
+ return false;
+ ]]>
+ </body>
+ </method>
+
+ <method name="onTabContextMenuShowing">
+ <body>
+ <![CDATA[
+ // The user might right-click on a non-tab area of the tab strip.
+ this.mContextTab = document.popupNode;
+ return this.mContextTab.localName == "tab";
+ ]]>
+ </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 tabInfo = this.currentTabInfo;
+ let browserFunc = tabInfo.mode.getBrowser ||
+ tabInfo.mode.tabType.getBrowser;
+ if (!browserFunc)
+ return null;
+ return browserFunc.call(tabInfo.mode.tabType, tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ return -1;
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIObserver implementation -->
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ const kAutoHide = "mail.tabs.autoHide";
+ if (aTopic == "nsPref:changed" && aData == kAutoHide)
+ this.mAutoHide = Services.prefs.getBoolPref(kAutoHide);
+ ]]>
+ </body>
+ </method>
+
+ <!-- nsIController implementation -->
+
+ <method name="supportsCommand">
+ <parameter name="aCommand"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return false;
+
+ let supportsCommandFunc = tabInfo.mode.supportsCommand ||
+ tabInfo.mode.tabType.supportsCommand;
+ if (!supportsCommandFunc)
+ return false;
+ return supportsCommandFunc.call(tabInfo.mode.tabType,
+ aCommand,
+ tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isCommandEnabled">
+ <parameter name="aCommand"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return false;
+
+ let isCommandEnabledFunc = tabInfo.mode.isCommandEnabled ||
+ tabInfo.mode.tabType.isCommandEnabled;
+ if (!isCommandEnabledFunc)
+ return false;
+ return isCommandEnabledFunc.call(tabInfo.mode.tabType,
+ aCommand,
+ tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="doCommand">
+ <parameter name="aCommand"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return;
+
+ let doCommandFunc = tabInfo.mode.doCommand ||
+ tabInfo.mode.tabType.doCommand;
+ if (!doCommandFunc)
+ return;
+ doCommandFunc.call(tabInfo.mode.tabType,
+ aCommand,
+ tabInfo);
+ ]]>
+ </body>
+ </method>
+
+ <method name="onEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // return early on startup when we haven't got a tab loaded yet
+ let tabInfo = this.currentTabInfo;
+ if (!tabInfo)
+ return;
+
+ let onEventFunc = tabInfo.mode.onEvent ||
+ tabInfo.mode.tabType.onEvent;
+ if (!onEventFunc)
+ return;
+
+ onEventFunc.call(tabInfo.mode.tabType, aCommand, tabInfo);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="tabmail-tab"
+ display="xul:box"
+ extends="chrome://global/content/bindings/tabbox.xml#tab">
+ <content closetabtext="&tabmailClose.label;">
+ <xul:hbox class="tab-middle box-inherit"
+ xbl:inherits="align,dir,pack,orient,selected"
+ flex="1">
+ <xul:image class="tab-icon tab-icon-image" 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"
+ tooltiptext="&tabmailClose.tooltip;"
+ tabindex="-1"
+ class="tabs-closebutton tab-close-button"/>
+ </content>
+
+ <implementation>
+ <field name="mCorrespondingMenuitem">null</field>
+ </implementation>
+ </binding>
+
+ <binding id="tabmail-arrowscrollbox"
+ extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <content>
+ <xul:toolbarbutton class="scrollbutton-up tab-scrollbutton-up"
+ collapsed="true"
+ xbl:inherits="orient"
+ anonid="scrollbutton-up"
+ onmousedown="_startScroll(-1);"
+ onmouseup="_stopScroll();"
+ onmouseout="_stopScroll();"/>
+ <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 tab-scrollbutton-down"
+ collapsed="true"
+ xbl:inherits="orient"
+ anonid="scrollbutton-down"
+ onmousedown="_startScroll(1);"
+ onmouseup="_stopScroll();"
+ onmouseout="_stopScroll();"/>
+ </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" phase="target">
+ <![CDATA[
+ // Ignore vertical events.
+ if (event.detail == 0)
+ return;
+ this._scrollButtonDownBox.collapsed = true;
+ this._scrollButtonDownBoxAnimate.collapsed = true;
+ ]]>
+ </handler>
+
+ <handler event="overflow" phase="target">
+ <![CDATA[
+ // 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:stack flex="1" class="tabs-stack">
+ <xul:vbox>
+ <xul:spacer flex="1"/>
+ <xul:hbox class="tabs-bottom" align="center"/>
+ </xul:vbox>
+ <xul:stack>
+ <xul:spacer class="tabs-left tabs-right"/>
+ <xul:hbox>
+ <xul:hbox class="tabs-newbutton-box"
+ pack="start"
+ anonid="tabstrip-newbutton">
+ <xul:toolbarbutton class="new-button tabs-newbutton"
+ tooltiptext="&tabmailNewButton.tooltip;"/>
+ </xul:hbox>
+ <xul:arrowscrollbox anonid="arrowscrollbox"
+ class="tabbrowser-arrowscrollbox tabmail-arrowscrollbox"
+ flex="1"
+ xbl:inherits="smoothscroll"
+ orient="horizontal"
+ style="min-width: 1px;">
+ <children includes="tab"/>
+ </xul:arrowscrollbox>
+ <children/>
+ <xul:hbox class="tabs-closebutton-box"
+ align="center"
+ pack="end"
+ anonid="tabstrip-closebutton">
+ <xul:toolbarbutton class="close-button tabs-closebutton"
+ tooltiptext="&tabmailCloseButton.tooltip;"/>
+ </xul:hbox>
+ <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="&tabmailAllTabs.tooltip;">
+ <xul:menupopup class="tabs-alltabs-popup"
+ anonid="alltabs-popup"
+ position="after_end"/>
+ </xul:toolbarbutton>
+ </xul:stack>
+ </xul:hbox>
+ </xul:stack>
+ </xul:stack>
+ </content>
+
+ <implementation implements="nsITimerCallback, nsIDOMEventListener, nsIObserver">
+ <constructor>
+ <![CDATA[
+ this.mTabMinWidth = Services.prefs.getIntPref ("browser.tabs.tabMinWidth");
+ this.mTabMaxWidth = Services.prefs.getIntPref ("browser.tabs.tabMaxWidth");
+ this.mTabClipWidth = Services.prefs.getIntPref ("browser.tabs.tabClipWidth");
+ this.mCloseButtons = Services.prefs.getIntPref ("browser.tabs.closeButtons");
+ this.firstChild.minWidth = this.mTabMinWidth;
+ this.firstChild.maxWidth = this.mTabMaxWidth;
+ this._updateCloseButtons();
+ Services.prefs.addObserver("browser.tabs.", this);
+ window.addEventListener("resize", this);
+
+ // 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 because these are all
+ // anonymous nodes.
+ this.arrowScrollbox.addEventListener("overflow", this);
+ this.arrowScrollbox.addEventListener("underflow", this);
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("browser.tabs.", this);
+
+ // Release timer to avoid reference cycles.
+ if (this._animateTimer)
+ {
+ this._animateTimer.cancel();
+ this._animateTimer = null;
+ }
+ this.arrowScrollbox.removeEventListener("overflow", this);
+ this.arrowScrollbox.removeEventListener("underflow", this);
+ ]]>
+ </destructor>
+
+ <field name="arrowScrollboxWidth">0</field>
+
+ <field name="arrowScrollbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+
+ <field name="arrowScrollboxClosebutton">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
+ </field>
+
+ <field name="mTabMinWidth">100</field>
+ <field name="mTabMaxWidth">250</field>
+ <field name="mTabClipWidth">140</field>
+ <field name="mCloseButtons">3</field>
+ <method name="_updateCloseButtons">
+ <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:
+ let 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:
+ this.setAttribute("closebuttons", "noclose");
+ break;
+ case 3:
+ this.setAttribute("closebuttons", "closeatend");
+ break;
+ }
+ this.arrowScrollboxClosebutton.collapsed = this.mCloseButtons != 3;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabSelect">
+ <body>
+ <![CDATA[
+ this.arrowScrollbox.ensureElementIsVisible(this.selectedItem);
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type)
+ {
+ case "overflow":
+ this.setAttribute("overflow", "true");
+ this.arrowScrollbox.scrollBoxObject
+ .ensureElementIsVisible(this.selectedItem);
+ break;
+ case "underflow":
+ this.removeAttribute("overflow");
+ break;
+ case "resize":
+ let width = this.arrowScrollbox.boxObject.width;
+ if (width != this.arrowScrollboxWidth)
+ {
+ this._updateCloseButtons();
+ // XXX without this line the tab bar won't budge
+ this.arrowScrollbox.scrollByPixels(1);
+ this._handleTabSelect();
+ this.arrowScrollboxWidth = 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.arrowScrollbox._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="aTabNode"/>
+ <body>
+ <![CDATA[
+ let tsbo = this.arrowScrollbox.scrollBoxObject;
+ let tsboStart = tsbo.screenX;
+ let tsboEnd = tsboStart + tsbo.width;
+ let ctbo = aTabNode.boxObject;
+ let ctboStart = ctbo.screenX;
+ let 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 =
+ Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ else
+ this._animateTimer.cancel();
+
+ this._animateTimer.initWithCallback(this,
+ this._animateDelay,
+ Ci.nsITimer.TYPE_REPEATING_SLACK);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="notify">
+ <parameter name="aTimer"/>
+ <body>
+ <![CDATA[
+ if (!document)
+ aTimer.cancel();
+
+ let 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>
+
+ <!-- nsIObserver implementation -->
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body>
+ <![CDATA[
+ const kCloseButtons = "browser.tabs.closeButtons";
+ if (aTopic == "nsPref:changed" && aData == kCloseButtons)
+ {
+ this.mCloseButtons = Services.prefs.getIntPref(kCloseButtons);
+ this._updateCloseButtons();
+ }
+ ]]>
+ </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 osx for example).
+ -->
+ <binding id="tabmail-alltabs-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <method name="_tabOnTabClose">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ let menuItem = aEvent.target.mCorrespondingMenuitem;
+ if (menuItem)
+ menuItem.remove();
+ ]]>
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ switch (aEvent.type)
+ {
+ 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[
+ let tabContainer = document.getBindingParent(this);
+ let tabstripBO = tabContainer.arrowScrollbox.scrollBoxObject;
+
+ for (let i = 0; i < this.childNodes.length; i++)
+ {
+ let curTabBO = this.childNodes[i].tab.boxObject;
+ if (curTabBO.screenX >= tabstripBO.screenX &&
+ curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+ this.childNodes[i].removeAttribute("tabIsScrolled");
+ else
+ this.childNodes[i].setAttribute("tabIsScrolled", "true");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_createTabMenuItem">
+ <parameter name="aTabNode"/>
+ <body>
+ <![CDATA[
+ let menuItem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+ menuItem.setAttribute("class", "menuitem-iconic alltabs-item icon-holder");
+ menuItem.setAttribute("label", aTabNode.label);
+ menuItem.setAttribute("crop", aTabNode.getAttribute("crop"));
+ menuItem.setAttribute("image", aTabNode.getAttribute("image"));
+
+ let attributes = ["busy", "selected", "type", "NewMessages", "ServerType",
+ "SpecialFolder", "ImapShared", "BiffState", "IsServer",
+ "IsSecure", "Attachment", "IMAPDeleted", "Offline",
+ "MessageType"];
+
+ attributes.forEach(
+ function(attribute)
+ {
+ if (aTabNode.hasAttribute(attribute))
+ {
+ menuItem.setAttribute(attribute, aTabNode.getAttribute(attribute));
+ }
+ }
+ );
+
+ // Keep some attributes of the menuitem in sync with its
+ // corresponding tab (e.g. the tab label)
+ aTabNode.mCorrespondingMenuitem = menuItem;
+ document.addBroadcastListenerFor(aTabNode, menuItem, "label");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "crop");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "image");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "busy");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "selected");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "NewMessages");
+ document.addBroadcastListenerFor(aTabNode, menuItem, "BiffState");
+ aTabNode.addEventListener("TabClose", this);
+ menuItem.tab = aTabNode;
+ menuItem.addEventListener("command", this);
+ this.appendChild(menuItem);
+ return menuItem;
+ ]]>
+ </body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ // set up the menu popup
+ let tabcontainer = document.getBindingParent(this);
+ let tabs = tabcontainer.childNodes;
+
+ // Listen for changes in the tab bar.
+ let tabbrowser = document.getBindingParent(tabcontainer);
+ tabbrowser.addEventListener("TabOpen", this);
+ tabcontainer.arrowScrollbox.addEventListener("scroll", this);
+
+ // if an animation is in progress and the user
+ // clicks on the "all tabs" button, stop the animation
+ tabcontainer._stopAnimation();
+
+ for (let 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())
+ {
+ let menuItem = this.lastChild;
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "label");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "crop");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "image");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "busy");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "selected");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "NewMessages");
+ document.removeBroadcastListenerFor(menuItem.tab, menuItem, "BiffState");
+ menuItem.removeEventListener("command", this);
+ menuItem.tab.removeEventListener("TabClose", this);
+ menuItem.tab.mCorrespondingMenuitem = null;
+ menuItem.remove();
+ }
+ let tabcontainer = document.getBindingParent(this);
+ tabcontainer.arrowScrollbox.removeEventListener("scroll", this);
+ document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this);
+ ]]>
+ </handler>
+
+ <handler event="command">
+ <![CDATA[
+ let tabcontainer = document.getBindingParent(this);
+ tabcontainer.selectedItem = event.target.tab;
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <!-- new-tab-button/close-tab-button binding
+ These bindings rely on the structure of the tabbrowser binding.
+ Therefore they should only be used as a child of the tab or the tabs
+ element (in both cases, when they are anonymous nodes of <tabbrowser>).
+ These bindings are exposed as pseudo-public-APIs, so themes can customize
+ the tabbar appearance without having to be scriptable
+ (see globalBindings.xml in osx for example).
+ -->
+ <binding id="tabmail-new-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ let bindingParent = document.getBindingParent(this);
+ if (bindingParent)
+ {
+ let tabmail = document.getBindingParent(bindingParent);
+ if (bindingParent.localName == "tabs")
+ {
+ // new-tab-button only appears in the tabstrip
+ // duplicate the current tab
+ tabmail.openTab("", {});
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="dblclick" button="0" phase="capturing">
+ <![CDATA[
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabmail-close-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <handlers>
+ <handler event="command">
+ <![CDATA[
+ let bindingParent = document.getBindingParent(this);
+ if (bindingParent)
+ {
+ let tabmail = 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;
+
+ tabmail.removeTab(bindingParent);
+ tabmail._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).
+ */
+ let clickedOnce = false;
+ function enableDblClick(event)
+ {
+ var target = event.originalTarget;
+ if (target.className == "tab-close-button")
+ target._ignoredClick = true;
+ if (!clickedOnce)
+ {
+ clickedOnce = true;
+ return;
+ }
+ tabContainer._blockDblClick = false;
+ tabContainer.removeEventListener("click", enableDblClick, true);
+ }
+ tabContainer.addEventListener("click", enableDblClick, true);
+ }
+ else
+ {
+ // "tabs"
+ tabmail.removeCurrentTab();
+ }
+ }
+ ]]>
+ </handler>
+ <handler event="dblclick" button="0" phase="capturing">
+ <![CDATA[
+ // for the one-close-button case
+ event.stopPropagation();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+</bindings>