gecko/browser/base/content/tabbrowser.xml
2010-09-24 09:29:50 +02:00

3573 lines
128 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>
- Rob Arnold <tellrob@gmail.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" class="tabbrowser-tabbox"
flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
<xul:notificationbox flex="1">
<xul:stack flex="1" anonid="browserStack">
<xul:browser type="content-primary" message="true" disablehistory="true"
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
</xul:stack>
</xul:notificationbox>
</xul:tabpanels>
</xul:tabbox>
<children/>
</content>
<implementation implements="nsIDOMEventListener">
<property name="tabContextMenu" readonly="true"
onget="return this.tabContainer.contextMenu;"/>
<field name="tabContainer" readonly="true">
document.getElementById(this.getAttribute("tabcontainer"));
</field>
<field name="tabs" readonly="true">
this.tabContainer.childNodes;
</field>
<property name="visibleTabs" readonly="true">
<getter><![CDATA[
return Array.filter(this.tabs, function(tab) {
return !tab.hidden && this._removingTabs.indexOf(tab) == -1;
}, this);
]]></getter>
</property>
<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="mBrowserHistory" readonly="true">
Components.classes["@mozilla.org/browser/nav-history-service;1"]
.getService(Components.interfaces.nsIBrowserHistory);
</field>
<field name="mTabBox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
</field>
<field name="mPanelContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
</field>
<field name="mStringBundle">
document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
</field>
<field name="mCurrentTab">
null
</field>
<field name="_lastRelatedTab">
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="arrowKeysShouldWrap" readonly="true">
#ifdef XP_MACOSX
true
#else
false
#endif
</field>
<field name="mAddProgressListenerWasCalled">
false
</field>
<field name="_browsers">
null
</field>
<field name="_autoScrollPopup">
null
</field>
<field name="_previewMode">
false
</field>
<property name="_numPinnedTabs" readonly="true">
<getter><![CDATA[
for (var i = 0; i < this.tabs.length; i++) {
if (!this.tabs[i].pinned)
break;
}
return i;
]]></getter>
</property>
<method name="pinTab">
<parameter name="aTab"/>
<body><![CDATA[
if (aTab.pinned)
return;
this.moveTabTo(aTab, this._numPinnedTabs);
aTab.setAttribute("pinned", "true");
this.tabContainer._positionPinnedTabs();
this.tabContainer.adjustTabstrip();
let event = document.createEvent("Events");
event.initEvent("TabPinned", true, false);
aTab.dispatchEvent(event);
]]></body>
</method>
<method name="unpinTab">
<parameter name="aTab"/>
<body><![CDATA[
if (!aTab.pinned)
return;
this.moveTabTo(aTab, this._numPinnedTabs - 1);
aTab.setAttribute("fadein", "true");
aTab.removeAttribute("pinned");
aTab.style.MozMarginStart = "";
this.tabContainer._positionPinnedTabs();
this.tabContainer.adjustTabstrip();
let event = document.createEvent("Events");
event.initEvent("TabUnpinned", true, false);
aTab.dispatchEvent(event);
]]></body>
</method>
<method name="previewTab">
<parameter name="aTab"/>
<parameter name="aCallback"/>
<body>
<![CDATA[
let currentTab = this.selectedTab;
try {
// Suppress focus, ownership and selected tab changes
this._previewMode = true;
this.selectedTab = aTab;
aCallback();
} finally {
this.selectedTab = currentTab;
this._previewMode = false;
}
]]>
</body>
</method>
<method name="getBrowserAtIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.browsers[aIndex];
]]>
</body>
</method>
<method name="getBrowserIndexForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var tab = this._getTabForContentWindow(aDocument.defaultView);
return tab ? tab._tPos : -1;
]]>
</body>
</method>
<method name="getBrowserForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var tab = this._getTabForContentWindow(aDocument.defaultView);
return tab ? tab.linkedBrowser : null;
]]>
</body>
</method>
<method name="_getTabForContentWindow">
<parameter name="aWindow"/>
<body>
<![CDATA[
for (let i = 0; i < this.browsers.length; i++) {
if (this.browsers[i].contentWindow == aWindow)
return this.tabs[i];
}
return null;
]]>
</body>
</method>
<method name="getNotificationBox">
<parameter name="aBrowser"/>
<body>
<![CDATA[
return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
]]>
</body>
</method>
<method name="_callProgressListeners">
<parameter name="aBrowser"/>
<parameter name="aMethod"/>
<parameter name="aArguments"/>
<parameter name="aCallGlobalListeners"/>
<parameter name="aCallTabsListeners"/>
<body><![CDATA[
var rv = true;
if (!aBrowser)
aBrowser = this.mCurrentBrowser;
if (aCallGlobalListeners != false &&
aBrowser == this.mCurrentBrowser) {
this.mProgressListeners.forEach(function (p) {
if (aMethod in p) {
try {
if (!p[aMethod].apply(p, aArguments))
rv = false;
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
});
}
if (aCallTabsListeners != false) {
aArguments.unshift(aBrowser);
this.mTabsProgressListeners.forEach(function (p) {
if (aMethod in p) {
try {
if (!p[aMethod].apply(p, aArguments))
rv = false;
} catch (e) {
// don't inhibit other listeners
Components.utils.reportError(e);
}
}
});
}
return rv;
]]></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 UI update after tab switching
mStateFlags: 0,
mStatus: 0,
mMessage: "",
mTotalProgress: 0,
// count of open requests (should always be 0 or 1)
mRequestCount: 0,
destroy: function () {
this._cancelStalledTimer();
this.mTab.removeAttribute("stalled");
delete this.mTab;
delete this.mBrowser;
delete this.mTabBrowser;
},
_callProgressListeners: function () {
Array.unshift(arguments, this.mBrowser);
return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
},
onProgressChange: function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress) {
this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
if (this.mBlank)
return;
if (this.mTotalProgress) {
let value = Math.ceil(this.mTotalProgress * 100);
this.mTab.setAttribute("progresspercent", value);
}
this._callProgressListeners("onProgressChange",
[aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress]);
},
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) {
if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
this.mTab.setAttribute("busy", "true");
this.mTab.setAttribute("progresspercent", "0");
this._startStalledTimer();
this.mTabBrowser.setTabTitleLoading(this.mTab);
}
if (this.mTab.selected)
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.mTab.removeAttribute("progresspercent");
this.mTab.removeAttribute("stalled");
this._cancelStalledTimer();
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.mTab.selected)
this.mTabBrowser.mIsBusy = false;
}
if (oldBlank) {
this._callProgressListeners("onUpdateCurrentBrowser",
[aStateFlags, aStatus, "", 0],
true, false);
} else {
this._callProgressListeners("onStateChange",
[aWebProgress, aRequest, aStateFlags, aStatus],
true, false);
}
this._callProgressListeners("onStateChange",
[aWebProgress, aRequest, aStateFlags, aStatus],
false);
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) {
// OnLocationChange is called for both the top-level content
// and the subframes.
let topLevel = aWebProgress.DOMWindow == this.mBrowser.contentWindow;
if (topLevel) {
// The document loaded correctly, clear the value if we should
if (this.mBrowser.userTypedClear > 0)
this.mBrowser.userTypedValue = null;
// Clear out the missing plugins list since it's related to the
// previous location.
this.mBrowser.missingPlugins = null;
// Don't clear the favicon if this onLocationChange was
// triggered by a pushState or a replaceState. See bug 550565.
if (aWebProgress.isLoadingDocument &&
!(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
this.mBrowser.mIconURL = null;
let browserHistory = this.mTabBrowser.mBrowserHistory;
if (this.mBrowser.registeredOpenURI) {
browserHistory.unregisterOpenPage(this.mBrowser.registeredOpenURI);
delete this.mBrowser.registeredOpenURI;
}
browserHistory.registerOpenPage(aLocation);
this.mBrowser.registeredOpenURI = aLocation;
}
if (!this.mBlank) {
this._callProgressListeners("onLocationChange",
[aWebProgress, aRequest, aLocation]);
}
if (topLevel)
this.mBrowser.lastURI = aLocation;
},
onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
if (this.mBlank)
return;
this._callProgressListeners("onStatusChange",
[aWebProgress, aRequest, aStatus, aMessage]);
this.mMessage = aMessage;
},
onSecurityChange: function (aWebProgress, aRequest, aState) {
this._callProgressListeners("onSecurityChange",
[aWebProgress, aRequest, aState]);
},
onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
return this._callProgressListeners("onRefreshAttempted",
[aWebProgress, aURI, aDelay, aSameURI]);
},
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;
},
_startStalledTimer: function () {
this._cancelStalledTimer();
this._stalledTimer = setTimeout(function (self) {
self.mTab.setAttribute("stalled", "true");
}, 700, this);
},
_cancelStalledTimer: function () {
if (this._stalledTimer) {
clearTimeout(this._stalledTimer);
this._stalledTimer = 0;
}
}
});
]]>
</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);
}
if ((browser.mIconURL || "") != aTab.getAttribute("image")) {
if (browser.mIconURL)
aTab.setAttribute("image", browser.mIconURL);
else
aTab.removeAttribute("image");
this._tabAttrModified(aTab);
}
this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
]]>
</body>
</method>
<method name="getIcon">
<parameter name="aTab"/>
<body>
<![CDATA[
let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
return browser.mIconURL;
]]>
</body>
</method>
<method name="shouldLoadFavIcon">
<parameter name="aURI"/>
<body>
<![CDATA[
return (aURI &&
Services.prefs.getBoolPref("browser.chrome.site_icons") &&
Services.prefs.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);
var docURIObject = browser.contentDocument.documentURIObject;
var icon = null;
if (browser.contentDocument instanceof ImageDocument) {
if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
try {
let req = browser.contentDocument.imageRequest;
if (req &&
req.image &&
req.image.width <= sz &&
req.image.height <= sz)
icon = 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(docURIObject)) {
let url = docURIObject.prePath + "/favicon.ico";
if (!this.isFailedIcon(url))
icon = url;
}
this.setIcon(aTab, icon);
]]>
</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="getWindowTitleForBrowser">
<parameter name="aBrowser"/>
<body>
<![CDATA[
var newTitle = "";
var docTitle;
var docElement = this.ownerDocument.documentElement;
var sep = docElement.getAttribute("titlemenuseparator");
if (aBrowser.docShell.contentViewer)
docTitle = aBrowser.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(
aBrowser.currentURI);
if (uri.scheme == "about")
newTitle = uri.spec + sep + newTitle;
else
newTitle = uri.prePath + sep + newTitle;
}
} catch (e) {}
if (window.TabView) {
let groupName = TabView.getActiveGroupName();
if (groupName)
newTitle = groupName + sep + newTitle;
}
return newTitle;
]]>
</body>
</method>
<method name="updateTitlebar">
<body>
<![CDATA[
if (window.TabView && TabView.isVisible()) {
// ToDo: this will be removed when we gain ability to draw to the menu bar.
// Bug 586175
this.ownerDocument.title = TabView.windowTitle;
}
else {
this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
}
]]>
</body>
</method>
<method name="updateCurrentBrowser">
<parameter name="aForceUpdate"/>
<body>
<![CDATA[
var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
return;
var oldTab = this.mCurrentTab;
// Preview mode should not reset the owner
if (!this._previewMode && oldTab != this.selectedTab)
oldTab.owner = null;
if (this._lastRelatedTab) {
if (this._lastRelatedTab != this.selectedTab)
this._lastRelatedTab.owner = null;
this._lastRelatedTab = null;
}
var oldBrowser = this.mCurrentBrowser;
if (oldBrowser) {
oldBrowser.setAttribute("type", "content-targetable");
oldBrowser.docShell.isActive = false;
}
var updatePageReport = false;
if (!oldBrowser ||
(oldBrowser.pageReport && !newBrowser.pageReport) ||
(!oldBrowser.pageReport && newBrowser.pageReport))
updatePageReport = true;
newBrowser.setAttribute("type", "content-primary");
newBrowser.docShell.isActive = true;
this.mCurrentBrowser = newBrowser;
this.mCurrentTab = this.selectedTab;
this.showTab(this.mCurrentTab);
if (updatePageReport)
this.mCurrentBrowser.updatePageReport();
// Update the URL bar.
var loc = this.mCurrentBrowser.currentURI;
var webProgress = this.mCurrentBrowser.webProgress;
var securityUI = this.mCurrentBrowser.securityUI;
this._callProgressListeners(null, "onLocationChange",
[webProgress, null, loc], true, false);
if (securityUI) {
this._callProgressListeners(null, "onSecurityChange",
[webProgress, null, securityUI.state], true, false);
}
var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
if (listener && listener.mStateFlags) {
this._callProgressListeners(null, "onUpdateCurrentBrowser",
[listener.mStateFlags, listener.mStatus,
listener.mMessage, listener.mTotalProgress],
true, false);
}
// Don't switch the fast find or update the titlebar (bug 540248) - this tab switch is temporary
if (!this._previewMode) {
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
this.updateTitlebar();
this.mCurrentTab.removeAttribute("titlechanged");
}
// 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;
this._callProgressListeners(null, "onStateChange",
[webProgress, null,
nsIWebProgressListener.STATE_START |
nsIWebProgressListener.STATE_IS_NETWORK, 0],
true, false);
}
// 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;
this._callProgressListeners(null, "onStateChange",
[webProgress, null,
nsIWebProgressListener.STATE_STOP |
nsIWebProgressListener.STATE_IS_NETWORK, 0],
true, false);
}
// TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
// that might rely upon the other changes suppressed.
// Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
if (!this._previewMode) {
// 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);
this._tabAttrModified(oldTab);
this._tabAttrModified(this.mCurrentTab);
// Change focus to the new browser unless the findbar is focused.
if (!gFindBarInitialized ||
gFindBar.hidden ||
gFindBar.getElement("findbar-textbox").getAttribute("focused") != "true") {
var fm = Components.classes["@mozilla.org/focus-manager;1"].
getService(Components.interfaces.nsIFocusManager);
var newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
// for anchors, use FLAG_SHOWRING so that it is clear what link was
// last clicked when switching back to that tab
var focusFlags = fm.FLAG_NOSCROLL;
if (newFocusedElement &&
(newFocusedElement instanceof HTMLAnchorElement ||
newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
focusFlags |= fm.FLAG_SHOWRING;
fm.setFocus(newBrowser, focusFlags);
}
}
]]>
</body>
</method>
<method name="_tabAttrModified">
<parameter name="aTab"/>
<body><![CDATA[
if (this._removingTabs.indexOf(aTab) > -1)
return;
// This event should be dispatched when any of these attributes change:
// label, crop, busy, image, selected
var event = document.createEvent("Events");
event.initEvent("TabAttrModified", true, false);
aTab.dispatchEvent(event);
]]></body>
</method>
<method name="setTabTitleLoading">
<parameter name="aTab"/>
<body>
<![CDATA[
aTab.label = this.mStringBundle.getString("tabs.loading");
aTab.crop = "end";
this._tabAttrModified(aTab);
]]>
</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.emptyTabTitle");
}
if (aTab.label == title &&
aTab.crop == crop)
return false;
aTab.label = title;
aTab.crop = crop;
this._tabAttrModified(aTab);
if (aTab.selected)
this.updateTitlebar();
return true;
]]>
</body>
</method>
<method name="enterTabbedMode">
<body>
<![CDATA[
if (this.mTabbedMode)
return;
this.mTabbedMode = true; // Welcome to multi-tabbed mode.
if (XULBrowserWindow.isBusy) {
this.mCurrentTab.setAttribute("busy", "true");
this.mIsBusy = true;
this.setTabTitleLoading(this.mCurrentTab);
} else {
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.
this.mProgressListeners.forEach(filter.removeProgressListener, filter);
// 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 aFromExternal;
var aRelatedToCurrent;
if (arguments.length == 2 &&
typeof arguments[1] == "object" &&
!(arguments[1] instanceof Ci.nsIURI)) {
let params = arguments[1];
aReferrerURI = params.referrerURI;
aCharset = params.charset;
aPostData = params.postData;
aLoadInBackground = params.inBackground;
aAllowThirdPartyFixup = params.allowThirdPartyFixup;
aFromExternal = params.fromExternal;
aRelatedToCurrent = params.relatedToCurrent;
}
var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
Services.prefs.getBoolPref("browser.tabs.loadInBackground");
var owner = bgLoad ? null : this.selectedTab;
var tab = this.addTab(aURI, {
referrerURI: aReferrerURI,
charset: aCharset,
postData: aPostData,
ownerTab: owner,
allowThirdPartyFixup: aAllowThirdPartyFixup,
fromExternal: aFromExternal,
relatedToCurrent: aRelatedToCurrent});
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 multiple = aURIs.length > 1;
var owner = multiple || 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], {ownerTab: owner, skipAnimation: multiple});
var tabNum = this.tabContainer.selectedIndex;
for (let i = 1; i < aURIs.length; ++i) {
let tab = this.addTab(aURIs[i], {skipAnimation: true});
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[
var aFromExternal;
var aRelatedToCurrent;
var aSkipAnimation;
if (arguments.length == 2 &&
typeof arguments[1] == "object" &&
!(arguments[1] instanceof Ci.nsIURI)) {
let params = arguments[1];
aReferrerURI = params.referrerURI;
aCharset = params.charset;
aPostData = params.postData;
aOwner = params.ownerTab;
aAllowThirdPartyFixup = params.allowThirdPartyFixup;
aFromExternal = params.fromExternal;
aRelatedToCurrent = params.relatedToCurrent;
aSkipAnimation = params.skipAnimation;
}
this._browsers = null; // invalidate cache
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 || (aURI == "about:blank");
if (blank)
t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
else
t.setAttribute("label", aURI);
t.setAttribute("crop", "end");
t.setAttribute("validate", "never");
t.setAttribute("onerror", "this.removeAttribute('image');");
t.className = "tabbrowser-tab";
// When overflowing, new tabs are scrolled into view smoothly, which
// doesn't go well together with the width transition. So we skip the
// transition in that case.
if (aSkipAnimation ||
this.tabContainer.getAttribute("overflow") == "true" ||
!Services.prefs.getBoolPref("browser.tabs.animate")) {
t.setAttribute("fadein", "true");
setTimeout(function (tabContainer) {
tabContainer._handleNewTab(t);
}, 0, this.tabContainer);
} else {
setTimeout(function (tabContainer) {
if (t.pinned)
tabContainer._handleNewTab(t);
else
t.setAttribute("fadein", "true");
}, 0, this.tabContainer);
}
this.tabContainer.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. See bug 508816. */
this.tabContainer.mTabstrip.scrollByPixels(t.clientWidth);
}
// invalidate cache, because tabContainer 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);
// Create the browserStack container
var stack = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"stack");
stack.setAttribute("anonid", "browserStack");
stack.appendChild(b);
stack.setAttribute("flex", "1");
// 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(stack);
var position = this.tabs.length - 1;
var uniqueId = "panel" + Date.now() + position;
notificationbox.id = uniqueId;
t.linkedPanel = uniqueId;
t.linkedBrowser = b;
t._tPos = position;
if (t.previousSibling.selected)
t.setAttribute("afterselected", true);
// NB: this appendChild call causes us to run constructors for the
// browser element, which fires off a bunch of notifications. Some
// of those notifications can cause code to run that inspects our
// state, so it is important that the tab element is fully
// initialized by this point.
this.mPanelContainer.appendChild(notificationbox);
this.tabContainer.updateVisibility();
// wire up a progress listener for the new browser object.
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;
b.droppedLinkHandler = handleDroppedLink;
// 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);
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;
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (aAllowThirdPartyFixup)
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
if (aFromExternal)
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
try {
b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
// We start our browsers out as inactive, and then maintain
// activeness in the tab switcher.
b.docShell.isActive = false;
}
catch (ex) { }
}
// Check if we're opening a tab related to the current tab and
// move it to after the current tab.
// aReferrerURI is null or undefined if the tab is opened from
// an external application or bookmark, i.e. somewhere other
// than the current tab.
if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
let newTabPos = (this._lastRelatedTab ||
this.selectedTab)._tPos + 1;
if (this._lastRelatedTab)
this._lastRelatedTab.owner = null;
else
t.owner = this.selectedTab;
this.moveTabTo(t, newTabPos);
this._lastRelatedTab = t;
}
return t;
]]>
</body>
</method>
<method name="warnAboutClosingTabs">
<parameter name="aAll"/>
<body>
<![CDATA[
var tabsToClose = (aAll ? this.tabs.length : this.visibleTabs.length - 1)
- gBrowser._numPinnedTabs;
if (tabsToClose <= 1)
return true;
const pref = "browser.tabs.warnOnClose";
var shouldPrompt = Services.prefs.getBoolPref(pref);
if (!shouldPrompt)
return true;
var ps = Services.prompt;
// default to true: if it were false, we wouldn't get this far
var warnOnClose = { value: true };
var bundle = this.mStringBundle;
// 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 =
ps.confirmEx(window,
bundle.getString("tabs.closeWarningTitle"),
bundle.getFormattedString("tabs.closeWarningMultipleTabs",
[tabsToClose]),
(ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
+ (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
bundle.getString("tabs.closeButtonMultiple"),
null, null,
bundle.getString('tabs.closeWarningPromptMe'),
warnOnClose);
var reallyClose = (buttonPressed == 0);
// don't set the pref unless they press OK and it's false
if (reallyClose && !warnOnClose.value)
Services.prefs.setBoolPref(pref, false);
return reallyClose;
]]>
</body>
</method>
<method name="removeAllTabsBut">
<parameter name="aTab"/>
<body>
<![CDATA[
if (aTab.pinned)
return;
if (this.warnAboutClosingTabs(false)) {
let tabs = this.visibleTabs;
this.selectedTab = aTab;
for (let i = tabs.length - 1; i >= 0; --i) {
if (tabs[i] != aTab && !tabs[i].pinned)
this.removeTab(tabs[i]);
}
}
]]>
</body>
</method>
<method name="removeCurrentTab">
<parameter name="aParams"/>
<body>
<![CDATA[
this.removeTab(this.mCurrentTab, aParams);
]]>
</body>
</method>
<field name="_removingTabs">
[]
</field>
<method name="removeTab">
<parameter name="aTab"/>
<parameter name="aParams"/>
<body>
<![CDATA[
if (aParams)
var animate = aParams.animate;
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate &&
this._removingTabs.indexOf(aTab) > -1) {
this._endRemoveTab(aTab);
return;
}
var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
if (!this._beginRemoveTab(aTab, false, null, true))
return;
/* Don't animate if:
- the caller didn't opt in
- this is the last tab in the window
- this is a pinned tab
- a bunch of other tabs are already closing (arbitrary threshold)
- the fadein attribute hasn't been set yet
- browser.tabs.animate is false */
if (!animate ||
isLastTab ||
aTab.pinned ||
this._removingTabs.length > 3 ||
aTab.getAttribute("fadein") != "true" ||
!Services.prefs.getBoolPref("browser.tabs.animate")) {
this._endRemoveTab(aTab);
return;
}
this._blurTab(aTab);
aTab.removeAttribute("fadein");
]]>
</body>
</method>
<!-- Tab close requests are ignored if the window is closing anyway,
e.g. when holding Ctrl+W. -->
<field name="_windowIsClosing">
false
</field>
<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 false;
var browser = this.getBrowserForTab(aTab);
if (!aTabWillBeMoved) {
let ds = browser.docShell;
if (ds && ds.contentViewer && !ds.contentViewer.permitUnload())
return false;
}
var closeWindow = false;
var newTab = false;
if (this.tabs.length - this._removingTabs.length == 1) {
closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
!window.toolbar.visible ||
this.tabContainer._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;
}
this._removingTabs.push(aTab);
this.tabContainer.updateVisibility();
// 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]);
this.mTabListeners[aTab._tPos].destroy();
if (browser.registeredOpenURI && !aTabWillBeMoved) {
this.mBrowserHistory.unregisterOpenPage(browser.registeredOpenURI);
delete browser.registeredOpenURI;
}
// 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.tabs, 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;
});
aTab._endRemoveArgs = [closeWindow, newTab];
return true;
]]>
</body>
</method>
<method name="_endRemoveTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (!aTab || !aTab._endRemoveArgs)
return;
var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
aTab._endRemoveArgs = null;
if (this._windowIsClosing) {
aCloseWindow = false;
aNewTab = false;
}
this._lastRelatedTab = null;
// update the UI early for responsiveness
aTab.collapsed = true;
if (aNewTab)
this.addTab("about:blank", {skipAnimation: true});
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]);
} else if (!this._windowIsClosing) {
if (aNewTab)
focusAndSelectUrlBar();
// 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;
var wasPinned = aTab.pinned;
// 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.tabs.length; i++)
this.tabs[i]._tPos = i;
if (!this._windowIsClosing) {
if (wasPinned)
this.tabContainer._positionPinnedTabs();
// update tab close buttons state
this.tabContainer.adjustTabstrip();
}
// update first-tab/last-tab/beforeselected/afterselected attributes
this.selectedTab._selected = true;
// Removing the panel requires fixing up selectedPanel immediately
// (see below), which would be hindered by the potentially expensive
// browser removal. So we remove the browser and the panel in two
// steps.
var panel = browser.parentNode.parentNode;
// 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.).
panel.removeChild(browser.parentNode);
// As the browser 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) {
let selectedPanel = this.mTabBox.selectedPanel;
this.mPanelContainer.removeChild(panel);
// Under the hood, a selectedIndex attribute controls which panel
// is displayed. Removing a panel A which precedes the selected
// panel B makes selectedIndex point to the panel next to B. We
// need to explicitly preserve B as the selected panel.
this.mTabBox.selectedPanel = selectedPanel;
}
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 &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
this.selectedTab = aTab.owner;
return;
}
// Switch to a visible tab unless there aren't any others remaining
let remainingTabs = this.visibleTabs;
let numTabs = remainingTabs.length;
if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
remainingTabs = Array.filter(this.tabs, function(tab) {
return this._removingTabs.indexOf(tab) == -1;
}, this);
}
// Try to find a remaining tab that comes after the given tab
var tab = aTab;
do {
tab = tab.nextSibling;
} while (tab && remainingTabs.indexOf(tab) == -1);
if (!tab) {
tab = aTab;
do {
tab = tab.previousSibling;
} while (tab && remainingTabs.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.gBrowser;
// 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.
if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
return;
// 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;
var otherBrowser = aOtherTab.linkedBrowser;
// Restore current registered open URI.
if (ourBrowser.registeredOpenURI)
this.mBrowserHistory.unregisterOpenPage(ourBrowser.registeredOpenURI);
if (otherBrowser.registeredOpenURI)
ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
// Workarounds for bug 458697
// Icon might have been set on DOMLinkAdded, don't override that.
if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
this.setIcon(aOurTab, otherBrowser.mIconURL);
var isBusy = aOtherTab.hasAttribute("busy");
if (isBusy) {
aOurTab.setAttribute("busy", "true");
this._tabAttrModified(aOurTab);
if (aOurTab == this.selectedTab)
this.mIsBusy = true;
}
// Swap the docshells
ourBrowser.swapDocShells(otherBrowser);
// Finish tearing down the tab that's going away.
remoteBrowser._endRemoveTab(aOtherTab);
// 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);
// 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[
let tabs = this.visibleTabs;
let l = tabs.length;
for (var i = 0; i < l; i++) {
try {
this.getBrowserForTab(tabs[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="addProgressListener">
<parameter name="aListener"/>
<parameter name="aMask"/>
<body>
<![CDATA[
if (!this.mAddProgressListenerWasCalled) {
this.mAddProgressListenerWasCalled = true;
this.tabContainer.updateVisibility();
}
if (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[
this.mProgressListeners =
this.mProgressListeners.filter(function (l) l != aListener);
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>
this.enterTabbedMode();
this.mTabsProgressListeners.push(aListener);
</body>
</method>
<method name="removeTabsProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
this.mTabsProgressListeners =
this.mTabsProgressListeners.filter(function (l) l != aListener);
]]>
</body>
</method>
<method name="getBrowserForTab">
<parameter name="aTab"/>
<body>
<![CDATA[
return aTab.linkedBrowser;
]]>
</body>
</method>
<method name="showOnlyTheseTabs">
<parameter name="aTabs"/>
<body>
<![CDATA[
Array.forEach(this.tabs, function(tab) {
if (aTabs.indexOf(tab) == -1)
this.hideTab(tab);
else
this.showTab(tab);
}, this);
]]>
</body>
</method>
<method name="showTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (aTab.hidden) {
aTab.hidden = false;
let event = document.createEvent("Events");
event.initEvent("TabShow", true, false);
aTab.dispatchEvent(event);
}
]]>
</body>
</method>
<method name="hideTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
this._removingTabs.indexOf(aTab) == -1) {
aTab.hidden = true;
let event = document.createEvent("Events");
event.initEvent("TabHide", true, false);
aTab.dispatchEvent(event);
}
]]>
</body>
</method>
<method name="selectTabAtIndex">
<parameter name="aIndex"/>
<parameter name="aEvent"/>
<body>
<![CDATA[
let tabs = this.visibleTabs;
// count backwards for aIndex < 0
if (aIndex < 0)
aIndex += tabs.length;
if (aIndex >= 0 && aIndex < tabs.length)
this.selectedTab = tabs[aIndex];
if (aEvent) {
aEvent.preventDefault();
aEvent.stopPropagation();
}
]]>
</body>
</method>
<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.tabs, function (tab) tab.linkedBrowser));
]]>
</getter>
</property>
<!-- 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.visibleTabs.length == 1)
return null;
// tell a new window to take the "dropped" tab
return Services.ww.openWindow(window,
getBrowserURL(),
null,
"chrome,dialog=no,all",
aTab);
]]>
</body>
</method>
<method name="moveTabTo">
<parameter name="aTab"/>
<parameter name="aIndex"/>
<body>
<![CDATA[
var oldPosition = aTab._tPos;
if (oldPosition == aIndex)
return;
// Don't allow mixing pinned and unpinned tabs.
if (aTab.pinned)
aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
else
aIndex = Math.max(aIndex, this._numPinnedTabs);
if (oldPosition == aIndex)
return;
this._lastRelatedTab = null;
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
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.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
// invalidate cache, because tabContainer is about to change
this._browsers = null;
for (let i = 0; i < this.tabs.length; i++) {
this.tabs[i]._tPos = i;
this.tabs[i]._selected = false;
}
this.mCurrentTab._selected = true;
this.tabContainer.mTabstrip.ensureElementIsVisible(this.mCurrentTab, false);
if (aTab.pinned)
this.tabContainer._positionPinnedTabs();
var evt = document.createEvent("UIEvents");
evt.initUIEvent("TabMove", true, false, window, oldPosition);
aTab.dispatchEvent(evt);
]]>
</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[
return Cc["@mozilla.org/browser/sessionstore;1"]
.getService(Ci.nsISessionStore)
.duplicateTab(window, aTab);
]]>
</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="_handleKeyEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (!aEvent.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
if (aEvent.altKey)
return;
// We need to take care of FAYT-watching as long as the findbar
// isn't initialized. The checks on aEvent are copied from
// _shouldFastFind (see findbar.xml).
if (!gFindBarInitialized &&
!(aEvent.ctrlKey || aEvent.metaKey) &&
!aEvent.getPreventDefault()) {
let charCode = aEvent.charCode;
if (charCode) {
let char = String.fromCharCode(charCode);
if (char == "'" || char == "/" ||
Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
gFindBar._onBrowserKeypress(aEvent);
return;
}
}
}
#ifdef XP_MACOSX
if (!aEvent.metaKey)
return;
var offset = 1;
switch (aEvent.charCode) {
case '}'.charCodeAt(0):
offset = -1;
case '{'.charCodeAt(0):
if (window.getComputedStyle(this, null).direction == "ltr")
offset *= -1;
this.tabContainer.advanceSelectedTab(offset, true);
aEvent.stopPropagation();
aEvent.preventDefault();
}
#else
if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
this.mTabBox.handleCtrlPageUpDown) {
this.removeCurrentTab({animate: true});
aEvent.stopPropagation();
aEvent.preventDefault();
}
#endif
]]></body>
</method>
<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 tab = document.tooltipNode;
if (tab.localName != "tab") {
event.preventDefault();
return;
}
event.target.setAttribute("label", tab.mOverCloseButton ?
tab.getAttribute("closetabtext") :
tab.getAttribute("label"));
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "keypress":
this._handleKeyEvent(aEvent);
break;
}
]]></body>
</method>
<constructor>
<![CDATA[
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild.firstChild;
this.mCurrentTab = this.tabContainer.firstChild;
document.addEventListener("keypress", this, false);
var uniqueId = "panel" + Date.now();
this.mPanelContainer.childNodes[0].id = uniqueId;
this.mCurrentTab.linkedPanel = uniqueId;
this.mCurrentTab._tPos = 0;
this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
// 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);
this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
]]>
</constructor>
<destructor>
<![CDATA[
for (var i = 0; i < this.mTabListeners.length; ++i) {
let browser = this.getBrowserAtIndex(i);
if (browser.registeredOpenURI) {
this.mBrowserHistory.unregisterOpenPage(browser.registeredOpenURI);
delete browser.registeredOpenURI;
}
browser.webProgress.removeProgressListener(this.mTabFilters[i]);
this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
this.mTabFilters[i] = null;
this.mTabListeners[i].destroy();
this.mTabListeners[i] = null;
}
document.removeEventListener("keypress", this, false);
]]>
</destructor>
<!-- Deprecated stuff, implemented for backwards compatibility. -->
<method name="setStripVisibilityTo">
<parameter name="aShow"/>
<body>
this.tabContainer.visible = aShow;
</body>
</method>
<method name="getStripVisibility">
<body>
return this.tabContainer.visible;
</body>
</method>
<property name="mContextTab" readonly="true"
onget="return TabContextMenu.contextTab;"/>
<property name="mPrefs" readonly="true"
onget="return Services.prefs;"/>
<property name="mTabContainer" readonly="true"
onget="return this.tabContainer;"/>
<property name="mTabs" readonly="true"
onget="return this.tabs;"/>
<!--
- Compatibility hack: several extensions depend on this property to
- access the tab context menu or tab container, so keep that working for
- now. Ideally we can remove this once extensions are using
- tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
-->
<property name="mStrip" readonly="true">
<getter>
<![CDATA[
return ({
self: this,
childNodes: [null, this.tabContextMenu, this.tabContainer],
firstChild: { nextSibling: this.tabContextMenu },
getElementsByAttribute: function (attr, attrValue) {
if (attr == "anonid" && attrValue == "tabContextMenu")
return [this.self.tabContextMenu];
return [];
},
// Also support adding event listeners (forward to the tab container)
addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
});
]]>
</getter>
</property>
</implementation>
<handlers>
<handler event="DOMWindowClose" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
if (this.tabs.length == 1)
return;
var tab = this._getTabForContentWindow(event.target);
if (tab) {
this.removeTab(tab);
event.preventDefault();
}
]]>
</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.
this.selectedTab = this._getTabForContentWindow(event.target.top);
]]>
</handler>
<handler event="DOMTitleChanged">
<![CDATA[
if (!event.isTrusted)
return;
var contentWin = event.target.defaultView;
if (contentWin != contentWin.top)
return;
var tab = this._getTabForContentWindow(contentWin);
var titleChanged = this.setTabTitle(tab);
if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
tab.setAttribute("titlechanged", "true");
]]>
</handler>
</handlers>
</binding>
<binding id="tabbrowser-tabbox"
extends="chrome://global/content/bindings/tabbox.xml#tabbox">
<implementation>
<property name="tabs" readonly="true"
onget="return document.getBindingParent(this).tabContainer;"/>
</implementation>
</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 Array.filter(document.getBindingParent(this).childNodes,
this._canScrollToElement, this);
]]></body>
</method>
<method name="_canScrollToElement">
<parameter name="tab"/>
<body><![CDATA[
return !tab.pinned && !tab.hidden;
]]></body>
</method>
</implementation>
<handlers>
<handler event="underflow"><![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
tabs.removeAttribute("overflow");
tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
tabs.tabbrowser);
tabs._positionPinnedTabs();
]]></handler>
<handler event="overflow"><![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
tabs.setAttribute("overflow", "true");
tabs._positionPinnedTabs();
]]></handler>
</handlers>
</binding>
<binding id="tabbrowser-tabs"
extends="chrome://global/content/bindings/tabbox.xml#tabs">
<resources>
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
</resources>
<content>
<xul:hbox align="start">
<xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
</xul:hbox>
<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"
onclick="checkForMiddleClick(this, event);"
tooltiptext="&newTabButton.tooltip;"/>
</xul:arrowscrollbox>
</content>
<implementation implements="nsIDOMEventListener">
<constructor>
<![CDATA[
this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
var tab = this.firstChild;
tab.setAttribute("label",
this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
tab.setAttribute("crop", "end");
tab.setAttribute("validate", "never");
tab.setAttribute("onerror", "this.removeAttribute('image');");
this.adjustTabstrip();
Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
window.addEventListener("resize", this, false);
]]>
</constructor>
<destructor>
<![CDATA[
Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
]]>
</destructor>
<field name="tabbrowser" readonly="true">
document.getElementById(this.getAttribute("tabbrowser"));
</field>
<field name="tabbox" readonly="true">
this.tabbrowser.mTabBox;
</field>
<field name="contextMenu" readonly="true">
document.getElementById("tabContextMenu");
</field>
<field name="mTabstripWidth">0</field>
<field name="mTabstrip">
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
</field>
<field name="_prefObserver"><![CDATA[({
tabContainer: this,
observe: function (subject, topic, data) {
switch (data) {
case "browser.tabs.closeButtons":
this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
this.tabContainer.adjustTabstrip();
break;
case "browser.tabs.autoHide":
this.tabContainer.updateVisibility();
break;
case "browser.tabs.closeWindowWithLastTab":
this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
this.tabContainer.adjustTabstrip();
break;
}
}
});]]></field>
<field name="_blockDblClick">false</field>
<field name="_tabDropIndicator">
document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
</field>
<field name="_dragOverDelay">350</field>
<field name="_dragTime">0</field>
<field name="_container" readonly="true"><![CDATA[
this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
]]></field>
<property name="visible"
onget="return !this._container.collapsed;">
<setter><![CDATA[
this._container.collapsed = !val;
if (val)
this.tabbrowser.enterTabbedMode();
document.getElementById("menu_closeWindow").hidden = !val;
document.getElementById("menu_close").setAttribute("label",
this.tabbrowser.mStringBundle.getString(val ? "tabs.closeTab" : "tabs.close"));
return val;
]]></setter>
</property>
<method name="updateVisibility">
<body><![CDATA[
if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1 &&
window.toolbar.visible)
this.visible = !Services.prefs.getBoolPref("browser.tabs.autoHide");
else
this.visible = true;
]]></body>
</method>
<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:
if (this.childNodes.length == 1 && this._closeWindowWithLastTab)
this.setAttribute("closebuttons", "noclose");
else
this.setAttribute("closebuttons", "activetab");
break;
case 1:
if (this.childNodes.length == 1) {
if (this._closeWindowWithLastTab)
this.setAttribute("closebuttons", "noclose");
else
this.setAttribute("closebuttons", "alltabs");
} else {
let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
this.setAttribute("closebuttons", "alltabs");
else
this.setAttribute("closebuttons", "activetab");
}
break;
case 2:
case 3:
this.setAttribute("closebuttons", "noclose");
break;
}
var tabstripClosebutton = document.getElementById("tabs-closebutton");
if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
tabstripClosebutton.collapsed = this.mCloseButtons != 3;
]]></body>
</method>
<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="_positionPinnedTabs">
<body><![CDATA[
var width = 0;
var scrollButtonWidth = this.getAttribute("overflow") != "true" ? 0 :
this.mTabstrip._scrollButtonDown.scrollWidth;
for (var i = this.tabbrowser._numPinnedTabs - 1; i >= 0; i--) {
let tab = this.childNodes[i];
width += tab.scrollWidth;
tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
}
this.style.MozMarginStart = width + "px";
this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
]]></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="_animateElement">
this.mTabstrip._scrollButtonDown;
</field>
<method name="_notifyBackgroundTab">
<parameter name="aTab"/>
<body><![CDATA[
if (aTab.pinned)
return;
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.pinned &&
this.selectedItem.getBoundingClientRect();
// Can we make both the new tab and the selected tab completely visible?
if (!selected ||
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);
}
if (!this._animateElement.hasAttribute("notifybgtab")) {
this._animateElement.setAttribute("notifybgtab", "true");
setTimeout(function (ele) {
ele.removeAttribute("notifybgtab");
}, 150, this._animateElement);
}
]]></body>
</method>
<method name="_getDragTargetTab">
<parameter name="event"/>
<body><![CDATA[
let tab = event.target.localName == "tab" ? event.target : null;
if (tab &&
(event.type == "drop" || event.type == "dragover") &&
event.dataTransfer.dropEffect == "link") {
let boxObject = tab.boxObject;
if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
event.screenX > boxObject.screenX + boxObject.width * .75)
return null;
}
return tab;
]]></body>
</method>
<method name="_getDropIndex">
<parameter name="event"/>
<body><![CDATA[
var tabs = this.childNodes;
var tab = this._getDragTargetTab(event);
if (window.getComputedStyle(this, null).direction == "ltr") {
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
return i;
} else {
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
return i;
}
return tabs.length;
]]></body>
</method>
<method name="_setEffectAllowedForDataTransfer">
<parameter name="event"/>
<body><![CDATA[
var dt = event.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 ||
(sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
if (sourceNode.parentNode == this &&
(event.screenX >= sourceNode.boxObject.screenX &&
event.screenX <= (sourceNode.boxObject.screenX +
sourceNode.boxObject.width))) {
return dt.effectAllowed = "none";
}
return dt.effectAllowed = "copyMove";
}
}
if (browserDragAndDrop.canDropLink(event)) {
// Here we need to do this manually
return dt.effectAllowed = dt.dropEffect = "link";
}
return dt.effectAllowed = "none";
]]></body>
</method>
<method name="_continueScroll">
<parameter name="event"/>
<body><![CDATA[
// Workaround for bug 481904: Dragging a tab stops scrolling at
// the tab's position when dragging to the first/last tab and back.
var t = this.selectedItem;
if (event.screenX >= t.boxObject.screenX &&
event.screenX <= t.boxObject.screenX + t.boxObject.width &&
event.screenY >= t.boxObject.screenY &&
event.screenY <= t.boxObject.screenY + t.boxObject.height)
this.mTabstrip.ensureElementIsVisible(t);
]]></body>
</method>
<method name="_handleNewTab">
<parameter name="tab"/>
<body><![CDATA[
if (tab.parentNode != this)
return;
this.adjustTabstrip();
if (tab.getAttribute("selected") == "true") {
this._fillTrailingGap();
this._handleTabSelect();
} else {
this._notifyBackgroundTab(tab);
}
// 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.mTabstrip._updateScrollButtonsDisabledState();
]]></body>
</method>
<method name="_canAdvanceToTab">
<parameter name="aTab"/>
<body>
<![CDATA[
return this.tabbrowser._removingTabs.indexOf(aTab) == -1;
]]>
</body>
</method>
<!-- Deprecated stuff, implemented for backwards compatibility. -->
<property name="mTabstripClosebutton" readonly="true"
onget="return document.getElementById('tabs-closebutton');"/>
<property name="mAllTabsPopup" readonly="true"
onget="return document.getElementById('alltabs-popup');"/>
</implementation>
<handlers>
<handler event="TabSelect" action="this._handleTabSelect();"/>
<handler event="transitionend"><![CDATA[
if (event.propertyName != "max-width")
return;
var tab = event.target;
if (tab.getAttribute("fadein") == "true")
this._handleNewTab(tab);
else if (this.tabbrowser._removingTabs.indexOf(tab) > -1)
this.tabbrowser._endRemoveTab(tab);
]]></handler>
<handler event="dblclick"><![CDATA[
// See hack note in the tabbrowser-close-button binding
if (!this._blockDblClick && event.button == 0 &&
event.originalTarget.localName == "box")
BrowserOpenTab();
]]></handler>
<handler event="click"><![CDATA[
if (event.button != 1)
return;
if (event.target.localName == "tab") {
if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
this.tabbrowser.removeTab(event.target, {animate: true});
} else if (event.originalTarget.localName == "box") {
BrowserOpenTab();
} else {
return;
}
event.stopPropagation();
]]></handler>
<handler event="keypress"><![CDATA[
if (event.altKey || event.shiftKey ||
#ifdef XP_MACOSX
!event.metaKey)
#else
!event.ctrlKey || event.metaKey)
#endif
return;
switch (event.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(event);
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;
}
event.stopPropagation();
event.preventDefault();
]]></handler>
<handler event="dragstart"><![CDATA[
var tab = this._getDragTargetTab(event);
if (!tab)
return;
let dt = event.dataTransfer;
dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
let uri = this.tabbrowser.getBrowserForTab(tab).currentURI;
let 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";
let canvas = tabPreviews.capture(tab, false);
dt.setDragImage(canvas, 0, 0);
event.stopPropagation();
]]></handler>
<handler event="dragover"><![CDATA[
var effects = this._setEffectAllowedForDataTransfer(event);
var ind = this._tabDropIndicator;
if (effects == "" || effects == "none") {
ind.collapsed = true;
this._continueScroll(event);
return;
}
event.preventDefault();
event.stopPropagation();
var tabStrip = this.mTabstrip;
var ltr = (window.getComputedStyle(this, 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.getAttribute("overflow") == "true") {
var targetAnonid = event.originalTarget.getAttribute("anonid");
switch (targetAnonid) {
case "scrollbutton-up":
pixelsToScroll = tabStrip.scrollIncrement * -1;
break;
case "scrollbutton-down":
pixelsToScroll = tabStrip.scrollIncrement;
break;
}
if (pixelsToScroll)
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
}
if (effects == "link") {
let tab = this._getDragTargetTab(event);
if (tab) {
if (!this._dragTime)
this._dragTime = Date.now();
if (Date.now() >= this._dragTime + this._dragOverDelay)
this.selectedItem = tab;
ind.collapsed = true;
return;
}
}
var newIndex = this._getDropIndex(event);
var scrollRect = tabStrip.scrollClientRect;
var rect = this.getBoundingClientRect();
var minMargin = scrollRect.left - rect.left;
var maxMargin = Math.min(minMargin + scrollRect.width,
scrollRect.right);
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.childNodes.length) {
let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
if (ltr)
newMargin = tabRect.right - rect.left;
else
newMargin = rect.right - tabRect.left;
}
else {
let tabRect = this.childNodes[newIndex].getBoundingClientRect();
if (ltr)
newMargin = tabRect.left - rect.left;
else
newMargin = rect.right - tabRect.right;
}
}
ind.collapsed = false;
newMargin += ind.clientWidth / 2;
if (!ltr)
newMargin *= -1;
ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)";
ind.style.MozMarginStart = (-ind.clientWidth) + "px";
ind.style.marginTop = (-ind.clientHeight) + "px";
]]></handler>
<handler event="drop"><![CDATA[
var dt = event.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._tabDropIndicator.collapsed = true;
event.stopPropagation();
if (draggedTab && (dropEffect == "copy" ||
draggedTab.parentNode == this)) {
let newIndex = this._getDropIndex(event);
if (dropEffect == "copy") {
// copy the dropped tab (wherever it's from)
let newTab = this.tabbrowser.duplicateTab(draggedTab);
this.tabbrowser.moveTabTo(newTab, newIndex);
if (draggedTab.parentNode != this || event.shiftKey)
this.selectedItem = newTab;
} else {
// move the dropped tab
if (newIndex > draggedTab._tPos)
newIndex--;
if (draggedTab.pinned) {
if (newIndex >= this.tabbrowser._numPinnedTabs)
this.tabbrowser.unpinTab(draggedTab);
} else {
if (newIndex <= this.tabbrowser._numPinnedTabs - 1)
this.tabbrowser.pinTab(draggedTab);
}
this.tabbrowser.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)
let newIndex = this._getDropIndex(event);
let newTab = this.tabbrowser.addTab("about:blank");
let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
// Stop the about:blank load
newBrowser.stop();
// make sure it has a docshell
newBrowser.docShell;
this.tabbrowser.moveTabTo(newTab, newIndex);
this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
// We need to select the tab after we've done
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
// it triggers will correctly update our URL bar.
this.tabbrowser.selectedTab = newTab;
} else {
let url = browserDragAndDrop.drop(event, { });
// 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;
let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
if (event.shiftKey)
bgLoad = !bgLoad;
let tab = this._getDragTargetTab(event);
if (!tab || dropEffect == "copy") {
// We're adding a new tab.
let newIndex = this._getDropIndex(event);
let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
this.tabbrowser.moveTabTo(newTab, newIndex);
} else {
// Load in an existing tab.
try {
this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
if (!bgLoad)
this.selectedItem = tab;
} catch(ex) {
// Just ignore invalid urls
}
}
}
]]></handler>
<handler event="dragend"><![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 = event.dataTransfer;
if (dt.mozUserCancelled || dt.dropEffect != "none")
return;
// Disable detach within the browser toolbox
var eX = event.screenX;
var wX = window.screenX;
// check if the drop point is horizontally within the window
if (eX > wX && eX < (wX + window.outerWidth)) {
let bo = this.mTabstrip.boxObject;
// also avoid detaching if the the tab was dropped too close to
// the tabbar (half a tab)
let endScreenY = bo.screenY + 1.5 * bo.height;
let eY = event.screenY;
if (eY < endScreenY && eY > window.screenY)
return;
}
var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
this.tabbrowser.replaceTabWithWindow(draggedTab);
event.stopPropagation();
]]></handler>
<handler event="dragleave"><![CDATA[
this._dragTime = 0;
// This does not work at all (see bug 458613)
var target = event.relatedTarget;
while (target && target != this)
target = target.parentNode;
if (target)
return;
this._tabDropIndicator.collapsed = true;
this._continueScroll(event);
event.stopPropagation();
]]></handler>
</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>).
-->
<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);
var tabContainer = bindingParent.parentNode;
/* 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;
tabContainer.tabbrowser.removeTab(bindingParent, {animate: true});
tabContainer._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 dblclick handler).
*/
var clickedOnce = false;
function enableDblClick(event) {
if (event.detail == 1 && !clickedOnce) {
clickedOnce = true;
return;
}
setTimeout(function() {
tabContainer._blockDblClick = false;
}, 0);
tabContainer.removeEventListener("click", enableDblClick, false);
}
tabContainer.addEventListener("click", enableDblClick, false);
]]></handler>
<handler event="dblclick" button="0" phase="capturing">
// for the one-close-button case
event.stopPropagation();
</handler>
<handler event="dragstart">
event.stopPropagation();
</handler>
</handlers>
</binding>
<binding id="tabbrowser-tab" display="xul:hbox"
extends="chrome://global/content/bindings/tabbox.xml#tab">
<resources>
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
</resources>
<content context="tabContextMenu" closetabtext="&closeTab.label;">
<xul:stack class="tab-stack" flex="1">
<xul:vbox class="tab-progress-container">
<xul:progressmeter class="tab-progress" mode="normal"
xbl:inherits="value=progresspercent,busy,stalled,fadein,selected"/>
</xul:vbox>
<xul:hbox class="tab-content" align="center">
<xul:image xbl:inherits="validate,src=image,fadein,pinned,busy,stalled,selected"
class="tab-icon-image"
role="presentation"/>
<xul:label flex="1"
xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
class="tab-text tab-label"
role="presentation"/>
<xul:toolbarbutton anonid="close-button"
xbl:inherits="fadein,pinned,selected"
tabindex="-1"
clickthrough="never"
class="tab-close-button"/>
</xul:hbox>
</xul:stack>
</content>
<implementation>
<property name="pinned" readonly="true">
<getter>
return this.getAttribute("pinned") == "true";
</getter>
</property>
<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[
gBrowser.selectedTab = aEvent.target.tab;
]]></body>
</method>
<method name="_tabOnAttrModified">
<parameter name="aEvent"/>
<body><![CDATA[
var tab = aEvent.target;
this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
]]></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 "TabAttrModified":
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 = gBrowser.tabContainer;
// 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-with-favicon");
this._setMenuitemAttributes(menuItem, aTab);
// Keep some attributes of the menuitem in sync with its
// corresponding tab (e.g. the tab label)
aTab.mCorrespondingMenuitem = menuItem;
menuItem.tab = aTab;
menuItem.addEventListener("command", this, false);
this.appendChild(menuItem);
return menuItem;
]]></body>
</method>
<method name="_setMenuitemAttributes">
<parameter name="aMenuitem"/>
<parameter name="aTab"/>
<body><![CDATA[
aMenuitem.setAttribute("label", aTab.label);
aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
aMenuitem.setAttribute("image", aTab.getAttribute("image"));
if (aTab.hasAttribute("busy"))
aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
else
aMenuitem.removeAttribute("busy");
if (aTab.selected)
aMenuitem.setAttribute("selected", "true");
else
aMenuitem.removeAttribute("selected");
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshowing">
<![CDATA[
// set up the menu popup
var tabcontainer = gBrowser.tabContainer;
let tabs = gBrowser.visibleTabs;
// Listen for changes in the tab bar.
tabcontainer.addEventListener("TabOpen", this, false);
tabcontainer.addEventListener("TabAttrModified", this, false);
tabcontainer.addEventListener("TabClose", this, false);
tabcontainer.mTabstrip.addEventListener("scroll", this, false);
for (var i = 0; i < tabs.length; i++) {
this._createTabMenuItem(tabs[i]);
}
this._updateTabsVisibilityStatus();
]]></handler>
<handler event="popuphidden">
<![CDATA[
// clear out the menu popup and remove the listeners
while (this.hasChildNodes()) {
var menuItem = this.lastChild;
menuItem.removeEventListener("command", this, false);
menuItem.tab.mCorrespondingMenuitem = null;
this.removeChild(menuItem);
}
var tabcontainer = gBrowser.tabContainer;
tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
tabcontainer.removeEventListener("TabOpen", this, false);
tabcontainer.removeEventListener("TabAttrModified", this, false);
tabcontainer.removeEventListener("TabClose", 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>