gecko/toolkit/content/widgets/tabbrowser.xml
jag@tty.nl 6ba0c4de3c Bug 383252: Cannot drag / drop URL or link onto tabbar, r=mconnor
When SeaMonkey switched to toolkit's nsDragAndDrop.js, it lost the dragDropSecurityCheck method, which for SM was on nsDragAndDrop, but for FF was on tabbrowser.

Moving that method from tabbrowser to toolkit's nsDragAndDrop.js, and cleaning it up a little.
2007-07-25 23:40:24 -07:00

3193 lines
121 KiB
XML

<?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>
-
- 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://global/locale/tabbrowser.dtd" >
%tabBrowserDTD;
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>
<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://global/skin/browser.css"/>
</resources>
<content>
<xul:stringbundle anonid="tbstringbundle" src="chrome://global/locale/tabbrowser.properties"/>
<xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
onselect="if (!('updateCurrentBrowser' in this.parentNode) || event.target.localName != 'tabpanels') return; this.parentNode.updateCurrentBrowser();">
<xul:hbox class="tab-drop-indicator-bar">
<xul:hbox class="tab-drop-indicator" mousethrough="always"/>
</xul:hbox>
<xul:hbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
anonid="strip"
ondraggesture="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
ondragdrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
<xul:tooltip onpopupshowing="return this.parentNode.parentNode.parentNode.createTooltip(event);"/>
<xul:menupopup anonid="tabContextMenu" onpopupshowing="this.parentNode.parentNode.parentNode.updatePopupMenu(this);">
<xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"
xbl:inherits="oncommand=onnewtab"/>
<xul:menuseparator/>
<xul:menuitem label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
<xul:menuitem label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.reloadAllTabs(tabbrowser.mContextTab);"/>
<xul:menuitem label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
<xul:menuseparator/>
<xul:menuitem label="&closeTab.label;" accesskey="&closeTab.accesskey;"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeTab(tabbrowser.mContextTab);"/>
</xul:menupopup>
<xul:tabs class="tabbrowser-tabs" flex="1"
anonid="tabcontainer"
setfocus="false"
onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
xbl:inherits="onnewtab"
ondblclick="this.parentNode.parentNode.parentNode.onTabBarDblClick(event);"
onclosetab="var node = this.parentNode;
while (node.localName != 'tabbrowser')
node = node.parentNode;
node.removeCurrentTab();">
<xul:tab selected="true" validate="never"
onerror="this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image'));
this.removeAttribute('image');"
maxwidth="250" width="0" minwidth="100" flex="100"
class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
</xul:tabs>
</xul:hbox>
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
<xul:notificationbox flex="1">
<xul:browser flex="1" type="content-primary" message="true" disablehistory="true"
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
</xul:notificationbox>
</xul:tabpanels>
</xul:tabbox>
<children/>
</content>
<implementation>
<field name="mPrefs" readonly="true">
Components.classes['@mozilla.org/preferences-service;1']
.getService(Components.interfaces.nsIPrefService)
.getBranch(null);
</field>
<field name="mURIFixup" readonly="true">
Components.classes["@mozilla.org/docshell/urifixup;1"]
.getService(Components.interfaces.nsIURIFixup);
</field>
<field name="mTabBox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
</field>
<field name="mTabDropIndicatorBar">
this.mTabBox.childNodes[0]
</field>
<field name="mStrip" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "strip");
</field>
<field name="mTabContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
</field>
<field name="mPanelContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
</field>
<field name="mTabs" readonly="true">
this.mTabContainer.childNodes
</field>
<field name="mStringBundle">
document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
</field>
<field name="mCurrentTab">
null
</field>
<field name="mCurrentBrowser">
null
</field>
<field name="mProgressListeners">
[]
</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="mMissedIconCache">
null
</field>
<field name="mContextTab">
null
</field>
<field name="arrowKeysShouldWrap" readonly="true">
#ifdef XP_MACOSX
true
#else
false
#endif
</field>
<field name="mAddProgressListenerWasCalled">
false
</field>
<field name="_browsers">
null
</field>
<field name="_blockDblClick">
false
</field>
<field name="_autoScrollPopup">
null
</field>
<method name="getBrowserAtIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.mTabContainer.childNodes[aIndex].linkedBrowser;
]]>
</body>
</method>
<method name="getBrowserIndexForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
for (var i = 0; i < this.mPanelContainer.childNodes.length; i++) {
if (this.getBrowserAtIndex(i).contentDocument == aDocument) {
return i;
}
}
return -1;
]]>
</body>
</method>
<method name="getBrowserForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var index = this.getBrowserIndexForDocument(aDocument);
if (index < 0)
return null;
return this.getBrowserAtIndex(index);
]]>
</body>
</method>
<method name="getNotificationBox">
<parameter name="aBrowser"/>
<body>
<![CDATA[
if (aBrowser)
return aBrowser.parentNode;
else if (this.mCurrentBrowser)
return this.mCurrentBrowser.parentNode;
return null;
]]>
</body>
</method>
<!-- A web progress listener object definition for a given tab. -->
<method name="mTabProgressListener">
<parameter name="aTab"/>
<parameter name="aBrowser"/>
<parameter name="aStartsBlank"/>
<body>
<![CDATA[
return ({
mTabBrowser: this,
mTab: aTab,
mBrowser: aBrowser,
mBlank: aStartsBlank,
mLastURI: null,
// cache flags for correct status bar update after tab switching
mStateFlags: 0,
mStatus: 0,
mMessage: "",
mTotalProgress: 0,
// count of open requests (should always be 0 or 1)
mRequestCount: 0,
onProgressChange : function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress)
{
if (!this.mBlank && this.mTabBrowser.mCurrentTab == this.mTab) {
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
var p = this.mTabBrowser.mProgressListeners[i];
if (p)
p.onProgressChange(aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress);
}
}
this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
},
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)
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
this.mBrowser.userTypedClear++;
if (!this.mBlank) {
this.mTab.setAttribute("busy", "true");
this.mTabBrowser.updateIcon(this.mTab);
this.mTabBrowser.setTabTitleLoading(this.mTab);
if (this.mTabBrowser.mCurrentTab == this.mTab)
this.mTabBrowser.mIsBusy = true;
}
}
else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
// The document is done loading, we no longer want the
// value cleared.
if (this.mBrowser.userTypedClear > 0)
this.mBrowser.userTypedClear--;
if (!this.mBrowser.mIconURL)
this.mTabBrowser.useDefaultIcon(this.mTab);
}
if (this.mBlank)
this.mBlank = false;
this.mTab.removeAttribute("busy");
this.mTabBrowser.updateIcon(this.mTab);
var location = aRequest.QueryInterface(nsIChannel).URI;
// For keyword URIs clear the user typed value since they will be changed into real URIs
if (location.scheme == "keyword")
this.mBrowser.userTypedValue = null;
if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
this.mTabBrowser.setTabTitle(this.mTab);
if (this.mTabBrowser.mCurrentTab == this.mTab)
this.mTabBrowser.mIsBusy = false;
}
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
var p = this.mTabBrowser.mProgressListeners[i];
if (p && !oldBlank)
p.onStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
// make sure that the visible status of new blank tabs is correctly set
else if (p && "onUpdateCurrentBrowser" in p)
p.onUpdateCurrentBrowser(aStateFlags, aStatus, "", 0);
}
}
if (aStateFlags & (nsIWebProgressListener.STATE_START |
nsIWebProgressListener.STATE_STOP)) {
// reset cached temporary values at beginning and end
this.mMessage = "";
this.mTotalProgress = 0;
}
this.mStateFlags = aStateFlags;
this.mStatus = aStatus;
},
onLocationChange : function(aWebProgress, aRequest, aLocation)
{
// The document loaded correctly, clear the value if we should
if (this.mBrowser.userTypedClear > 0 && aRequest)
this.mBrowser.userTypedValue = null;
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow &&
aWebProgress.isLoadingDocument)
this.mTabBrowser.setIcon(this.mTab, null);
// changing location, clear out the missing plugins list
this.mTab.missingPlugins = null;
if (!this.mBlank && this.mTabBrowser.mCurrentTab == this.mTab) {
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
var p = this.mTabBrowser.mProgressListeners[i];
if (p)
p.onLocationChange(aWebProgress, aRequest, aLocation);
}
}
},
onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
{
if (this.mBlank)
return;
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
var p = this.mTabBrowser.mProgressListeners[i];
if (p)
p.onStatusChange(aWebProgress, aRequest, aStatus, aMessage);
}
}
this.mMessage = aMessage;
},
onSecurityChange : function(aWebProgress, aRequest, aState)
{
if (this.mTabBrowser.mCurrentTab == this.mTab) {
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
var p = this.mTabBrowser.mProgressListeners[i];
if (p)
p.onSecurityChange(aWebProgress, aRequest, aState);
}
}
},
onRefreshAttempted : function(aWebProgress, aURI, aDelay, aSameURI)
{
var allowRefresh = true;
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
var p = this.mTabBrowser.mProgressListeners[i];
if (p && "onRefreshAttempted" in p) {
if (!p.onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI))
allowRefresh = false;
}
}
return allowRefresh;
},
QueryInterface : function(aIID)
{
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
}
});
]]>
</body>
</method>
<method name="setIcon">
<parameter name="aTab"/>
<parameter name="aURI"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
browser.mIconURL = aURI;
this.updateIcon(aTab);
for (var i = 0; i < this.mProgressListeners.length; i++) {
var p = this.mProgressListeners[i];
if ('onLinkIconAvailable' in p)
p.onLinkIconAvailable(browser);
}
]]>
</body>
</method>
<method name="updateIcon">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
if (!aTab.hasAttribute("busy") && browser.mIconURL)
aTab.setAttribute("image", browser.mIconURL);
else
aTab.removeAttribute("image");
]]>
</body>
</method>
<method name="shouldLoadFavIcon">
<parameter name="aURI"/>
<body>
<![CDATA[
return (aURI && this.mPrefs.getBoolPref("browser.chrome.site_icons") &&
this.mPrefs.getBoolPref("browser.chrome.favicons") &&
("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
]]>
</body>
</method>
<method name="useDefaultIcon">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
if (browser.contentDocument instanceof ImageDocument) {
if (this.mPrefs.getBoolPref("browser.chrome.site_icons")) {
try {
var sz = this.mPrefs.getIntPref("browser.chrome.image_icons.max_size");
if (!sz)
return;
var req = browser.contentDocument.imageRequest;
if (!req || !req.image ||
req.image.width > sz ||
req.image.height > sz)
return;
this.setIcon(aTab, browser.currentURI.spec);
} catch (e) { }
}
}
else if (this.shouldLoadFavIcon(browser.currentURI)) {
var url = browser.currentURI.prePath + "/favicon.ico";
if (!this.isIconKnownMissing(url))
this.setIcon(aTab, url);
}
]]>
</body>
</method>
<method name="addToMissedIconCache">
<parameter name="aURI"/>
<body>
<![CDATA[
var entry = this.openCacheEntry(aURI, Components.interfaces.nsICache.ACCESS_READ_WRITE);
if (!entry)
return;
if (entry.accessGranted == Components.interfaces.nsICache.ACCESS_WRITE)
// It's a new entry. Just write a bit of metadata in to the entry.
entry.setMetaDataElement("Icon", "Missed");
entry.markValid();
entry.close();
]]>
</body>
</method>
<method name="openCacheEntry">
<parameter name="key"/>
<parameter name="access"/>
<body>
<![CDATA[
try {
if (!this.mMissedIconCache) {
var cacheService = Components.classes['@mozilla.org/network/cache-service;1'].getService(Components.interfaces.nsICacheService);
this.mMissedIconCache = cacheService.createSession("MissedIconCache", Components.interfaces.nsICache.STORE_ANYWHERE, true);
if (!this.mMissedIconCache)
return null;
}
return this.mMissedIconCache.openCacheEntry(key, access, true);
}
catch (e) {
return null;
}
]]>
</body>
</method>
<method name="isIconKnownMissing">
<parameter name="key"/>
<body>
<![CDATA[
var e = this.openCacheEntry(key, Components.interfaces.nsICache.ACCESS_READ);
if (e) {
e.close();
return true;
}
return false;
]]>
</body>
</method>
<method name="updateTitlebar">
<body>
<![CDATA[
var newTitle = "";
var docTitle;
var docElement = this.ownerDocument.documentElement;
var sep = docElement.getAttribute("titlemenuseparator");
if (this.docShell.contentViewer)
docTitle = this.contentTitle;
if (!docTitle)
docTitle = docElement.getAttribute("titledefault");
var modifier = docElement.getAttribute("titlemodifier");
if (docTitle) {
newTitle += docElement.getAttribute("titlepreface");
newTitle += docTitle;
if (modifier)
newTitle += sep;
}
newTitle += modifier;
// If location bar is hidden and the URL type supports a host,
// add the scheme and host to the title to prevent spoofing.
// XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
try {
if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
var uri = this.mURIFixup.createExposableURI(
this.mCurrentBrowser.currentURI);
if (uri.scheme == "about")
newTitle = uri.spec + sep + newTitle;
else
newTitle = uri.prePath + sep + newTitle;
}
} catch (e) {}
this.ownerDocument.title = newTitle;
]]>
</body>
</method>
<method name="updatePopupMenu">
<parameter name="aPopupMenu"/>
<body>
<![CDATA[
this.mContextTab = document.popupNode;
var disabled = this.mPanelContainer.childNodes.length == 1;
var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
for (var i = 0; i < menuItems.length; i++)
menuItems[i].disabled = disabled;
]]>
</body>
</method>
<method name="updateCurrentBrowser">
<body>
<![CDATA[
var newBrowser = this.getBrowserAtIndex(this.mTabContainer.selectedIndex);
if (this.mCurrentBrowser == newBrowser)
return;
if (this.mCurrentBrowser) {
// Only save the focused element if it is in our content window
// or in an ancestor window.
var focusedWindow = document.commandDispatcher.focusedWindow;
var saveFocus = false;
if (focusedWindow && focusedWindow.top == window.content) {
saveFocus = true;
} else {
var contentWindow = window;
while (contentWindow) {
if (contentWindow == focusedWindow) {
saveFocus = true;
break;
}
if (contentWindow.parent == contentWindow) {
break;
}
contentWindow = contentWindow.parent;
}
}
if (saveFocus) {
// Preserve the currently-focused element or DOM window for
// this tab.
this.mCurrentBrowser.focusedWindow = focusedWindow;
this.mCurrentBrowser.focusedElement = document.commandDispatcher.focusedElement;
}
if (this.mCurrentBrowser.focusedElement &&
this.mCurrentBrowser.focusedElement.parentNode !=
this.mCurrentTab.parentNode) {
// Clear focus outline before we draw on top of it.
// Only blur the focused element if it isn't a tab,
// to avoid breaking keyboard tab navigation
var elem = this.mCurrentBrowser.focusedElement;
if (elem instanceof HTMLElement || elem instanceof XULElement) {
elem.blur();
}
else {
var content = elem.ownerDocument.defaultView;
if (content instanceof Components.interfaces.nsIInterfaceRequestor)
content.getInterface(Components.interfaces.nsIDOMWindowUtils).focus(null);
}
}
this.mCurrentBrowser.setAttribute("type", "content-targetable");
}
var updatePageReport = false;
if ((this.mCurrentBrowser.pageReport && !newBrowser.pageReport) ||
(!this.mCurrentBrowser.pageReport && newBrowser.pageReport))
updatePageReport = true;
newBrowser.setAttribute("type", "content-primary");
this.mCurrentBrowser = newBrowser;
this.mCurrentTab = this.selectedTab;
if (updatePageReport)
this.mCurrentBrowser.updatePageReport();
// Update the URL bar.
var loc = this.mCurrentBrowser.currentURI;
var webProgress = this.mCurrentBrowser.webProgress;
var securityUI = this.mCurrentBrowser.securityUI;
var i, p;
for (i = 0; i < this.mProgressListeners.length; i++) {
p = this.mProgressListeners[i];
if (p) {
p.onLocationChange(webProgress, null, loc);
if (securityUI)
p.onSecurityChange(webProgress, null, securityUI.state);
// make sure that all status indicators are properly updated
if ("onUpdateCurrentBrowser" in p) {
var listener = this.mTabListeners[this.mTabContainer.selectedIndex] || null;
if (listener && listener.mStateFlags)
p.onUpdateCurrentBrowser(listener.mStateFlags, listener.mStatus,
listener.mMessage, listener.mTotalProgress);
}
}
}
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
// Update the window title.
this.updateTitlebar();
// If the new tab is busy, and our current state is not busy, then
// we need to fire a start to all progress listeners.
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
this.mIsBusy = true;
webProgress = this.mCurrentBrowser.webProgress;
for (i = 0; i < this.mProgressListeners.length; i++) {
p = this.mProgressListeners[i];
if (p)
p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0);
}
}
// If the new tab is not busy, and our current state is busy, then
// we need to fire a stop to all progress listeners.
if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
this.mIsBusy = false;
webProgress = this.mCurrentBrowser.webProgress;
for (i = 0; i < this.mProgressListeners.length; i++) {
p = this.mProgressListeners[i];
if (p)
p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0);
}
}
// 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);
if (document.commandDispatcher.focusedElement &&
document.commandDispatcher.focusedElement.parentNode ==
this.mCurrentTab.parentNode) {
// The focus is on a tab in the same tab panel
return; // If focus was on a tab, switching tabs focuses the new tab
}
var whatToFocus = window.content;
// Focus the previously focused element or window
if (newBrowser.focusedElement) {
if (newBrowser.focusedElement.parentNode !=
this.mCurrentTab.parentNode) {
// Focus the remembered element unless it's in the current tab panel
whatToFocus = newBrowser.focusedElement;
}
}
else if (newBrowser.focusedWindow) {
whatToFocus = newBrowser.focusedWindow;
}
// Change focus for this window to |whatToFocus|, without
// focusing the window itself.
var cmdDispatcher = document.commandDispatcher;
var ww =
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
if (ww.activeWindow == window) {
cmdDispatcher.suppressFocusScroll = true;
if (whatToFocus instanceof HTMLElement ||
whatToFocus instanceof XULElement ||
whatToFocus instanceof Window) {
whatToFocus.focus();
}
else if (whatToFocus instanceof Node) {
var content = window.content;
if (content instanceof Components.interfaces.nsIInterfaceRequestor)
content.getInterface(Components.interfaces.nsIDOMWindowUtils).focus(whatToFocus);
}
cmdDispatcher.suppressFocusScroll = false;
}
else {
// set the element in command dispatcher so focus will restore
// properly when the window does become active
if (whatToFocus instanceof Window) {
cmdDispatcher.focusedWindow = whatToFocus;
cmdDispatcher.focusedElement = null;
}
else {
cmdDispatcher.focusedWindow = whatToFocus.ownerDocument.defaultView;
cmdDispatcher.focusedElement = whatToFocus;
}
}
]]>
</body>
</method>
<method name="onTabClick">
<parameter name="event"/>
<body>
<![CDATA[
if (event.button != 1 || event.target.localName != 'tab')
return;
this.removeTab(event.target);
event.stopPropagation();
]]>
</body>
</method>
<method name="onLinkAdded">
<parameter name="event"/>
<body>
<![CDATA[
if (!this.mPrefs.getBoolPref("browser.chrome.site_icons"))
return;
if (!event.originalTarget.rel.match((/(?:^|\s)icon(?:\s|$)/i)))
return;
// We have an icon.
var href = event.originalTarget.href;
if (!href)
return;
const nsIContentPolicy = Components.interfaces.nsIContentPolicy;
try {
var contentPolicy =
Components.classes['@mozilla.org/layout/content-policy;1']
.getService(nsIContentPolicy);
} catch(e) {
return; // Refuse to load if we can't do a security check.
}
// Get the IOService so we can make URIs
const ioService =
Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
const targetDoc = event.target.ownerDocument;
// Make a URI out of our href.
var uri = ioService.newURI(href, targetDoc.characterSet, null);
try {
// Verify that the load of this icon is legal.
// error pages can load their favicon, to be on the safe side,
// only allow chrome:// favicons
const nsIScriptSecMan =
Components.interfaces.nsIScriptSecurityManager;
var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
.getService(nsIScriptSecMan);
const aboutNeterr = "about:neterror?";
if (targetDoc.documentURI.substr(0, aboutNeterr.length) != aboutNeterr ||
!uri.schemeIs("chrome"))
secMan.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
nsIScriptSecMan.DISALLOW_SCRIPT);
} catch(e) {
return;
}
// Security says okay, now ask content policy
if (contentPolicy.shouldLoad(nsIContentPolicy.TYPE_IMAGE,
uri, targetDoc.documentURIObject,
event.target, event.target.type,
null) != nsIContentPolicy.ACCEPT)
return;
var browserIndex = this.getBrowserIndexForDocument(targetDoc);
// no browser? no favicon.
if (browserIndex == -1)
return;
var tab = this.mTabContainer.childNodes[browserIndex];
this.setIcon(tab, href);
]]>
</body>
</method>
<method name="onTitleChanged">
<parameter name="evt"/>
<body>
<![CDATA[
if (evt.target != this.contentDocument)
return;
var i = 0;
for ( ; i < this.parentNode.parentNode.childNodes.length; i++) {
if (this.parentNode.parentNode.childNodes[i].firstChild == this)
break;
}
var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;
var tab = document.getAnonymousElementByAttribute(tabBrowser, "linkedpanel", this.parentNode.id);
tabBrowser.setTabTitle(tab);
if (tab == tabBrowser.mCurrentTab)
tabBrowser.updateTitlebar();
]]>
</body>
</method>
<method name="setTabTitleLoading">
<parameter name="aTab"/>
<body>
<![CDATA[
aTab.label = this.mStringBundle.getString("tabs.loading");
aTab.setAttribute("crop", "end");
]]>
</body>
</method>
<method name="setTabTitle">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
var crop = "end";
var title = browser.contentTitle;
if (!title) {
if (browser.currentURI.spec) {
try {
title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
} catch(ex) {
title = browser.currentURI.spec;
}
}
if (title && title != "about:blank") {
// At this point, we now have a URI.
// Let's try to unescape it using a character set
// in case the URI is not ASCII.
try {
var characterSet = browser.contentDocument.characterSet;
const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
.getService(Components.interfaces.nsITextToSubURI);
title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
} catch(ex) { /* Do nothing. */ }
crop = "center";
} else // Still no title? Fall back to our untitled string.
title = this.mStringBundle.getString("tabs.untitled");
}
aTab.label = title;
aTab.setAttribute("crop", crop);
]]>
</body>
</method>
<method name="setStripVisibilityTo">
<parameter name="aShow"/>
<body>
<![CDATA[
this.mStrip.collapsed = !aShow;
if (aShow) {
// XXXdwh temporary unclean dependency on specific menu items in navigator.xul
document.getElementById("menu_closeWindow").hidden = false;
document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.closeTab"));
if (!this.mTabbedMode)
this.enterTabbedMode();
}
else {
// XXXdwh temporary unclean dependency on specific menu items in navigator.xul
document.getElementById("menu_closeWindow").hidden = true;
document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.close"));
}
]]>
</body>
</method>
<method name="getStripVisibility">
<body>
return !this.mStrip.collapsed;
</body>
</method>
<method name="enterTabbedMode">
<body>
<![CDATA[
this.mTabbedMode = true; // Welcome to multi-tabbed mode.
// Get the first tab all hooked up with a title listener and popup blocking listener.
this.mCurrentBrowser.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
var throbberElement = document.getElementById("navigator-throbber");
if (throbberElement && throbberElement.hasAttribute("busy")) {
this.mCurrentTab.setAttribute("busy", "true");
this.mIsBusy = true;
this.setTabTitleLoading(this.mCurrentTab);
this.updateIcon(this.mCurrentTab);
} else {
this.setTabTitle(this.mCurrentTab);
this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
}
var filter;
if (this.mTabFilters.length > 0) {
// Use the filter hooked up in our addProgressListener
filter = this.mTabFilters[0];
} else {
// create a filter and hook it up to our first browser
filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Components.interfaces.nsIWebProgress);
this.mTabFilters[0] = filter;
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
}
// Remove all our progress listeners from the active browser's filter.
for (var i = 0; i < this.mProgressListeners.length; i++) {
var p = this.mProgressListeners[i];
if (p)
filter.removeProgressListener(p);
}
// Wire up a progress listener to our filter.
const listener = this.mTabProgressListener(this.mCurrentTab,
this.mCurrentBrowser,
false);
filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[0] = listener;
]]>
</body>
</method>
<method name="loadOneTab">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<parameter name="aLoadInBackground"/>
<parameter name="aAllowThirdPartyFixup"/>
<body>
<![CDATA[
var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
var owner = bgLoad ? null : this.selectedTab;
var tab = this.addTab(aURI, aReferrerURI, aCharset, aPostData, owner,
aAllowThirdPartyFixup);
// Set newly selected tab after quick timeout, otherwise hideous focus problems
// can occur when "browser.tabs.loadInBackground" is false and presshell is not ready
if (!bgLoad) {
function selectNewForegroundTab(browser, tab) {
browser.selectedTab = tab;
}
setTimeout(selectNewForegroundTab, 0, getBrowser(), tab);
}
if (!bgLoad)
this.selectedTab = tab;
return tab;
]]>
</body>
</method>
<method name="loadTabs">
<parameter name="aURIs"/>
<parameter name="aLoadInBackground"/>
<parameter name="aReplace"/>
<body><![CDATA[
// The tab selected after this new tab is closed (i.e. the new tab's
// "owner") is the next adjacent tab (i.e. not the previously viewed tab)
// when several urls are opened here (i.e. closing the first should select
// the next of many URLs opened) or if the pref to have UI links opened in
// the background is set (i.e. the link is not being opened modally)
//
// i.e.
// Number of URLs Load UI Links in BG Focus Last Viewed?
// == 1 false YES
// == 1 true NO
// > 1 false/true NO
var owner = (aURIs.length > 1) || aLoadInBackground ? null : gBrowser.selectedTab;
var firstTabAdded = null;
if (aReplace)
this.loadURI(aURIs[0], null, null);
else
firstTabAdded = gBrowser.addTab(aURIs[0], null, null, null, owner, false);
var tabNum = this.mTabContainer.selectedIndex;
for (var i = 1; i < aURIs.length; ++i) {
var tab = gBrowser.addTab(aURIs[i]);
if (aReplace)
this.moveTabTo(tab, ++tabNum);
}
if (!aLoadInBackground) {
if (firstTabAdded) {
// .selectedTab setter focuses the content area
this.selectedTab = firstTabAdded;
}
else
window.content.focus();
}
]]></body>
</method>
<method name="addTab">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<parameter name="aOwner"/>
<parameter name="aAllowThirdPartyFixup"/>
<body>
<![CDATA[
this._browsers = null; // invalidate cache
if (!this.mTabbedMode)
this.enterTabbedMode();
// if we're adding tabs, we're past interrupt mode, ditch the owner
if (this.mCurrentTab.owner)
this.mCurrentTab.owner = null;
var t = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"tab");
var blank = (aURI == "about:blank");
if (blank)
t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
else
t.setAttribute("label", aURI);
t.setAttribute("crop", "end");
t.maxWidth = this.mTabContainer.mTabMaxWidth;
t.minWidth = this.mTabContainer.mTabMinWidth;
t.width = 0;
t.setAttribute("flex", "100");
t.setAttribute("validate", "never");
t.setAttribute("onerror", "this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image')); this.removeAttribute('image');");
t.className = "tabbrowser-tab";
this.mTabContainer.appendChild(t);
if (document.defaultView
.getComputedStyle(this.mTabContainer, "")
.direction == "rtl") {
/* In RTL UI, the tab is visually added to the left side of the
* tabstrip. This means the tabstip has to be scrolled back in
* order to make sure the same set of tabs is visible before and
* after the new tab is added */
this.mTabContainer.mTabstrip.scrollBoxObject
.scrollBy(this.mTabContainer.firstChild.boxObject.width, 0);
}
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
// If this new tab is owned by another, assert that relationship
if (aOwner !== undefined && aOwner !== null) {
t.owner = aOwner;
var self = this;
function attrChanged(event) {
if (event.attrName == "selectedIndex" &&
event.prevValue != event.newValue)
self.resetOwner(parseInt(event.prevValue));
}
if (!this.mTabChangedListenerAdded) {
this.mTabBox.addEventListener("DOMAttrModified", attrChanged, false);
this.mTabChangedListenerAdded = true;
}
}
var b = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"browser");
b.setAttribute("type", "content-targetable");
b.setAttribute("message", "true");
b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
if (this.hasAttribute("autocompletepopup"))
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
// Add the Message and the Browser to the box
var notificationbox = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"notificationbox");
notificationbox.setAttribute("flex", "1");
notificationbox.appendChild(b);
b.setAttribute("flex", "1");
this.mPanelContainer.appendChild(notificationbox);
b.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
if (this.mStrip.collapsed)
this.setStripVisibilityTo(true);
this.mPrefs.setBoolPref("browser.tabs.forceHide", false);
// wire up a progress listener for the new browser object.
var position = this.mTabContainer.childNodes.length-1;
var tabListener = this.mTabProgressListener(t, b, blank);
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Components.interfaces.nsIWebProgress);
filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[position] = tabListener;
this.mTabFilters[position] = filter;
b._fastFind = this.fastFind;
var uniqueId = "panel" + Date.now() + position;
this.mPanelContainer.lastChild.id = uniqueId;
t.linkedPanel = uniqueId;
t.linkedBrowser = b;
t._tPos = position;
if (t.previousSibling.selected)
t.setAttribute("afterselected", true);
if (!blank) {
// Stop the existing about:blank load. Otherwise, if aURI
// doesn't stop in-progress loads on its own, we'll get into
// trouble with multiple parallel loads running at once.
b.stop();
// pretend the user typed this so it'll be available till
// the document successfully loads
b.userTypedValue = aURI;
if (aPostData === undefined)
aPostData = null;
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
if (aAllowThirdPartyFixup) {
flags = nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
}
b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
}
// |setTimeout| here to ensure we're post reflow
var _delayedUpdate = function(aTabContainer) {
aTabContainer.adjustTabstrip();
if (aTabContainer.selectedItem != t)
aTabContainer._notifyBackgroundTab(t);
// XXXmano: this is a temporary workaround to bug 343585
// 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.
aTabContainer.mTabstrip._updateScrollButtonsDisabledState();
}
setTimeout(_delayedUpdate, 0, this.mTabContainer);
// Dispatch a new tab notification. We do this once we're
// entirely done, so that things are in a consistent state
// even if the event listener opens or closes tabs.
var evt = document.createEvent("Events");
evt.initEvent("TabOpen", true, false);
t.dispatchEvent(evt);
return t;
]]>
</body>
</method>
<method name="warnAboutClosingTabs">
<parameter name="aAll"/>
<body>
<![CDATA[
var numTabs = this.mTabContainer.childNodes.length;
var reallyClose = true;
if (numTabs <= 1)
return reallyClose;
const pref = "browser.tabs.warnOnClose";
var shouldPrompt = this.mPrefs.getBoolPref(pref);
if (shouldPrompt) {
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
//default to true: if it were false, we wouldn't get this far
var warnOnClose = { value:true };
var bundle = this.mStringBundle;
var tabsToClose = numTabs; //number of tabs to be removed
if (!aAll)
--tabsToClose;
var messageKey = (tabsToClose == 1) ? "tabs.closeWarningOneTab" : "tabs.closeWarningMultipleTabs";
var closeKey = (tabsToClose == 1) ? "tabs.closeButtonOne" : "tabs.closeButtonMultiple";
// focus the window before prompting.
// this will raise any minimized window, which will
// make it obvious which window the prompt is for and will
// solve the problem of windows "obscuring" the prompt.
// see bug #350299 for more details
window.focus();
var buttonPressed = promptService.confirmEx(window,
bundle.getString('tabs.closeWarningTitle'),
bundle.getFormattedString(messageKey, [tabsToClose]),
(promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
+ (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
bundle.getString(closeKey),
null, null,
bundle.getString('tabs.closeWarningPromptMe'),
warnOnClose);
reallyClose = (buttonPressed == 0);
// don't set the pref unless they press OK and it's false
if (reallyClose && !warnOnClose.value)
this.mPrefs.setBoolPref(pref, false);
}
return reallyClose;
]]>
</body>
</method>
<method name="removeAllTabsBut">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.warnAboutClosingTabs(false)) {
if (aTab.localName != "tab")
aTab = this.mCurrentTab;
else
this.mTabContainer.selectedItem = aTab;
var childNodes = this.mTabContainer.childNodes;
for (var i = childNodes.length - 1; i >= 0; --i) {
if (childNodes[i] != aTab)
this.removeTab(childNodes[i]);
}
}
]]>
</body>
</method>
<method name="removeCurrentTab">
<body>
<![CDATA[
return this.removeTab(this.mCurrentTab);
]]>
</body>
</method>
<method name="resetOwner">
<parameter name="oldIndex"/>
<body>
<![CDATA[
// Reset the owner property, since we're leaving the modally opened
// tab for another.
if (oldIndex < this.mTabContainer.childNodes.length) {
var tab = this.mTabContainer.childNodes[oldIndex];
tab.owner = null;
}
]]>
</body>
</method>
<method name="removeTab">
<parameter name="aTab"/>
<body>
<![CDATA[
this._browsers = null; // invalidate cache
if (aTab.localName != "tab")
aTab = this.mCurrentTab;
var l = this.mTabContainer.childNodes.length;
if (l == 1 && this.mPrefs.getBoolPref("browser.tabs.autoHide")) {
// hide the tab bar
this.mPrefs.setBoolPref("browser.tabs.forceHide", true);
this.setStripVisibilityTo(false);
return;
}
var ds = this.getBrowserForTab(aTab).docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
return;
// see notes in addTab
var _delayedUpdate = function(aTabContainer) {
aTabContainer.adjustTabstrip();
aTabContainer.mTabstrip._updateScrollButtonsDisabledState();
}
setTimeout(_delayedUpdate, 0, this.mTabContainer);
if (l == 1) {
// add a new blank tab to replace the one we're about to close
// (this ensures that the remaining tab is as good as new)
this.addTab("about:blank");
l++;
}
else if (l == 2) {
var autohide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
var tabStripHide = !window.toolbar.visible;
if (autohide || tabStripHide)
this.setStripVisibilityTo(false);
}
// 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("Events");
evt.initEvent("TabClose", true, false);
aTab.dispatchEvent(evt);
var index = -1;
if (this.mCurrentTab == aTab)
index = this.mTabContainer.selectedIndex;
else {
// Find and locate the tab in our list.
for (var i = 0; i < l; i++)
if (this.mTabContainer.childNodes[i] == aTab)
index = i;
}
// Remove the tab's filter and progress listener.
const filter = this.mTabFilters[index];
var oldBrowser = this.getBrowserAtIndex(index);
oldBrowser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(this.mTabListeners[index]);
this.mTabFilters.splice(index, 1);
this.mTabListeners.splice(index, 1);
// Remove our title change and blocking listeners
oldBrowser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
// We are no longer the primary content area.
oldBrowser.setAttribute("type", "content-targetable");
// Get the index of the tab we're removing before unselecting it
var currentIndex = this.mTabContainer.selectedIndex;
var oldTab = aTab;
// clean up the before/afterselected attributes before removing the tab
oldTab._selected = false;
// Remove this tab as the owner of any other tabs, since it's going away.
for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
var tab = this.mTabContainer.childNodes[i];
if ("owner" in tab && tab.owner == oldTab)
// |tab| is a child of the tab we're removing, make it an orphan
tab.owner = null;
}
// 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. But
// clearing focusedWindow happens below because it gets
// reset by updateCurrentBrowser.
oldBrowser.destroy();
// Remove the tab
this.mTabContainer.removeChild(oldTab);
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
this.mPanelContainer.removeChild(oldBrowser.parentNode);
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.mTabContainer.mTabstrip;
var scrollPos = {};
tabStrip.scrollBoxObject.getPosition(scrollPos, {});
var scrolledSize = {};
tabStrip.scrollBoxObject.getScrolledSize(scrolledSize, {});
if (scrollPos.value + tabStrip.boxObject.width >=
scrolledSize.value) {
tabStrip.scrollByPixels(-1 * this.mTabContainer.firstChild
.boxObject.width);
}
}
catch (ex) {
}
// Find the tab to select
var newIndex = -1;
if (currentIndex > index)
newIndex = currentIndex-1;
else if (currentIndex < index)
newIndex = currentIndex;
else {
if ("owner" in oldTab && oldTab.owner &&
this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
tab = this.mTabContainer.childNodes[i];
if (tab == oldTab.owner) {
newIndex = i;
break;
}
}
}
if (newIndex == -1)
newIndex = (index == l - 1) ? index - 1 : index;
}
// Select the new tab
this.selectedTab = this.mTabContainer.childNodes[newIndex];
for (i = oldTab._tPos; i < this.mTabContainer.childNodes.length; i++) {
this.mTabContainer.childNodes[i]._tPos = i;
}
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
this.mCurrentTab._selected = true;
this.updateCurrentBrowser();
// see comment above destroy above
oldBrowser.focusedWindow = null;
oldBrowser.focusedElement = null;
]]>
</body>
</method>
<method name="reloadAllTabs">
<body>
<![CDATA[
var l = this.mPanelContainer.childNodes.length;
for (var i = 0; i < l; i++) {
try {
this.getBrowserAtIndex(i).reload();
} catch (e) {
// ignore failure to reload so others will be reloaded
}
}
]]>
</body>
</method>
<method name="reloadTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (aTab.localName != "tab")
aTab = this.mCurrentTab;
this.getBrowserForTab(aTab).reload();
]]>
</body>
</method>
<method name="onTabBarDblClick">
<parameter name="aEvent"/>
<body>
<![CDATA[
// See hack note in the tabbrowser-close-button binding
if (!this._blockDblClick && aEvent.button == 0 &&
aEvent.originalTarget.localName == "box") {
// xxx this needs to check that we're in the empty area of the tabstrip
var e = document.createEvent("Events");
e.initEvent("NewTab", true, true);
this.dispatchEvent(e);
}
]]>
</body>
</method>
<method name="addProgressListener">
<parameter name="aListener"/>
<parameter name="aMask"/>
<body>
<![CDATA[
if (!this.mAddProgressListenerWasCalled) {
this.mAddProgressListenerWasCalled = true;
var autoHide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
var forceHide = this.mPrefs.getBoolPref("browser.tabs.forceHide");
var tabStripHide = !window.toolbar.visible;
if (!autoHide && !forceHide && !tabStripHide)
this.setStripVisibilityTo(true);
}
if (!this.mTabbedMode && this.mProgressListeners.length == 1) {
// If we are adding a 2nd progress listener, we need to enter tabbed mode
// because the browser status filter can only handle one progress listener.
// In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
this.enterTabbedMode();
}
this.mProgressListeners.push(aListener);
if (!this.mTabbedMode) {
// If someone does this:
// addProgressListener, removeProgressListener, addProgressListener
// don't create a new filter; reuse the existing filter.
if (this.mTabFilters.length == 0) {
// hook a filter up to our first browser
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Components.interfaces.nsIWebProgress);
this.mTabFilters[0] = filter;
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
}
// Directly hook the listener up to the filter for better performance
this.mTabFilters[0].addProgressListener(aListener, aMask);
}
]]>
</body>
</method>
<method name="removeProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
for (var i = 0; i < this.mProgressListeners.length; i++) {
if (this.mProgressListeners[i] == aListener) {
this.mProgressListeners.splice(i, 1);
break;
}
}
if (!this.mTabbedMode)
// Don't forget to remove it from the filter we hooked it up to
this.mTabFilters[0].removeProgressListener(aListener);
]]>
</body>
</method>
<method name="getBrowserForTab">
<parameter name="aTab"/>
<body>
<![CDATA[
return aTab.linkedBrowser;
]]>
</body>
</method>
<property name="tabContainer" readonly="true">
<getter>
return this.mTabContainer;
</getter>
</property>
<property name="selectedTab">
<getter>
return this.mTabBox.selectedTab;
</getter>
<setter>
<![CDATA[
// Update the tab
this.mTabBox.selectedTab = val;
return val;
]]>
</setter>
</property>
<property name="selectedBrowser"
onget="return this.mCurrentBrowser;"
readonly="true"/>
<property name="browsers" readonly="true">
<getter>
<![CDATA[
if (!this._browsers) {
var browsers = [];
var i;
browsers.item = function(i) {return this[i];}
for (i = 0; i < this.mTabContainer.childNodes.length; i++)
browsers.push(this.mTabContainer.childNodes[i].linkedBrowser);
this._browsers = browsers;
}
return this._browsers;
]]>
</getter>
</property>
<!-- Drag and drop observer API -->
<method name="onDragStart">
<parameter name="aEvent"/>
<parameter name="aXferData"/>
<parameter name="aDragAction"/>
<body>
<![CDATA[
if (aEvent.target.localName == "tab" &&
aEvent.originalTarget.localName != "toolbarbutton") {
aXferData.data = new TransferData();
var URI = this.getBrowserForTab(aEvent.target).currentURI;
if (URI) {
aXferData.data.addDataForFlavour("text/x-moz-url", URI.spec + "\n" + aEvent.target.label);
aXferData.data.addDataForFlavour("text/unicode", URI.spec);
aXferData.data.addDataForFlavour("text/html", '<a href="' + URI.spec + '">' + aEvent.target.label + '</a>');
} else {
aXferData.data.addDataForFlavour("text/unicode", "about:blank");
}
}
]]>
</body>
</method>
<method name="canDrop">
<parameter name="aEvent"/>
<parameter name="aDragSession"/>
<body>
<![CDATA[
if (aDragSession.sourceNode &&
aDragSession.sourceNode.parentNode == this.mTabContainer &&
(aEvent.screenX >= aDragSession.sourceNode.boxObject.screenX &&
aEvent.screenX <= (aDragSession.sourceNode.boxObject.screenX +
aDragSession.sourceNode.boxObject.width)))
return false;
return true;
]]>
</body>
</method>
<field name="mDragTime">0</field>
<field name="mDragOverDelay">350</field>
<method name="onDragOver">
<parameter name="aEvent"/>
<parameter name="aFlavour"/>
<parameter name="aDragSession"/>
<body>
<![CDATA[
var tabStrip = this.mTabContainer.mTabstrip;
// 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;
var targetAnonid = aEvent.originalTarget.getAttribute("anonid");
if (targetAnonid == "scrollbutton-up") {
pixelsToScroll = tabStrip.scrollIncrement * -1;
tabStrip.scrollByPixels(pixelsToScroll);
}
else if (targetAnonid == "scrollbutton-down" ||
(targetAnonid == "alltabs-button" &&
this.mTabContainer.getAttribute("overflow") == "true")) {
pixelsToScroll = tabStrip.scrollIncrement;
tabStrip.scrollByPixels(pixelsToScroll);
}
var isTabDrag = (aDragSession.sourceNode &&
aDragSession.sourceNode.parentNode == this.mTabContainer);
if (!isTabDrag && aEvent.target.localName == "tab") {
if (!this.mDragTime)
this.mDragTime = Date.now();
if (Date.now() >= this.mDragTime + this.mDragOverDelay)
this.mTabContainer.selectedItem = aEvent.target;
return;
}
var newIndex = this.getNewIndex(aEvent);
var ib = this.mTabDropIndicatorBar;
var ind = ib.firstChild;
ib.setAttribute('dragging',
aDragSession.canDrop ? 'true' : 'false');
var tabStripBoxObject = tabStrip.scrollBoxObject;
var halfIndWidth = Math.floor((ind.boxObject.width + 1) / 2);
if (window.getComputedStyle(this.parentNode, null)
.direction == "ltr") {
var newMarginLeft;
var minMarginLeft = tabStripBoxObject.x - halfIndWidth -
ib.boxObject.x;
// make sure we don't place the tab drop indicator past the
// edge, or the containing box will flex and stretch
// the tab drop indicator bar, which will flex the url bar.
// XXX todo
// just use first value if you can figure out how to get
// the tab drop indicator to crop instead of flex and stretch
// the tab drop indicator bar.
var maxMarginLeft = Math.min(
(minMarginLeft + tabStripBoxObject.width),
(ib.boxObject.x + ib.boxObject.width - ind.boxObject.width));
// if we are scrolling, put the drop indicator at the edge
// so that it doesn't jump while scrolling
if (pixelsToScroll > 0)
newMarginLeft = maxMarginLeft;
else if (pixelsToScroll < 0)
newMarginLeft = minMarginLeft;
else {
if (newIndex == this.mTabs.length) {
newMarginLeft = this.mTabs[newIndex-1].boxObject.screenX +
this.mTabs[newIndex-1].boxObject.width -
this.boxObject.screenX - halfIndWidth;
} else {
newMarginLeft = this.mTabs[newIndex].boxObject.screenX -
this.boxObject.screenX - halfIndWidth;
}
// ensure we never place the drop indicator beyond
// our limits
if (newMarginLeft < minMarginLeft)
newMarginLeft = minMarginLeft;
else if (newMarginLeft > maxMarginLeft)
newMarginLeft = maxMarginLeft;
}
ind.style.marginLeft = newMarginLeft + 'px';
} else {
var newMarginRight;
var minMarginRight = tabStripBoxObject.x - halfIndWidth -
ib.boxObject.x;
// make sure we don't place the tab drop indicator past the
// edge, or the containing box will flex and stretch
// the tab drop indicator bar, which will flex the url bar.
// XXX todo
// just use first value if you can figure out how to get
// the tab drop indicator to crop instead of flex and stretch
// the tab drop indicator bar.
var maxMarginRight = Math.min(
(minMarginRight + tabStripBoxObject.width),
(ib.boxObject.x + ib.boxObject.width - ind.boxObject.width));
// if we are scrolling, put the drop indicator at the edge
// so that it doesn't jump while scrolling
if (pixelsToScroll > 0)
newMarginRight = maxMarginRight;
else if (pixelsToScroll < 0)
newMarginRight = minMarginRight;
else {
if (newIndex == this.mTabs.length) {
newMarginRight = this.boxObject.width +
this.boxObject.screenX -
this.mTabs[newIndex-1].boxObject.screenX -
halfIndWidth;
} else {
newMarginRight = this.boxObject.width +
this.boxObject.screenX -
this.mTabs[newIndex].boxObject.screenX -
this.mTabs[newIndex].boxObject.width -
halfIndWidth;
}
// ensure we never place the drop indicator beyond
// our limits
if (newMarginRight < minMarginRight)
newMarginRight = minMarginRight;
else if (newMarginRight > maxMarginRight)
newMarginRight = maxMarginRight;
}
ind.style.marginRight = newMarginRight + 'px';
}
]]>
</body>
</method>
<method name="onDrop">
<parameter name="aEvent"/>
<parameter name="aXferData"/>
<parameter name="aDragSession"/>
<body>
<![CDATA[
if (aDragSession.sourceNode && aDragSession.sourceNode.parentNode == this.mTabContainer) {
var newIndex = this.getNewIndex(aEvent);
var oldIndex = aDragSession.sourceNode._tPos;
if (newIndex > oldIndex)
newIndex--;
if (newIndex != oldIndex)
this.moveTabTo(this.mTabs[oldIndex], newIndex);
} else {
var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
// 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;
nsDragAndDrop.dragDropSecurityCheck(aEvent, aDragSession, url);
var bgLoad = true;
try {
bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
}
catch (e) { }
if (aEvent.shiftKey)
bgLoad = !bgLoad;
if (document.getBindingParent(aEvent.originalTarget).localName != "tab") {
// We're adding a new tab.
this.loadOneTab(getShortcutOrURI(url), null, null, null, bgLoad, false);
}
else {
// Load in an existing tab.
var tab = aEvent.target;
try {
this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
if (this.mCurrentTab != tab && !bgLoad)
this.selectedTab = tab;
} catch(ex) {
// Just ignore invalid urls
}
}
}
]]>
</body>
</method>
<method name="onDragExit">
<parameter name="aEvent"/>
<parameter name="aDragSession"/>
<body>
<![CDATA[
this.mDragTime = 0;
if (aDragSession.sourceNode &&
aDragSession.sourceNode.parentNode == this.mTabContainer &&
aDragSession.canDrop) {
var target = aEvent.relatedTarget;
while (target && target != this.mStrip)
target = target.parentNode;
if (target)
return;
}
this.mTabDropIndicatorBar.setAttribute('dragging','false');
]]>
</body>
</method>
<method name="getSupportedFlavours">
<body>
<![CDATA[
var flavourSet = new FlavourSet();
flavourSet.appendFlavour("text/x-moz-url");
flavourSet.appendFlavour("text/unicode");
flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
return flavourSet;
]]>
</body>
</method>
<method name="moveTabTo">
<parameter name="aTab"/>
<parameter name="aIndex"/>
<body>
<![CDATA[
this._browsers = null; // invalidate cache
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
var oldPosition = aTab._tPos;
aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
this.mCurrentTab._selected = false;
// use .item() instead of [] because dragging to the end of the strip goes out of
// bounds: .item() returns null (so it acts like appendChild), but [] throws
this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes.item(aIndex));
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
var i;
for (i = 0; i < this.mTabContainer.childNodes.length; i++) {
this.mTabContainer.childNodes[i]._tPos = i;
this.mTabContainer.childNodes[i]._selected = false;
}
this.mCurrentTab._selected = true;
this.mTabContainer.mTabstrip.scrollBoxObject.ensureElementIsVisible(this.mCurrentTab);
var evt = document.createEvent("UIEvents");
evt.initUIEvent("TabMove", true, false, window, oldPosition);
aTab.dispatchEvent(evt);
return aTab;
]]>
</body>
</method>
<method name="getNewIndex">
<parameter name="aEvent"/>
<body>
<![CDATA[
var i;
if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
if (aEvent.screenX < this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
return i;
} else {
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
if (aEvent.screenX > this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
return i;
}
return this.mTabs.length;
]]>
</body>
</method>
<method name="moveTabForward">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab, tabPos + 1);
this.mCurrentTab.focus();
}
else if (this.arrowKeysShouldWrap)
this.moveTabToStart();
]]>
</body>
</method>
<method name="moveTabBackward">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, tabPos - 1);
this.mCurrentTab.focus();
}
else if (this.arrowKeysShouldWrap)
this.moveTabToEnd();
]]>
</body>
</method>
<method name="moveTabToStart">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, 0);
this.mCurrentTab.focus();
}
]]>
</body>
</method>
<method name="moveTabToEnd">
<body>
<![CDATA[
var tabPos = this.mCurrentTab._tPos;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab,
this.browsers.length - 1);
this.mCurrentTab.focus();
}
]]>
</body>
</method>
<method name="moveTabOver">
<parameter name="aEvent"/>
<body>
<![CDATA[
var direction = window.getComputedStyle(this.parentNode, null).direction;
if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
(direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
this.moveTabForward();
else
this.moveTabBackward();
]]>
</body>
</method>
<!-- 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"/>
<body>
<![CDATA[
return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset);
]]>
</body>
</method>
<method name="goHome">
<body>
<![CDATA[
return this.mCurrentBrowser.goHome();
]]>
</body>
</method>
<property name="homePage">
<getter>
<![CDATA[
return this.mCurrentBrowser.homePage;
]]>
</getter>
<setter>
<![CDATA[
this.mCurrentBrowser.homePage = val;
return val;
]]>
</setter>
</property>
<method name="gotoIndex">
<parameter name="aIndex"/>
<body>
<![CDATA[
return this.mCurrentBrowser.gotoIndex(aIndex);
]]>
</body>
</method>
<method name="attachFormFill">
<body><![CDATA[
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
var cb = this.getBrowserAtIndex(i);
cb.attachFormFill();
}
]]></body>
</method>
<method name="detachFormFill">
<body><![CDATA[
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
var cb = this.getBrowserAtIndex(i);
cb.detachFormFill();
}
]]></body>
</method>
<property name="pageReport"
onget="return this.mCurrentBrowser.pageReport;"
readonly="true"/>
<property name="currentURI"
onget="return this.mCurrentBrowser.currentURI;"
readonly="true"/>
<field name="_fastFind">null</field>
<property name="fastFind"
readonly="true">
<getter>
<![CDATA[
if (!this._fastFind) {
this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
.createInstance(Components.interfaces.nsITypeAheadFind);
this._fastFind.init(this.docShell);
}
return this._fastFind;
]]>
</getter>
</property>
<property name="docShell"
onget="return this.mCurrentBrowser.docShell"
readonly="true"/>
<property name="webNavigation"
onget="return this.mCurrentBrowser.webNavigation"
readonly="true"/>
<property name="webBrowserFind"
readonly="true"
onget="return this.mCurrentBrowser.webBrowserFind"/>
<property name="webProgress"
readonly="true"
onget="return this.mCurrentBrowser.webProgress"/>
<property name="contentWindow"
readonly="true"
onget="return this.mCurrentBrowser.contentWindow"/>
<property name="sessionHistory"
onget="return this.mCurrentBrowser.sessionHistory;"
readonly="true"/>
<property name="markupDocumentViewer"
onget="return this.mCurrentBrowser.markupDocumentViewer;"
readonly="true"/>
<property name="contentViewerEdit"
onget="return this.mCurrentBrowser.contentViewerEdit;"
readonly="true"/>
<property name="contentViewerFile"
onget="return this.mCurrentBrowser.contentViewerFile;"
readonly="true"/>
<property name="documentCharsetInfo"
onget="return this.mCurrentBrowser.documentCharsetInfo;"
readonly="true"/>
<property name="contentDocument"
onget="return this.mCurrentBrowser.contentDocument;"
readonly="true"/>
<property name="contentTitle"
onget="return this.mCurrentBrowser.contentTitle;"
readonly="true"/>
<property name="contentPrincipal"
onget="return this.mCurrentBrowser.contentPrincipal;"
readonly="true"/>
<property name="securityUI"
onget="return this.mCurrentBrowser.securityUI;"
readonly="true"/>
<method name="dragDropSecurityCheck">
<parameter name="aEvent"/>
<parameter name="aDragSession"/>
<parameter name="aUri"/>
<body>
<![CDATA[
nsDragAndDrop.dragDropSecurityCheck(aEvent, aDragSession, aUri);
]]>
</body>
</method>
<field name="_keyEventHandler" readonly="true">
<![CDATA[({
tabbrowser: this,
handleEvent: function handleEvent(aEvent) {
if (!aEvent.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
if (('shiftKey' in aEvent && aEvent.shiftKey) ||
('altKey' in aEvent && aEvent.altKey))
return;
#ifdef XP_MACOSX
if ('metaKey' in aEvent && aEvent.metaKey) {
#else
if (('ctrlKey' in aEvent && aEvent.ctrlKey) &&
!('metaKey' in aEvent && aEvent.metaKey)) {
if (aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
this.tabbrowser.mTabBox.handleCtrlPageUpDown) {
this.tabbrowser.removeCurrentTab();
aEvent.stopPropagation();
aEvent.preventDefault();
return;
}
#endif
if (aEvent.target.localName == "tabbrowser") {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_UP:
this.tabbrowser.moveTabBackward();
break;
case KeyEvent.DOM_VK_DOWN:
this.tabbrowser.moveTabForward();
break;
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_LEFT:
this.tabbrowser.moveTabOver(aEvent);
break;
case KeyEvent.DOM_VK_HOME:
this.tabbrowser.moveTabToStart();
break;
case KeyEvent.DOM_VK_END:
this.tabbrowser.moveTabToEnd();
break;
default:
// Stop the keypress event for the above keyboard
// shortcuts only.
return;
}
aEvent.stopPropagation();
aEvent.preventDefault();
}
}
}
})]]>
</field>
<property name="userTypedClear"
onget="return this.mCurrentBrowser.userTypedClear;"
onset="return this.mCurrentBrowser.userTypedClear = val;"/>
<property name="userTypedValue"
onget="return this.mCurrentBrowser.userTypedValue;"
onset="return this.mCurrentBrowser.userTypedValue = val;"/>
<method name="createTooltip">
<parameter name="event"/>
<body>
<![CDATA[
event.stopPropagation();
var tn = document.tooltipNode;
if (tn.localName != "tab")
return false; // Not a tab, so cancel the tooltip
if ("mOverCloseButton" in tn && tn.mOverCloseButton) {
event.target.setAttribute("label", tn.getAttribute("closetabtext"));
return true;
}
if (tn.hasAttribute("label")) {
event.target.setAttribute("label", tn.getAttribute("label"));
return true;
}
return false;
]]>
</body>
</method>
<constructor>
<![CDATA[
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild;
this.mCurrentTab = this.mTabContainer.firstChild;
document.addEventListener("keypress", this._keyEventHandler, false);
var uniqueId = "panel" + Date.now();
this.mPanelContainer.childNodes[0].id = uniqueId;
this.mTabContainer.childNodes[0].linkedPanel = uniqueId;
this.mTabContainer.childNodes[0]._tPos = 0;
this.mTabContainer.childNodes[0].linkedBrowser = this.mPanelContainer.childNodes[0].firstChild;
// set up the shared autoscroll popup
this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
this.appendChild(this._autoScrollPopup);
this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
]]>
</constructor>
<destructor>
<![CDATA[
for (var i = 0; i < this.mTabListeners.length; ++i) {
this.getBrowserAtIndex(i).webProgress.removeProgressListener(this.mTabFilters[i]);
this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
this.mTabFilters[i] = null;
this.mTabListeners[i] = null;
this.getBrowserAtIndex(i).removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
}
document.removeEventListener("keypress", this._keyEventHandler, false);
]]>
</destructor>
</implementation>
<handlers>
<handler event="DOMLinkAdded" phase="capturing" action="this.onLinkAdded(event);"/>
<handler event="DOMWindowClose" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
const browsers = this.mPanelContainer.childNodes;
if (browsers.length == 1) {
// There's only one browser left. If a window is being
// closed and the window is *not* the window in the
// browser that's still around, prevent the event's default
// action to prevent closing a window that's being closed
// already.
if (this.getBrowserAtIndex(0).contentWindow != event.target)
event.preventDefault();
return;
}
var i = 0;
for (; i < browsers.length; ++i) {
if (this.getBrowserAtIndex(i).contentWindow == event.target) {
this.removeTab(this.mTabContainer.childNodes[i]);
event.preventDefault();
break;
}
}
]]>
</handler>
<handler event="DOMWillOpenModalDialog" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
// We're about to open a modal dialog, make sure the opening
// tab is brought to the front.
var targetTop = event.target.top;
for (var i = 0; i < browsers.length; ++i) {
if (this.getBrowserAtIndex(i).contentWindow == targetTop) {
this.selectedTab = this.mTabContainer.childNodes[i];
break;
}
}
]]>
</handler>
</handlers>
</binding>
<binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
<content>
<xul:toolbarbutton class="scrollbutton-up" collapsed="true"
xbl:inherits="orient"
anonid="scrollbutton-up"
onmousedown="_startScroll(-1);"
onmouseup="_stopScroll();"
onmouseout="_stopScroll();"
chromedir="&locale.dir;"/>
<xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
<children/>
</xul:scrollbox>
<xul:stack align="center" pack="end" class="scrollbutton-down-stack">
<xul:hbox flex="1" class="scrollbutton-down-box"
collapsed="true" anonid="down-box"/>
<xul:hbox flex="1" class="scrollbutton-down-box-animate"
collapsed="true" anonid="down-box-animate"/>
<xul:toolbarbutton class="scrollbutton-down" collapsed="true"
xbl:inherits="orient"
anonid="scrollbutton-down"
onmousedown="_startScroll(1);"
onmouseup="_stopScroll();"
onmouseout="_stopScroll();"
chromedir="&locale.dir;"/>
</xul:stack>
</content>
<implementation>
<field name="_scrollButtonDownBox">
document.getAnonymousElementByAttribute(this, "anonid", "down-box");
</field>
<field name="_scrollButtonDownBoxAnimate">
document.getAnonymousElementByAttribute(this, "anonid", "down-box-animate");
</field>
</implementation>
<handlers>
<handler event="underflow"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// Ignore vertical events.
if (event.detail == 0) {
return;
}
this._scrollButtonDownBox.collapsed = true;
this._scrollButtonDownBoxAnimate.collapsed = true;
]]></handler>
<handler event="overflow"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// Ignore vertical events.
if (event.detail == 0) {
return;
}
this._scrollButtonDownBox.collapsed = false;
this._scrollButtonDownBoxAnimate.collapsed = false;
]]></handler>
<handler event="UpdatedScrollButtonsDisabledState"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// fix for bug #352353
// unlike the scrollup button on the tab strip (which is a
// simple toolbarbutton) the scrolldown button is
// a more complicated stack of boxes and a toolbarbutton
// so that we can animate when a tab is opened offscreen.
// in order to style the box with the actual background image
// we need to manually set the disable state to match the
// disable state of the toolbarbutton.
this._scrollButtonDownBox
.setAttribute("disabled", this._scrollButtonDown.disabled);
]]></handler>
</handlers>
</binding>
<binding id="tabbrowser-tabs"
extends="chrome://global/content/bindings/tabbox.xml#tabs">
<content>
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1" style="min-width: 1px;" class="tabbrowser-arrowscrollbox">
<children includes="tab"/>
</xul:arrowscrollbox>
<xul:stack align="center" pack="end" class="tabs-alltabs-stack">
<xul:hbox flex="1" class="tabs-alltabs-box" anonid="alltabs-box"/>
<xul:hbox flex="1" class="tabs-alltabs-box-animate"
anonid="alltabs-box-animate"/>
<xul:toolbarbutton class="tabs-alltabs-button" type="menu"
anonid="alltabs-button"
tooltipstring="&listAllTabs.label;">
<xul:menupopup class="tabs-alltabs-popup"
anonid="alltabs-popup"
position="after_end"/>
</xul:toolbarbutton>
</xul:stack>
<xul:hbox class="tabs-closebutton-box" align="center" pack="end" anonid="tabstrip-closebutton">
<xul:toolbarbutton class="close-button tabs-closebutton"/>
</xul:hbox>
</content>
<implementation implements="nsITimerCallback, nsIDOMEventListener">
<constructor>
<![CDATA[
var pb2 =
Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefBranch2);
try {
this.mTabMinWidth = pb2.getIntPref("browser.tabs.tabMinWidth");
} catch (e) {
}
try {
this.mTabMaxWidth = pb2.getIntPref("browser.tabs.tabMaxWidth");
} catch (e) {
}
try {
this.mTabClipWidth = pb2.getIntPref("browser.tabs.tabClipWidth");
} catch (e) {
}
try {
this.mCloseButtons = pb2.getIntPref("browser.tabs.closeButtons");
} catch (e) {
}
this.firstChild.minWidth = this.mTabMinWidth;
this.firstChild.maxWidth = this.mTabMaxWidth;
this.adjustTabstrip();
pb2.addObserver("browser.tabs.closeButtons",
this._prefObserver, false);
var self = this;
function onResize() {
var width = self.mTabstrip.boxObject.width;
if (width != self.mTabstripWidth) {
self.adjustTabstrip();
self.mTabstrip.scrollByIndex(1);
self.mTabstrip.scrollBoxObject
.ensureElementIsVisible(self.selectedItem);
self.mTabstripWidth = width;
}
}
window.addEventListener("resize", onResize, false);
// Listen to overflow/underflow events on the tabstrip,
// we cannot put these as xbl handlers on the entire binding because
// they would also get called for the all-tabs popup scrollbox.
// Also, we can't rely on event.target becuase these are all
// anonymous nodes.
this.mTabstrip.addEventListener("overflow", this, false);
this.mTabstrip.addEventListener("underflow", this, false);
]]>
</constructor>
<destructor>
<![CDATA[
var pb2 =
Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefBranch2);
pb2.removeObserver("browser.tabs.closeButtons", this._prefObserver);
// Release timer to avoid reference cycles.
if (this._animateTimer) {
this._animateTimer.cancel();
this._animateTimer = null;
}
this.mTabstrip.removeEventListener("overflow", this, false);
this.mTabstrip.removeEventListener("underflow", this, false);
]]>
</destructor>
<field name="mTabstripWidth">0</field>
<field name="mTabstrip">
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
</field>
<field name="mTabstripClosebutton">
document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
</field>
<field name="_prefObserver">({
tabbox: this,
observe: function(subject, topic, data)
{
if (topic == "nsPref:changed") {
switch (data) {
case "browser.tabs.closeButtons":
subject.QueryInterface(Components.interfaces.nsIPrefBranch);
this.tabbox.mCloseButtons = subject.getIntPref("browser.tabs.closeButtons");
this.tabbox.adjustTabstrip();
break;
}
}
},
QueryInterface: function(aIID)
{
if (aIID.equals(Components.interfaces.nsIObserver) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
}
});
</field>
<field name="mTabMinWidth">100</field>
<field name="mTabMaxWidth">250</field>
<field name="mTabClipWidth">140</field>
<field name="mCloseButtons">1</field>
<method name="adjustTabstrip">
<body><![CDATA[
// modes for tabstrip
// 0 - activetab = close button on active tab only
// 1 - alltabs = close buttons on all tabs
// 2 - noclose = no close buttons at all
// 3 - closeatend = close button at the end of the tabstrip
switch (this.mCloseButtons) {
case 0:
this.setAttribute("closebuttons", "activetab");
break;
case 1:
var width = this.firstChild.boxObject.width;
// 0 width is an invalid value and indicates
// an item without display, so ignore.
if (width > this.mTabClipWidth || width == 0)
this.setAttribute("closebuttons", "alltabs");
else
this.setAttribute("closebuttons", "activetab");
break;
case 2:
case 3:
this.setAttribute("closebuttons", "noclose");
break;
}
this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
]]></body>
</method>
<field name="_mPrefs">null</field>
<property name="mPrefs" readonly="true">
<getter>
<![CDATA[
if (!this._mPrefs) {
this._mPrefs =
Components.classes['@mozilla.org/preferences-service;1'].
getService(Components.interfaces.nsIPrefBranch2);
}
return this._mPrefs;
]]>
</getter>
</property>
<method name="_handleTabSelect">
<body><![CDATA[
this.mTabstrip.scrollBoxObject
.ensureElementIsVisible(this.selectedItem);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "overflow":
this.setAttribute("overflow", "true");
this.mTabstrip.scrollBoxObject
.ensureElementIsVisible(this.selectedItem);
break;
case "underflow":
this.removeAttribute("overflow");
break;
}
]]></body>
</method>
<field name="mAllTabsPopup">
document.getAnonymousElementByAttribute(this,
"anonid", "alltabs-popup");
</field>
<field name="mAllTabsBoxAnimate">
document.getAnonymousElementByAttribute(this,
"anonid",
"alltabs-box-animate");
</field>
<field name="mDownBoxAnimate">
this.mTabstrip._scrollButtonDownBoxAnimate;
</field>
<field name="mAllTabsButton">
document.getAnonymousElementByAttribute(this,
"anonid", "alltabs-button");
</field>
<field name="_animateTimer">null</field>
<field name="_animateStep">-1</field>
<field name="_animateDelay">25</field>
<field name="_animatePercents">
[1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
</field>
<method name="_stopAnimation">
<body><![CDATA[
if (this._animateStep != -1) {
if (this._animateTimer)
this._animateTimer.cancel();
this._animateStep = -1;
this.mAllTabsBoxAnimate.style.opacity = 0.0;
this.mDownBoxAnimate.style.opacity = 0.0;
}
]]></body>
</method>
<method name="_notifyBackgroundTab">
<parameter name="aTab"/>
<body><![CDATA[
var tsbo = this.mTabstrip.scrollBoxObject;
var tsboStart = tsbo.screenX;
var tsboEnd = tsboStart + tsbo.width;
var ctbo = aTab.boxObject;
var ctboStart = ctbo.screenX;
var ctboEnd = ctboStart + ctbo.width;
// only start the flash timer if the new tab (which was loaded in
// the background) is not completely visible
if (tsboStart > ctboStart || ctboEnd > tsboEnd) {
this._animateStep = 0;
if (!this._animateTimer)
this._animateTimer =
Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
else
this._animateTimer.cancel();
this._animateTimer.initWithCallback(this,
this._animateDelay,
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
}
]]></body>
</method>
<method name="notify">
<parameter name="aTimer"/>
<body><![CDATA[
if (!document)
aTimer.cancel();
var percent = this._animatePercents[this._animateStep];
this.mAllTabsBoxAnimate.style.opacity = percent;
this.mDownBoxAnimate.style.opacity = percent;
if (this._animateStep < (this._animatePercents.length - 1))
this._animateStep++;
else
this._stopAnimation();
]]></body>
</method>
</implementation>
<handlers>
<handler event="TabSelect" action="this._handleTabSelect();"/>
<handler event="mouseover"><![CDATA[
if (event.originalTarget == this.mAllTabsButton) {
this.mAllTabsButton
.setAttribute("tooltiptext",
this.mAllTabsButton.getAttribute("tooltipstring"));
}
else
this.mAllTabsButton.removeAttribute("tooltiptext");
]]></handler>
</handlers>
</binding>
<!-- alltabs-popup binding
This binding relies on the structure of the tabbrowser binding.
Therefore it should only be used as a child of the tabs element.
This binding is exposed as a pseudo-public-API so themes can customize
the tabbar appearance without having to be scriptable
(see globalBindings.xml in Pinstripe for example).
-->
<binding id="tabbrowser-alltabs-popup"
extends="chrome://global/content/bindings/popup.xml#popup">
<implementation implements="nsIDOMEventListener">
<field name="_xulWindow">
null
</field>
<constructor><![CDATA[
// We cannot cache the XULBrowserWindow object itself since it might
// be set after this binding is constructed.
try {
this._xulWindow =
window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
.treeOwner
.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIXULWindow);
}
catch(ex) { }
]]></constructor>
<method name="_menuItemOnCommand">
<parameter name="aEvent"/>
<body><![CDATA[
var tabcontainer = document.getBindingParent(this);
tabcontainer.selectedItem = aEvent.target.tab;
]]></body>
</method>
<method name="_tabOnAttrModified">
<parameter name="aEvent"/>
<body><![CDATA[
var menuItem = aEvent.target.mCorrespondingMenuitem;
if (menuItem) {
var attrName = aEvent.attrName;
switch (attrName) {
case "label":
case "crop":
case "busy":
case "image":
case "selected":
if (aEvent.attrChange == aEvent.REMOVAL)
menuItem.removeAttribute(attrName);
else
menuItem.setAttribute(attrName, aEvent.newValue);
}
}
]]></body>
</method>
<method name="_tabOnTabClose">
<parameter name="aEvent"/>
<body><![CDATA[
var menuItem = aEvent.target.mCorrespondingMenuitem;
if (menuItem)
this.removeChild(menuItem);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (!aEvent.isTrusted)
return;
switch (aEvent.type) {
case "command":
this._menuItemOnCommand(aEvent);
break;
case "DOMAttrModified":
this._tabOnAttrModified(aEvent);
break;
case "TabClose":
this._tabOnTabClose(aEvent);
break;
case "TabOpen":
this._createTabMenuItem(aEvent.originalTarget);
break;
case "scroll":
this._updateTabsVisibilityStatus();
break;
}
]]></body>
</method>
<method name="_updateTabsVisibilityStatus">
<body><![CDATA[
var tabContainer = document.getBindingParent(this);
// We don't want menu item decoration unless there is overflow.
if (tabContainer.getAttribute("overflow") != "true")
return;
var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
for (var i = 0; i < this.childNodes.length; i++) {
var curTabBO = this.childNodes[i].tab.boxObject;
if (curTabBO.screenX >= tabstripBO.screenX &&
curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
this.childNodes[i].setAttribute("tabIsVisible", "true");
else
this.childNodes[i].removeAttribute("tabIsVisible");
}
]]></body>
</method>
<method name="_createTabMenuItem">
<parameter name="aTab"/>
<body><![CDATA[
var menuItem = document.createElementNS(
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
"menuitem");
menuItem.setAttribute("class", "menuitem-iconic alltabs-item");
menuItem.setAttribute("label", aTab.label);
menuItem.setAttribute("crop", aTab.getAttribute("crop"));
menuItem.setAttribute("image", aTab.getAttribute("image"));
if (aTab.hasAttribute("busy"))
menuItem.setAttribute("busy", aTab.getAttribute("busy"));
if (aTab.selected)
menuItem.setAttribute("selected", "true");
// Keep some attributes of the menuitem in sync with its
// corresponding tab (e.g. the tab label)
aTab.mCorrespondingMenuitem = menuItem;
aTab.addEventListener("DOMAttrModified", this, false);
aTab.addEventListener("TabClose", this, false);
menuItem.tab = aTab;
menuItem.addEventListener("command", this, false);
this.appendChild(menuItem);
return menuItem;
]]></body>
</method>
</implementation>
<handlers>
<handler event="popupshowing">
<![CDATA[
// set up the menu popup
var tabcontainer = document.getBindingParent(this);
var tabs = tabcontainer.childNodes;
// Listen for changes in the tab bar.
var tabbrowser = document.getBindingParent(tabcontainer);
tabbrowser.addEventListener("TabOpen", this, false);
tabcontainer.mTabstrip.addEventListener("scroll", this, false);
// if an animation is in progress and the user
// clicks on the "all tabs" button, stop the animation
tabcontainer._stopAnimation();
for (var i = 0; i < tabs.length; i++) {
this._createTabMenuItem(tabs[i]);
}
this._updateTabsVisibilityStatus();
]]></handler>
<handler event="popuphiding">
<![CDATA[
// clear out the menu popup and remove the listeners
while (this.hasChildNodes()) {
var menuItem = this.lastChild;
menuItem.removeEventListener("command", this, false);
menuItem.tab.removeEventListener("DOMAttrModified", this, false);
menuItem.tab.removeEventListener("TabClose", this, false);
menuItem.tab.mCorrespondingMenuitem = null;
this.removeChild(menuItem);
}
var tabcontainer = document.getBindingParent(this);
tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this, false);
]]></handler>
<handler event="DOMMenuItemActive">
<![CDATA[
if (!this._xulWindow || !this._xulWindow.XULBrowserWindow)
return;
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 = " ";
}
this._xulWindow.XULBrowserWindow.setOverLink(statusText, null);
}
]]></handler>
<handler event="DOMMenuItemInactive">
<![CDATA[
if (!this._xulWindow || !this._xulWindow.XULBrowserWindow)
return;
this._xulWindow.XULBrowserWindow.setOverLink("", null);
]]></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>).
This binding is exposed as a pseudo-public-API so themes can customize
the tabbar appearance without having to be scriptable
(see globalBindings.xml in Pinstripe for example).
-->
<binding id="tabbrowser-close-tab-button"
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
<handlers>
<handler event="click" button="0"><![CDATA[
var bindingParent = document.getBindingParent(this);
if (bindingParent) {
var tabbedBrowser = document.getBindingParent(bindingParent);
if (bindingParent.localName == "tab") {
/* The only sequence in which a second click event (i.e. dblclik)
* can be dispatched on an in-tab close button is when it is shown
* after the first click (i.e. the first click event was dispatched
* on the tab). This happens when we show the close button only on
* the active tab. (bug 352021)
* The only sequence in which a third click event can be dispatched
* on an in-tab close button is when the tab was opened with a
* double click on the tabbar. (bug 378344)
* In both cases, it is most likely that the close button area has
* been accidentally clicked, therefore we do not close the tab.
*/
if (event.detail > 1)
return;
tabbedBrowser.removeTab(bindingParent);
tabbedBrowser._blockDblClick = true;
/* XXXmano hack (see bug 343628):
* Since we're removing the event target, if the user
* double-clicks this button, the dblclick event will be dispatched
* with the tabbar as its event target (and explicit/originalTarget),
* which treats that as a mouse gesture for opening a new tab.
* In this context, we're manually blocking the dblclick event
* (see onTabBarDblClick).
*/
var clickedOnce = false;
function enableDblClick(event) {
if (event.detail == 1 && !clickedOnce) {
clickedOnce = true;
return;
}
setTimeout(function() {
tabbedBrowser._blockDblClick = false;
}, 0);
tabbedBrowser.removeEventListener("click", enableDblClick, false);
}
tabbedBrowser.addEventListener("click", enableDblClick, false);
}
else // "tabs"
tabbedBrowser.removeCurrentTab();
}
]]></handler>
<handler event="dblclick" button="0" phase="capturing">
// for the one-close-button case
event.stopPropagation();
</handler>
</handlers>
</binding>
<binding id="tabbrowser-tab" display="xul:box"
extends="chrome://global/content/bindings/tabbox.xml#tab">
<content chromedir="&locale.dir;"
closetabtext="&closeTab.label;">
<xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
<xul:image class="tab-icon" xbl:inherits="validate,src=image"/>
<xul:label class="tab-text" xbl:inherits="value=label,accesskey,crop,disabled" flex="1"/>
</xul:hbox>
<xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button"/>
</content>
<implementation>
<field name="mOverCloseButton">false</field>
<field name="mCorrespondingMenuitem">null</field>
</implementation>
<handlers>
<handler event="mouseover">
var anonid = event.originalTarget.getAttribute("anonid");
if (anonid == "close-button")
this.mOverCloseButton = true;
</handler>
<handler event="mouseout">
var anonid = event.originalTarget.getAttribute("anonid");
if (anonid == "close-button")
this.mOverCloseButton = false;
</handler>
<handler event="mousedown" button="0" phase="capturing">
<![CDATA[
if (this.mOverCloseButton)
event.stopPropagation();
]]>
</handler>
</handlers>
</binding>
</bindings>