1576 строки
61 KiB
XML
1576 строки
61 KiB
XML
<?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, arguments...): Open a tab of the given "mode",
|
|
- passing the provided arguments. The tab type author should tell you
|
|
- the modes they implement and the required/optional arguments.
|
|
- * setTabTitle([aOptionalTabInfo]): Tells us that the title of the current
|
|
- tab (if no argument is provided) or provided tab needs to be updated.
|
|
- This will result in a call to the tab mode's logic to update the title.
|
|
- In the event this is not for the current tab, the caller is responsible
|
|
- for ensuring that the underlying tab mode is capable of providing a tab
|
|
- title when it is in the background.
|
|
- * 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(arguments...): Optional function. Called when
|
|
- openTab is called on the top-level tabmail binding. It is used to
|
|
- decide if the openTab function should switch to an existing tab or
|
|
- actually open a new tab.
|
|
- If the openTab function should switch to an existing tab, return the
|
|
- index of that tab; otherwise return -1.
|
|
- * openTab(aTabInfo, arguments...): 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.
|
|
- * 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:tabbox anonid="tabbox"
|
|
flex="1"
|
|
eventnode="document"
|
|
xbl:inherits="handleCtrlPageUpDown"
|
|
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 = "browser.tabs.autoHide";
|
|
this.mAutoHide = this.mPrefs.getBoolPref(kAutoHide);
|
|
this.mPrefs.addObserver(kAutoHide, this, false);
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
this.mPrefs.removeObserver("browser.tabs.autoHide", this);
|
|
window.controllers.removeController(this);
|
|
]]>
|
|
</destructor>
|
|
|
|
<field name="mPrefs">
|
|
Components.classes["@mozilla.org/preferences-service;1"]
|
|
.getService(Components.interfaces.nsIPrefBranch);
|
|
</field>
|
|
|
|
<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="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.indexOf(aTabMonitor) == -1)
|
|
this.tabMonitors.push(aTabMonitor);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="unregisterTabMonitor">
|
|
<parameter name="aTabMonitor"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (this.tabMonitors.indexOf(aTabMonitor) >= 0)
|
|
this.tabMonitors.splice(this.tabMonitors.indexOf(aTabMonitor), 1);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="openFirstTab">
|
|
<body>
|
|
<![CDATA[
|
|
// From the moment of creation, our XBL binding already has a
|
|
// visible tab. We need to create a tab information structure for
|
|
// this tab. In the process we also generate a synthetic "tab title
|
|
// changed" event to ensure we have an accurate title.
|
|
// 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="aMoreParameters..."/-->
|
|
<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;
|
|
}
|
|
|
|
// If the mode wants us to, we should switch to an existing tab
|
|
// rather than open a new one.
|
|
let shouldSwitchToFunc = tabMode.shouldSwitchTo ||
|
|
tabMode.tabType.shouldSwitchTo;
|
|
|
|
if (shouldSwitchToFunc)
|
|
{
|
|
let args = Array.prototype.slice.call(arguments, 1);
|
|
let tabIndex = shouldSwitchToFunc.apply(tabMode.tabType, args);
|
|
if (tabIndex >= 0)
|
|
{
|
|
this.selectTabByIndex(tabIndex);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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.adjustTabstrip();
|
|
}
|
|
|
|
let oldPanel = this.panelContainer.selectedPanel;
|
|
|
|
// Open new tabs in the background?
|
|
let switchToNewTab = !this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
|
|
tabInfo.switchToNewTab = switchToNewTab;
|
|
|
|
// the order of the following statements is important
|
|
let oldTabInfo = this.currentTabInfo;
|
|
this.tabInfo[this.tabContainer.childNodes.length - 1] = tabInfo;
|
|
|
|
if (switchToNewTab)
|
|
{
|
|
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 (switchToNewTab)
|
|
this.panelContainer.selectedPanel = selectedPanel;
|
|
|
|
oldPanel.removeAttribute("selected");
|
|
this.panelContainer.selectedPanel.setAttribute("selected", "true");
|
|
|
|
let tabOpenFunc = tabInfo.mode.openTab ||
|
|
tabInfo.mode.tabType.openTab;
|
|
let args = [tabInfo].concat(Array.prototype.slice.call(arguments, 1));
|
|
if (tabOpenFunc)
|
|
tabOpenFunc.apply(tabInfo.mode.tabType, args);
|
|
|
|
if (!switchToNewTab)
|
|
{
|
|
// if the new tab isn't made current,
|
|
// its title won't change automatically
|
|
this.setTabTitle(tabInfo);
|
|
}
|
|
|
|
if (this.tabMonitors.length)
|
|
{
|
|
if (switchToNewTab)
|
|
for (let tabMonitor of this.tabMonitors)
|
|
tabMonitor.onTabSwitched(tabInfo, oldTabInfo);
|
|
}
|
|
|
|
t.setAttribute("label", tabInfo.title);
|
|
if (switchToNewTab)
|
|
{
|
|
let docTitle = tabInfo.title;
|
|
if (!/Mac/.test(navigator.platform))
|
|
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 (!/Mac/.test(navigator.platform))
|
|
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="onTabClick">
|
|
<parameter name="event"/>
|
|
<body>
|
|
<![CDATA[
|
|
// a middle mouse button click on a tab is a short cut for closing a tab
|
|
if (event.button != 1 || event.target.localName != "tab")
|
|
return;
|
|
this.removeTab(event.target);
|
|
event.stopPropagation();
|
|
]]>
|
|
</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 (!/Mac/.test(navigator.platform))
|
|
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>
|
|
|
|
<!-- nsIObserver implementation -->
|
|
|
|
<method name="observe">
|
|
<parameter name="aSubject"/>
|
|
<parameter name="aTopic"/>
|
|
<parameter name="aData"/>
|
|
<body>
|
|
<![CDATA[
|
|
const kAutoHide = "browser.tabs.autoHide";
|
|
if (aTopic == "nsPref:changed" && aData == kAutoHide)
|
|
this.mAutoHide = this.mPrefs.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 = this.mPrefs.getIntPref ("browser.tabs.tabMinWidth");
|
|
this.mTabMaxWidth = this.mPrefs.getIntPref ("browser.tabs.tabMaxWidth");
|
|
this.mTabClipWidth = this.mPrefs.getIntPref ("browser.tabs.tabClipWidth");
|
|
this.mCloseButtons = this.mPrefs.getIntPref ("browser.tabs.closeButtons");
|
|
this.firstChild.minWidth = this.mTabMinWidth;
|
|
this.firstChild.maxWidth = this.mTabMaxWidth;
|
|
this.adjustTabstrip();
|
|
this.mPrefs.addObserver("browser.tabs.", this, false);
|
|
window.addEventListener("resize", this, false);
|
|
|
|
// Listen to overflow/underflow events on the tabstrip,
|
|
// we cannot put these as xbl handlers on the entire binding because
|
|
// they would also get called for the all-tabs popup scrollbox.
|
|
// Also, we can't rely on event.target because these are all
|
|
// anonymous nodes.
|
|
this.mTabstrip.addEventListener("overflow", this, false);
|
|
this.mTabstrip.addEventListener("underflow", this, false);
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
this.mPrefs.removeObserver("browser.tabs.", this);
|
|
|
|
// Release timer to avoid reference cycles.
|
|
if (this._animateTimer)
|
|
{
|
|
this._animateTimer.cancel();
|
|
this._animateTimer = null;
|
|
}
|
|
this.mTabstrip.removeEventListener("overflow", this, false);
|
|
this.mTabstrip.removeEventListener("underflow", this, false);
|
|
]]>
|
|
</destructor>
|
|
|
|
<field name="mPrefs">
|
|
Components.classes["@mozilla.org/preferences-service;1"]
|
|
.getService(Components.interfaces.nsIPrefBranch);
|
|
</field>
|
|
|
|
<field name="mTabstripWidth">0</field>
|
|
|
|
<field name="mTabstrip">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
|
|
</field>
|
|
|
|
<field name="mTabstripClosebutton">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
|
|
</field>
|
|
|
|
<field name="mTabMinWidth">100</field>
|
|
<field name="mTabMaxWidth">250</field>
|
|
<field name="mTabClipWidth">140</field>
|
|
<field name="mCloseButtons">3</field>
|
|
<method name="adjustTabstrip">
|
|
<body>
|
|
<![CDATA[
|
|
// modes for tabstrip
|
|
// 0 - activetab = close button on active tab only
|
|
// 1 - alltabs = close buttons on all tabs
|
|
// 2 - noclose = no close buttons at all
|
|
// 3 - closeatend = close button at the end of the tabstrip
|
|
switch (this.mCloseButtons)
|
|
{
|
|
case 0:
|
|
this.setAttribute("closebuttons", "activetab");
|
|
break;
|
|
case 1:
|
|
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.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_handleTabSelect">
|
|
<body>
|
|
<![CDATA[
|
|
this.mTabstrip.ensureElementIsVisible(this.selectedItem);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="handleEvent">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
switch (aEvent.type)
|
|
{
|
|
case "overflow":
|
|
this.setAttribute("overflow", "true");
|
|
this.mTabstrip.scrollBoxObject
|
|
.ensureElementIsVisible(this.selectedItem);
|
|
break;
|
|
case "underflow":
|
|
this.removeAttribute("overflow");
|
|
break;
|
|
case "resize":
|
|
let width = this.mTabstrip.boxObject.width;
|
|
if (width != this.mTabstripWidth)
|
|
{
|
|
this.adjustTabstrip();
|
|
// XXX without this line the tab bar won't budge
|
|
this.mTabstrip.scrollByPixels(1);
|
|
this._handleTabSelect();
|
|
this.mTabstripWidth = width;
|
|
}
|
|
break;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<field name="mAllTabsPopup">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "alltabs-popup");
|
|
</field>
|
|
|
|
<field name="mAllTabsBoxAnimate">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "alltabs-box-animate");
|
|
</field>
|
|
|
|
<field name="mDownBoxAnimate">
|
|
this.mTabstrip._scrollButtonDownBoxAnimate;
|
|
</field>
|
|
|
|
<field name="mAllTabsButton">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "alltabs-button");
|
|
</field>
|
|
|
|
<field name="_animateTimer">null</field>
|
|
<field name="_animateStep">-1</field>
|
|
<field name="_animateDelay">25</field>
|
|
<field name="_animatePercents">
|
|
[1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
|
|
0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
|
|
0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
|
|
0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
|
|
0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
|
|
0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
|
|
</field>
|
|
|
|
<method name="_stopAnimation">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._animateStep != -1)
|
|
{
|
|
if (this._animateTimer)
|
|
this._animateTimer.cancel();
|
|
|
|
this._animateStep = -1;
|
|
this.mAllTabsBoxAnimate.style.opacity = 0.0;
|
|
this.mDownBoxAnimate.style.opacity = 0.0;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="_notifyBackgroundTab">
|
|
<parameter name="aTabNode"/>
|
|
<body>
|
|
<![CDATA[
|
|
let tsbo = this.mTabstrip.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 =
|
|
Components.classes["@mozilla.org/timer;1"]
|
|
.createInstance(Components.interfaces.nsITimer);
|
|
else
|
|
this._animateTimer.cancel();
|
|
|
|
this._animateTimer.initWithCallback(this,
|
|
this._animateDelay,
|
|
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="notify">
|
|
<parameter name="aTimer"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!document)
|
|
aTimer.cancel();
|
|
|
|
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 = this.mPrefs.getIntPref(kCloseButtons);
|
|
this.adjustTabstrip();
|
|
}
|
|
]]>
|
|
</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.mTabstrip.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, false);
|
|
menuItem.tab = aTabNode;
|
|
menuItem.addEventListener("command", this, false);
|
|
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, false);
|
|
tabcontainer.mTabstrip.addEventListener("scroll", this, false);
|
|
|
|
// if an animation is in progress and the user
|
|
// clicks on the "all tabs" button, stop the animation
|
|
tabcontainer._stopAnimation();
|
|
|
|
for (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, false);
|
|
menuItem.tab.removeEventListener("TabClose", this, false);
|
|
menuItem.tab.mCorrespondingMenuitem = null;
|
|
menuItem.remove();
|
|
}
|
|
let tabcontainer = document.getBindingParent(this);
|
|
tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
|
|
document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this, false);
|
|
]]>
|
|
</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-image">
|
|
<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-image">
|
|
<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>
|