gecko/browser/base/content/tabbrowser.xml

4038 lines
146 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>
- Frank Yan <fyan@mozilla.com>
- Patrick Walton <pcwalton@mozilla.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 && !tab.closing;
});
]]></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="_placesAutocomplete" readonly="true">
Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
.getService(Components.interfaces.mozIPlacesAutoComplete);
</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">
[]
</field>
<field name="mTabFilters">
[]
</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="updateWindowResizers">
<body><![CDATA[
if (!window.gShowPageResizers)
return;
var show = document.getElementById("addon-bar").collapsed &&
window.windowState == window.STATE_NORMAL;
for (let i = 0; i < this.browsers.length; i++) {
this.browsers[i].showWindowResizer = show;
}
]]></body>
</method>
<method name="_setCloseKeyState">
<parameter name="aEnabled"/>
<body><![CDATA[
let keyClose = document.getElementById("key_close");
let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
if (closeKeyEnabled == aEnabled)
return;
if (aEnabled)
keyClose.removeAttribute("disabled");
else
keyClose.setAttribute("disabled", "true");
// We also want to remove the keyboard shortcut from the file menu
// when the shortcut is disabled, and bring it back when it's
// renabled.
//
// Fixing bug 630826 could make that happen automatically.
// Fixing bug 630830 could avoid the ugly hack below.
let closeMenuItem = document.getElementById("menu_close");
let parentPopup = closeMenuItem.parentNode;
let nextItem = closeMenuItem.nextSibling;
let clonedItem = closeMenuItem.cloneNode(true);
parentPopup.removeChild(closeMenuItem);
if (aEnabled)
clonedItem.setAttribute("key", "key_close");
else
clonedItem.removeAttribute("key");
parentPopup.insertBefore(clonedItem, nextItem);
]]></body>
</method>
<method name="pinTab">
<parameter name="aTab"/>
<body><![CDATA[
if (aTab.pinned)
return;
if (aTab.hidden)
this.showTab(aTab);
this.moveTabTo(aTab, this._numPinnedTabs);
aTab.setAttribute("pinned", "true");
this.tabContainer._unlockTabSizing();
this.tabContainer._positionPinnedTabs();
this.tabContainer.adjustTabstrip();
this.getBrowserForTab(aTab).docShell.isAppTab = true;
if (aTab.selected)
this._setCloseKeyState(false);
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._unlockTabSizing();
this.tabContainer._positionPinnedTabs();
this.tabContainer.adjustTabstrip();
this.getBrowserForTab(aTab).docShell.isAppTab = false;
if (aTab.selected)
this._setCloseKeyState(true);
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="getTabModalPromptBox">
<parameter name="aBrowser"/>
<body>
<![CDATA[
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let browser = (aBrowser || this.mCurrentBrowser);
let stack = browser.parentNode;
let self = this;
let promptBox = {
appendPrompt : function(args, onCloseCallback) {
let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
stack.appendChild(newPrompt);
browser.setAttribute("tabmodalPromptShowing", true);
newPrompt.clientTop; // style flush to assure binding is attached
let tab = self._getTabForContentWindow(browser.contentWindow);
newPrompt.init(args, tab, onCloseCallback);
return newPrompt;
},
removePrompt : function(aPrompt) {
stack.removeChild(aPrompt);
let prompts = this.listPrompts();
if (prompts.length) {
let prompt = prompts[prompts.length - 1];
prompt.Dialog.setDefaultFocus();
} else {
browser.removeAttribute("tabmodalPromptShowing");
browser.focus();
}
},
listPrompts : function(aPrompt) {
let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
// NodeList --> real JS array
let prompts = Array.slice(els);
return prompts;
},
};
return promptBox;
]]>
</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 () {
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)
this.mTab.setAttribute("progress", "true");
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.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;
if (this.mTab.hasAttribute("busy")) {
this.mTab.removeAttribute("busy");
this.mTabBrowser._tabAttrModified(this.mTab);
if (!this.mTab.selected)
this.mTab.setAttribute("unread", "true");
}
this.mTab.removeAttribute("progress");
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.connecting"))
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 autocomplete = this.mTabBrowser._placesAutocomplete;
if (this.mBrowser.registeredOpenURI) {
autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
delete this.mBrowser.registeredOpenURI;
}
if (aLocation.spec != "about:blank") {
autocomplete.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;
}
});
]]>
</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 docElement = this.ownerDocument.documentElement;
var sep = docElement.getAttribute("titlemenuseparator");
var 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) {}
return newTitle;
]]>
</body>
</method>
<method name="updateTitlebar">
<body>
<![CDATA[
if ("TabView" in window && 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.docShellIsActive = false;
}
var updatePageReport = false;
if (!oldBrowser ||
(oldBrowser.pageReport && !newBrowser.pageReport) ||
(!oldBrowser.pageReport && newBrowser.pageReport))
updatePageReport = true;
newBrowser.setAttribute("type", "content-primary");
newBrowser.docShellIsActive =
(window.windowState != window.STATE_MINIMIZED);
this.mCurrentBrowser = newBrowser;
this.mCurrentTab = this.selectedTab;
this.showTab(this.mCurrentTab);
var backForwardContainer = document.getElementById("unified-back-forward-button");
if (backForwardContainer)
backForwardContainer.setAttribute("switchingtabs", "true");
if (updatePageReport)
this.mCurrentBrowser.updatePageReport();
// Update the URL bar.
var loc = this.mCurrentBrowser.currentURI;
#ifdef MOZ_E10S_COMPAT
// Bug 666801 - WebProgress support for e10s and
// Bug 666809 - SecurityUI support for e10s
#else
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);
}
#endif
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);
}
if (!this._previewMode) {
this.mCurrentTab.removeAttribute("unread");
#ifdef MOZ_E10S_COMPAT
// Bug 666816 - TypeAheadFind support for e10s
#else
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
#endif
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);
}
if (backForwardContainer)
backForwardContainer.removeAttribute("switchingtabs");
if (this.mCurrentTab.selected)
this._setCloseKeyState(!this.mCurrentTab.pinned);
// 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.
let event = document.createEvent("Events");
event.initEvent("TabSelect", true, false);
this.mCurrentTab.dispatchEvent(event);
this._tabAttrModified(oldTab);
this._tabAttrModified(this.mCurrentTab);
// Adjust focus
oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
do {
// When focus is in the tab bar, retain it there.
if (document.activeElement == oldTab) {
// We need to explicitly focus the new tab, because
// tabbox.xml does this only in some cases.
this.mCurrentTab.focus();
break;
}
// If there's a tabmodal prompt showing, focus it.
if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
let prompt = prompts[prompts.length - 1];
prompt.Dialog.setDefaultFocus();
break;
}
// Focus the location bar if it was previously focused for that tab.
// In full screen mode, only bother making the location bar visible
// if the tab is a blank one.
if (newBrowser._urlbarFocused && gURLBar) {
// Explicitly close the popup if the URL bar retains focus
gURLBar.closePopup();
if (!window.fullScreen) {
gURLBar.focus();
break;
} else if (isTabEmpty(this.mCurrentTab)) {
focusAndSelectUrlBar();
break;
}
}
// If the find bar is focused, keep it focused.
if (gFindBarInitialized &&
!gFindBar.hidden &&
gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
break;
// Otherwise, focus the content area.
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
let 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
let 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);
} while (false);
}
]]>
</body>
</method>
<method name="_tabAttrModified">
<parameter name="aTab"/>
<body><![CDATA[
if (aTab.closing)
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.connecting");
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="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
// 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";
this.tabContainer._unlockTabSizing();
// 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);
// 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 (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL &&
Services.prefs.getBoolPref("browser.tabs.remote")) {
b.setAttribute("remote", "true");
}
if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed &&
window.windowState == window.STATE_NORMAL) {
b.setAttribute("showresizer", "true");
}
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);
#ifdef MOZ_E10S_COMPAT
// Bug 666801 - WebProgress support for e10s
#else
b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
#endif
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);
} catch (ex) {
Cu.reportError(ex);
}
}
// We start our browsers out as inactive, and then maintain
// activeness in the tab switcher.
b.docShellIsActive = false;
// 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;
var byMouse = aParams.byMouse;
}
// Handle requests for synchronously removing an already
// asynchronously closing tab.
if (!animate &&
aTab.closing) {
this._endRemoveTab(aTab);
return;
}
var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
if (!this._beginRemoveTab(aTab, false, null, true))
return;
if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
this.tabContainer._lockTabSizing(aTab);
else
this.tabContainer._unlockTabSizing();
if (!animate /* the caller didn't opt in */ ||
isLastTab ||
aTab.pinned ||
this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
!Services.prefs.getBoolPref("browser.tabs.animate")) {
this._endRemoveTab(aTab);
return;
}
this._blurTab(aTab);
aTab.style.maxWidth = ""; // ensure that fade-out transition happens
aTab.removeAttribute("fadein");
setTimeout(function (tab, tabbrowser) {
if (tab.parentNode &&
window.getComputedStyle(tab).maxWidth == "0.1px") {
NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
tabbrowser._endRemoveTab(tab);
}
}, 3000, aTab, this);
]]>
</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 (aTab.closing || 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;
}
aTab.closing = true;
this._removingTabs.push(aTab);
if (newTab)
this.addTab("about:blank", {skipAnimation: true});
else
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];
#ifdef MOZ_E10S_COMPAT
// Bug 666801 - WebProgress support for e10s
#else
browser.webProgress.removeProgressListener(filter);
#endif
filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
this.mTabListeners[aTab._tPos].destroy();
if (browser.registeredOpenURI && !aTabWillBeMoved) {
this._placesAutocomplete.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;
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();
setTimeout(function(tabs) {
tabs._lastTabClosedByMouse = false;
}, 0, this.tabContainer);
}
// 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);
// Release the browser in case something is erroneously holding a
// reference to the tab after its removal.
aTab.linkedBrowser = null;
// 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 &&
!aTab.owner.hidden &&
!aTab.owner.closing &&
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 !tab.closing;
}, 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._placesAutocomplete.unregisterOpenPage(ourBrowser.registeredOpenURI);
delete ourBrowser.registeredOpenURI;
}
if (otherBrowser.registeredOpenURI) {
ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
delete 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"/>
<body>
<![CDATA[
if (arguments.length != 1) {
Components.utils.reportError("gBrowser.addProgressListener was " +
"called with a second argument, " +
"which is not supported. See bug " +
"608628.");
}
if (!this.mAddProgressListenerWasCalled) {
this.mAddProgressListenerWasCalled = true;
this.tabContainer.updateVisibility();
}
this.mProgressListeners.push(aListener);
]]>
</body>
</method>
<method name="removeProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
this.mProgressListeners =
this.mProgressListeners.filter(function (l) l != aListener);
]]>
</body>
</method>
<method name="addTabsProgressListener">
<parameter name="aListener"/>
<body>
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.removeAttribute("hidden");
this.tabContainer.adjustTabstrip();
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 &&
!aTab.closing) {
aTab.setAttribute("hidden", "true");
this.tabContainer.adjustTabstrip();
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"/>
<parameter name="aOptions"/>
<body>
<![CDATA[
if (this.tabs.length == 1)
return null;
var options = "chrome,dialog=no,all";
for (var name in aOptions)
options += "," + name + "=" + aOptions[name];
// tell a new window to take the "dropped" tab
return window.openDialog(getBrowserURL(), "_blank", options, 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;
case "sizemodechange":
if (aEvent.target == window) {
this.mCurrentBrowser.docShellIsActive =
(window.windowState != window.STATE_MINIMIZED);
}
break;
}
]]></body>
</method>
<constructor>
<![CDATA[
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild.firstChild;
this.mCurrentTab = this.tabContainer.firstChild;
document.addEventListener("keypress", this, false);
window.addEventListener("sizemodechange", this, false);
var uniqueId = "panel" + Date.now();
this.mPanelContainer.childNodes[0].id = uniqueId;
this.mCurrentTab.linkedPanel = uniqueId;
this.mCurrentTab._tPos = 0;
this.mCurrentTab._fullyOpen = true;
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;
this.updateWindowResizers();
// Hook up the event listeners to the first browser
var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
const nsIWebProgress = Components.interfaces.nsIWebProgress;
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(nsIWebProgress);
filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[0] = tabListener;
this.mTabFilters[0] = filter;
this.init();
this.style.backgroundColor =
Services.prefs.getBoolPref("browser.display.use_system_colors") ?
"-moz-default-background-color" :
Services.prefs.getCharPref("browser.display.background_color");
]]>
</constructor>
<method name="init">
<body><![CDATA[
if (!this._initialProgressListenerAdded) {
this._initialProgressListenerAdded = true;
try {
this.webProgress.addProgressListener(this.mTabFilters[0], Components.interfaces.nsIWebProgress.NOTIFY_ALL);
} catch (e) {
// The binding was constructed too early, need to try this again later. See bug 463384.
this._initialProgressListenerAdded = false;
}
}
]]></body>
</method>
<destructor>
<![CDATA[
for (var i = 0; i < this.mTabListeners.length; ++i) {
let browser = this.getBrowserAtIndex(i);
if (browser.registeredOpenURI) {
this._placesAutocomplete.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);
window.removeEventListener("sizemodechange", this, false);
]]>
</destructor>
<!-- Deprecated stuff, implemented for backwards compatibility. -->
<method name="enterTabbedMode">
<body>
Application.console.log("enterTabbedMode is an obsolete method and " +
"will be removed in a future release.");
</body>
</method>
<field name="mTabbedMode" readonly="true">true</field>
<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" phase="capturing"><![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
tabs.removeAttribute("overflow");
if (tabs._lastTabClosedByMouse)
tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
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="end">
<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:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
style="width: 0;"/>
</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);
if (window.TabsInTitlebar)
TabsInTitlebar.allowedBy("tabs-visible", this.visible);
]]>
</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[
if (val == this.visible)
return val;
this._container.collapsed = !val;
document.getElementById("menu_closeWindow").hidden = !val;
document.getElementById("menu_close").setAttribute("label",
this.tabbrowser.mStringBundle.getString(val ? "tabs.closeTab" : "tabs.close"));
if (window.TabsInTitlebar)
TabsInTitlebar.allowedBy("tabs-visible", val);
return val;
]]></setter>
</property>
<method name="updateVisibility">
<body><![CDATA[
if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
this.visible = window.toolbar.visible &&
!Services.prefs.getBoolPref("browser.tabs.autoHide");
else
this.visible = true;
]]></body>
</method>
<method name="adjustTabstrip">
<body><![CDATA[
// modes for tabstrip
// 0 - button on active tab only
// 1 - close buttons on all tabs
// 2 - no close buttons at all
// 3 - close button at the end of the tabstrip
switch (this.mCloseButtons) {
case 0:
if (this.childNodes.length == 1 && this._closeWindowWithLastTab)
this.setAttribute("closebuttons", "hidden");
else
this.setAttribute("closebuttons", "activetab");
break;
case 1:
if (this.childNodes.length == 1) {
if (this._closeWindowWithLastTab)
this.setAttribute("closebuttons", "hidden");
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", "never");
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>
<field name="_closingTabsSpacer">
document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
</field>
<field name="_tabDefaultMaxWidth">NaN</field>
<field name="_lastTabClosedByMouse">false</field>
<field name="_hasTabTempMaxWidth">false</field>
<field name="_usingClosingTabsSpacer">false</field>
<!-- Try to keep the active tab's close button under the mouse cursor -->
<method name="_lockTabSizing">
<parameter name="aTab"/>
<body><![CDATA[
var tabs = this.tabbrowser.visibleTabs;
if (!tabs.length)
return;
var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
var tabWidth = aTab.getBoundingClientRect().width;
if (!this._tabDefaultMaxWidth)
this._tabDefaultMaxWidth =
parseFloat(window.getComputedStyle(aTab).maxWidth);
this._lastTabClosedByMouse = true;
if (this.getAttribute("overflow") == "true") {
// Don't need to do anything if we're in overflow mode and aren't scrolled
// all the way to the right, or if we're closing the last tab.
if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
return;
// If the tab has an owner that will become the active tab, the owner will
// be to the left of it, so we actually want the left tab to slide over.
// This can't be done as easily in non-overflow mode, so we don't bother.
if (aTab.owner)
return;
this._expandSpacerBy(tabWidth);
} else { // non-overflow mode
// Locking is neither in effect nor needed, so let tabs expand normally.
if (isEndTab && !this._hasTabTempMaxWidth)
return;
let numPinned = this.tabbrowser._numPinnedTabs;
// Force tabs to stay the same width, unless we're closing the last tab,
// which case we need to let them expand just enough so that the overall
// tabbar width is the same.
if (isEndTab) {
let numNormalTabs = tabs.length - numPinned;
tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
if (tabWidth > this._tabDefaultMaxWidth)
tabWidth = this._tabDefaultMaxWidth;
}
tabWidth += "px";
for (let i = numPinned; i < tabs.length; i++) {
let tab = tabs[i];
tab.style.setProperty("max-width", tabWidth, "important");
if (!isEndTab) { // keep tabs the same width
tab.style.MozTransition = "none";
tab.clientTop; // flush styles to skip animation; see bug 649247
tab.style.MozTransition = "";
}
}
this._hasTabTempMaxWidth = true;
this.tabbrowser.addEventListener("mousemove", this, false);
window.addEventListener("mouseout", this, false);
}
]]></body>
</method>
<method name="_expandSpacerBy">
<parameter name="pixels"/>
<body><![CDATA[
let spacer = this._closingTabsSpacer;
spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
this._usingClosingTabsSpacer = true;
this.tabbrowser.addEventListener("mousemove", this, false);
window.addEventListener("mouseout", this, false);
]]></body>
</method>
<method name="_unlockTabSizing">
<body><![CDATA[
this.tabbrowser.removeEventListener("mousemove", this, false);
window.removeEventListener("mouseout", this, false);
if (this._hasTabTempMaxWidth) {
this._hasTabTempMaxWidth = false;
let tabs = this.tabbrowser.visibleTabs;
for (let i = 0; i < tabs.length; i++)
tabs[i].style.maxWidth = "";
}
if (this._usingClosingTabsSpacer) {
this._usingClosingTabsSpacer = false;
this._closingTabsSpacer.style.width = 0;
}
]]></body>
</method>
<method name="_positionPinnedTabs">
<body><![CDATA[
var numPinned = this.tabbrowser._numPinnedTabs;
var doPosition = this.getAttribute("overflow") == "true" &&
numPinned > 0;
if (doPosition) {
this.setAttribute("positionpinnedtabs", "true");
let scrollButtonWidth = this.mTabstrip._scrollButtonDown.scrollWidth;
let paddingStart = this.mTabstrip.scrollboxPaddingStart;
let width = 0;
for (let i = numPinned - 1; i >= 0; i--) {
let tab = this.childNodes[i];
width += tab.scrollWidth;
tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
}
this.style.MozMarginStart = width + paddingStart + "px";
} else {
this.removeAttribute("positionpinnedtabs");
for (let i = 0; i < numPinned; i++) {
let tab = this.childNodes[i];
tab.style.MozMarginStart = "";
}
this.style.MozMarginStart = "";
}
this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "resize":
if (aEvent.target != window)
break;
var width = this.mTabstrip.boxObject.width;
if (width != this.mTabstripWidth) {
this.adjustTabstrip();
this._fillTrailingGap();
this._handleTabSelect();
this.mTabstripWidth = width;
}
this.tabbrowser.updateWindowResizers();
break;
case "mouseout":
// If the "related target" (the node to which the pointer went) is not
// a child of the current document, the mouse just left the window.
let relatedTarget = aEvent.relatedTarget;
if (relatedTarget && relatedTarget.ownerDocument == document)
break;
case "mousemove":
if (document.getElementById("tabContextMenu").state != "open")
this._unlockTabSizing();
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;
tab._fullyOpen = true;
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 !aTab.closing;
]]>
</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") {
if (tab._fullyOpen)
this.adjustTabstrip();
else
this._handleNewTab(tab);
} else if (tab.closing) {
this.tabbrowser._endRemoveTab(tab);
}
]]></handler>
<handler event="dblclick"><![CDATA[
#ifndef XP_MACOSX
// When the tabbar has an unified appearance with the titlebar
// and menubar, a double-click in it should have the same behavior
// as double-clicking the titlebar
if (TabsInTitlebar.enabled ||
(TabsOnTop.enabled && this.parentNode._dragBindingAlive))
return;
#endif
if (event.button != 0 ||
event.originalTarget.localName != "box")
return;
// See hack note in the tabbrowser-close-tab-button binding
if (!this._blockDblClick)
BrowserOpenTab();
event.preventDefault();
]]></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, byMouse: 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);
// _dragOffsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
// dragend such that the mouse appears to have the same position
// relative to the corner of the dragged tab.
function clientX(ele) ele.getBoundingClientRect().left;
let tabOffsetX = clientX(tab) -
clientX(this.children[0].pinned ? this.children[0] : this);
tab._dragOffsetX = event.screenX - window.screenX - tabOffsetX;
tab._dragOffsetY = event.screenY - window.screenY;
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";
]]></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
}
}
}
// these offsets are only used in dragend, but we need to free them here
// as well
delete draggedTab._dragOffsetX;
delete draggedTab._dragOffsetY;
]]></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 eY = event.screenY;
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;
if (eY < endScreenY && eY > window.screenY)
return;
}
var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
// screen.availLeft et. al. only check the screen that this window is on,
// but we want to look at the screen the tab is being dropped onto.
var sX = {}, sY = {}, sWidth = {}, sHeight = {};
Cc["@mozilla.org/gfx/screenmanager;1"]
.getService(Ci.nsIScreenManager)
.screenForRect(eX, eY, 1, 1)
.GetAvailRect(sX, sY, sWidth, sHeight);
// ensure new window entirely within screen
var winWidth = Math.min(window.outerWidth, sWidth.value);
var winHeight = Math.min(window.outerHeight, sHeight.value);
var left = Math.min(Math.max(eX - draggedTab._dragOffsetX, sX.value),
sX.value + sWidth.value - winWidth);
var top = Math.min(Math.max(eY - draggedTab._dragOffsetY, sY.value),
sY.value + sHeight.value - winHeight);
delete draggedTab._dragOffsetX;
delete draggedTab._dragOffsetY;
if (this.tabbrowser.tabs.length == 1) {
// resize _before_ move to ensure the window fits the new screen. if
// the window is too large for its screen, the window manager may do
// automatic repositioning.
window.resizeTo(winWidth, winHeight);
window.moveTo(left, top);
window.focus();
} else {
this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
screenY: top,
#ifndef XP_WIN
outerWidth: winWidth,
outerHeight: winHeight
#endif
});
}
event.stopPropagation();
]]></handler>
<handler event="dragexit"><![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, byMouse: 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:hbox xbl:inherits="pinned,selected,titlechanged"
class="tab-background">
<xul:hbox xbl:inherits="pinned,selected,titlechanged"
class="tab-background-start"/>
<xul:hbox xbl:inherits="pinned,selected,titlechanged"
class="tab-background-middle"/>
<xul:hbox xbl:inherits="pinned,selected,titlechanged"
class="tab-background-end"/>
</xul:hbox>
<xul:hbox xbl:inherits="pinned,selected,titlechanged"
class="tab-content" align="center">
<xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
class="tab-throbber"
role="presentation"/>
<xul:image xbl:inherits="validate,src=image,fadein,pinned,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"
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>
<property name="hidden" readonly="true">
<getter>
return this.getAttribute("hidden") == "true";
</getter>
</property>
<field name="mOverCloseButton">false</field>
<field name="mCorrespondingMenuitem">null</field>
<field name="_fullyOpen">false</field>
<field name="closing">false</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" phase="capturing">
<![CDATA[
if (this.selected) {
this.style.MozUserFocus = 'ignore';
this.clientTop; // just using this to flush style updates
} else if (this.mOverCloseButton) {
// Prevent tabbox.xml from selecting the tab.
event.stopPropagation();
}
]]>
</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++) {
let curTab = this.childNodes[i].tab;
if (!curTab) // "Tab Groups" menuitem and its menuseparator
continue;
let curTabBO = curTab.boxObject;
if (!curTabBO) // "Tabs From Other Computers" menuitem
continue;
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"));
if (aTab.hasAttribute("busy")) {
aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
aMenuitem.removeAttribute("image");
} else {
aMenuitem.setAttribute("image", aTab.getAttribute("image"));
aMenuitem.removeAttribute("busy");
}
if (aTab.hasAttribute("pending"))
aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
else
aMenuitem.removeAttribute("pending");
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
for (let i = this.childNodes.length - 1; i > 0; i--) {
let menuItem = this.childNodes[i];
if (menuItem.tab) {
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) {
let overLink = tab.linkedBrowser.currentURI.spec;
if (overLink == "about:blank")
overLink = "";
XULBrowserWindow.setOverLink(overLink, null);
}
]]></handler>
<handler event="DOMMenuItemInactive">
<![CDATA[
XULBrowserWindow.setOverLink("", null);
]]></handler>
</handlers>
</binding>
<binding id="statuspanel" display="xul:hbox">
<content>
<xul:hbox class="statuspanel-inner">
<xul:label class="statuspanel-label"
role="status"
xbl:inherits="value=label,crop,mirror"
flex="1"
crop="end"/>
</xul:hbox>
</content>
<implementation implements="nsIDOMEventListener">
<constructor><![CDATA[
window.addEventListener("findbaropen", this, false);
]]></constructor>
<destructor><![CDATA[
window.removeEventListener("findbaropen", this, false);
]]></destructor>
<property name="label">
<setter><![CDATA[
if (!this.label) {
if (window.gFindBarInitialized && !window.gFindBar.hidden)
this.setAttribute("mirror", "true");
else
this.removeAttribute("mirror");
}
this.style.minWidth = this.getAttribute("type") == "status" &&
this.getAttribute("previoustype") == "status"
? getComputedStyle(this).width : "";
if (val) {
this.setAttribute("label", val);
this.removeAttribute("inactive");
} else {
this.setAttribute("inactive", "true");
}
return val;
]]></setter>
<getter>
return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
</getter>
</property>
<method name="handleEvent">
<parameter name="event"/>
<body><![CDATA[
if (event.type == "findbaropen" &&
this.label)
this.setAttribute("mirror", "true");
]]></body>
</method>
<method name="_mirror">
<body>
if (this.hasAttribute("mirror"))
this.removeAttribute("mirror");
else
this.setAttribute("mirror", "true");
</body>
</method>
</implementation>
<handlers>
<handler event="mouseover" action="this._mirror();"/>
<handler event="dragover" action="this._mirror();"/>
</handlers>
</binding>
</bindings>