gecko/browser/base/content/tabbrowser.xml

3469 lines
130 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is this file as it was released on March 28, 2001.
-
- The Initial Developer of the Original Code is
- David Hyatt.
- Portions created by the Initial Developer are Copyright (C) 2001
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
- Mike Connor <mconnor@steelgryphon.com>
- Peter Parente <parente@cs.unc.edu>
- Giorgio Maone <g.maone@informaction.com>
- Asaf Romano <mozilla.mano@sent.com>
- Seth Spitzer <sspitzer@mozilla.org>
- Simon Bünzli <zeniko@gmail.com>
- Michael Ventnor <ventnor.bugzilla@yahoo.com.au>
- Mark Pilgrim <pilgrim@gmail.com>
- Dão Gottwald <dao@mozilla.com>
- Paul OShannessy <paul@oshannessy.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<!DOCTYPE bindings [
<!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
%tabBrowserDTD;
]>
<bindings id="tabBrowserBindings"
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">
<binding id="tabbrowser">
<resources>
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
</resources>
<content>
<xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
<xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
onselect="if (!('updateCurrentBrowser' in this.parentNode) || event.target.localName != 'tabpanels') return; this.parentNode.updateCurrentBrowser();">
<xul:hbox class="tab-drop-indicator-bar" collapsed="true"
ondragover="this.parentNode.parentNode._onDragOver(event);"
ondragleave="this.parentNode.parentNode._onDragLeave(event);"
ondrop="this.parentNode.parentNode._onDrop(event);">
<xul:hbox class="tab-drop-indicator" mousethrough="always"/>
</xul:hbox>
<xul:hbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
anonid="strip"
ondragstart="this.parentNode.parentNode._onDragStart(event);"
ondragover="this.parentNode.parentNode._onDragOver(event);"
ondrop="this.parentNode.parentNode._onDrop(event);"
ondragend="this.parentNode.parentNode._onDragEnd(event);"
ondragleave="this.parentNode.parentNode._onDragLeave(event);">
<xul:tooltip onpopupshowing="return this.parentNode.parentNode.parentNode.createTooltip(event);"/>
<xul:menupopup anonid="tabContextMenu" onpopupshowing="this.parentNode.parentNode.parentNode.updatePopupMenu(this);">
<xul:menuitem id="context_newTab" label="&newTab.label;" accesskey="&newTab.accesskey;"
xbl:inherits="oncommand=onnewtab"/>
<xul:menuseparator/>
<xul:menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
<xul:menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.reloadAllTabs(tabbrowser.mContextTab);"/>
<xul:menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
<xul:menuseparator/>
<xul:menuitem id="context_openTabInWindow" label="&openTabInNewWindow.label;"
accesskey="&openTabInNewWindow.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.replaceTabWithWindow(tabbrowser.mContextTab);"/>
<xul:menuseparator/>
<xul:menuitem id="context_bookmarkTab"
label="&bookmarkThisTab.label;"
accesskey="&bookmarkThisTab.accesskey;"
oncommand="BookmarkThisTab();"/>
<xul:menuitem id="context_bookmarkAllTabs"
label="&bookmarkAllTabs.label;"
accesskey="&bookmarkAllTabs.accesskey;"
command="Browser:BookmarkAllTabs"/>
<xul:menuitem id="context_undoCloseTab"
label="&undoCloseTab.label;"
accesskey="&undoCloseTab.accesskey;"
command="History:UndoCloseTab"
anonid="undoCloseTabMenuItem"/>
<xul:menuseparator/>
<xul:menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeTab(tabbrowser.mContextTab);"/>
</xul:menupopup>
<xul:tabs class="tabbrowser-tabs" flex="1"
anonid="tabcontainer"
setfocus="false"
onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
xbl:inherits="onnewtab"
ondblclick="this.parentNode.parentNode.parentNode.onTabBarDblClick(event);"
onclosetab="var node = this.parentNode;
while (node.localName != 'tabbrowser')
node = node.parentNode;
node.removeCurrentTab();">
<xul:tab selected="true" validate="never"
onerror="this.removeAttribute('image');"
maxwidth="250" width="0" minwidth="100" flex="100"
class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
</xul:tabs>
</xul:hbox>
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
<xul:notificationbox flex="1">
<xul:browser flex="1" type="content-primary" message="true" disablehistory="true"
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
</xul:notificationbox>
</xul:tabpanels>
</xul:tabbox>
<children/>
</content>
<implementation>
<field name="mPrefs" readonly="true">
Components.classes['@mozilla.org/preferences-service;1']
.getService(Components.interfaces.nsIPrefService)
.getBranch(null);
</field>
<field name="mURIFixup" readonly="true">
Components.classes["@mozilla.org/docshell/urifixup;1"]
.getService(Components.interfaces.nsIURIFixup);
</field>
<field name="mFaviconService" readonly="true">
Components.classes["@mozilla.org/browser/favicon-service;1"]
.getService(Components.interfaces.nsIFaviconService);
</field>
<field name="mTabBox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
</field>
<field name="mTabDropIndicatorBar">
this.mTabBox.childNodes[0]
</field>
<field name="mStrip" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "strip");
</field>
<field name="mTabContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
</field>
<field name="mPanelContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
</field>
<field name="mTabs" readonly="true">
this.mTabContainer.childNodes
</field>
<field name="mStringBundle">
document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
</field>
<field name="mUndoCloseTabMenuItem">
document.getAnonymousElementByAttribute(this, "anonid", "undoCloseTabMenuItem");
</field>
<field name="mCurrentTab">
null
</field>
<field name="mCurrentBrowser">
null
</field>
<field name="mProgressListeners">
[]
</field>
<field name="mTabsProgressListeners">
[]
</field>
<field name="mTabListeners">
new Array()
</field>
<field name="mTabFilters">
new Array()
</field>
<field name="mTabbedMode">
false
</field>
<field name="mIsBusy">
false
</field>
<field name="mContextTab">
null
</field>
<field name="arrowKeysShouldWrap" readonly="true">
#ifdef XP_MACOSX
true
#else
false
#endif
</field>
<field name="mAddProgressListenerWasCalled">
false
</field>
<field name="_browsers">
null
</field>
<field name="_blockDblClick">
false
</field>
<field name="_autoScrollPopup">
null
</field>
<method name="getBrowserAtIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.browsers[aIndex];
]]>
</body>
</method>
<method name="getBrowserIndexForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var browsers = this.browsers;
for (var i = 0; i < browsers.length; i++)
if (browsers[i].contentDocument == aDocument)
return i;
return -1;
]]>
</body>
</method>
<method name="getBrowserForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var index = this.getBrowserIndexForDocument(aDocument);
if (index < 0)
return null;
return this.getBrowserAtIndex(index);
]]>
</body>
</method>
<method name="getNotificationBox">
<parameter name="aBrowser"/>
<body>
<![CDATA[
if (aBrowser)
return aBrowser.parentNode;
else if (this.mCurrentBrowser)
return this.mCurrentBrowser.parentNode;
return null;
]]>
</body>
</method>
<!-- A web progress listener object definition for a given tab. -->
<method name="mTabProgressListener">
<parameter name="aTab"/>
<parameter name="aBrowser"/>
<parameter name="aStartsBlank"/>
<body>
<![CDATA[
return ({
mTabBrowser: this,
mTab: aTab,
mBrowser: aBrowser,
mBlank: aStartsBlank,
// cache flags for correct status bar update after tab switching
mStateFlags: 0,
mStatus: 0,
mMessage: "",
mTotalProgress: 0,
// count of open requests (should always be 0 or 1)
mRequestCount: 0,
onProgressChange : function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress)
{
this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
if (this.mBlank)
return;
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
let p = this.mTabBrowser.mProgressListeners[i];
if (p)
try {
p.onProgressChange(aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
let p = this.mTabBrowser.mTabsProgressListeners[i];
if (p)
try {
p.onProgressChange(this.mBrowser, aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
},
onProgressChange64 : function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress)
{
return this.onProgressChange(aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
aMaxTotalProgress);
},
onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
{
if (!aRequest)
return;
var oldBlank = this.mBlank;
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
const nsIChannel = Components.interfaces.nsIChannel;
if (aStateFlags & nsIWebProgressListener.STATE_START) {
this.mRequestCount++;
}
else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
const NS_ERROR_UNKNOWN_HOST = 2152398878;
if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
// to prevent bug 235825: wait for the request handled
// by the automatic keyword resolver
return;
}
// since we (try to) only handle STATE_STOP of the last request,
// the count of open requests should now be 0
this.mRequestCount = 0;
}
if (aStateFlags & nsIWebProgressListener.STATE_START &&
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
// It's okay to clear what the user typed when we start
// loading a document. If the user types, this counter gets
// set to zero, if the document load ends without an
// onLocationChange, this counter gets decremented
// (so we keep it while switching tabs after failed loads)
// We need to add 2 because loadURIWithFlags may have
// cancelled a pending load which would have cleared
// its anchor scroll detection temporary increment.
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
this.mBrowser.userTypedClear += 2;
if (!this.mBlank) {
this.mTab.setAttribute("busy", "true");
this.mTabBrowser.updateIcon(this.mTab);
this.mTabBrowser.setTabTitleLoading(this.mTab);
if (this.mTabBrowser.mCurrentTab == this.mTab)
this.mTabBrowser.mIsBusy = true;
}
}
else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
// The document is done loading, we no longer want the
// value cleared.
if (this.mBrowser.userTypedClear > 1)
this.mBrowser.userTypedClear -= 2;
else if (this.mBrowser.userTypedClear > 0)
this.mBrowser.userTypedClear--;
if (!this.mBrowser.mIconURL)
this.mTabBrowser.useDefaultIcon(this.mTab);
}
if (this.mBlank)
this.mBlank = false;
this.mTab.removeAttribute("busy");
this.mTabBrowser.updateIcon(this.mTab);
var location = aRequest.QueryInterface(nsIChannel).URI;
// For keyword URIs clear the user typed value since they will be changed into real URIs
if (location.scheme == "keyword")
this.mBrowser.userTypedValue = null;
if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
this.mTabBrowser.setTabTitle(this.mTab);
if (this.mTabBrowser.mCurrentTab == this.mTab)
this.mTabBrowser.mIsBusy = false;
}
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
let p = this.mTabBrowser.mProgressListeners[i];
if (p)
try {
if (!oldBlank)
p.onStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
// make sure that the visible status of new blank tabs is correctly set
else if ("onUpdateCurrentBrowser" in p)
p.onUpdateCurrentBrowser(aStateFlags, aStatus, "", 0);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
let p = this.mTabBrowser.mTabsProgressListeners[i];
if (p)
try {
p.onStateChange(this.mBrowser, aWebProgress, aRequest, aStateFlags, aStatus);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
if (aStateFlags & (nsIWebProgressListener.STATE_START |
nsIWebProgressListener.STATE_STOP)) {
// reset cached temporary values at beginning and end
this.mMessage = "";
this.mTotalProgress = 0;
}
this.mStateFlags = aStateFlags;
this.mStatus = aStatus;
},
onLocationChange : function(aWebProgress, aRequest, aLocation)
{
// The document loaded correctly, clear the value if we should
if (this.mBrowser.userTypedClear > 0)
this.mBrowser.userTypedValue = null;
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow &&
aWebProgress.isLoadingDocument)
this.mTabBrowser.setIcon(this.mTab, null);
// changing location, clear out the missing plugins list
this.mBrowser.missingPlugins = null;
if (this.mBlank)
return;
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
let p = this.mTabBrowser.mProgressListeners[i];
if (p)
try {
p.onLocationChange(aWebProgress, aRequest, aLocation);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
let p = this.mTabBrowser.mTabsProgressListeners[i];
if (p)
try {
p.onLocationChange(this.mBrowser, aWebProgress, aRequest, aLocation);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
},
onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
{
if (this.mBlank)
return;
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
let p = this.mTabBrowser.mProgressListeners[i];
if (p)
try {
p.onStatusChange(aWebProgress, aRequest, aStatus, aMessage);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
let p = this.mTabBrowser.mTabsProgressListeners[i];
if (p)
try {
p.onStatusChange(this.mBrowser, aWebProgress, aRequest, aStatus, aMessage);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
this.mMessage = aMessage;
},
onSecurityChange : function(aWebProgress, aRequest, aState)
{
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
let p = this.mTabBrowser.mProgressListeners[i];
if (p)
try {
p.onSecurityChange(aWebProgress, aRequest, aState);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
let p = this.mTabBrowser.mTabsProgressListeners[i];
if (p)
try {
p.onSecurityChange(this.mBrowser, aWebProgress, aRequest, aState);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
},
onRefreshAttempted : function(aWebProgress, aURI, aDelay, aSameURI)
{
var allowRefresh = true;
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
let p = this.mTabBrowser.mProgressListeners[i];
if (p && "onRefreshAttempted" in p) {
try {
if (!p.onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI))
allowRefresh = false;
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
}
for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
let p = this.mTabBrowser.mTabsProgressListeners[i];
if (p && "onRefreshAttempted" in p) {
try {
if (!p.onRefreshAttempted(this.mBrowser, aWebProgress, aURI, aDelay, aSameURI))
allowRefresh = false;
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
return allowRefresh;
},
QueryInterface : function(aIID)
{
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
}
});
]]>
</body>
</method>
<method name="setIcon">
<parameter name="aTab"/>
<parameter name="aURI"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
if (aURI && this.mFaviconService) {
if (!(aURI instanceof Ci.nsIURI))
aURI = makeURI(aURI);
this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI,
aURI, false);
}
this.updateIcon(aTab);
if (browser == this.mCurrentBrowser) {
for (let i = 0; i < this.mProgressListeners.length; i++) {
let p = this.mProgressListeners[i];
if ('onLinkIconAvailable' in p)
try {
p.onLinkIconAvailable(browser);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
}
for (let i = 0; i < this.mTabsProgressListeners.length; i++) {
let p = this.mTabsProgressListeners[i];
if ('onLinkIconAvailable' in p)
try {
p.onLinkIconAvailable(browser);
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
]]>
</body>
</method>
<method name="updateIcon">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
if (!aTab.hasAttribute("busy") && browser.mIconURL)
aTab.setAttribute("image", browser.mIconURL);
else
aTab.removeAttribute("image");
]]>
</body>
</method>
<method name="shouldLoadFavIcon">
<parameter name="aURI"/>
<body>
<![CDATA[
return (aURI && this.mPrefs.getBoolPref("browser.chrome.site_icons") &&
this.mPrefs.getBoolPref("browser.chrome.favicons") &&
("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
]]>
</body>
</method>
<method name="useDefaultIcon">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
if (browser.contentDocument instanceof ImageDocument) {
if (this.mPrefs.getBoolPref("browser.chrome.site_icons")) {
try {
var sz = this.mPrefs.getIntPref("browser.chrome.image_icons.max_size");
if (!sz)
return;
var req = browser.contentDocument.imageRequest;
if (!req || !req.image ||
req.image.width > sz ||
req.image.height > sz)
return;
this.setIcon(aTab, browser.currentURI);
} catch (e) { }
}
}
// Use documentURIObject in the check for shouldLoadFavIcon so that we
// do the right thing with about:-style error pages. Bug 453442
else if (this.shouldLoadFavIcon(browser.contentDocument.documentURIObject)) {
var url = browser.currentURI.prePath + "/favicon.ico";
if (!this.isFailedIcon(url))
this.setIcon(aTab, url);
}
]]>
</body>
</method>
<method name="isFailedIcon">
<parameter name="aURI"/>
<body>
<![CDATA[
if (this.mFaviconService) {
if (!(aURI instanceof Ci.nsIURI))
aURI = makeURI(aURI);
return this.mFaviconService.isFailedFavicon(aURI);
}
return null;
]]>
</body>
</method>
<method name="updateTitlebar">
<body>
<![CDATA[
var newTitle = "";
var docTitle;
var docElement = this.ownerDocument.documentElement;
var sep = docElement.getAttribute("titlemenuseparator");
if (this.docShell.contentViewer)
docTitle = this.contentTitle;
if (!docTitle)
docTitle = docElement.getAttribute("titledefault");
var modifier = docElement.getAttribute("titlemodifier");
if (docTitle) {
newTitle += docElement.getAttribute("titlepreface");
newTitle += docTitle;
if (modifier)
newTitle += sep;
}
newTitle += modifier;
// If location bar is hidden and the URL type supports a host,
// add the scheme and host to the title to prevent spoofing.
// XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
try {
if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
var uri = this.mURIFixup.createExposableURI(
this.mCurrentBrowser.currentURI);
if (uri.scheme == "about")
newTitle = uri.spec + sep + newTitle;
else
newTitle = uri.prePath + sep + newTitle;
}
} catch (e) {}
this.ownerDocument.title = newTitle;
]]>
</body>
</method>
<method name="updatePopupMenu">
<parameter name="aPopupMenu"/>
<body>
<![CDATA[
this.mContextTab = document.popupNode.localName == "tab" ?
document.popupNode : this.selectedTab;
var disabled = this.mTabs.length == 1;
var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
for (var i = 0; i < menuItems.length; i++)
menuItems[i].disabled = disabled;
// Session store
// XXXzeniko should't we just disable this item as we disable
// the tabbrowser-multiple items above - for consistency?
this.mUndoCloseTabMenuItem.hidden =
Cc["@mozilla.org/browser/sessionstore;1"].
getService(Ci.nsISessionStore).
getClosedTabCount(window) == 0;
]]>
</body>
</method>
<method name="updateCurrentBrowser">
<parameter name="aForceUpdate"/>
<body>
<![CDATA[
var newBrowser = this.getBrowserAtIndex(this.mTabContainer.selectedIndex);
if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
return;
if (this.mCurrentTab != this.selectedTab)
this.mCurrentTab.owner = null;
var fm = Components.classes["@mozilla.org/focus-manager;1"].
getService(Components.interfaces.nsIFocusManager);
var focusedChromeElement = fm.getFocusedElementForWindow(window, false, {});
var oldBrowser = this.mCurrentBrowser;
if (oldBrowser)
oldBrowser.setAttribute("type", "content-targetable");
var updatePageReport = false;
if (!oldBrowser ||
(oldBrowser.pageReport && !newBrowser.pageReport) ||
(!oldBrowser.pageReport && newBrowser.pageReport))
updatePageReport = true;
newBrowser.setAttribute("type", "content-primary");
this.mCurrentBrowser = newBrowser;
this.mCurrentTab = this.selectedTab;
if (updatePageReport)
this.mCurrentBrowser.updatePageReport();
// Update the URL bar.
var loc = this.mCurrentBrowser.currentURI;
var webProgress = this.mCurrentBrowser.webProgress;
var securityUI = this.mCurrentBrowser.securityUI;
var i, p;
for (i = 0; i < this.mProgressListeners.length; i++) {
p = this.mProgressListeners[i];
if (p)
try {
p.onLocationChange(webProgress, null, loc);
if (securityUI)
p.onSecurityChange(webProgress, null, securityUI.state);
// make sure that all status indicators are properly updated
if ("onUpdateCurrentBrowser" in p) {
var listener = this.mTabListeners[this.mTabContainer.selectedIndex] || null;
if (listener && listener.mStateFlags)
p.onUpdateCurrentBrowser(listener.mStateFlags, listener.mStatus,
listener.mMessage, listener.mTotalProgress);
}
} catch (e) {
// don't inhibit other listeners or following code
Components.utils.reportError(e);
}
}
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
// Update the window title.
this.updateTitlebar();
// If the new tab is busy, and our current state is not busy, then
// we need to fire a start to all progress listeners.
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
this.mIsBusy = true;
webProgress = this.mCurrentBrowser.webProgress;
for (i = 0; i < this.mProgressListeners.length; i++) {
p = this.mProgressListeners[i];
if (p)
try {
p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0);
} catch (e) {
// don't inhibit other listeners or following code
Components.utils.reportError(e);
}
}
}
// If the new tab is not busy, and our current state is busy, then
// we need to fire a stop to all progress listeners.
if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
this.mIsBusy = false;
webProgress = this.mCurrentBrowser.webProgress;
for (i = 0; i < this.mProgressListeners.length; i++) {
p = this.mProgressListeners[i];
if (p)
try {
p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0);
} catch (e) {
// don't inhibit other listeners or following code
Components.utils.reportError(e);
}
}
}
// We've selected the new tab, so go ahead and notify listeners.
var event = document.createEvent("Events");
event.initEvent("TabSelect", true, false);
this.mCurrentTab.dispatchEvent(event);
// change focus to the new tab if nothing is focused, the old tab
// is focused or there is something in the new tab to focus. One
// specific case where focus is not changed is when the new tab
// has no focused element and a chrome element is focused.
if (!focusedChromeElement || focusedChromeElement == oldBrowser ||
fm.getFocusedElementForWindow(window.content, true, {}))
fm.setFocus(newBrowser, fm.FLAG_NOSCROLL);
]]>
</body>
</method>
<method name="onTabClick">
<parameter name="event"/>
<body>
<![CDATA[
if (event.button != 1 || event.target.localName != 'tab')
return;
if (this.mTabs.length > 1 ||
!this.mPrefs.getBoolPref("browser.tabs.closeWindowWithLastTab"))
this.removeTab(event.target);
event.stopPropagation();
]]>
</body>
</method>
<method name="onTitleChanged">
<parameter name="evt"/>
<body>
<![CDATA[
if (evt.target != this.contentDocument)
return;
var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;
var tab = document.getAnonymousElementByAttribute(tabBrowser, "linkedpanel", this.parentNode.id);
tabBrowser.setTabTitle(tab);
if (tab == tabBrowser.mCurrentTab)
tabBrowser.updateTitlebar();
]]>
</body>
</method>
<method name="setTabTitleLoading">
<parameter name="aTab"/>
<body>
<![CDATA[
aTab.label = this.mStringBundle.getString("tabs.loading");
aTab.setAttribute("crop", "end");
]]>
</body>
</method>
<method name="setTabTitle">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
var crop = "end";
var title = browser.contentTitle;
if (!title) {
if (browser.currentURI.spec) {
try {
title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
} catch(ex) {
title = browser.currentURI.spec;
}
}
if (title && title != "about:blank") {
// At this point, we now have a URI.
// Let's try to unescape it using a character set
// in case the URI is not ASCII.
try {
var characterSet = browser.contentDocument.characterSet;
const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
.getService(Components.interfaces.nsITextToSubURI);
title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
} catch(ex) { /* Do nothing. */ }
crop = "center";
} else // Still no title? Fall back to our untitled string.
title = this.mStringBundle.getString("tabs.untitled");
}
aTab.label = title;
aTab.setAttribute("crop", crop);
]]>
</body>
</method>
<method name="setStripVisibilityTo">
<parameter name="aShow"/>
<body>
<![CDATA[
this.mStrip.collapsed = !aShow;
if (aShow) {
// XXXdwh temporary unclean dependency on specific menu items in navigator.xul
document.getElementById("menu_closeWindow").hidden = false;
document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.closeTab"));
if (!this.mTabbedMode)
this.enterTabbedMode();
}
else {
// XXXdwh temporary unclean dependency on specific menu items in navigator.xul
document.getElementById("menu_closeWindow").hidden = true;
document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.close"));
}
]]>
</body>
</method>
<method name="getStripVisibility">
<body>
return !this.mStrip.collapsed;
</body>
</method>
<method name="enterTabbedMode">
<body>
<![CDATA[
this.mTabbedMode = true; // Welcome to multi-tabbed mode.
// Get the first tab all hooked up with a title listener and popup blocking listener.
this.mCurrentBrowser.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
if (XULBrowserWindow.isBusy) {
this.mCurrentTab.setAttribute("busy", "true");
this.mIsBusy = true;
this.setTabTitleLoading(this.mCurrentTab);
this.updateIcon(this.mCurrentTab);
} else {
this.setTabTitle(this.mCurrentTab);
this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
}
var filter;
if (this.mTabFilters.length > 0) {
// Use the filter hooked up in our addProgressListener
filter = this.mTabFilters[0];
} else {
// create a filter and hook it up to our first browser
filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Components.interfaces.nsIWebProgress);
this.mTabFilters[0] = filter;
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
}
// Remove all our progress listeners from the active browser's filter.
for (var i = 0; i < this.mProgressListeners.length; i++) {
var p = this.mProgressListeners[i];
if (p)
filter.removeProgressListener(p);
}
// Wire up a progress listener to our filter.
const listener = this.mTabProgressListener(this.mCurrentTab,
this.mCurrentBrowser,
false);
filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[0] = listener;
]]>
</body>
</method>
<method name="loadOneTab">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<parameter name="aLoadInBackground"/>
<parameter name="aAllowThirdPartyFixup"/>
<body>
<![CDATA[
var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
var owner = bgLoad ? null : this.selectedTab;
var tab = this.addTab(aURI, aReferrerURI, aCharset, aPostData, owner,
aAllowThirdPartyFixup);
if (!bgLoad)
this.selectedTab = tab;
return tab;
]]>
</body>
</method>
<method name="loadTabs">
<parameter name="aURIs"/>
<parameter name="aLoadInBackground"/>
<parameter name="aReplace"/>
<body><![CDATA[
if (!aURIs.length)
return;
// The tab selected after this new tab is closed (i.e. the new tab's
// "owner") is the next adjacent tab (i.e. not the previously viewed tab)
// when several urls are opened here (i.e. closing the first should select
// the next of many URLs opened) or if the pref to have UI links opened in
// the background is set (i.e. the link is not being opened modally)
//
// i.e.
// Number of URLs Load UI Links in BG Focus Last Viewed?
// == 1 false YES
// == 1 true NO
// > 1 false/true NO
var owner = (aURIs.length > 1) || aLoadInBackground ? null : this.selectedTab;
var firstTabAdded = null;
if (aReplace) {
try {
this.loadURI(aURIs[0], null, null);
} catch (e) {
// Ignore failure in case a URI is wrong, so we can continue
// opening the next ones.
}
}
else
firstTabAdded = this.addTab(aURIs[0], null, null, null, owner, false);
var tabNum = this.mTabContainer.selectedIndex;
for (let i = 1; i < aURIs.length; ++i) {
let tab = this.addTab(aURIs[i]);
if (aReplace)
this.moveTabTo(tab, ++tabNum);
}
if (!aLoadInBackground) {
if (firstTabAdded) {
// .selectedTab setter focuses the content area
this.selectedTab = firstTabAdded;
}
else
window.content.focus();
}
]]></body>
</method>
<method name="addTab">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<parameter name="aOwner"/>
<parameter name="aAllowThirdPartyFixup"/>
<body>
<![CDATA[
this._browsers = null; // invalidate cache
if (!this.mTabbedMode)
this.enterTabbedMode();
// if we're adding tabs, we're past interrupt mode, ditch the owner
if (this.mCurrentTab.owner)
this.mCurrentTab.owner = null;
var t = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tab");
var blank = (aURI == "about:blank");
if (blank)
t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
else
t.setAttribute("label", aURI);
t.setAttribute("crop", "end");
t.maxWidth = this.mTabContainer.mTabMaxWidth;
t.minWidth = this.mTabContainer.mTabMinWidth;
t.width = 0;
t.setAttribute("flex", "100");
t.setAttribute("validate", "never");
t.setAttribute("onerror", "this.removeAttribute('image');");
t.className = "tabbrowser-tab";
this.mTabContainer.appendChild(t);
if (this.tabContainer.mTabstrip._isRTLScrollbox) {
/* In RTL UI, the tab is visually added to the left side of the
* tabstrip. This means the tabstip has to be scrolled back in
* order to make sure the same set of tabs is visible before and
* after the new tab is added */
this.tabContainer.mTabstrip.scrollByPixels(this.mTabs[0].clientWidth);
}
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
// If this new tab is owned by another, assert that relationship
if (aOwner)
t.owner = aOwner;
var b = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"browser");
b.setAttribute("type", "content-targetable");
b.setAttribute("message", "true");
b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
if (this.hasAttribute("autocompletepopup"))
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
// Add the Message and the Browser to the box
var notificationbox = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"notificationbox");
notificationbox.setAttribute("flex", "1");
notificationbox.appendChild(b);
b.setAttribute("flex", "1");
this.mPanelContainer.appendChild(notificationbox);
b.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
if (this.mStrip.collapsed)
this.setStripVisibilityTo(true);
// wire up a progress listener for the new browser object.
var position = this.mTabContainer.childNodes.length-1;
var tabListener = this.mTabProgressListener(t, b, blank);
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Components.interfaces.nsIWebProgress);
filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[position] = tabListener;
this.mTabFilters[position] = filter;
b._fastFind = this.fastFind;
var uniqueId = "panel" + Date.now() + position;
this.mPanelContainer.lastChild.id = uniqueId;
t.linkedPanel = uniqueId;
t.linkedBrowser = b;
t._tPos = position;
if (t.previousSibling.selected)
t.setAttribute("afterselected", true);
if (!blank) {
// Stop the existing about:blank load. Otherwise, if aURI
// doesn't stop in-progress loads on its own, we'll get into
// trouble with multiple parallel loads running at once.
b.stop();
// pretend the user typed this so it'll be available till
// the document successfully loads
b.userTypedValue = aURI;
if (aPostData === undefined)
aPostData = null;
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
if (aAllowThirdPartyFixup) {
flags = nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
}
try {
b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
}
catch (ex) {
}
}
this.tabContainer.adjustTabstrip();
// Do this asynchronically, as we don't know yet if the tab
// will be selected.
setTimeout(function (aTabContainer) {
if (aTabContainer.selectedItem != t)
aTabContainer._notifyBackgroundTab(t);
}, 0, this.tabContainer);
// XXXmano: this is a temporary workaround for bug 345399
// We need to manually update the scroll buttons disabled state
// if a tab was inserted to the overflow area or removed from it
// without any scrolling and when the tabbar has already
// overflowed.
this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
// Dispatch a new tab notification. We do this once we're
// entirely done, so that things are in a consistent state
// even if the event listener opens or closes tabs.
var evt = document.createEvent("Events");
evt.initEvent("TabOpen", true, false);
t.dispatchEvent(evt);
return t;
]]>
</body>
</method>
<method name="warnAboutClosingTabs">
<parameter name="aAll"/>
<body>
<![CDATA[
var numTabs = this.mTabContainer.childNodes.length;
var reallyClose = true;
if (numTabs <= 1)
return reallyClose;
const pref = "browser.tabs.warnOnClose";
var shouldPrompt = this.mPrefs.getBoolPref(pref);
if (shouldPrompt) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
//default to true: if it were false, we wouldn't get this far
var warnOnClose = { value:true };
var bundle = this.mStringBundle;
var tabsToClose = numTabs; //number of tabs to be removed
if (!aAll)
--tabsToClose;
var messageKey = (tabsToClose == 1) ? "tabs.closeWarningOneTab" : "tabs.closeWarningMultipleTabs";
var closeKey = (tabsToClose == 1) ? "tabs.closeButtonOne" : "tabs.closeButtonMultiple";
// focus the window before prompting.
// this will raise any minimized window, which will
// make it obvious which window the prompt is for and will
// solve the problem of windows "obscuring" the prompt.
// see bug #350299 for more details
window.focus();
var buttonPressed = promptService.confirmEx(window,
bundle.getString('tabs.closeWarningTitle'),
bundle.getFormattedString(messageKey, [tabsToClose]),
(promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
+ (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
bundle.getString(closeKey),
null, null,
bundle.getString('tabs.closeWarningPromptMe'),
warnOnClose);
reallyClose = (buttonPressed == 0);
// don't set the pref unless they press OK and it's false
if (reallyClose && !warnOnClose.value)
this.mPrefs.setBoolPref(pref, false);
}
return reallyClose;
]]>
</body>
</method>
<method name="removeAllTabsBut">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.warnAboutClosingTabs(false)) {
this.selectedTab = aTab;
for (let i = this.mTabs.length - 1; i >= 0; --i) {
if (this.mTabs[i] != aTab)
this.removeTab(this.mTabs[i]);
}
}
]]>
</body>
</method>
<method name="removeCurrentTab">
<body>
<![CDATA[
this.removeTab(this.mCurrentTab);
]]>
</body>
</method>
<field name="_removingTabs">
[]
</field>
<method name="removeTab">
<parameter name="aTab"/>
<body>
<![CDATA[
this._endRemoveTab(this._beginRemoveTab(aTab, false, null, true));
]]>
</body>
</method>
<!-- Tab close requests are ignored if the window is closing anyway,
e.g. when holding Ctrl+W. -->
<field name="_windowIsClosing">
false
</field>
<!-- Returns everything that _endRemoveTab needs in an array. -->
<method name="_beginRemoveTab">
<parameter name="aTab"/>
<parameter name="aTabWillBeMoved"/>
<parameter name="aCloseWindowWithLastTab"/>
<parameter name="aCloseWindowFastpath"/>
<body>
<![CDATA[
if (this._removingTabs.indexOf(aTab) > -1 || this._windowIsClosing)
return null;
var browser = this.getBrowserForTab(aTab);
if (!aTabWillBeMoved) {
let ds = browser.docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
return null;
}
var closeWindow = false;
var l = this.mTabs.length - this._removingTabs.length;
var newTab = false;
if (l == 1) {
closeWindow = aCloseWindowWithLastTab != null ?
aCloseWindowWithLastTab :
this.mPrefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
// Closing the tab and replacing it with a blank one is notably slower
// than closing the window right away. If the caller opts in, take
// the fast path.
if (closeWindow &&
aCloseWindowFastpath &&
this._removingTabs.length == 0 &&
(this._windowIsClosing = window.closeWindow(true)))
return null;
newTab = true;
l++;
}
if (l == 2) {
let autohide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
let tabStripHide = !window.toolbar.visible;
if (autohide || tabStripHide)
this.setStripVisibilityTo(false);
}
this._removingTabs.push(aTab);
// We're committed to closing the tab now.
// Dispatch a notification.
// We dispatch it before any teardown so that event listeners can
// inspect the tab that's about to close.
var evt = document.createEvent("UIEvent");
evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
aTab.dispatchEvent(evt);
// Remove the tab's filter and progress listener.
const filter = this.mTabFilters[aTab._tPos];
browser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
// Remove our title change and blocking listeners
browser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
// We are no longer the primary content area.
browser.setAttribute("type", "content-targetable");
// Remove this tab as the owner of any other tabs, since it's going away.
Array.forEach(this.mTabs, function (tab) {
if ("owner" in tab && tab.owner == aTab)
// |tab| is a child of the tab we're removing, make it an orphan
tab.owner = null;
});
return [aTab, closeWindow, newTab];
]]>
</body>
</method>
<method name="_endRemoveTab">
<parameter name="args"/>
<body>
<![CDATA[
if (!args)
return;
var [aTab, aCloseWindow, aNewTab] = args;
// update the UI early for responsiveness
aTab.collapsed = true;
if (aNewTab)
this.addTab("about:blank");
this.tabContainer._fillTrailingGap();
this._blurTab(aTab);
this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
if (aCloseWindow) {
this._windowIsClosing = true;
while (this._removingTabs.length)
this._endRemoveTab([this._removingTabs[0], false]);
} else if (!this._windowIsClosing) {
if (aNewTab && gURLBar)
gURLBar.focus();
this.tabContainer.adjustTabstrip();
// workaround for bug 345399
this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
}
// We're going to remove the tab and the browser now.
// Clean up mTabFilters and mTabListeners now rather than in
// _beginRemoveTab, so that their size is always in sync with the
// number of tabs and browsers (the xbl destructor depends on this).
this.mTabFilters.splice(aTab._tPos, 1);
this.mTabListeners.splice(aTab._tPos, 1);
var browser = this.getBrowserForTab(aTab);
// Because of the way XBL works (fields just set JS
// properties on the element) and the code we have in place
// to preserve the JS objects for any elements that have
// JS properties set on them, the browser element won't be
// destroyed until the document goes away. So we force a
// cleanup ourselves.
// This has to happen before we remove the child so that the
// XBL implementation of nsIObserver still works.
browser.destroy();
if (browser == this.mCurrentBrowser)
this.mCurrentBrowser = null;
// Invalidate browsers cache, as the tab is removed from the
// tab container.
this._browsers = null;
// Remove the tab ...
this.tabContainer.removeChild(aTab);
// ... and fix up the _tPos properties immediately.
for (let i = aTab._tPos; i < this.mTabs.length; i++)
this.mTabs[i]._tPos = i;
// update first-tab/last-tab/beforeselected/afterselected attributes
this.selectedTab._selected = true;
// This will unload the document. An unload handler could remove
// dependant tabs, so it's important that the tabbrowser is now in
// a consistent state (tab removed, tab positions updated, etc.).
// Also, it's important that another tab has been selected before
// the panel is removed; otherwise, a random sibling panel can flash.
this.mPanelContainer.removeChild(browser.parentNode);
// As the panel is removed, the removal of a dependent document can
// cause the whole window to close. So at this point, it's possible
// that the binding is destructed.
if (this.mTabBox)
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
if (aCloseWindow)
this._windowIsClosing = closeWindow(true);
]]>
</body>
</method>
<method name="_blurTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.mCurrentTab != aTab)
return;
if (aTab.owner &&
this._removingTabs.indexOf(aTab.owner) == -1 &&
this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
this.selectedTab = aTab.owner;
return;
}
var tab = aTab;
do {
tab = tab.nextSibling;
} while (tab && this._removingTabs.indexOf(tab) != -1);
if (!tab) {
tab = aTab;
do {
tab = tab.previousSibling;
} while (tab && this._removingTabs.indexOf(tab) != -1);
}
this.selectedTab = tab;
]]>
</body>
</method>
<method name="swapBrowsersAndCloseOther">
<parameter name="aOurTab"/>
<parameter name="aOtherTab"/>
<body>
<![CDATA[
// That's gBrowser for the other window, not the tab's browser!
var remoteBrowser =
aOtherTab.ownerDocument.defaultView.getBrowser();
// First, start teardown of the other browser. Make sure to not
// fire the beforeunload event in the process. Close the other
// window if this was its last tab.
var endRemoveArgs = remoteBrowser._beginRemoveTab(aOtherTab, true, true);
// Unhook our progress listener
var ourIndex = aOurTab._tPos;
const filter = this.mTabFilters[ourIndex];
var tabListener = this.mTabListeners[ourIndex];
var ourBrowser = this.getBrowserForTab(aOurTab);
ourBrowser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(tabListener);
var tabListenerBlank = tabListener.mBlank;
// Workarounds for bug 458697
// Icon might have been set on DOMLinkAdded, don't override that.
if (!ourBrowser.mIconURL && aOtherTab.linkedBrowser.mIconURL)
this.setIcon(aOurTab, aOtherTab.linkedBrowser.mIconURL);
var isBusy = aOtherTab.hasAttribute("busy");
if (isBusy) {
aOurTab.setAttribute("busy", "true");
if (aOurTab == this.selectedTab)
this.mIsBusy = true;
}
// Swap the docshells
ourBrowser.swapDocShells(aOtherTab.linkedBrowser);
// Finish tearing down the tab that's going away.
remoteBrowser._endRemoveTab(endRemoveArgs);
// Restore the progress listener
tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
tabListenerBlank);
this.mTabListeners[ourIndex] = tabListener;
filter.addProgressListener(tabListener,
Components.interfaces.nsIWebProgress.NOTIFY_ALL);
ourBrowser.webProgress.addProgressListener(filter,
Components.interfaces.nsIWebProgress.NOTIFY_ALL);
if (isBusy)
this.setTabTitleLoading(aOurTab);
else
this.setTabTitle(aOurTab);
this.updateIcon(aOurTab);
// If the tab was already selected (this happpens in the scenario
// of replaceTabWithWindow), notify onLocationChange, etc.
if (aOurTab == this.selectedTab)
this.updateCurrentBrowser(true);
]]>
</body>
</method>
<method name="reloadAllTabs">
<body>
<![CDATA[
var l = this.mPanelContainer.childNodes.length;
for (var i = 0; i < l; i++) {
try {
this.getBrowserAtIndex(i).reload();
} catch (e) {
// ignore failure to reload so others will be reloaded
}
}
]]>
</body>
</method>
<method name="reloadTab">
<parameter name="aTab"/>
<body>
<![CDATA[
this.getBrowserForTab(aTab).reload();
]]>
</body>
</method>
<method name="onTabBarDblClick">
<parameter name="aEvent"/>
<body>
<![CDATA[
// See hack note in the tabbrowser-close-button binding
if (!this._blockDblClick && aEvent.button == 0 &&
aEvent.originalTarget.localName == "box") {
// xxx this needs to check that we're in the empty area of the tabstrip
var e = document.createEvent("Events");
e.initEvent("NewTab", true, true);
this.dispatchEvent(e);
}
]]>
</body>
</method>
<method name="addProgressListener">
<parameter name="aListener"/>
<parameter name="aMask"/>
<body>
<![CDATA[
if (!this.mAddProgressListenerWasCalled) {
this.mAddProgressListenerWasCalled = true;
var autoHide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
var tabStripHide = !window.toolbar.visible;
if (!autoHide && !tabStripHide)
this.setStripVisibilityTo(true);
}
if (!this.mTabbedMode && this.mProgressListeners.length == 1) {
// If we are adding a 2nd progress listener, we need to enter tabbed mode
// because the browser status filter can only handle one progress listener.
// In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
this.enterTabbedMode();
}
this.mProgressListeners.push(aListener);
if (!this.mTabbedMode) {
// If someone does this:
// addProgressListener, removeProgressListener, addProgressListener
// don't create a new filter; reuse the existing filter.
if (this.mTabFilters.length == 0) {
// hook a filter up to our first browser
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Components.interfaces.nsIWebProgress);
this.mTabFilters[0] = filter;
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
}
// Directly hook the listener up to the filter for better performance
this.mTabFilters[0].addProgressListener(aListener, aMask);
}
]]>
</body>
</method>
<method name="removeProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
for (var i = 0; i < this.mProgressListeners.length; i++) {
if (this.mProgressListeners[i] == aListener) {
this.mProgressListeners.splice(i, 1);
break;
}
}
if (!this.mTabbedMode)
// Don't forget to remove it from the filter we hooked it up to
this.mTabFilters[0].removeProgressListener(aListener);
]]>
</body>
</method>
<method name="addTabsProgressListener">
<parameter name="aListener"/>
<body>
if (!this.mTabbedMode)
this.enterTabbedMode();
this.mTabsProgressListeners.push(aListener);
</body>
</method>
<method name="removeTabsProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
var pos = this.mTabsProgressListeners.indexOf(aListener);
if (pos >= 0)
this.mTabsProgressListeners.splice(pos, 1);
]]>
</body>
</method>
<method name="getBrowserForTab">
<parameter name="aTab"/>
<body>
<![CDATA[
return aTab.linkedBrowser;
]]>
</body>
</method>
<method name="selectTabAtIndex">
<parameter name="aIndex"/>
<parameter name="aEvent"/>
<body>
<![CDATA[
// count backwards for aIndex < 0
if (aIndex < 0)
aIndex += this.mTabs.length;
if (aIndex >= 0 &&
aIndex < this.mTabs.length &&
aIndex != this.tabContainer.selectedIndex)
this.selectedTab = this.mTabs[aIndex];
if (aEvent) {
aEvent.preventDefault();
aEvent.stopPropagation();
}
]]>
</body>
</method>
<property name="tabContainer" readonly="true">
<getter>
return this.mTabContainer;
</getter>
</property>
<property name="selectedTab">
<getter>
return this.mTabBox.selectedTab;
</getter>
<setter>
<![CDATA[
// Update the tab
this.mTabBox.selectedTab = val;
return val;
]]>
</setter>
</property>
<property name="selectedBrowser"
onget="return this.mCurrentBrowser;"
readonly="true"/>
<property name="browsers" readonly="true">
<getter>
<![CDATA[
return this._browsers ||
(this._browsers = Array.map(this.mTabs, function (tab) tab.linkedBrowser));
]]>
</getter>
</property>
<method name="_onDragStart">
<parameter name="aEvent"/>
<body>
<![CDATA[
var target = aEvent.target;
if (target.localName == "tab" &&
aEvent.originalTarget.localName != "toolbarbutton") {
var dt = aEvent.dataTransfer;
dt.mozSetDataAt(TAB_DROP_TYPE, target, 0);
var uri = this.getBrowserForTab(aEvent.target).currentURI;
var spec = uri ? uri.spec : "about:blank";
// We must not set text/x-moz-url or text/plain data here,
// otherwise trying to deatch the tab by dropping it on the desktop
// may result in an "internet shortcut"
dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
// Set the cursor to an arrow during tab drags.
dt.mozCursor = "default";
var canvas = tabPreviews.capture(target, false);
dt.setDragImage(canvas, 0, 0);
aEvent.stopPropagation();
}
this._dragLeftWindow = false;
]]>
</body>
</method>
<field name="mDragTime">0</field>
<field name="mDragOverDelay">350</field>
<field name="_supportedLinkDropTypes"><![CDATA[
["text/x-moz-url", "text/uri-list", "text/plain", "application/x-moz-file"]
]]></field>
<method name="_setEffectAllowedForDataTransfer">
<parameter name="aEvent"/>
<body>
<![CDATA[
var dt = aEvent.dataTransfer;
// Disallow dropping multiple items
if (dt.mozItemCount > 1)
return dt.effectAllowed = "none";
var types = dt.mozTypesAt(0);
var sourceNode = null;
// tabs are always added as the first type
if (types[0] == TAB_DROP_TYPE) {
var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (sourceNode instanceof XULElement &&
sourceNode.localName == "tab" &&
(sourceNode.parentNode == this.mTabContainer ||
(sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
if (sourceNode.parentNode == this.mTabContainer &&
(aEvent.screenX >= sourceNode.boxObject.screenX &&
aEvent.screenX <= (sourceNode.boxObject.screenX +
sourceNode.boxObject.width))) {
return dt.effectAllowed = "none";
}
return dt.effectAllowed = "copyMove";
}
}
for (var i=0; i < this._supportedLinkDropTypes.length; i++) {
if (types.contains(this._supportedLinkDropTypes[i])) {
// Here we need to to do this manually
return dt.effectAllowed = dt.dropEffect = "link";
}
}
return dt.effectAllowed = "none";
]]>
</body>
</method>
<method name="_onDragOver">
<parameter name="aEvent"/>
<body>
<![CDATA[
var effects = this._setEffectAllowedForDataTransfer(aEvent);
var ib = this.mTabDropIndicatorBar;
if (effects == "" || effects == "none") {
ib.collapsed = "true";
return;
}
aEvent.preventDefault();
aEvent.stopPropagation();
var tabStrip = this.mTabContainer.mTabstrip;
var ltr = (window.getComputedStyle(this.parentNode, null).direction
== "ltr");
// autoscroll the tab strip if we drag over the scroll
// buttons, even if we aren't dragging a tab, but then
// return to avoid drawing the drop indicator
var pixelsToScroll = 0;
if (this.mTabContainer.getAttribute("overflow") == "true") {
var targetAnonid = aEvent.originalTarget.getAttribute("anonid");
switch (targetAnonid) {
case "scrollbutton-up":
pixelsToScroll = tabStrip.scrollIncrement * -1;
break;
case "scrollbutton-down":
case "alltabs-button":
case "newtab-button":
pixelsToScroll = tabStrip.scrollIncrement;
break;
}
if (pixelsToScroll)
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
}
if (effects == "link" && aEvent.target.localName == "tab") {
if (!this.mDragTime)
this.mDragTime = Date.now();
if (Date.now() >= this.mDragTime + this.mDragOverDelay)
this.mTabContainer.selectedItem = aEvent.target;
return;
}
var newIndex = this.getNewIndex(aEvent);
var ib = this.mTabDropIndicatorBar;
var ind = ib.firstChild;
var scrollRect = tabStrip.scrollClientRect;
var rect = this.getBoundingClientRect();
var minMargin = scrollRect.left - rect.left;
// make sure we don't place the tab drop indicator past the
// edge, or the containing box will flex and stretch
// the tab drop indicator bar, which will flex the url bar.
// XXX todo
// just use first value if you can figure out how to get
// the tab drop indicator to crop instead of flex and stretch
// the tab drop indicator bar.
var maxMargin = Math.min(minMargin + scrollRect.width,
ib.getBoundingClientRect().right -
ind.clientWidth);
if (!ltr)
[minMargin, maxMargin] = [this.clientWidth - maxMargin,
this.clientWidth - minMargin];
var newMargin;
if (pixelsToScroll) {
// if we are scrolling, put the drop indicator at the edge
// so that it doesn't jump while scrolling
newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
}
else {
if (newIndex == this.mTabs.length) {
let tabRect = this.mTabs[newIndex-1].getBoundingClientRect();
if (ltr)
newMargin = tabRect.right - rect.left;
else
newMargin = rect.right - tabRect.left;
}
else {
let tabRect = this.mTabs[newIndex].getBoundingClientRect();
if (ltr)
newMargin = tabRect.left - rect.left;
else
newMargin = rect.right - tabRect.right;
}
// ensure we never place the drop indicator beyond our limits
if (newMargin < minMargin)
newMargin = minMargin;
else if (newMargin > maxMargin)
newMargin = maxMargin;
}
ind.style.MozMarginStart = newMargin + 'px';
ib.collapsed = false;
]]>
</body>
</method>
<method name="_onDrop">
<parameter name="aEvent"/>
<body>
<![CDATA[
var dt = aEvent.dataTransfer;
var dropEffect = dt.dropEffect;
var draggedTab;
if (dropEffect != "link") { // copy or move
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// not our drop then
if (!draggedTab)
return;
}
this.mTabDropIndicatorBar.collapsed = true;
aEvent.stopPropagation();
if (draggedTab && (dropEffect == "copy" ||
draggedTab.parentNode == this.mTabContainer)) {
var newIndex = this.getNewIndex(aEvent);
if (dropEffect == "copy") {
// copy the dropped tab (wherever it's from)
var newTab = this.duplicateTab(draggedTab);
this.moveTabTo(newTab, newIndex);
if (draggedTab.parentNode != this.mTabContainer || aEvent.shiftKey)
this.selectedTab = newTab;
}
else {
// move the dropped tab
if (newIndex > draggedTab._tPos)
newIndex--;
if (newIndex != draggedTab._tPos)
this.moveTabTo(draggedTab, newIndex);
}
}
else if (draggedTab) {
// swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows)
newIndex = this.getNewIndex(aEvent);
newTab = this.addTab("about:blank");
var newBrowser = this.getBrowserForTab(newTab);
// Stop the about:blank load
newBrowser.stop();
// make sure it has a docshell
newBrowser.docShell;
this.moveTabTo(newTab, newIndex);
this.swapBrowsersAndCloseOther(newTab, draggedTab);
// We need to set selectedTab after we've done
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
// it triggers will correctly update our URL bar.
this.selectedTab = newTab;
}
else {
var url;
for (var i=0; i < this._supportedLinkDropTypes.length; i++) {
let dataType = this._supportedLinkDropTypes[i];
// uri-list: for now, support dropping of the first URL
// only
var isURLList = dataType == "text/uri-list";
let urlData = isURLList ?
dt.mozGetDataAt("URL", 0) : dt.mozGetDataAt(dataType, 0);
if (urlData) {
url = transferUtils.retrieveURLFromData(urlData, isURLList ? "text/plain" : dataType);
break;
}
}
NS_ASSERT(url, "In the drop event, at least one mime-type should match our supported types");
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
// Also disallow dropping javascript: or data: urls--bail out
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
/^\s*(javascript|data):/.test(url))
return;
// XXXmano: temporary fix until dragDropSecurityCheck make the
// drag-session an optional paramter
var dragService = Cc["@mozilla.org/widget/dragservice;1"].
getService(Ci.nsIDragService);
var dragSession = dragService.getCurrentSession();
nsDragAndDrop.dragDropSecurityCheck(aEvent, dragSession, url);
var bgLoad = true;
try {
bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
}
catch (e) { }
if (aEvent.shiftKey)
bgLoad = !bgLoad;
if (document.getBindingParent(aEvent.originalTarget).localName != "tab" || dropEffect == "copy") {
// We're adding a new tab.
newIndex = this.getNewIndex(aEvent);
newTab = this.loadOneTab(getShortcutOrURI(url), null, null, null, bgLoad, false);
this.moveTabTo(newTab, newIndex);
}
else {
// Load in an existing tab.
var tab = aEvent.target;
try {
this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
if (this.mCurrentTab != tab && !bgLoad)
this.selectedTab = tab;
} catch(ex) {
// Just ignore invalid urls
}
}
}
]]>
</body>
</method>
<method name="_onDragEnd">
<parameter name="aEvent"/>
<body>
<![CDATA[
// Note: while this case is correctly handled here, this event
// isn't dispatched when the tab is moved within the tabstrip,
// see bug 460801.
// * mozUserCancelled = the user pressed ESC to cancel the drag
var dt = aEvent.dataTransfer;
if (dt.mozUserCancelled || dt.dropEffect != "none")
return;
// Disable detach within the browser toolbox
var eX = aEvent.screenX;
var wX = window.screenX;
// check if the drop point is horizontally within the window
if (eX > wX && eX < (wX + window.outerWidth)) {
var bo = this.mTabContainer.mTabstrip.boxObject;
// also avoid detaching if the the tab was dropped too close to
// the tabbar (half a tab)
var endScreenY = bo.screenY + 1.5 * bo.height;
var eY = aEvent.screenY;
if (eY < endScreenY && eY > window.screenY)
return;
}
var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
this.replaceTabWithWindow(draggedTab);
aEvent.stopPropagation();
]]>
</body>
</method>
<!-- Moves a tab to a new browser window, unless it's already the only tab
in the current window, in which case this will do nothing. -->
<method name="replaceTabWithWindow">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.mTabs.length == 1)
return null;
// tell a new window to take the "dropped" tab
var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
return ww.openWindow(window,
getBrowserURL(),
null,
"chrome,dialog=no,all",
aTab);
]]>
</body>
</method>
<method name="_onDragLeave">
<parameter name="aEvent"/>
<body>
<![CDATA[
this.mDragTime = 0;
// This does not work at all (see bug 458613)
var target = aEvent.relatedTarget;
while (target && target != this)
target = target.parentNode;
if (target)
return;
this.mTabDropIndicatorBar.collapsed = true;
aEvent.stopPropagation();
]]>
</body>
</method>
<method name="moveTabTo">
<parameter name="aTab"/>
<parameter name="aIndex"/>
<body>
<![CDATA[
this._browsers = null; // invalidate cache
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
var oldPosition = aTab._tPos;
aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
this.mCurrentTab._selected = false;
// use .item() instead of [] because dragging to the end of the strip goes out of
// bounds: .item() returns null (so it acts like appendChild), but [] throws
this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes.item(aIndex));
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
var i;
for (i = 0; i < this.mTabContainer.childNodes.length; i++) {
this.mTabContainer.childNodes[i]._tPos = i;
this.mTabContainer.childNodes[i]._selected = false;
}
this.mCurrentTab._selected = true;
this.mTabContainer.mTabstrip.ensureElementIsVisible(this.mCurrentTab, false);
var evt = document.createEvent("UIEvents");
evt.initUIEvent("TabMove", true, false, window, oldPosition);
aTab.dispatchEvent(evt);
return aTab;
]]>
</body>
</method>
<method name="getNewIndex">
<parameter name="aEvent"/>
<body>
<![CDATA[
var i;
if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
if (aEvent.screenX < this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
return i;
} else {
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
if (aEvent.screenX > this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
return i;
}
return this.mTabs.length;
]]>
</body>
</method>
<method name="moveTabForward">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab, tabPos + 1);
this.mCurrentTab.focus();
}
else if (this.arrowKeysShouldWrap)
this.moveTabToStart();
]]>
</body>
</method>
<method name="moveTabBackward">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, tabPos - 1);
this.mCurrentTab.focus();
}
else if (this.arrowKeysShouldWrap)
this.moveTabToEnd();
]]>
</body>
</method>
<method name="moveTabToStart">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, 0);
this.mCurrentTab.focus();
}
]]>
</body>
</method>
<method name="moveTabToEnd">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab,
this.browsers.length - 1);
this.mCurrentTab.focus();
}
]]>
</body>
</method>
<method name="moveTabOver">
<parameter name="aEvent"/>
<body>
<![CDATA[
var direction = window.getComputedStyle(this.parentNode, null).direction;
if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
(direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
this.moveTabForward();
else
this.moveTabBackward();
]]>
</body>
</method>
<method name="duplicateTab">
<parameter name="aTab"/><!-- can be from a different window as well -->
<body>
<![CDATA[
// try to have SessionStore duplicate the given tab
try {
var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
.getService(Components.interfaces.nsISessionStore);
return ss.duplicateTab(window, aTab);
} catch (ex) {
// fall back to basic URL copying
return this.loadOneTab(this.getBrowserForTab(aTab).currentURI.spec);
}
]]>
</body>
</method>
<!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
MAKE SURE TO ADD IT HERE AS WELL. -->
<property name="canGoBack"
onget="return this.mCurrentBrowser.canGoBack;"
readonly="true"/>
<property name="canGoForward"
onget="return this.mCurrentBrowser.canGoForward;"
readonly="true"/>
<method name="goBack">
<body>
<![CDATA[
return this.mCurrentBrowser.goBack();
]]>
</body>
</method>
<method name="goForward">
<body>
<![CDATA[
return this.mCurrentBrowser.goForward();
]]>
</body>
</method>
<method name="reload">
<body>
<![CDATA[
return this.mCurrentBrowser.reload();
]]>
</body>
</method>
<method name="reloadWithFlags">
<parameter name="aFlags"/>
<body>
<![CDATA[
return this.mCurrentBrowser.reloadWithFlags(aFlags);
]]>
</body>
</method>
<method name="stop">
<body>
<![CDATA[
return this.mCurrentBrowser.stop();
]]>
</body>
</method>
<!-- throws exception for unknown schemes -->
<method name="loadURI">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<body>
<![CDATA[
return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
]]>
</body>
</method>
<!-- throws exception for unknown schemes -->
<method name="loadURIWithFlags">
<parameter name="aURI"/>
<parameter name="aFlags"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<body>
<![CDATA[
return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
]]>
</body>
</method>
<method name="goHome">
<body>
<![CDATA[
return this.mCurrentBrowser.goHome();
]]>
</body>
</method>
<property name="homePage">
<getter>
<![CDATA[
return this.mCurrentBrowser.homePage;
]]>
</getter>
<setter>
<![CDATA[
this.mCurrentBrowser.homePage = val;
return val;
]]>
</setter>
</property>
<method name="gotoIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.mCurrentBrowser.gotoIndex(aIndex);
]]>
</body>
</method>
<method name="attachFormFill">
<body><![CDATA[
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
var cb = this.getBrowserAtIndex(i);
cb.attachFormFill();
}
]]></body>
</method>
<method name="detachFormFill">
<body><![CDATA[
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
var cb = this.getBrowserAtIndex(i);
cb.detachFormFill();
}
]]></body>
</method>
<property name="pageReport"
onget="return this.mCurrentBrowser.pageReport;"
readonly="true"/>
<property name="currentURI"
onget="return this.mCurrentBrowser.currentURI;"
readonly="true"/>
<field name="_fastFind">null</field>
<property name="fastFind"
readonly="true">
<getter>
<![CDATA[
if (!this._fastFind) {
this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
.createInstance(Components.interfaces.nsITypeAheadFind);
this._fastFind.init(this.docShell);
}
return this._fastFind;
]]>
</getter>
</property>
<property name="docShell"
onget="return this.mCurrentBrowser.docShell"
readonly="true"/>
<property name="webNavigation"
onget="return this.mCurrentBrowser.webNavigation"
readonly="true"/>
<property name="webBrowserFind"
readonly="true"
onget="return this.mCurrentBrowser.webBrowserFind"/>
<property name="webProgress"
readonly="true"
onget="return this.mCurrentBrowser.webProgress"/>
<property name="contentWindow"
readonly="true"
onget="return this.mCurrentBrowser.contentWindow"/>
<property name="sessionHistory"
onget="return this.mCurrentBrowser.sessionHistory;"
readonly="true"/>
<property name="markupDocumentViewer"
onget="return this.mCurrentBrowser.markupDocumentViewer;"
readonly="true"/>
<property name="contentViewerEdit"
onget="return this.mCurrentBrowser.contentViewerEdit;"
readonly="true"/>
<property name="contentViewerFile"
onget="return this.mCurrentBrowser.contentViewerFile;"
readonly="true"/>
<property name="documentCharsetInfo"
onget="return this.mCurrentBrowser.documentCharsetInfo;"
readonly="true"/>
<property name="contentDocument"
onget="return this.mCurrentBrowser.contentDocument;"
readonly="true"/>
<property name="contentTitle"
onget="return this.mCurrentBrowser.contentTitle;"
readonly="true"/>
<property name="contentPrincipal"
onget="return this.mCurrentBrowser.contentPrincipal;"
readonly="true"/>
<property name="securityUI"
onget="return this.mCurrentBrowser.securityUI;"
readonly="true"/>
<method name="dragDropSecurityCheck">
<parameter name="aEvent"/>
<parameter name="aDragSession"/>
<parameter name="aUri"/>
<body>
<![CDATA[
nsDragAndDrop.dragDropSecurityCheck(aEvent, aDragSession, aUri);
]]>
</body>
</method>
<field name="_keyEventHandler" readonly="true">
<![CDATA[({
tabbrowser: this,
handleEvent: function handleEvent(aEvent) {
if (!aEvent.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
if ('altKey' in aEvent && aEvent.altKey)
return;
#ifdef XP_MACOSX
if ('metaKey' in aEvent && aEvent.metaKey) {
var offset = 1;
switch (aEvent.charCode) {
case '}'.charCodeAt(0):
offset *= -1;
case '{'.charCodeAt(0):
if (window.getComputedStyle(this.tabbrowser, null).direction == "ltr")
offset *= -1;
this.tabbrowser.mTabContainer.advanceSelectedTab(offset, true);
aEvent.stopPropagation();
aEvent.preventDefault();
return;
}
if ('shiftKey' in aEvent && aEvent.shiftKey)
return;
#else
if (('ctrlKey' in aEvent && aEvent.ctrlKey) &&
!('shiftKey' in aEvent && aEvent.shiftKey) &&
!('metaKey' in aEvent && aEvent.metaKey)) {
if (aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
this.tabbrowser.mTabBox.handleCtrlPageUpDown) {
this.tabbrowser.removeCurrentTab();
aEvent.stopPropagation();
aEvent.preventDefault();
return;
}
#endif
if (aEvent.target.localName == "tabbrowser") {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_UP:
this.tabbrowser.moveTabBackward();
break;
case KeyEvent.DOM_VK_DOWN:
this.tabbrowser.moveTabForward();
break;
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_LEFT:
this.tabbrowser.moveTabOver(aEvent);
break;
case KeyEvent.DOM_VK_HOME:
this.tabbrowser.moveTabToStart();
break;
case KeyEvent.DOM_VK_END:
this.tabbrowser.moveTabToEnd();
break;
default:
// Stop the keypress event for the above keyboard
// shortcuts only.
return;
}
aEvent.stopPropagation();
aEvent.preventDefault();
}
}
}
})]]>
</field>
<property name="userTypedClear"
onget="return this.mCurrentBrowser.userTypedClear;"
onset="return this.mCurrentBrowser.userTypedClear = val;"/>
<property name="userTypedValue"
onget="return this.mCurrentBrowser.userTypedValue;"
onset="return this.mCurrentBrowser.userTypedValue = val;"/>
<method name="createTooltip">
<parameter name="event"/>
<body>
<![CDATA[
event.stopPropagation();
var tn = document.tooltipNode;
if (tn.localName != "tab")
return false; // Not a tab, so cancel the tooltip
if ("mOverCloseButton" in tn && tn.mOverCloseButton) {
event.target.setAttribute("label", tn.getAttribute("closetabtext"));
return true;
}
if (tn.hasAttribute("label")) {
event.target.setAttribute("label", tn.getAttribute("label"));
return true;
}
return false;
]]>
</body>
</method>
<constructor>
<![CDATA[
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild;
this.mCurrentTab = this.mTabContainer.firstChild;
document.addEventListener("keypress", this._keyEventHandler, false);
var uniqueId = "panel" + Date.now();
this.mPanelContainer.childNodes[0].id = uniqueId;
this.mTabContainer.childNodes[0].linkedPanel = uniqueId;
this.mTabContainer.childNodes[0]._tPos = 0;
this.mTabContainer.childNodes[0].linkedBrowser = this.mPanelContainer.childNodes[0].firstChild;
// set up the shared autoscroll popup
this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
this._autoScrollPopup.id = "autoscroller";
this.appendChild(this._autoScrollPopup);
this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
]]>
</constructor>
<destructor>
<![CDATA[
for (var i = 0; i < this.mTabListeners.length; ++i) {
this.getBrowserAtIndex(i).webProgress.removeProgressListener(this.mTabFilters[i]);
this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
this.mTabFilters[i] = null;
this.mTabListeners[i] = null;
this.getBrowserAtIndex(i).removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
}
document.removeEventListener("keypress", this._keyEventHandler, false);
]]>
</destructor>
</implementation>
<handlers>
<handler event="DOMWindowClose" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
const browsers = this.mPanelContainer.childNodes;
if (browsers.length == 1) {
// There's only one browser left. If a window is being
// closed and the window is *not* the window in the
// browser that's still around, prevent the event's default
// action to prevent closing a window that's being closed
// already.
if (this.getBrowserAtIndex(0).contentWindow != event.target)
event.preventDefault();
return;
}
var i = 0;
for (; i < browsers.length; ++i) {
if (this.getBrowserAtIndex(i).contentWindow == event.target) {
this.removeTab(this.mTabContainer.childNodes[i]);
event.preventDefault();
break;
}
}
]]>
</handler>
<handler event="DOMWillOpenModalDialog" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
// We're about to open a modal dialog, make sure the opening
// tab is brought to the front.
var targetTop = event.target.top;
for (var i = 0; i < browsers.length; ++i) {
if (this.getBrowserAtIndex(i).contentWindow == targetTop) {
this.selectedTab = this.mTabContainer.childNodes[i];
break;
}
}
]]>
</handler>
</handlers>
</binding>
<binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
<implementation>
<!-- Override scrollbox.xml method, since our scrollbox's children are
inherited from the binding parent -->
<method name="_getScrollableElements">
<body><![CDATA[
return document.getBindingParent(this).childNodes;
]]></body>
</method>
#ifdef XP_MACOSX
<field name="_scrollButtonDownBox">
document.getAnonymousElementByAttribute(this, "anonid", "down-box");
</field>
<field name="_scrollButtonDownBoxAnimate">
document.getAnonymousElementByAttribute(this, "anonid", "down-box-animate");
</field>
#endif
</implementation>
<handlers>
<handler event="underflow"><![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
tabs.removeAttribute("overflow");
#ifdef XP_MACOSX
this._scrollButtonDownBox.collapsed = true;
this._scrollButtonDownBoxAnimate.collapsed = true;
#endif
]]></handler>
<handler event="overflow"><![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
tabs.setAttribute("overflow", "true");
#ifdef XP_MACOSX
this._scrollButtonDownBox.collapsed = false;
this._scrollButtonDownBoxAnimate.collapsed = false;
#endif
this.ensureElementIsVisible(tabs.selectedItem, false);
]]></handler>
#ifdef XP_MACOSX
<handler event="UpdatedScrollButtonsDisabledState"><![CDATA[
// 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>
#endif
</handlers>
#ifdef XP_MACOSX
<content>
<xul:toolbarbutton class="scrollbutton-up" collapsed="true"
xbl:inherits="orient"
anonid="scrollbutton-up"
onclick="_distanceScroll(event);"
onmousedown="_startScroll(-1);"
onmouseover="_continueScroll(-1);"
onmouseup="_stopScroll();"
onmouseout="_pauseScroll();"/>
<xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
<children/>
</xul:scrollbox>
<xul:stack align="center" pack="end" class="scrollbutton-down-stack">
<xul:hbox flex="1" class="scrollbutton-down-box"
collapsed="true" anonid="down-box"/>
<xul:hbox flex="1" class="scrollbutton-down-box-animate"
collapsed="true" anonid="down-box-animate"/>
<xul:toolbarbutton class="scrollbutton-down" collapsed="true"
xbl:inherits="orient"
anonid="scrollbutton-down"
onclick="_distanceScroll(event);"
onmousedown="_startScroll(1);"
onmouseover="_continueScroll(1);"
onmouseup="_stopScroll();"
onmouseout="_pauseScroll();"/>
</xul:stack>
</content>
#endif
</binding>
<binding id="tabbrowser-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:hbox xbl:inherits="overflow" class="tabs-container">
#ifdef XP_MACOSX
<xul:stack>
<xul:spacer class="tabs-left"/>
</xul:stack>
#endif
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
style="min-width: 1px;"
#ifndef XP_MACOSX
clicktoscroll="true"
#endif
class="tabbrowser-arrowscrollbox">
# This is a hack to circumvent bug 472020, otherwise the tabs show up on the
# right of the newtab button.
<children includes="tab"/>
# This is to ensure anything extensions put here will go before the newtab
# button, necessary due to the previous hack.
<children/>
<xul:toolbarbutton class="tabs-newtab-button"
command="cmd_newNavigatorTab"
tooltiptext="&newTabButton.tooltip;"/>
</xul:arrowscrollbox>
<xul:toolbarbutton class="tabs-newtab-button" anonid="newtab-button"
command="cmd_newNavigatorTab"
tooltiptext="&newTabButton.tooltip;"/>
<xul:stack align="center" pack="end">
<xul:hbox flex="1" class="tabs-alltabs-box-animate" anonid="alltabs-box-animate"/>
<xul:toolbarbutton class="tabs-alltabs-button" anonid="alltabs-button"
type="menu"
tooltiptext="&listAllTabs.label;">
<xul:menupopup class="tabs-alltabs-popup" anonid="alltabs-popup"
position="after_end"/>
</xul:toolbarbutton>
</xul:stack>
<xul:toolbarbutton anonid="tabs-closebutton"
class="close-button tabs-closebutton"/>
</xul:hbox>
</xul:stack>
</content>
<implementation implements="nsITimerCallback, nsIDOMEventListener">
<constructor>
<![CDATA[
var pb2 =
Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefBranch2);
try {
this.mTabMinWidth = pb2.getIntPref("browser.tabs.tabMinWidth");
} catch (e) {
}
try {
this.mTabMaxWidth = pb2.getIntPref("browser.tabs.tabMaxWidth");
} catch (e) {
}
try {
this.mTabClipWidth = pb2.getIntPref("browser.tabs.tabClipWidth");
} catch (e) {
}
try {
this.mCloseButtons = pb2.getIntPref("browser.tabs.closeButtons");
} catch (e) {
}
this.firstChild.minWidth = this.mTabMinWidth;
this.firstChild.maxWidth = this.mTabMaxWidth;
this.adjustTabstrip();
pb2.addObserver("browser.tabs.closeButtons",
this._prefObserver, false);
window.addEventListener("resize", this, false);
]]>
</constructor>
<destructor>
<![CDATA[
var pb2 =
Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefBranch2);
pb2.removeObserver("browser.tabs.closeButtons", this._prefObserver);
// Release timer to avoid reference cycles.
if (this._animateTimer) {
this._animateTimer.cancel();
this._animateTimer = null;
}
]]>
</destructor>
<field name="mTabstripWidth">0</field>
<field name="mTabstrip">
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
</field>
<field name="mTabstripClosebutton">
document.getAnonymousElementByAttribute(this, "anonid", "tabs-closebutton");
</field>
<field name="_prefObserver">({
tabbox: this,
observe: function(subject, topic, data)
{
if (topic == "nsPref:changed") {
switch (data) {
case "browser.tabs.closeButtons":
subject.QueryInterface(Components.interfaces.nsIPrefBranch);
this.tabbox.mCloseButtons = subject.getIntPref("browser.tabs.closeButtons");
this.tabbox.adjustTabstrip();
break;
}
}
},
QueryInterface: function(aIID)
{
if (aIID.equals(Components.interfaces.nsIObserver) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
}
});
</field>
<field name="mTabMinWidth">100</field>
<field name="mTabMaxWidth">250</field>
<field name="mTabClipWidth">140</field>
<field name="mCloseButtons">1</field>
<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:
if (this.firstChild.getBoundingClientRect().width > this.mTabClipWidth)
this.setAttribute("closebuttons", "alltabs");
else
this.setAttribute("closebuttons", "activetab");
break;
case 2:
case 3:
this.setAttribute("closebuttons", "noclose");
break;
}
this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
]]></body>
</method>
<field name="_mPrefs">null</field>
<property name="mPrefs" readonly="true">
<getter>
<![CDATA[
if (!this._mPrefs) {
this._mPrefs =
Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefBranch2);
}
return this._mPrefs;
]]>
</getter>
</property>
<method name="_handleTabSelect">
<body><![CDATA[
this.mTabstrip.ensureElementIsVisible(this.selectedItem);
]]></body>
</method>
<method name="_fillTrailingGap">
<body><![CDATA[
try {
// if we're at the right side (and not the logical end,
// which is why this works for both LTR and RTL)
// of the tabstrip, we need to ensure that we stay
// completely scrolled to the right side
var tabStrip = this.mTabstrip;
if (tabStrip.scrollPosition + tabStrip.scrollClientSize >=
tabStrip.scrollSize)
tabStrip.scrollByPixels(-1);
} catch (e) {}
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "resize":
var width = this.mTabstrip.boxObject.width;
if (width != this.mTabstripWidth) {
this.adjustTabstrip();
this._fillTrailingGap();
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>
#ifdef XP_MACOSX
<field name="mDownBoxAnimate">
this.mTabstrip._scrollButtonDownBoxAnimate;
</field>
#endif
<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;
#ifdef XP_MACOSX
this.mDownBoxAnimate.style.opacity = 0.0;
#endif
}
]]></body>
</method>
<method name="_notifyBackgroundTab">
<parameter name="aTab"/>
<body><![CDATA[
var scrollRect = this.mTabstrip.scrollClientRect;
var tab = aTab.getBoundingClientRect();
// Is the new tab already completely visible?
if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
return;
if (this.mTabstrip.smoothScroll) {
let selected = this.selectedItem.getBoundingClientRect();
// Can we make both the new tab and the selected tab completely visible?
if (Math.max(tab.right - selected.left, selected.right - tab.left) <=
scrollRect.width) {
this.mTabstrip.ensureElementIsVisible(aTab);
return;
}
this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
selected.right - scrollRect.right :
selected.left - scrollRect.left);
}
// start the flash timer
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,
this._animateTimer.TYPE_REPEATING_SLACK);
]]></body>
</method>
<method name="notify">
<parameter name="aTimer"/>
<body><![CDATA[
if (!document)
aTimer.cancel();
var percent = this._animatePercents[this._animateStep];
this.mAllTabsBoxAnimate.style.opacity = percent;
#ifdef XP_MACOSX
this.mDownBoxAnimate.style.opacity = percent;
#endif
if (this._animateStep < (this._animatePercents.length - 1))
this._animateStep++;
else
this._stopAnimation();
]]></body>
</method>
</implementation>
<handlers>
<handler event="TabSelect" action="this._handleTabSelect();"/>
</handlers>
</binding>
<!-- close-tab-button binding
This binding relies on the structure of the tabbrowser binding.
Therefore it should only be used as a child of the tab or the tabs
element (in both cases, when they are anonymous nodes of <tabbrowser>).
This binding is exposed as a pseudo-public-API so themes can customize
the tabbar appearance without having to be scriptable
(see globalBindings.xml in Pinstripe for example).
-->
<binding id="tabbrowser-close-tab-button"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
<handlers>
<handler event="click" button="0"><![CDATA[
var bindingParent = document.getBindingParent(this);
if (bindingParent) {
var tabbedBrowser = document.getBindingParent(bindingParent);
if (bindingParent.localName == "tab") {
/* The only sequence in which a second click event (i.e. dblclik)
* can be dispatched on an in-tab close button is when it is shown
* after the first click (i.e. the first click event was dispatched
* on the tab). This happens when we show the close button only on
* the active tab. (bug 352021)
* The only sequence in which a third click event can be dispatched
* on an in-tab close button is when the tab was opened with a
* double click on the tabbar. (bug 378344)
* In both cases, it is most likely that the close button area has
* been accidentally clicked, therefore we do not close the tab.
*
* We don't want to ignore processing of more than one click event,
* though, since the user might actually be repeatedly clicking to
* close many tabs at once.
*/
if (event.detail > 1 && !this._ignoredClick) {
this._ignoredClick = true;
return;
}
// Reset the "ignored click" flag
this._ignoredClick = false;
tabbedBrowser.removeTab(bindingParent);
tabbedBrowser._blockDblClick = true;
/* XXXmano hack (see bug 343628):
* Since we're removing the event target, if the user
* double-clicks this button, the dblclick event will be dispatched
* with the tabbar as its event target (and explicit/originalTarget),
* which treats that as a mouse gesture for opening a new tab.
* In this context, we're manually blocking the dblclick event
* (see onTabBarDblClick).
*/
var clickedOnce = false;
function enableDblClick(event) {
if (event.detail == 1 && !clickedOnce) {
clickedOnce = true;
return;
}
setTimeout(function() {
tabbedBrowser._blockDblClick = false;
}, 0);
tabbedBrowser.removeEventListener("click", enableDblClick, false);
}
tabbedBrowser.addEventListener("click", enableDblClick, false);
}
else // "tabs"
tabbedBrowser.removeCurrentTab();
}
]]></handler>
<handler event="dblclick" button="0" phase="capturing">
// for the one-close-button case
event.stopPropagation();
</handler>
</handlers>
</binding>
<binding id="tabbrowser-tab" display="xul:hbox"
extends="chrome://global/content/bindings/tabbox.xml#tab">
<content closetabtext="&closeTab.label;">
<xul:image xbl:inherits="validate,src=image" class="tab-icon-image"/>
<xul:label flex="1" xbl:inherits="value=label,crop,accesskey" class="tab-text"/>
<xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button"/>
</content>
<implementation>
<field name="mOverCloseButton">false</field>
<field name="mCorrespondingMenuitem">null</field>
</implementation>
<handlers>
<handler event="mouseover">
var anonid = event.originalTarget.getAttribute("anonid");
if (anonid == "close-button")
this.mOverCloseButton = true;
</handler>
<handler event="mouseout">
var anonid = event.originalTarget.getAttribute("anonid");
if (anonid == "close-button")
this.mOverCloseButton = false;
</handler>
<handler event="dragstart" phase="capturing">
this.style.MozUserFocus = '';
</handler>
<handler event="mousedown" button="0" phase="capturing">
<![CDATA[
if (this.mOverCloseButton) {
event.stopPropagation();
}
else {
this.style.MozUserFocus = 'ignore';
this.clientTop; // just using this to flush style updates
}
]]>
</handler>
<handler event="mousedown" button="1">
this.style.MozUserFocus = 'ignore';
this.clientTop;
</handler>
<handler event="mousedown" button="2">
this.style.MozUserFocus = 'ignore';
this.clientTop;
</handler>
<handler event="mouseup">
this.style.MozUserFocus = '';
</handler>
</handlers>
</binding>
<binding id="tabbrowser-alltabs-popup"
extends="chrome://global/content/bindings/popup.xml#popup">
<implementation implements="nsIDOMEventListener">
<method name="_menuItemOnCommand">
<parameter name="aEvent"/>
<body><![CDATA[
var tabcontainer = document.getBindingParent(this);
tabcontainer.selectedItem = aEvent.target.tab;
]]></body>
</method>
<method name="_tabOnAttrModified">
<parameter name="aEvent"/>
<body><![CDATA[
var menuItem = aEvent.target.mCorrespondingMenuitem;
if (menuItem) {
var attrName = aEvent.attrName;
switch (attrName) {
case "label":
case "crop":
case "busy":
case "image":
case "selected":
if (aEvent.attrChange == aEvent.REMOVAL)
menuItem.removeAttribute(attrName);
else
menuItem.setAttribute(attrName, aEvent.newValue);
}
}
]]></body>
</method>
<method name="_tabOnTabClose">
<parameter name="aEvent"/>
<body><![CDATA[
var menuItem = aEvent.target.mCorrespondingMenuitem;
if (menuItem)
this.removeChild(menuItem);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (!aEvent.isTrusted)
return;
switch (aEvent.type) {
case "command":
this._menuItemOnCommand(aEvent);
break;
case "DOMAttrModified":
this._tabOnAttrModified(aEvent);
break;
case "TabClose":
this._tabOnTabClose(aEvent);
break;
case "TabOpen":
this._createTabMenuItem(aEvent.originalTarget);
break;
case "scroll":
this._updateTabsVisibilityStatus();
break;
}
]]></body>
</method>
<method name="_updateTabsVisibilityStatus">
<body><![CDATA[
var tabContainer = document.getBindingParent(this);
// We don't want menu item decoration unless there is overflow.
if (tabContainer.getAttribute("overflow") != "true")
return;
var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
for (var i = 0; i < this.childNodes.length; i++) {
var curTabBO = this.childNodes[i].tab.boxObject;
if (curTabBO.screenX >= tabstripBO.screenX &&
curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
this.childNodes[i].setAttribute("tabIsVisible", "true");
else
this.childNodes[i].removeAttribute("tabIsVisible");
}
]]></body>
</method>
<method name="_createTabMenuItem">
<parameter name="aTab"/>
<body><![CDATA[
var menuItem = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"menuitem");
menuItem.setAttribute("class", "menuitem-iconic alltabs-item");
menuItem.setAttribute("label", aTab.label);
menuItem.setAttribute("crop", aTab.getAttribute("crop"));
menuItem.setAttribute("image", aTab.getAttribute("image"));
if (aTab.hasAttribute("busy"))
menuItem.setAttribute("busy", aTab.getAttribute("busy"));
if (aTab.selected)
menuItem.setAttribute("selected", "true");
// Keep some attributes of the menuitem in sync with its
// corresponding tab (e.g. the tab label)
aTab.mCorrespondingMenuitem = menuItem;
aTab.addEventListener("DOMAttrModified", this, false);
aTab.addEventListener("TabClose", this, false);
menuItem.tab = aTab;
menuItem.addEventListener("command", this, false);
this.appendChild(menuItem);
return menuItem;
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshowing">
<![CDATA[
// set up the menu popup
var tabcontainer = document.getBindingParent(this);
var tabs = tabcontainer.childNodes;
// Listen for changes in the tab bar.
var tabbrowser = document.getBindingParent(tabcontainer);
tabbrowser.addEventListener("TabOpen", this, false);
tabcontainer.mTabstrip.addEventListener("scroll", this, false);
// if an animation is in progress and the user
// clicks on the "all tabs" button, stop the animation
tabcontainer._stopAnimation();
for (var i = 0; i < tabs.length; i++) {
this._createTabMenuItem(tabs[i]);
}
this._updateTabsVisibilityStatus();
]]></handler>
<handler event="popuphiding">
<![CDATA[
// clear out the menu popup and remove the listeners
while (this.hasChildNodes()) {
var menuItem = this.lastChild;
menuItem.removeEventListener("command", this, false);
menuItem.tab.removeEventListener("DOMAttrModified", this, false);
menuItem.tab.removeEventListener("TabClose", this, false);
menuItem.tab.mCorrespondingMenuitem = null;
this.removeChild(menuItem);
}
var tabcontainer = document.getBindingParent(this);
tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this, false);
]]></handler>
<handler event="DOMMenuItemActive">
<![CDATA[
var tab = event.target.tab;
if (tab) {
var statusText = tab.linkedBrowser.currentURI.spec;
if (statusText == "about:blank") {
// XXXhack: Passing a space here (and not "")
// to make sure the the browser implementation would
// still consider it a hovered link.
statusText = " ";
}
XULBrowserWindow.setOverLink(statusText, null);
}
]]></handler>
<handler event="DOMMenuItemInactive">
<![CDATA[
XULBrowserWindow.setOverLink("", null);
]]></handler>
</handlers>
</binding>
</bindings>