diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/mailnews/content/tabmail.xml | |
parent | Initial commit. (diff) | |
download | thunderbird-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.xml | 1583 |
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' && + '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> |