Revision control

Copy as Markdown

Other Tools

<?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"
<!-- 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"
<resources>
</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(
"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(
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"
<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"
<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"
<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, 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"
<implementation>
<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(
"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"
<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"
<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>