mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
5048134510
additional, after clicking "Restart Now", we need to disable the "Restart Now" and "Later" buttons, so that the user can't click on them again (while they are dealing with the "confirm close" prompts. finally, improve the prompt scenario (by first focusing the wizard) where the user has paused in the middle downloading a software update, minimized the software update wizard, and then quit the app. r=gavin.sharp, sr=mconnor
3046 lines
114 KiB
XML
3046 lines
114 KiB
XML
<?xml version="1.0"?>
|
|
|
|
<!-- ***** BEGIN LICENSE BLOCK *****
|
|
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
-
|
|
- The contents of this file are subject to the Mozilla Public License Version
|
|
- 1.1 (the "License"); you may not use this file except in compliance with
|
|
- the License. You may obtain a copy of the License at
|
|
- http://www.mozilla.org/MPL/
|
|
-
|
|
- Software distributed under the License is distributed on an "AS IS" basis,
|
|
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
- for the specific language governing rights and limitations under the
|
|
- License.
|
|
-
|
|
- The Original Code is this file as it was released on March 28, 2001.
|
|
-
|
|
- The Initial Developer of the Original Code is
|
|
- David Hyatt.
|
|
- Portions created by the Initial Developer are Copyright (C) 2001
|
|
- the Initial Developer. All Rights Reserved.
|
|
-
|
|
- Contributor(s):
|
|
- David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
|
|
- Mike Connor <mconnor@steelgryphon.com>
|
|
- Peter Parente <parente@cs.unc.edu>
|
|
- Giorgio Maone <g.maone@informaction.com>
|
|
- Asaf Romano <mozilla.mano@sent.com>
|
|
- Seth Spitzer <sspitzer@mozilla.org>
|
|
- Simon Bünzli <zeniko@gmail.com>
|
|
-
|
|
- Alternatively, the contents of this file may be used under the terms of
|
|
- either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
- in which case the provisions of the GPL or the LGPL are applicable instead
|
|
- of those above. If you wish to allow use of your version of this file only
|
|
- under the terms of either the GPL or the LGPL, and not to allow others to
|
|
- use your version of this file under the terms of the MPL, indicate your
|
|
- decision by deleting the provisions above and replace them with the notice
|
|
- and other provisions required by the GPL or the LGPL. If you do not delete
|
|
- the provisions above, a recipient may use your version of this file under
|
|
- the terms of any one of the MPL, the GPL or the LGPL.
|
|
-
|
|
- ***** END LICENSE BLOCK ***** -->
|
|
|
|
<!DOCTYPE bindings [
|
|
<!ENTITY % tabBrowserDTD SYSTEM "chrome://global/locale/tabbrowser.dtd" >
|
|
%tabBrowserDTD;
|
|
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
|
|
%globalDTD;
|
|
]>
|
|
|
|
<bindings id="tabBrowserBindings"
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
|
xmlns:xbl="http://www.mozilla.org/xbl">
|
|
|
|
<binding id="tabbrowser">
|
|
<resources>
|
|
<stylesheet src="chrome://global/skin/browser.css"/>
|
|
</resources>
|
|
|
|
<content>
|
|
<xul:stringbundle anonid="tbstringbundle" src="chrome://global/locale/tabbrowser.properties"/>
|
|
<xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
|
|
onselect="if (!('updateCurrentBrowser' in this.parentNode) || event.target.localName != 'tabpanels') return; this.parentNode.updateCurrentBrowser();">
|
|
<xul:hbox class="tab-drop-indicator-bar">
|
|
<xul:hbox class="tab-drop-indicator" mousethrough="always"/>
|
|
</xul:hbox>
|
|
<xul:hbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
|
|
anonid="strip"
|
|
ondraggesture="nsDragAndDrop.startDrag(event, this.parentNode.parentNode); event.stopPropagation();"
|
|
ondragover="nsDragAndDrop.dragOver(event, this.parentNode.parentNode); event.stopPropagation();"
|
|
ondragdrop="nsDragAndDrop.drop(event, this.parentNode.parentNode); event.stopPropagation();"
|
|
ondragexit="nsDragAndDrop.dragExit(event, this.parentNode.parentNode); event.stopPropagation();">
|
|
<xul:tooltip onpopupshowing="return this.parentNode.parentNode.parentNode.createTooltip(event);"/>
|
|
<xul:menupopup anonid="tabContextMenu" onpopupshowing="this.parentNode.parentNode.parentNode.updatePopupMenu(this);">
|
|
<xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"
|
|
xbl:inherits="oncommand=onnewtab"/>
|
|
<xul:menuseparator/>
|
|
<xul:menuitem label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
|
|
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
|
|
tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
|
|
<xul:menuitem label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
|
|
tbattr="tabbrowser-multiple"
|
|
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
|
|
tabbrowser.reloadAllTabs(tabbrowser.mContextTab);"/>
|
|
<xul:menuitem label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
|
|
tbattr="tabbrowser-multiple"
|
|
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
|
|
tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
|
|
<xul:menuseparator/>
|
|
<xul:menuitem label="&closeTab.label;" accesskey="&closeTab.accesskey;"
|
|
tbattr="tabbrowser-multiple"
|
|
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
|
|
tabbrowser.removeTab(tabbrowser.mContextTab);"/>
|
|
</xul:menupopup>
|
|
|
|
<xul:tabs class="tabbrowser-tabs" flex="1"
|
|
anonid="tabcontainer"
|
|
setfocus="false"
|
|
onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
|
|
xbl:inherits="onnewtab"
|
|
ondblclick="this.parentNode.parentNode.parentNode.onTabBarDblClick(event);"
|
|
onclosetab="var node = this.parentNode;
|
|
while (node.localName != 'tabbrowser')
|
|
node = node.parentNode;
|
|
node.removeCurrentTab();">
|
|
<xul:tab selected="true" validate="never"
|
|
onerror="this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image'));
|
|
this.removeAttribute('image');"
|
|
maxwidth="250" width="0" minwidth="100" flex="100"
|
|
class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
|
|
</xul:tabs>
|
|
</xul:hbox>
|
|
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
|
|
<xul:notificationbox flex="1">
|
|
<xul:browser flex="1" type="content-primary" message="true" disablehistory="true"
|
|
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
|
|
</xul:notificationbox>
|
|
</xul:tabpanels>
|
|
</xul:tabbox>
|
|
<children/>
|
|
</content>
|
|
<implementation>
|
|
<field name="mPrefs" readonly="true">
|
|
Components.classes['@mozilla.org/preferences-service;1']
|
|
.getService(Components.interfaces.nsIPrefService)
|
|
.getBranch(null);
|
|
</field>
|
|
<field name="mURIFixup" readonly="true">
|
|
Components.classes["@mozilla.org/docshell/urifixup;1"]
|
|
.getService(Components.interfaces.nsIURIFixup);
|
|
</field>
|
|
<field name="mTabBox" readonly="true">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
|
|
</field>
|
|
<field name="mTabDropIndicatorBar">
|
|
this.mTabBox.childNodes[0]
|
|
</field>
|
|
<field name="mStrip" readonly="true">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "strip");
|
|
</field>
|
|
<field name="mTabContainer" readonly="true">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
|
|
</field>
|
|
<field name="mPanelContainer" readonly="true">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
|
|
</field>
|
|
<field name="mTabs" readonly="true">
|
|
this.mTabContainer.childNodes
|
|
</field>
|
|
<field name="mStringBundle">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
|
|
</field>
|
|
<field name="mCurrentTab">
|
|
null
|
|
</field>
|
|
<field name="mCurrentBrowser">
|
|
null
|
|
</field>
|
|
<field name="mProgressListeners">
|
|
[]
|
|
</field>
|
|
<field name="mTabListeners">
|
|
new Array()
|
|
</field>
|
|
<field name="mTabFilters">
|
|
new Array()
|
|
</field>
|
|
<field name="mTabbedMode">
|
|
false
|
|
</field>
|
|
<field name="mIsBusy">
|
|
false
|
|
</field>
|
|
<field name="mMissedIconCache">
|
|
null
|
|
</field>
|
|
<field name="mContextTab">
|
|
null
|
|
</field>
|
|
<field name="mModalDialogShowing">
|
|
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="_blockDblClick">
|
|
false
|
|
</field>
|
|
|
|
<method name="getBrowserAtIndex">
|
|
<parameter name="aIndex"/>
|
|
<body>
|
|
<![CDATA[
|
|
return this.mTabContainer.childNodes[aIndex].linkedBrowser;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getBrowserIndexForDocument">
|
|
<parameter name="aDocument"/>
|
|
<body>
|
|
<![CDATA[
|
|
for (var i = 0; i < this.mPanelContainer.childNodes.length; i++) {
|
|
if (this.getBrowserAtIndex(i).contentDocument == aDocument) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getBrowserForDocument">
|
|
<parameter name="aDocument"/>
|
|
<body>
|
|
<![CDATA[
|
|
var index = this.getBrowserIndexForDocument(aDocument);
|
|
if (index < 0)
|
|
return null;
|
|
return this.getBrowserAtIndex(index);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getNotificationBox">
|
|
<parameter name="aBrowser"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aBrowser)
|
|
return aBrowser.parentNode;
|
|
else if (this.mCurrentBrowser)
|
|
return this.mCurrentBrowser.parentNode;
|
|
return null;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- A web progress listener object definition for a given tab. -->
|
|
<method name="mTabProgressListener">
|
|
<parameter name="aTab"/>
|
|
<parameter name="aBrowser"/>
|
|
<parameter name="aStartsBlank"/>
|
|
<body>
|
|
<![CDATA[
|
|
return ({
|
|
mTabBrowser: this,
|
|
mTab: aTab,
|
|
mBrowser: aBrowser,
|
|
mBlank: aStartsBlank,
|
|
mLastURI: null,
|
|
|
|
// cache flags for correct status bar update after tab switching
|
|
mStateFlags: 0,
|
|
mStatus: 0,
|
|
mMessage: "",
|
|
mTotalProgress: 0,
|
|
|
|
// count of open requests (should always be 0 or 1)
|
|
mRequestCount: 0,
|
|
|
|
onProgressChange : function (aWebProgress, aRequest,
|
|
aCurSelfProgress, aMaxSelfProgress,
|
|
aCurTotalProgress, aMaxTotalProgress)
|
|
{
|
|
if (!this.mBlank && this.mTabBrowser.mCurrentTab == this.mTab) {
|
|
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
|
|
var p = this.mTabBrowser.mProgressListeners[i];
|
|
if (p)
|
|
p.onProgressChange(aWebProgress, aRequest,
|
|
aCurSelfProgress, aMaxSelfProgress,
|
|
aCurTotalProgress, aMaxTotalProgress);
|
|
}
|
|
}
|
|
|
|
this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
|
|
},
|
|
|
|
onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
|
|
{
|
|
if (!aRequest)
|
|
return;
|
|
|
|
var oldBlank = this.mBlank;
|
|
|
|
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
|
|
const nsIChannel = Components.interfaces.nsIChannel;
|
|
|
|
if (aStateFlags & nsIWebProgressListener.STATE_START) {
|
|
this.mRequestCount++;
|
|
}
|
|
else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
|
|
const NS_ERROR_UNKNOWN_HOST = 2152398878;
|
|
if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
|
|
// to prevent bug 235825: wait for the request handled
|
|
// by the automatic keyword resolver
|
|
return;
|
|
}
|
|
// since we (try to) only handle STATE_STOP of the last request,
|
|
// the count of open requests should now be 0
|
|
this.mRequestCount = 0;
|
|
}
|
|
|
|
if (aStateFlags & nsIWebProgressListener.STATE_START &&
|
|
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
|
|
// It's okay to clear what the user typed when we start
|
|
// loading a document. If the user types, this counter gets
|
|
// set to zero, if the document load ends without an
|
|
// onLocationChange, this counter gets decremented
|
|
// (so we keep it while switching tabs after failed loads)
|
|
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
|
|
this.mBrowser.userTypedClear++;
|
|
|
|
if (!this.mBlank) {
|
|
this.mTab.setAttribute("busy", "true");
|
|
this.mTabBrowser.updateIcon(this.mTab);
|
|
this.mTabBrowser.setTabTitleLoading(this.mTab);
|
|
|
|
if (this.mTabBrowser.mCurrentTab == this.mTab)
|
|
this.mTabBrowser.mIsBusy = true;
|
|
}
|
|
}
|
|
else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
|
|
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
|
|
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
|
|
// The document is done loading, we no longer want the
|
|
// value cleared.
|
|
if (this.mBrowser.userTypedClear > 0)
|
|
this.mBrowser.userTypedClear--;
|
|
|
|
if (!this.mBrowser.mIconURL)
|
|
this.mTabBrowser.useDefaultIcon(this.mTab);
|
|
}
|
|
|
|
if (this.mBlank)
|
|
this.mBlank = false;
|
|
|
|
this.mTab.removeAttribute("busy");
|
|
this.mTabBrowser.updateIcon(this.mTab);
|
|
|
|
var location = aRequest.QueryInterface(nsIChannel).URI;
|
|
|
|
// For keyword URIs clear the user typed value since they will be changed into real URIs
|
|
if (location.scheme == "keyword")
|
|
this.mBrowser.userTypedValue = null;
|
|
|
|
if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
|
|
this.mTabBrowser.setTabTitle(this.mTab);
|
|
|
|
if (this.mTabBrowser.mCurrentTab == this.mTab)
|
|
this.mTabBrowser.mIsBusy = false;
|
|
}
|
|
|
|
if (this.mTabBrowser.mCurrentTab == this.mTab) {
|
|
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
|
|
var p = this.mTabBrowser.mProgressListeners[i];
|
|
if (p && !oldBlank)
|
|
p.onStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
|
|
// make sure that the visible status of new blank tabs is correctly set
|
|
else if (p && "onUpdateCurrentBrowser" in p)
|
|
p.onUpdateCurrentBrowser(aStateFlags, aStatus, "", 0);
|
|
}
|
|
}
|
|
|
|
if (aStateFlags & (nsIWebProgressListener.STATE_START |
|
|
nsIWebProgressListener.STATE_STOP)) {
|
|
// reset cached temporary values at beginning and end
|
|
this.mMessage = "";
|
|
this.mTotalProgress = 0;
|
|
}
|
|
this.mStateFlags = aStateFlags;
|
|
this.mStatus = aStatus;
|
|
},
|
|
|
|
onLocationChange : function(aWebProgress, aRequest, aLocation)
|
|
{
|
|
// The document loaded correctly, clear the value if we should
|
|
if (this.mBrowser.userTypedClear > 0 && aRequest)
|
|
this.mBrowser.userTypedValue = null;
|
|
|
|
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow &&
|
|
aWebProgress.isLoadingDocument)
|
|
this.mTabBrowser.setIcon(this.mTab, null);
|
|
|
|
// changing location, clear out the missing plugins list
|
|
this.mTab.missingPlugins = null;
|
|
|
|
if (!this.mBlank && this.mTabBrowser.mCurrentTab == this.mTab) {
|
|
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
|
|
var p = this.mTabBrowser.mProgressListeners[i];
|
|
if (p)
|
|
p.onLocationChange(aWebProgress, aRequest, aLocation);
|
|
}
|
|
}
|
|
},
|
|
|
|
onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
|
|
{
|
|
if (this.mBlank)
|
|
return;
|
|
|
|
if (this.mTabBrowser.mCurrentTab == this.mTab) {
|
|
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
|
|
var p = this.mTabBrowser.mProgressListeners[i];
|
|
if (p)
|
|
p.onStatusChange(aWebProgress, aRequest, aStatus, aMessage);
|
|
}
|
|
}
|
|
|
|
this.mMessage = aMessage;
|
|
},
|
|
|
|
onSecurityChange : function(aWebProgress, aRequest, aState)
|
|
{
|
|
if (this.mTabBrowser.mCurrentTab == this.mTab) {
|
|
for (var i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
|
|
var p = this.mTabBrowser.mProgressListeners[i];
|
|
if (p)
|
|
p.onSecurityChange(aWebProgress, aRequest, aState);
|
|
}
|
|
}
|
|
},
|
|
|
|
QueryInterface : function(aIID)
|
|
{
|
|
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
|
|
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
|
|
aIID.equals(Components.interfaces.nsISupports))
|
|
return this;
|
|
throw Components.results.NS_NOINTERFACE;
|
|
}
|
|
});
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="setIcon">
|
|
<parameter name="aTab"/>
|
|
<parameter name="aURI"/>
|
|
<body>
|
|
<![CDATA[
|
|
var browser = this.getBrowserForTab(aTab);
|
|
browser.mIconURL = aURI;
|
|
|
|
this.updateIcon(aTab);
|
|
|
|
for (var i = 0; i < this.mProgressListeners.length; i++) {
|
|
var p = this.mProgressListeners[i];
|
|
if ('onLinkIconAvailable' in p)
|
|
p.onLinkIconAvailable(browser);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateIcon">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
var browser = this.getBrowserForTab(aTab);
|
|
if (!aTab.hasAttribute("busy") && browser.mIconURL)
|
|
aTab.setAttribute("image", browser.mIconURL);
|
|
else
|
|
aTab.removeAttribute("image");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="shouldLoadFavIcon">
|
|
<parameter name="aURI"/>
|
|
<body>
|
|
<![CDATA[
|
|
return (aURI && this.mPrefs.getBoolPref("browser.chrome.site_icons") &&
|
|
this.mPrefs.getBoolPref("browser.chrome.favicons") &&
|
|
("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="useDefaultIcon">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
var browser = this.getBrowserForTab(aTab);
|
|
if (browser.contentDocument instanceof ImageDocument) {
|
|
if (this.mPrefs.getBoolPref("browser.chrome.site_icons")) {
|
|
try {
|
|
var sz = this.mPrefs.getIntPref("browser.chrome.image_icons.max_size");
|
|
if (!sz)
|
|
return;
|
|
|
|
var req = browser.contentDocument.imageRequest;
|
|
if (!req || !req.image ||
|
|
req.image.width > sz ||
|
|
req.image.height > sz)
|
|
return;
|
|
|
|
this.setIcon(aTab, browser.currentURI.spec);
|
|
} catch (e) { }
|
|
}
|
|
}
|
|
else if (this.shouldLoadFavIcon(browser.currentURI)) {
|
|
var url = browser.currentURI.prePath + "/favicon.ico";
|
|
if (!this.isIconKnownMissing(url))
|
|
this.setIcon(aTab, url);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="addToMissedIconCache">
|
|
<parameter name="aURI"/>
|
|
<body>
|
|
<![CDATA[
|
|
var entry = this.openCacheEntry(aURI, Components.interfaces.nsICache.ACCESS_READ_WRITE);
|
|
if (!entry)
|
|
return;
|
|
|
|
if (entry.accessGranted == Components.interfaces.nsICache.ACCESS_WRITE)
|
|
// It's a new entry. Just write a bit of metadata in to the entry.
|
|
entry.setMetaDataElement("Icon", "Missed");
|
|
entry.markValid();
|
|
entry.close();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="openCacheEntry">
|
|
<parameter name="key"/>
|
|
<parameter name="access"/>
|
|
<body>
|
|
<![CDATA[
|
|
try {
|
|
if (!this.mMissedIconCache) {
|
|
var cacheService = Components.classes['@mozilla.org/network/cache-service;1'].getService(Components.interfaces.nsICacheService);
|
|
this.mMissedIconCache = cacheService.createSession("MissedIconCache", Components.interfaces.nsICache.STORE_ANYWHERE, true);
|
|
if (!this.mMissedIconCache)
|
|
return null;
|
|
}
|
|
return this.mMissedIconCache.openCacheEntry(key, access, true);
|
|
}
|
|
catch (e) {
|
|
return null;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="isIconKnownMissing">
|
|
<parameter name="key"/>
|
|
<body>
|
|
<![CDATA[
|
|
var e = this.openCacheEntry(key, Components.interfaces.nsICache.ACCESS_READ);
|
|
if (e) {
|
|
e.close();
|
|
return true;
|
|
}
|
|
return false;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateTitlebar">
|
|
<body>
|
|
<![CDATA[
|
|
var newTitle = "";
|
|
var docTitle;
|
|
var docElement = this.ownerDocument.documentElement;
|
|
var sep = docElement.getAttribute("titlemenuseparator");
|
|
|
|
if (this.docShell.contentViewer)
|
|
docTitle = this.contentTitle;
|
|
|
|
if (!docTitle)
|
|
docTitle = docElement.getAttribute("titledefault");
|
|
|
|
var modifier = docElement.getAttribute("titlemodifier");
|
|
if (docTitle) {
|
|
newTitle += docElement.getAttribute("titlepreface");
|
|
newTitle += docTitle;
|
|
if (modifier)
|
|
newTitle += sep;
|
|
}
|
|
newTitle += modifier;
|
|
|
|
// If location bar is hidden and the URL type supports a host,
|
|
// add the scheme and host to the title to prevent spoofing.
|
|
// XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
|
|
// (only for schemes that support a host)
|
|
try {
|
|
if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
|
|
var uri = this.mURIFixup.createExposableURI(
|
|
this.mCurrentBrowser.currentURI);
|
|
if (uri.host)
|
|
newTitle = uri.prePath + sep + newTitle;
|
|
}
|
|
} catch (e) {}
|
|
|
|
this.ownerDocument.title = newTitle;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updatePopupMenu">
|
|
<parameter name="aPopupMenu"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mContextTab = document.popupNode;
|
|
var disabled = this.mPanelContainer.childNodes.length == 1;
|
|
var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
|
|
for (var i = 0; i < menuItems.length; i++)
|
|
menuItems[i].disabled = disabled;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="updateCurrentBrowser">
|
|
<body>
|
|
<![CDATA[
|
|
var newBrowser = this.getBrowserAtIndex(this.mTabContainer.selectedIndex);
|
|
if (this.mCurrentBrowser == newBrowser)
|
|
return;
|
|
|
|
if (this.mCurrentBrowser) {
|
|
// Only save the focused element if it is in our content window
|
|
// or in an ancestor window.
|
|
var focusedWindow = document.commandDispatcher.focusedWindow;
|
|
var saveFocus = false;
|
|
|
|
if (focusedWindow && focusedWindow.top == window.content) {
|
|
saveFocus = true;
|
|
} else {
|
|
var contentWindow = window;
|
|
|
|
while (contentWindow) {
|
|
if (contentWindow == focusedWindow) {
|
|
saveFocus = true;
|
|
break;
|
|
}
|
|
|
|
if (contentWindow.parent == contentWindow) {
|
|
break;
|
|
}
|
|
|
|
contentWindow = contentWindow.parent;
|
|
}
|
|
}
|
|
|
|
if (saveFocus) {
|
|
// Preserve the currently-focused element or DOM window for
|
|
// this tab.
|
|
|
|
this.mCurrentBrowser.focusedWindow = focusedWindow;
|
|
this.mCurrentBrowser.focusedElement = document.commandDispatcher.focusedElement;
|
|
}
|
|
|
|
if (this.mCurrentBrowser.focusedElement) {
|
|
// Clear focus outline before we draw on top of it
|
|
this.mCurrentBrowser.focusedElement.blur();
|
|
}
|
|
this.mCurrentBrowser.setAttribute("type", "content-targetable");
|
|
}
|
|
|
|
var updatePageReport = false;
|
|
if ((this.mCurrentBrowser.pageReport && !newBrowser.pageReport) ||
|
|
(!this.mCurrentBrowser.pageReport && newBrowser.pageReport))
|
|
updatePageReport = true;
|
|
|
|
newBrowser.setAttribute("type", "content-primary");
|
|
this.mCurrentBrowser = newBrowser;
|
|
this.mCurrentTab = this.selectedTab;
|
|
|
|
if (updatePageReport)
|
|
this.mCurrentBrowser.updatePageReport();
|
|
|
|
// Update the URL bar.
|
|
var loc = this.mCurrentBrowser.currentURI;
|
|
if (!loc)
|
|
loc = ({ spec: "" });
|
|
|
|
var webProgress = this.mCurrentBrowser.webProgress;
|
|
var securityUI = this.mCurrentBrowser.securityUI;
|
|
|
|
var i, p;
|
|
for (i = 0; i < this.mProgressListeners.length; i++) {
|
|
p = this.mProgressListeners[i];
|
|
if (p) {
|
|
p.onLocationChange(webProgress, null, loc);
|
|
if (securityUI)
|
|
p.onSecurityChange(webProgress, null, securityUI.state);
|
|
|
|
// make sure that all status indicators are properly updated
|
|
if ("onUpdateCurrentBrowser" in p) {
|
|
var listener = this.mTabListeners[this.mTabContainer.selectedIndex] || null;
|
|
if (listener && listener.mStateFlags)
|
|
p.onUpdateCurrentBrowser(listener.mStateFlags, listener.mStatus,
|
|
listener.mMessage, listener.mTotalProgress);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
|
|
|
|
// Update the window title.
|
|
this.updateTitlebar();
|
|
|
|
// If the new tab is busy, and our current state is not busy, then
|
|
// we need to fire a start to all progress listeners.
|
|
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
|
|
if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
|
|
this.mIsBusy = true;
|
|
webProgress = this.mCurrentBrowser.webProgress;
|
|
for (i = 0; i < this.mProgressListeners.length; i++) {
|
|
p = this.mProgressListeners[i];
|
|
if (p)
|
|
p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0);
|
|
}
|
|
}
|
|
|
|
// If the new tab is not busy, and our current state is busy, then
|
|
// we need to fire a stop to all progress listeners.
|
|
if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
|
|
this.mIsBusy = false;
|
|
webProgress = this.mCurrentBrowser.webProgress;
|
|
for (i = 0; i < this.mProgressListeners.length; i++) {
|
|
p = this.mProgressListeners[i];
|
|
if (p)
|
|
p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0);
|
|
}
|
|
}
|
|
|
|
// We've selected the new tab, so go ahead and notify listeners.
|
|
var event = document.createEvent("Events");
|
|
event.initEvent("TabSelect", true, false);
|
|
this.mCurrentTab.dispatchEvent(event);
|
|
|
|
if (document.commandDispatcher.focusedElement &&
|
|
document.commandDispatcher.focusedElement.parentNode ==
|
|
this.mCurrentTab.parentNode) {
|
|
// The focus is on a tab in the same tab panel
|
|
return; // If focus was on a tab, switching tabs focuses the new tab
|
|
}
|
|
|
|
var whatToFocus = window.content;
|
|
|
|
// Focus the previously focused element or window
|
|
if (newBrowser.focusedElement) {
|
|
if (newBrowser.focusedElement.parentNode !=
|
|
this.mCurrentTab.parentNode) {
|
|
// Focus the remembered element unless it's in the current tab panel
|
|
whatToFocus = newBrowser.focusedElement;
|
|
}
|
|
}
|
|
else if (newBrowser.focusedWindow) {
|
|
whatToFocus = newBrowser.focusedWindow;
|
|
}
|
|
|
|
function setFocus(element) {
|
|
document.commandDispatcher.suppressFocusScroll = true;
|
|
try {
|
|
element.focus();
|
|
}
|
|
catch(ex) {
|
|
dump("XXX focus() failed, see bug #348183: ex = " + ex + "\n");
|
|
}
|
|
document.commandDispatcher.suppressFocusScroll = false;
|
|
}
|
|
|
|
// Use setTimeout to avoid focus outline ghosting.
|
|
setTimeout(setFocus, 0, whatToFocus);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onTabClick">
|
|
<parameter name="event"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (event.button != 1 || event.target.localName != 'tab')
|
|
return;
|
|
|
|
this.removeTab(event.target);
|
|
event.stopPropagation();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onLinkAdded">
|
|
<parameter name="event"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.mPrefs.getBoolPref("browser.chrome.site_icons"))
|
|
return;
|
|
|
|
if (!event.originalTarget.rel.match((/(?:^|\s)icon(?:\s|$)/i)))
|
|
return;
|
|
|
|
// We have an icon.
|
|
var href = event.originalTarget.href;
|
|
if (!href)
|
|
return;
|
|
|
|
const nsIContentPolicy = Components.interfaces.nsIContentPolicy;
|
|
try {
|
|
var contentPolicy =
|
|
Components.classes['@mozilla.org/layout/content-policy;1']
|
|
.getService(nsIContentPolicy);
|
|
} catch(e) {
|
|
return; // Refuse to load if we can't do a security check.
|
|
}
|
|
|
|
// Verify that the load of this icon is legal.
|
|
// We check first with the security manager
|
|
const secMan =
|
|
Components.classes["@mozilla.org/scriptsecuritymanager;1"]
|
|
.getService(Components.interfaces.nsIScriptSecurityManager);
|
|
|
|
// Get the IOService so we can make URIs
|
|
const ioService =
|
|
Components.classes["@mozilla.org/network/io-service;1"]
|
|
.getService(Components.interfaces.nsIIOService);
|
|
|
|
const targetDoc = event.target.ownerDocument;
|
|
// Make a URI out of our href.
|
|
var uri = ioService.newURI(href, targetDoc.characterSet, null);
|
|
|
|
var origURI = ioService.newURI(targetDoc.documentURI, targetDoc.characterSet, null);
|
|
|
|
const nsIScriptSecMan =
|
|
Components.interfaces.nsIScriptSecurityManager;
|
|
|
|
try {
|
|
// error pages can load their favicon
|
|
// to be on the safe side, only allow chrome:// favicons
|
|
const aboutNeterr = "about:neterror?";
|
|
if (origURI.spec.substr(0, aboutNeterr.length) != aboutNeterr ||
|
|
!uri.schemeIs("chrome"))
|
|
secMan.checkLoadURI(origURI, uri,
|
|
nsIScriptSecMan.DISALLOW_SCRIPT);
|
|
} catch(e) {
|
|
return;
|
|
}
|
|
|
|
// Security says okay, now ask content policy
|
|
if (contentPolicy.shouldLoad(nsIContentPolicy.TYPE_IMAGE,
|
|
uri, origURI, event.target,
|
|
event.target.type,
|
|
null) != nsIContentPolicy.ACCEPT)
|
|
return;
|
|
|
|
var browserIndex = this.getBrowserIndexForDocument(targetDoc);
|
|
// no browser? no favicon.
|
|
if (browserIndex == -1)
|
|
return;
|
|
|
|
var tab = this.mTabContainer.childNodes[browserIndex];
|
|
this.setIcon(tab, href);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onTitleChanged">
|
|
<parameter name="evt"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (evt.target != this.contentDocument)
|
|
return;
|
|
|
|
var i = 0;
|
|
for ( ; i < this.parentNode.parentNode.childNodes.length; i++) {
|
|
if (this.parentNode.parentNode.childNodes[i].firstChild == this)
|
|
break;
|
|
}
|
|
|
|
var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;
|
|
|
|
var tab = document.getAnonymousElementByAttribute(tabBrowser, "linkedpanel", this.parentNode.id);
|
|
tabBrowser.setTabTitle(tab);
|
|
|
|
if (tab == tabBrowser.mCurrentTab)
|
|
tabBrowser.updateTitlebar();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="setTabTitleLoading">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
aTab.label = this.mStringBundle.getString("tabs.loading");
|
|
aTab.setAttribute("crop", "end");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="setTabTitle">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
var browser = this.getBrowserForTab(aTab);
|
|
var crop = "end";
|
|
var title = browser.contentDocument.title;
|
|
|
|
if (!title) {
|
|
if (browser.currentURI.spec) {
|
|
try {
|
|
title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
|
|
} catch(ex) {
|
|
title = browser.currentURI.spec;
|
|
}
|
|
}
|
|
|
|
if (title && title != "about:blank") {
|
|
// At this point, we now have a URI.
|
|
// Let's try to unescape it using a character set
|
|
// in case the URI is not ASCII.
|
|
try {
|
|
var characterSet = browser.contentDocument.characterSet;
|
|
const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
|
|
.getService(Components.interfaces.nsITextToSubURI);
|
|
title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
|
|
} catch(ex) { /* Do nothing. */ }
|
|
|
|
crop = "center";
|
|
|
|
} else // Still no title? Fall back to our untitled string.
|
|
title = this.mStringBundle.getString("tabs.untitled");
|
|
}
|
|
|
|
aTab.label = title;
|
|
aTab.setAttribute("crop", crop);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="setStripVisibilityTo">
|
|
<parameter name="aShow"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mStrip.collapsed = !aShow;
|
|
if (aShow) {
|
|
// XXXdwh temporary unclean dependency on specific menu items in navigator.xul
|
|
document.getElementById("menu_closeWindow").hidden = false;
|
|
document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.closeTab"));
|
|
if (!this.mTabbedMode)
|
|
this.enterTabbedMode();
|
|
}
|
|
else {
|
|
// XXXdwh temporary unclean dependency on specific menu items in navigator.xul
|
|
document.getElementById("menu_closeWindow").hidden = true;
|
|
document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.close"));
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getStripVisibility">
|
|
<body>
|
|
return !this.mStrip.collapsed;
|
|
</body>
|
|
</method>
|
|
|
|
<method name="enterTabbedMode">
|
|
<body>
|
|
<![CDATA[
|
|
this.mTabbedMode = true; // Welcome to multi-tabbed mode.
|
|
|
|
// Get the first tab all hooked up with a title listener and popup blocking listener.
|
|
this.mCurrentBrowser.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
|
|
|
|
var throbberElement = document.getElementById("navigator-throbber");
|
|
if (throbberElement && throbberElement.hasAttribute("busy")) {
|
|
this.mCurrentTab.setAttribute("busy", "true");
|
|
this.mIsBusy = true;
|
|
this.setTabTitleLoading(this.mCurrentTab);
|
|
this.updateIcon(this.mCurrentTab);
|
|
} else {
|
|
this.setTabTitle(this.mCurrentTab);
|
|
this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
|
|
}
|
|
|
|
var filter;
|
|
if (this.mTabFilters.length > 0) {
|
|
// Use the filter hooked up in our addProgressListener
|
|
filter = this.mTabFilters[0];
|
|
} else {
|
|
// create a filter and hook it up to our first browser
|
|
filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
|
|
.createInstance(Components.interfaces.nsIWebProgress);
|
|
this.mTabFilters[0] = filter;
|
|
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
|
}
|
|
|
|
// Remove all our progress listeners from the active browser's filter.
|
|
for (var i = 0; i < this.mProgressListeners.length; i++) {
|
|
var p = this.mProgressListeners[i];
|
|
if (p)
|
|
filter.removeProgressListener(p);
|
|
}
|
|
|
|
// Wire up a progress listener to our filter.
|
|
const listener = this.mTabProgressListener(this.mCurrentTab,
|
|
this.mCurrentBrowser,
|
|
false);
|
|
filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
|
this.mTabListeners[0] = listener;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="loadOneTab">
|
|
<parameter name="aURI"/>
|
|
<parameter name="aReferrerURI"/>
|
|
<parameter name="aCharset"/>
|
|
<parameter name="aPostData"/>
|
|
<parameter name="aLoadInBackground"/>
|
|
<parameter name="aAllowThirdPartyFixup"/>
|
|
<body>
|
|
<![CDATA[
|
|
var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
|
|
this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
|
|
var owner = bgLoad ? null : this.selectedTab;
|
|
var tab = this.addTab(aURI, aReferrerURI, aCharset, aPostData, owner,
|
|
aAllowThirdPartyFixup);
|
|
// Set newly selected tab after quick timeout, otherwise hideous focus problems
|
|
// can occur when "browser.tabs.loadInBackground" is false and presshell is not ready
|
|
if (!bgLoad) {
|
|
function selectNewForegroundTab(browser, tab) {
|
|
browser.selectedTab = tab;
|
|
}
|
|
setTimeout(selectNewForegroundTab, 0, getBrowser(), tab);
|
|
}
|
|
if (!bgLoad)
|
|
this.selectedTab = tab;
|
|
|
|
return tab;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="loadTabs">
|
|
<parameter name="aURIs"/>
|
|
<parameter name="aLoadInBackground"/>
|
|
<parameter name="aReplace"/>
|
|
<body><![CDATA[
|
|
// The tab selected after this new tab is closed (i.e. the new tab's
|
|
// "owner") is the next adjacent tab (i.e. not the previously viewed tab)
|
|
// when several urls are opened here (i.e. closing the first should select
|
|
// the next of many URLs opened) or if the pref to have UI links opened in
|
|
// the background is set (i.e. the link is not being opened modally)
|
|
//
|
|
// i.e.
|
|
// Number of URLs Load UI Links in BG Focus Last Viewed?
|
|
// == 1 false YES
|
|
// == 1 true NO
|
|
// > 1 false/true NO
|
|
var owner = (aURIs.length > 1) || aLoadInBackground ? null : gBrowser.selectedTab;
|
|
var firstTabAdded = null;
|
|
if (aReplace)
|
|
this.loadURI(aURIs[0], null, null);
|
|
else
|
|
firstTabAdded = gBrowser.addTab(aURIs[0], null, null, null, owner, false);
|
|
|
|
var tabNum = this.mTabContainer.selectedIndex;
|
|
for (var i = 1; i < aURIs.length; ++i) {
|
|
var tab = gBrowser.addTab(aURIs[i]);
|
|
if (aReplace)
|
|
this.moveTabTo(tab, ++tabNum);
|
|
}
|
|
|
|
if (!aLoadInBackground) {
|
|
if (firstTabAdded) {
|
|
// .selectedTab setter focuses the content area
|
|
this.selectedTab = firstTabAdded;
|
|
}
|
|
else
|
|
window.content.focus();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="addTab">
|
|
<parameter name="aURI"/>
|
|
<parameter name="aReferrerURI"/>
|
|
<parameter name="aCharset"/>
|
|
<parameter name="aPostData"/>
|
|
<parameter name="aOwner"/>
|
|
<parameter name="aAllowThirdPartyFixup"/>
|
|
<body>
|
|
<![CDATA[
|
|
this._browsers = null; // invalidate cache
|
|
|
|
if (!this.mTabbedMode)
|
|
this.enterTabbedMode();
|
|
|
|
// if we're adding tabs, we're past interrupt mode, ditch the owner
|
|
if (this.mCurrentTab.owner)
|
|
this.mCurrentTab.owner = null;
|
|
|
|
var t = document.createElementNS(
|
|
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
|
"tab");
|
|
|
|
var blank = (aURI == "about:blank");
|
|
|
|
if (blank)
|
|
t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
|
|
else
|
|
t.setAttribute("label", aURI);
|
|
|
|
t.setAttribute("crop", "end");
|
|
t.maxWidth = 250;
|
|
t.minWidth = this.mTabContainer.mTabMinWidth;
|
|
t.width = 0;
|
|
t.setAttribute("flex", "100");
|
|
t.setAttribute("validate", "never");
|
|
t.setAttribute("onerror", "this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image')); this.removeAttribute('image');");
|
|
t.className = "tabbrowser-tab";
|
|
|
|
this.mTabContainer.appendChild(t);
|
|
|
|
if (document.defaultView
|
|
.getComputedStyle(this.mTabContainer, "")
|
|
.direction == "rtl") {
|
|
/* In RTL UI, the tab is visually added to the left side of the
|
|
* tabstrip. This means the tabstip has to be scrolled back in
|
|
* order to make sure the same set of tabs is visible before and
|
|
* after the new tab is added */
|
|
|
|
this.mTabContainer.mTabstrip.scrollBoxObject
|
|
.scrollBy(this.mTabContainer.firstChild.boxObject.width, 0);
|
|
}
|
|
|
|
// invalidate cache, because mTabContainer is about to change
|
|
this._browsers = null;
|
|
|
|
// If this new tab is owned by another, assert that relationship
|
|
if (aOwner !== undefined && aOwner !== null) {
|
|
t.owner = aOwner;
|
|
|
|
var self = this;
|
|
function attrChanged(event) {
|
|
if (event.attrName == "selectedIndex" &&
|
|
event.prevValue != event.newValue)
|
|
self.resetOwner(parseInt(event.prevValue));
|
|
}
|
|
if (!this.mTabChangedListenerAdded) {
|
|
this.mTabBox.addEventListener("DOMAttrModified", attrChanged, false);
|
|
this.mTabChangedListenerAdded = true;
|
|
}
|
|
}
|
|
|
|
var b = document.createElementNS(
|
|
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
|
"browser");
|
|
b.setAttribute("type", "content-targetable");
|
|
b.setAttribute("message", "true");
|
|
b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
|
|
b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
|
|
if (this.hasAttribute("autocompletepopup"))
|
|
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
|
|
|
|
// Add the Message and the Browser to the box
|
|
var notificationbox = document.createElementNS(
|
|
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
|
"notificationbox");
|
|
notificationbox.setAttribute("flex", "1");
|
|
notificationbox.appendChild(b);
|
|
b.setAttribute("flex", "1");
|
|
this.mPanelContainer.appendChild(notificationbox);
|
|
|
|
b.addEventListener("DOMTitleChanged", this.onTitleChanged, true);
|
|
|
|
if (this.mStrip.collapsed)
|
|
this.setStripVisibilityTo(true);
|
|
|
|
this.mPrefs.setBoolPref("browser.tabs.forceHide", false);
|
|
|
|
// wire up a progress listener for the new browser object.
|
|
var position = this.mTabContainer.childNodes.length-1;
|
|
var tabListener = this.mTabProgressListener(t, b, blank);
|
|
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
|
|
.createInstance(Components.interfaces.nsIWebProgress);
|
|
filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
|
b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
|
this.mTabListeners[position] = tabListener;
|
|
this.mTabFilters[position] = filter;
|
|
|
|
b._fastFind = this.fastFind;
|
|
|
|
var uniqueId = "panel" + Date.now() + position;
|
|
this.mPanelContainer.lastChild.id = uniqueId;
|
|
t.linkedPanel = uniqueId;
|
|
t.linkedBrowser = b;
|
|
t._tPos = position;
|
|
if (t.previousSibling.selected)
|
|
t.setAttribute("afterselected", true);
|
|
|
|
if (!blank) {
|
|
// pretend the user typed this so it'll be available till
|
|
// the document successfully loads
|
|
b.userTypedValue = aURI;
|
|
|
|
if (aPostData === undefined)
|
|
aPostData = null;
|
|
const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
|
|
var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
|
|
if (aAllowThirdPartyFixup) {
|
|
flags = nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
|
}
|
|
b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
|
|
}
|
|
|
|
// |setTimeout| here to ensure we're post reflow
|
|
var _delayedUpdate = function(aTabContainer) {
|
|
aTabContainer.adjustTabstrip();
|
|
|
|
if (aTabContainer.selectedItem != t)
|
|
aTabContainer._notifyBackgroundTab(t);
|
|
|
|
// XXXmano: this is a temporary workaround to bug 343585
|
|
// We need to manually update the scroll buttons disabled state
|
|
// if a tab was inserted to the overflow area or removed from it
|
|
// without any scrolling and when the tabbar has already
|
|
// overflowed.
|
|
aTabContainer.mTabstrip._updateScrollButtonsDisabledState();
|
|
}
|
|
setTimeout(_delayedUpdate, 0, this.mTabContainer);
|
|
|
|
// Dispatch a new tab notification. We do this once we're
|
|
// entirely done, so that things are in a consistent state
|
|
// even if the event listener opens or closes tabs.
|
|
var evt = document.createEvent("Events");
|
|
evt.initEvent("TabOpen", true, false);
|
|
t.dispatchEvent(evt);
|
|
|
|
return t;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="warnAboutClosingTabs">
|
|
<parameter name="aAll"/>
|
|
<body>
|
|
<![CDATA[
|
|
var numTabs = this.mTabContainer.childNodes.length;
|
|
var reallyClose = true;
|
|
if (numTabs <= 1)
|
|
return reallyClose;
|
|
|
|
const pref = "browser.tabs.warnOnClose";
|
|
var shouldPrompt = this.mPrefs.getBoolPref(pref);
|
|
|
|
if (shouldPrompt) {
|
|
var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
|
|
.getService(Components.interfaces.nsIPromptService);
|
|
|
|
//default to true: if it were false, we wouldn't get this far
|
|
var warnOnClose = { value:true };
|
|
var bundle = this.mStringBundle;
|
|
var tabsToClose = numTabs; //number of tabs to be removed
|
|
if (!aAll)
|
|
--tabsToClose;
|
|
|
|
var messageKey = (tabsToClose == 1) ? "tabs.closeWarningOne" : "tabs.closeWarningMultiple";
|
|
var closeKey = (tabsToClose == 1) ? "tabs.closeButtonOne" : "tabs.closeButtonMultiple";
|
|
// focus the window before prompting.
|
|
// this will raise any minimized window, which will
|
|
// make it obvious which window the prompt is for and will
|
|
// solve the problem of windows "obscuring" the prompt.
|
|
// see bug #350299 for more details
|
|
window.focus();
|
|
var buttonPressed = promptService.confirmEx(window,
|
|
bundle.getString('tabs.closeWarningTitle'),
|
|
bundle.getFormattedString(messageKey, [tabsToClose]),
|
|
(promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
|
|
+ (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
|
|
bundle.getString(closeKey),
|
|
null, null,
|
|
bundle.getString('tabs.closeWarningPromptMe'),
|
|
warnOnClose);
|
|
reallyClose = (buttonPressed == 0);
|
|
// don't set the pref unless they press OK and it's false
|
|
if (reallyClose && !warnOnClose.value)
|
|
this.mPrefs.setBoolPref(pref, false);
|
|
}
|
|
return reallyClose;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeAllTabsBut">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (this.warnAboutClosingTabs(false)) {
|
|
if (aTab.localName != "tab")
|
|
aTab = this.mCurrentTab;
|
|
else
|
|
this.mTabContainer.selectedItem = aTab;
|
|
|
|
var childNodes = this.mTabContainer.childNodes;
|
|
|
|
for (var i = childNodes.length - 1; i >= 0; --i) {
|
|
if (childNodes[i] != aTab)
|
|
this.removeTab(childNodes[i]);
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeCurrentTab">
|
|
<body>
|
|
<![CDATA[
|
|
return this.removeTab(this.mCurrentTab);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="resetOwner">
|
|
<parameter name="oldIndex"/>
|
|
<body>
|
|
<![CDATA[
|
|
// Reset the owner property, since we're leaving the modally opened
|
|
// tab for another.
|
|
if (oldIndex < this.mTabContainer.childNodes.length) {
|
|
var tab = this.mTabContainer.childNodes[oldIndex];
|
|
tab.owner = null;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeTab">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
this._browsers = null; // invalidate cache
|
|
if (aTab.localName != "tab")
|
|
aTab = this.mCurrentTab;
|
|
|
|
var l = this.mTabContainer.childNodes.length;
|
|
if (l == 1 && this.mPrefs.getBoolPref("browser.tabs.autoHide")) {
|
|
// hide the tab bar
|
|
this.mPrefs.setBoolPref("browser.tabs.forceHide", true);
|
|
this.setStripVisibilityTo(false);
|
|
return;
|
|
}
|
|
|
|
var ds = this.getBrowserForTab(aTab).docShell;
|
|
if (ds.contentViewer && !ds.contentViewer.permitUnload())
|
|
return;
|
|
|
|
// see notes in addTab
|
|
var _delayedUpdate = function(aTabContainer) {
|
|
aTabContainer.adjustTabstrip();
|
|
aTabContainer.mTabstrip._updateScrollButtonsDisabledState();
|
|
}
|
|
setTimeout(_delayedUpdate, 0, this.mTabContainer);
|
|
|
|
if (l == 1) {
|
|
// add a new blank tab to replace the one we're about to close
|
|
// (this ensures that the remaining tab is as good as new)
|
|
this.addTab("about:blank");
|
|
l++;
|
|
}
|
|
else if (l == 2) {
|
|
var autohide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
|
|
var tabStripHide = !window.toolbar.visible;
|
|
if (autohide || tabStripHide)
|
|
this.setStripVisibilityTo(false);
|
|
}
|
|
|
|
// We're committed to closing the tab now.
|
|
// Dispatch a notification.
|
|
// We dispatch it before any teardown so that event listeners can
|
|
// inspect the tab that's about to close.
|
|
var evt = document.createEvent("Events");
|
|
evt.initEvent("TabClose", true, false);
|
|
aTab.dispatchEvent(evt);
|
|
|
|
var index = -1;
|
|
if (this.mCurrentTab == aTab)
|
|
index = this.mTabContainer.selectedIndex;
|
|
else {
|
|
// Find and locate the tab in our list.
|
|
for (var i = 0; i < l; i++)
|
|
if (this.mTabContainer.childNodes[i] == aTab)
|
|
index = i;
|
|
}
|
|
|
|
// Remove the tab's filter and progress listener.
|
|
const filter = this.mTabFilters[index];
|
|
var oldBrowser = this.getBrowserAtIndex(index);
|
|
oldBrowser.webProgress.removeProgressListener(filter);
|
|
filter.removeProgressListener(this.mTabListeners[index]);
|
|
this.mTabFilters.splice(index, 1);
|
|
this.mTabListeners.splice(index, 1);
|
|
|
|
// Remove our title change and blocking listeners
|
|
oldBrowser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
|
|
|
|
// We are no longer the primary content area.
|
|
oldBrowser.setAttribute("type", "content-targetable");
|
|
|
|
// Get the index of the tab we're removing before unselecting it
|
|
var currentIndex = this.mTabContainer.selectedIndex;
|
|
|
|
var oldTab = aTab;
|
|
|
|
// clean up the before/afterselected attributes before removing the tab
|
|
oldTab.selected = false;
|
|
|
|
// Remove this tab as the owner of any other tabs, since it's going away.
|
|
for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
|
|
var tab = this.mTabContainer.childNodes[i];
|
|
if ("owner" in tab && tab.owner == oldTab)
|
|
// |tab| is a child of the tab we're removing, make it an orphan
|
|
tab.owner = null;
|
|
}
|
|
|
|
// Because of the way XBL works (fields just set JS
|
|
// properties on the element) and the code we have in place
|
|
// to preserve the JS objects for any elements that have
|
|
// JS properties set on them, the browser element won't be
|
|
// destroyed until the document goes away. So we force a
|
|
// cleanup ourselves.
|
|
// This has to happen before we remove the child so that the
|
|
// XBL implementation of nsIObserver still works. But
|
|
// clearing focusedWindow happens below because it gets
|
|
// reset by updateCurrentBrowser.
|
|
oldBrowser.destroy();
|
|
|
|
// Remove the tab
|
|
this.mTabContainer.removeChild(oldTab);
|
|
// invalidate cache, because mTabContainer is about to change
|
|
this._browsers = null;
|
|
this.mPanelContainer.removeChild(oldBrowser.parentNode);
|
|
|
|
try {
|
|
// if we're at the right side (and not the logical end,
|
|
// which is why this works for both LTR and RTL)
|
|
// of the tabstrip, we need to ensure that we stay
|
|
// completely scrolled to the right side
|
|
var tabStrip = this.mTabContainer.mTabstrip;
|
|
var scrollPos = {};
|
|
tabStrip.scrollBoxObject.getPosition(scrollPos, {});
|
|
var scrolledSize = {};
|
|
tabStrip.scrollBoxObject.getScrolledSize(scrolledSize, {});
|
|
|
|
if (scrollPos.value + tabStrip.boxObject.width >=
|
|
scrolledSize.value) {
|
|
tabStrip.scrollByPixels(-1 * this.mTabContainer.firstChild
|
|
.boxObject.width);
|
|
}
|
|
}
|
|
catch (ex) {
|
|
}
|
|
|
|
// Find the tab to select
|
|
var newIndex = -1;
|
|
if (currentIndex > index)
|
|
newIndex = currentIndex-1;
|
|
else if (currentIndex < index)
|
|
newIndex = currentIndex;
|
|
else {
|
|
if ("owner" in oldTab && oldTab.owner &&
|
|
this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
|
|
for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
|
|
tab = this.mTabContainer.childNodes[i];
|
|
if (tab == oldTab.owner) {
|
|
newIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (newIndex == -1)
|
|
newIndex = (index == l - 1) ? index - 1 : index;
|
|
}
|
|
|
|
// Select the new tab
|
|
this.selectedTab = this.mTabContainer.childNodes[newIndex];
|
|
|
|
for (i = oldTab._tPos; i < this.mTabContainer.childNodes.length; i++) {
|
|
this.mTabContainer.childNodes[i]._tPos = i;
|
|
}
|
|
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
|
|
this.mCurrentTab.selected = true;
|
|
|
|
this.updateCurrentBrowser();
|
|
|
|
// see comment above destroy above
|
|
oldBrowser.focusedWindow = null;
|
|
oldBrowser.focusedElement = null;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="reloadAllTabs">
|
|
<body>
|
|
<![CDATA[
|
|
var l = this.mPanelContainer.childNodes.length;
|
|
for (var i = 0; i < l; i++) {
|
|
try {
|
|
this.getBrowserAtIndex(i).reload();
|
|
} catch (e) {
|
|
// ignore failure to reload so others will be reloaded
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="reloadTab">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aTab.localName != "tab")
|
|
aTab = this.mCurrentTab;
|
|
|
|
this.getBrowserForTab(aTab).reload();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onTabBarDblClick">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
// See hack note in the tabbrowser-close-button binding
|
|
if (!this._blockDblClick && aEvent.button == 0 &&
|
|
aEvent.originalTarget.localName == "box") {
|
|
// xxx this needs to check that we're in the empty area of the tabstrip
|
|
var e = document.createEvent("Events");
|
|
e.initEvent("NewTab", true, true);
|
|
this.dispatchEvent(e);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="addProgressListener">
|
|
<parameter name="aListener"/>
|
|
<parameter name="aMask"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.mAddProgressListenerWasCalled) {
|
|
this.mAddProgressListenerWasCalled = true;
|
|
var autoHide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
|
|
var forceHide = this.mPrefs.getBoolPref("browser.tabs.forceHide");
|
|
var tabStripHide = !window.toolbar.visible;
|
|
if (!autoHide && !forceHide && !tabStripHide)
|
|
this.setStripVisibilityTo(true);
|
|
}
|
|
|
|
if (!this.mTabbedMode && this.mProgressListeners.length == 1) {
|
|
// If we are adding a 2nd progress listener, we need to enter tabbed mode
|
|
// because the browser status filter can only handle one progress listener.
|
|
// In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
|
|
this.enterTabbedMode();
|
|
}
|
|
|
|
this.mProgressListeners.push(aListener);
|
|
|
|
if (!this.mTabbedMode) {
|
|
// If someone does this:
|
|
// addProgressListener, removeProgressListener, addProgressListener
|
|
// don't create a new filter; reuse the existing filter.
|
|
if (this.mTabFilters.length == 0) {
|
|
// hook a filter up to our first browser
|
|
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
|
|
.createInstance(Components.interfaces.nsIWebProgress);
|
|
this.mTabFilters[0] = filter;
|
|
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
|
}
|
|
|
|
// Directly hook the listener up to the filter for better performance
|
|
this.mTabFilters[0].addProgressListener(aListener, aMask);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="removeProgressListener">
|
|
<parameter name="aListener"/>
|
|
<body>
|
|
<![CDATA[
|
|
for (var i = 0; i < this.mProgressListeners.length; i++) {
|
|
if (this.mProgressListeners[i] == aListener) {
|
|
this.mProgressListeners.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!this.mTabbedMode)
|
|
// Don't forget to remove it from the filter we hooked it up to
|
|
this.mTabFilters[0].removeProgressListener(aListener);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getBrowserForTab">
|
|
<parameter name="aTab"/>
|
|
<body>
|
|
<![CDATA[
|
|
return aTab.linkedBrowser;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="tabContainer">
|
|
<getter>
|
|
return this.mTabContainer;
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="selectedTab">
|
|
<getter>
|
|
return this.mTabBox.selectedTab;
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
// Update the tab
|
|
this.mTabBox.selectedTab = val;
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<property name="selectedBrowser"
|
|
onget="return this.mCurrentBrowser;"
|
|
readonly="true"/>
|
|
|
|
<property name="browsers" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
if (!this._browsers) {
|
|
var browsers = [];
|
|
var i;
|
|
browsers.item = function(i) {return this[i];}
|
|
for (i = 0; i < this.mTabContainer.childNodes.length; i++)
|
|
browsers.push(this.mTabContainer.childNodes[i].linkedBrowser);
|
|
this._browsers = browsers;
|
|
}
|
|
return this._browsers;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<!-- Drag and drop observer API -->
|
|
<method name="onDragStart">
|
|
<parameter name="aEvent"/>
|
|
<parameter name="aXferData"/>
|
|
<parameter name="aDragAction"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aEvent.target.localName == "tab" &&
|
|
aEvent.originalTarget.localName != "toolbarbutton") {
|
|
aXferData.data = new TransferData();
|
|
|
|
var URI = this.getBrowserForTab(aEvent.target).currentURI;
|
|
if (URI) {
|
|
aXferData.data.addDataForFlavour("text/x-moz-url", URI.spec + "\n" + aEvent.target.label);
|
|
aXferData.data.addDataForFlavour("text/unicode", URI.spec);
|
|
aXferData.data.addDataForFlavour("text/html", '<a href="' + URI.spec + '">' + aEvent.target.label + '</a>');
|
|
} else {
|
|
aXferData.data.addDataForFlavour("text/unicode", "about:blank");
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="canDrop">
|
|
<parameter name="aEvent"/>
|
|
<parameter name="aDragSession"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aDragSession.sourceNode &&
|
|
aDragSession.sourceNode.parentNode == this.mTabContainer &&
|
|
(aEvent.screenX >= aDragSession.sourceNode.boxObject.screenX &&
|
|
aEvent.screenX <= (aDragSession.sourceNode.boxObject.screenX +
|
|
aDragSession.sourceNode.boxObject.width)))
|
|
return false;
|
|
return true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onDragOver">
|
|
<parameter name="aEvent"/>
|
|
<parameter name="aFlavour"/>
|
|
<parameter name="aDragSession"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aDragSession.sourceNode) {
|
|
var tabStrip = this.mTabContainer.mTabstrip;
|
|
|
|
// autoscroll the tab strip if we drag over the scroll
|
|
// buttons, even if we aren't dragging a tab, but then
|
|
// return to avoid drawing the drop indicator
|
|
var pixelsToScroll = 0;
|
|
|
|
var targetAnonid = aEvent.originalTarget.getAttribute("anonid");
|
|
if (targetAnonid == "scrollbutton-up") {
|
|
pixelsToScroll = tabStrip.scrollIncrement * -1;
|
|
tabStrip.scrollByPixels(pixelsToScroll);
|
|
}
|
|
else if (targetAnonid == "scrollbutton-down" ||
|
|
(targetAnonid == "alltabs-button" &&
|
|
this.mTabContainer.getAttribute("overflow") == "true")) {
|
|
pixelsToScroll = tabStrip.scrollIncrement;
|
|
tabStrip.scrollByPixels(pixelsToScroll);
|
|
}
|
|
|
|
var isTabDrag = (aDragSession.sourceNode.parentNode == this.mTabContainer);
|
|
if (!isTabDrag)
|
|
return;
|
|
|
|
var newIndex = this.getNewIndex(aEvent);
|
|
|
|
var ib = this.mTabDropIndicatorBar;
|
|
var ind = ib.firstChild;
|
|
ib.setAttribute('dragging',
|
|
aDragSession.canDrop ? 'true' : 'false');
|
|
|
|
var tabStripBoxObject = tabStrip.scrollBoxObject;
|
|
var halfIndWidth = Math.floor((ind.boxObject.width + 1) / 2);
|
|
if (window.getComputedStyle(this.parentNode, null)
|
|
.direction == "ltr") {
|
|
var newMarginLeft;
|
|
var minMarginLeft = tabStripBoxObject.x - halfIndWidth -
|
|
ib.boxObject.x;
|
|
// make sure we don't place the tab drop indicator past the
|
|
// edge, or the containing box will flex and stretch
|
|
// the tab drop indicator bar, which will flex the url bar.
|
|
// XXX todo
|
|
// just use first value if you can figure out how to get
|
|
// the tab drop indicator to crop instead of flex and stretch
|
|
// the tab drop indicator bar.
|
|
var maxMarginLeft = Math.min(
|
|
(minMarginLeft + tabStripBoxObject.width),
|
|
(ib.boxObject.x + ib.boxObject.width - ind.boxObject.width));
|
|
|
|
// if we are scrolling, put the drop indicator at the edge
|
|
// so that it doesn't jump while scrolling
|
|
if (pixelsToScroll > 0)
|
|
newMarginLeft = maxMarginLeft;
|
|
else if (pixelsToScroll < 0)
|
|
newMarginLeft = minMarginLeft;
|
|
else {
|
|
if (newIndex == this.mTabs.length) {
|
|
newMarginLeft = this.mTabs[newIndex-1].boxObject.screenX +
|
|
this.mTabs[newIndex-1].boxObject.width -
|
|
this.boxObject.screenX - halfIndWidth;
|
|
} else {
|
|
newMarginLeft = this.mTabs[newIndex].boxObject.screenX -
|
|
this.boxObject.screenX - halfIndWidth;
|
|
}
|
|
|
|
// ensure we never place the drop indicator beyond
|
|
// our limits
|
|
if (newMarginLeft < minMarginLeft)
|
|
newMarginLeft = minMarginLeft;
|
|
else if (newMarginLeft > maxMarginLeft)
|
|
newMarginLeft = maxMarginLeft;
|
|
}
|
|
ind.style.marginLeft = newMarginLeft + 'px';
|
|
} else {
|
|
var newMarginRight;
|
|
var minMarginRight = tabStripBoxObject.x - halfIndWidth -
|
|
ib.boxObject.x;
|
|
// make sure we don't place the tab drop indicator past the
|
|
// edge, or the containing box will flex and stretch
|
|
// the tab drop indicator bar, which will flex the url bar.
|
|
// XXX todo
|
|
// just use first value if you can figure out how to get
|
|
// the tab drop indicator to crop instead of flex and stretch
|
|
// the tab drop indicator bar.
|
|
var maxMarginRight = Math.min(
|
|
(minMarginRight + tabStripBoxObject.width),
|
|
(ib.boxObject.x + ib.boxObject.width - ind.boxObject.width));
|
|
|
|
// if we are scrolling, put the drop indicator at the edge
|
|
// so that it doesn't jump while scrolling
|
|
if (pixelsToScroll > 0)
|
|
newMarginRight = maxMarginRight;
|
|
else if (pixelsToScroll < 0)
|
|
newMarginRight = minMarginRight;
|
|
else {
|
|
if (newIndex == this.mTabs.length) {
|
|
newMarginRight = this.boxObject.width +
|
|
this.boxObject.screenX -
|
|
this.mTabs[newIndex-1].boxObject.screenX -
|
|
halfIndWidth;
|
|
} else {
|
|
newMarginRight = this.boxObject.width +
|
|
this.boxObject.screenX -
|
|
this.mTabs[newIndex].boxObject.screenX -
|
|
this.mTabs[newIndex].boxObject.width -
|
|
halfIndWidth;
|
|
}
|
|
|
|
// ensure we never place the drop indicator beyond
|
|
// our limits
|
|
if (newMarginRight < minMarginRight)
|
|
newMarginRight = minMarginRight;
|
|
else if (newMarginRight > maxMarginRight)
|
|
newMarginRight = maxMarginRight;
|
|
}
|
|
ind.style.marginRight = newMarginRight + 'px';
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onDrop">
|
|
<parameter name="aEvent"/>
|
|
<parameter name="aXferData"/>
|
|
<parameter name="aDragSession"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aDragSession.sourceNode && aDragSession.sourceNode.parentNode == this.mTabContainer) {
|
|
var newIndex = this.getNewIndex(aEvent);
|
|
var oldIndex = aDragSession.sourceNode._tPos;
|
|
|
|
if (newIndex > oldIndex)
|
|
newIndex--;
|
|
if (newIndex != oldIndex)
|
|
this.moveTabTo(this.mTabs[oldIndex], newIndex);
|
|
} else {
|
|
var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);
|
|
|
|
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
|
|
// Also disallow dropping javascript: or data: urls--bail out
|
|
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
|
|
/^\s*(javascript|data):/.test(url))
|
|
return;
|
|
|
|
this.dragDropSecurityCheck(aEvent, aDragSession, url);
|
|
|
|
var bgLoad = true;
|
|
try {
|
|
bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
|
|
}
|
|
catch (e) { }
|
|
|
|
if (aEvent.shiftKey)
|
|
bgLoad = !bgLoad;
|
|
|
|
if (document.getBindingParent(aEvent.originalTarget).localName != "tab") {
|
|
// We're adding a new tab.
|
|
this.loadOneTab(getShortcutOrURI(url), null, null, null, bgLoad, false);
|
|
}
|
|
else {
|
|
// Load in an existing tab.
|
|
var tab = aEvent.target;
|
|
try {
|
|
this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
|
|
if (this.mCurrentTab != tab && !bgLoad)
|
|
this.selectedTab = tab;
|
|
} catch(ex) {
|
|
// Just ignore invalid urls
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onDragExit">
|
|
<parameter name="aEvent"/>
|
|
<parameter name="aDragSession"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aDragSession.sourceNode &&
|
|
aDragSession.sourceNode.parentNode == this.mTabContainer &&
|
|
aDragSession.canDrop) {
|
|
var target = aEvent.relatedTarget;
|
|
while (target && target != this.mStrip)
|
|
target = target.parentNode;
|
|
if (target)
|
|
return;
|
|
}
|
|
this.mTabDropIndicatorBar.setAttribute('dragging','false');
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getSupportedFlavours">
|
|
<body>
|
|
<![CDATA[
|
|
var flavourSet = new FlavourSet();
|
|
flavourSet.appendFlavour("text/x-moz-url");
|
|
flavourSet.appendFlavour("text/unicode");
|
|
flavourSet.appendFlavour("application/x-moz-file", "nsIFile");
|
|
return flavourSet;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="moveTabTo">
|
|
<parameter name="aTab"/>
|
|
<parameter name="aIndex"/>
|
|
<body>
|
|
<![CDATA[
|
|
this._browsers = null; // invalidate cache
|
|
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
|
|
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
|
|
|
|
var oldPosition = aTab._tPos;
|
|
|
|
aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
|
|
this.mCurrentTab.selected = false;
|
|
this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes[aIndex]);
|
|
// invalidate cache, because mTabContainer is about to change
|
|
this._browsers = null;
|
|
|
|
var i;
|
|
for (i = 0; i < this.mTabContainer.childNodes.length; i++) {
|
|
this.mTabContainer.childNodes[i]._tPos = i;
|
|
this.mTabContainer.childNodes[i].selected = false;
|
|
}
|
|
this.mCurrentTab.selected = true;
|
|
this.mTabContainer.mTabstrip.scrollBoxObject.ensureElementIsVisible(this.mCurrentTab);
|
|
|
|
var evt = document.createEvent("UIEvents");
|
|
evt.initUIEvent("TabMove", true, false, window, oldPosition);
|
|
aTab.dispatchEvent(evt);
|
|
|
|
return aTab;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="getNewIndex">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var i;
|
|
if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
|
|
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
|
|
if (aEvent.screenX < this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
|
|
return i;
|
|
} else {
|
|
for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
|
|
if (aEvent.screenX > this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
|
|
return i;
|
|
}
|
|
|
|
return this.mTabs.length;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
|
|
<method name="moveTabForward">
|
|
<body>
|
|
<![CDATA[
|
|
var tabPos = this.mCurrentTab._tPos;
|
|
if (tabPos < this.browsers.length - 1) {
|
|
this.moveTabTo(this.mCurrentTab, tabPos + 1);
|
|
this.mCurrentTab.focus();
|
|
}
|
|
else if (this.arrowKeysShouldWrap)
|
|
this.moveTabToStart();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="moveTabBackward">
|
|
<body>
|
|
<![CDATA[
|
|
var tabPos = this.mCurrentTab._tPos;
|
|
if (tabPos > 0) {
|
|
this.moveTabTo(this.mCurrentTab, tabPos - 1);
|
|
this.mCurrentTab.focus();
|
|
}
|
|
else if (this.arrowKeysShouldWrap)
|
|
this.moveTabToEnd();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="moveTabToStart">
|
|
<body>
|
|
<![CDATA[
|
|
var tabPos = this.mCurrentTab._tPos;
|
|
if (tabPos > 0) {
|
|
this.moveTabTo(this.mCurrentTab, 0);
|
|
this.mCurrentTab.focus();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="moveTabToEnd">
|
|
<body>
|
|
<![CDATA[
|
|
var tabPos = this.mCurrentTab._tPos;
|
|
if (tabPos < this.browsers.length - 1) {
|
|
this.moveTabTo(this.mCurrentTab,
|
|
this.browsers.length - 1);
|
|
this.mCurrentTab.focus();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="moveTabOver">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var direction = window.getComputedStyle(this.parentNode, null).direction;
|
|
if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
|
|
(direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
|
|
this.moveTabForward();
|
|
else
|
|
this.moveTabBackward();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
|
|
MAKE SURE TO ADD IT HERE AS WELL. -->
|
|
<property name="canGoBack"
|
|
onget="return this.mCurrentBrowser.canGoBack;"
|
|
readonly="true"/>
|
|
|
|
<property name="canGoForward"
|
|
onget="return this.mCurrentBrowser.canGoForward;"
|
|
readonly="true"/>
|
|
|
|
<method name="goBack">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.goBack();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="goForward">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.goForward();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="reload">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.reload();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="reloadWithFlags">
|
|
<parameter name="aFlags"/>
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.reloadWithFlags(aFlags);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="stop">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.stop();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- throws exception for unknown schemes -->
|
|
<method name="loadURI">
|
|
<parameter name="aURI"/>
|
|
<parameter name="aReferrerURI"/>
|
|
<parameter name="aCharset"/>
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- throws exception for unknown schemes -->
|
|
<method name="loadURIWithFlags">
|
|
<parameter name="aURI"/>
|
|
<parameter name="aFlags"/>
|
|
<parameter name="aReferrerURI"/>
|
|
<parameter name="aCharset"/>
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="goHome">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.goHome();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<property name="homePage">
|
|
<getter>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.homePage;
|
|
]]>
|
|
</getter>
|
|
<setter>
|
|
<![CDATA[
|
|
this.mCurrentBrowser.homePage = val;
|
|
return val;
|
|
]]>
|
|
</setter>
|
|
</property>
|
|
|
|
<method name="gotoIndex">
|
|
<parameter name="aIndex"/>
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.gotoIndex(aIndex);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="attachFormFill">
|
|
<body><![CDATA[
|
|
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
|
|
var cb = this.getBrowserAtIndex(i);
|
|
cb.attachFormFill();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="detachFormFill">
|
|
<body><![CDATA[
|
|
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
|
|
var cb = this.getBrowserAtIndex(i);
|
|
cb.detachFormFill();
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<property name="pageReport"
|
|
onget="return this.mCurrentBrowser.pageReport;"
|
|
readonly="true"/>
|
|
|
|
<property name="currentURI"
|
|
onget="return this.mCurrentBrowser.currentURI;"
|
|
readonly="true"/>
|
|
|
|
<field name="_fastFind">null</field>
|
|
<property name="fastFind"
|
|
readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
if (!this._fastFind) {
|
|
this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
|
|
.createInstance(Components.interfaces.nsITypeAheadFind);
|
|
this._fastFind.init(this.docShell);
|
|
}
|
|
return this._fastFind;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<property name="findString"
|
|
onget="return this.mCurrentBrowser.findString;"
|
|
readonly="true"/>
|
|
|
|
<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="securityUI"
|
|
onget="return this.mCurrentBrowser.securityUI;"
|
|
readonly="true"/>
|
|
|
|
<method name="find">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.find();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="findAgain">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.findAgain();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="findPrevious">
|
|
<body>
|
|
<![CDATA[
|
|
return this.mCurrentBrowser.findPrevious();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="dragDropSecurityCheck">
|
|
<parameter name="aEvent"/>
|
|
<parameter name="aDragSession"/>
|
|
<parameter name="aUri"/>
|
|
<body>
|
|
<![CDATA[
|
|
// Do a security check for drag n' drop. Make sure the
|
|
// source document can load the dragged link.
|
|
var sourceDoc = aDragSession.sourceDocument;
|
|
|
|
if (sourceDoc) {
|
|
// Strip leading and trailing whitespace, then try to
|
|
// create a URI from the dropped string. If that
|
|
// succeeds, we're dropping a URI and we need to do a
|
|
// security check to make sure the source document can
|
|
// load the dropped URI. We don't so much care about
|
|
// creating the real URI here (i.e. encoding differences
|
|
// etc don't matter), we just want to know if aUri
|
|
// really is a URI.
|
|
|
|
var uriStr = aUri.replace(/^\s*|\s*$/g, '');
|
|
var uri = null;
|
|
|
|
try {
|
|
uri = Components.classes["@mozilla.org/network/io-service;1"]
|
|
.getService(Components.interfaces.nsIIOService)
|
|
.newURI(uriStr, null, null);
|
|
} catch (e) {
|
|
}
|
|
|
|
if (uri) {
|
|
// aUri is a URI, do the security check.
|
|
var sourceURI = sourceDoc.documentURI;
|
|
|
|
const nsIScriptSecurityManager =
|
|
Components.interfaces.nsIScriptSecurityManager;
|
|
var secMan =
|
|
Components.classes["@mozilla.org/scriptsecuritymanager;1"]
|
|
.getService(nsIScriptSecurityManager);
|
|
|
|
try {
|
|
secMan.checkLoadURIStr(sourceURI, uriStr,
|
|
nsIScriptSecurityManager.STANDARD);
|
|
} catch (e) {
|
|
// Stop event propagation right here.
|
|
aEvent.stopPropagation();
|
|
|
|
throw "Drop of " + aUri + " denied.";
|
|
}
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<field name="_keyEventHandler" readonly="true">
|
|
<![CDATA[({
|
|
tabbrowser: this,
|
|
handleEvent: function handleEvent(aEvent) {
|
|
if (!aEvent.isTrusted) {
|
|
// Don't let untrusted events mess with tabs.
|
|
return;
|
|
}
|
|
|
|
if (('shiftKey' in aEvent && aEvent.shiftKey) ||
|
|
('altKey' in aEvent && aEvent.altKey))
|
|
return;
|
|
#ifdef XP_MACOSX
|
|
if ('metaKey' in aEvent && aEvent.metaKey) {
|
|
#else
|
|
if (('ctrlKey' in aEvent && aEvent.ctrlKey) &&
|
|
!('metaKey' in aEvent && aEvent.metaKey)) {
|
|
if (aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
|
|
this.tabbrowser.mTabBox.handleCtrlPageUpDown) {
|
|
this.tabbrowser.removeCurrentTab();
|
|
|
|
aEvent.stopPropagation();
|
|
aEvent.preventDefault();
|
|
return;
|
|
}
|
|
#endif
|
|
if (aEvent.target.localName == "tabbrowser") {
|
|
switch (aEvent.keyCode) {
|
|
case KeyEvent.DOM_VK_UP:
|
|
this.tabbrowser.moveTabBackward();
|
|
break;
|
|
case KeyEvent.DOM_VK_DOWN:
|
|
this.tabbrowser.moveTabForward();
|
|
break;
|
|
case KeyEvent.DOM_VK_RIGHT:
|
|
case KeyEvent.DOM_VK_LEFT:
|
|
this.tabbrowser.moveTabOver(aEvent);
|
|
break;
|
|
case KeyEvent.DOM_VK_HOME:
|
|
this.tabbrowser.moveTabToStart();
|
|
break;
|
|
case KeyEvent.DOM_VK_END:
|
|
this.tabbrowser.moveTabToEnd();
|
|
break;
|
|
default:
|
|
// Stop the keypress event for the above keyboard
|
|
// shortcuts only.
|
|
return;
|
|
}
|
|
aEvent.stopPropagation();
|
|
aEvent.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
})]]>
|
|
</field>
|
|
|
|
<property name="userTypedClear"
|
|
onget="return this.mCurrentBrowser.userTypedClear;"
|
|
onset="return this.mCurrentBrowser.userTypedClear = val;"/>
|
|
|
|
<property name="userTypedValue"
|
|
onget="return this.mCurrentBrowser.userTypedValue;"
|
|
onset="return this.mCurrentBrowser.userTypedValue = val;"/>
|
|
|
|
<property name="forceSyncURLBarUpdate"
|
|
onget="return this.mModalDialogShowing;"/>
|
|
|
|
<method name="createTooltip">
|
|
<parameter name="event"/>
|
|
<body>
|
|
<![CDATA[
|
|
event.stopPropagation();
|
|
var tn = document.tooltipNode;
|
|
if (tn.localName != "tab")
|
|
return false; // Not a tab, so cancel the tooltip
|
|
if ("mOverCloseButton" in tn && tn.mOverCloseButton) {
|
|
event.target.setAttribute("label", tn.getAttribute("closetabtext"));
|
|
return true;
|
|
}
|
|
if (tn.hasAttribute("label")) {
|
|
event.target.setAttribute("label", tn.getAttribute("label"));
|
|
return true;
|
|
}
|
|
return false;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<constructor>
|
|
<![CDATA[
|
|
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild;
|
|
this.mCurrentTab = this.mTabContainer.firstChild;
|
|
document.addEventListener("keypress", this._keyEventHandler, false);
|
|
|
|
var uniqueId = "panel" + Date.now();
|
|
this.mPanelContainer.childNodes[0].id = uniqueId;
|
|
this.mTabContainer.childNodes[0].linkedPanel = uniqueId;
|
|
this.mTabContainer.childNodes[0]._tPos = 0;
|
|
this.mTabContainer.childNodes[0].linkedBrowser = this.mPanelContainer.childNodes[0].firstChild;
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
for (var i = 0; i < this.mTabListeners.length; ++i) {
|
|
this.getBrowserAtIndex(i).webProgress.removeProgressListener(this.mTabFilters[i]);
|
|
this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
|
|
this.mTabFilters[i] = null;
|
|
this.mTabListeners[i] = null;
|
|
this.getBrowserAtIndex(i).removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
|
|
}
|
|
document.removeEventListener("keypress", this._keyEventHandler, false);
|
|
]]>
|
|
</destructor>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="DOMLinkAdded" phase="capturing" action="this.onLinkAdded(event);"/>
|
|
|
|
<handler event="DOMWindowClose" phase="capturing">
|
|
<![CDATA[
|
|
if (!event.isTrusted)
|
|
return;
|
|
|
|
const browsers = this.mPanelContainer.childNodes;
|
|
if (browsers.length == 1) {
|
|
// There's only one browser left. If a window is being
|
|
// closed and the window is *not* the window in the
|
|
// browser that's still around, prevent the event's default
|
|
// action to prevent closing a window that's being closed
|
|
// already.
|
|
if (this.getBrowserAtIndex(0).contentWindow != event.target)
|
|
event.preventDefault();
|
|
|
|
return;
|
|
}
|
|
|
|
var i = 0;
|
|
for (; i < browsers.length; ++i) {
|
|
if (this.getBrowserAtIndex(i).contentWindow == event.target) {
|
|
this.removeTab(this.mTabContainer.childNodes[i]);
|
|
event.preventDefault();
|
|
|
|
break;
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
<handler event="DOMWillOpenModalDialog" phase="capturing">
|
|
<![CDATA[
|
|
if (!event.isTrusted)
|
|
return;
|
|
|
|
// We're about to open a modal dialog, make sure the opening
|
|
// tab is brought to the front.
|
|
|
|
var targetTop = event.target.top;
|
|
|
|
for (var i = 0; i < browsers.length; ++i) {
|
|
if (this.getBrowserAtIndex(i).contentWindow == targetTop) {
|
|
this.mModalDialogShowing = true;
|
|
this.selectedTab = this.mTabContainer.childNodes[i];
|
|
|
|
break;
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
<handler event="DOMModalDialogClosed" phase="capturing">
|
|
<![CDATA[
|
|
if (!event.isTrusted)
|
|
return;
|
|
|
|
this.mModalDialogShowing = false;
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="tabbrowser-tabs"
|
|
extends="chrome://global/content/bindings/tabbox.xml#tabs">
|
|
<content>
|
|
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1" style="min-width: 1px;" clicktoscroll="true">
|
|
<children includes="tab"/>
|
|
</xul:arrowscrollbox>
|
|
<xul:stack align="center" pack="end" class="scrollbutton-down-stack">
|
|
<!-- XXXsspitzer hack
|
|
this extra hbox with position: relative
|
|
is needed to work around two bugs.
|
|
see bugs #346307 and #346035
|
|
-->
|
|
<xul:hbox style="position: relative;">
|
|
<xul:hbox flex="1" class="tabs-alltabs-box" anonid="alltabs-box"/>
|
|
</xul:hbox>
|
|
<!-- XXXsspitzer hack
|
|
this extra hbox with position: relative
|
|
is needed to work around two bugs.
|
|
see bugs #346307 and #346035
|
|
-->
|
|
<xul:hbox style="position: relative;">
|
|
<xul:toolbarbutton class="tabs-alltabs-button" type="menu"
|
|
anonid="alltabs-button" tooltipstring="&listAllTabs.label;">
|
|
<xul:menupopup class="tabs-alltabs-popup"
|
|
anonid="alltabs-popup"
|
|
position="after_end"/>
|
|
</xul:toolbarbutton>
|
|
</xul:hbox>
|
|
</xul:stack>
|
|
<xul:hbox class="tabs-closebutton-box" align="center" pack="end" anonid="tabstrip-closebutton">
|
|
<xul:toolbarbutton class="close-button tabs-closebutton"/>
|
|
</xul:hbox>
|
|
</content>
|
|
<implementation implements="nsITimerCallback, nsIDOMEventListener">
|
|
<constructor>
|
|
<![CDATA[
|
|
var pb2 =
|
|
Components.classes['@mozilla.org/preferences-service;1'].
|
|
getService(Components.interfaces.nsIPrefBranch2);
|
|
|
|
try {
|
|
this.mTabMinWidth = pb2.getIntPref("browser.tabs.tabMinWidth");
|
|
this.mTabClipWidth = pb2.getIntPref("browser.tabs.tabClipWidth");
|
|
this.mCloseButtons = pb2.getIntPref("browser.tabs.closeButtons");
|
|
}
|
|
catch (e) {
|
|
}
|
|
|
|
this.firstChild.minWidth = this.mTabMinWidth;
|
|
this._updateDisableBackgroundClose();
|
|
this.adjustTabstrip();
|
|
|
|
pb2.addObserver("browser.tabs.disableBackgroundClose",
|
|
this._prefObserver, true);
|
|
pb2.addObserver("browser.tabs.closeButtons",
|
|
this._prefObserver, true);
|
|
|
|
var self = this;
|
|
function onResize() {
|
|
var width = self.mTabstrip.boxObject.width;
|
|
if (width != self.mTabstripWidth) {
|
|
self.adjustTabstrip();
|
|
self.mTabstrip.scrollByIndex(1);
|
|
self.mTabstrip.scrollBoxObject
|
|
.ensureElementIsVisible(self.selectedItem);
|
|
self.mTabstripWidth = width;
|
|
}
|
|
}
|
|
window.addEventListener("resize", onResize, false);
|
|
|
|
// Listen to overflow/underflow events on the tabstrip,
|
|
// we cannot put these as xbl handlers on the entire binding because
|
|
// they would also get called for the all-tabs popup scrollbox.
|
|
// Also, we can't rely on event.target becuase these are all
|
|
// anonymous nodes.
|
|
this.mTabstrip.addEventListener("overflow", this, false);
|
|
this.mTabstrip.addEventListener("underflow", this, false);
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
// Release timer to avoid reference cycles.
|
|
if (this._animateTimer) {
|
|
this._animateTimer.cancel();
|
|
this._animateTimer = null;
|
|
}
|
|
|
|
this.mTabstrip.removeEventListener("overflow", this, false);
|
|
this.mTabstrip.removeEventListener("underflow", this, false);
|
|
]]>
|
|
</destructor>
|
|
|
|
<field name="mTabstripWidth">0</field>
|
|
|
|
<field name="mTabstrip">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
|
|
</field>
|
|
|
|
<field name="mTabstripClosebutton">
|
|
document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
|
|
</field>
|
|
|
|
<method name="_updateDisableBackgroundClose">
|
|
<body><![CDATA[
|
|
var prefs =
|
|
Components.classes['@mozilla.org/preferences-service;1'].
|
|
getService(Components.interfaces.nsIPrefBranch);
|
|
try {
|
|
if (prefs.getBoolPref("browser.tabs.disableBackgroundClose"))
|
|
this.setAttribute("disablebackgroundclose", "true");
|
|
else
|
|
this.removeAttribute("disablebackgroundclose");
|
|
}
|
|
catch (e) {
|
|
this.setAttribute("disablebackgroundclose", "true");
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<field name="_prefObserver">({
|
|
tabbox: this,
|
|
|
|
observe: function(subject, topic, data)
|
|
{
|
|
if (topic == "nsPref:changed") {
|
|
switch (data) {
|
|
case "browser.tabs.disableBackgroundClose":
|
|
this.tabbox._updateDisableBackgroundClose();
|
|
break;
|
|
case "browser.tabs.closeButtons":
|
|
var pb2 =
|
|
Components.classes['@mozilla.org/preferences-service;1'].
|
|
getService(Components.interfaces.nsIPrefBranch2);
|
|
this.tabbox.mCloseButtons = pb2.getIntPref("browser.tabs.closeButtons");
|
|
this.tabbox.adjustTabstrip();
|
|
break;
|
|
}
|
|
}
|
|
},
|
|
|
|
QueryInterface : function(aIID)
|
|
{
|
|
if (aIID.equals(Components.interfaces.nsIObserver) ||
|
|
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
|
|
aIID.equals(Components.interfaces.nsISupports))
|
|
return this;
|
|
throw Components.results.NS_NOINTERFACE;
|
|
}
|
|
});
|
|
</field>
|
|
<field name="mTabMinWidth">100</field>
|
|
<field name="mTabClipWidth">140</field>
|
|
<field name="mCloseButtons">1</field>
|
|
|
|
<method name="adjustTabstrip">
|
|
<body><![CDATA[
|
|
// modes for tabstrip
|
|
// 0 - activetab = close button on active tab only
|
|
// 1 - alltabs = close buttons on all tabs
|
|
// 2 - noclose = no close buttons at all
|
|
// 3 - closeatend = close button at the end of the tabstrip
|
|
switch (this.mCloseButtons) {
|
|
case 0:
|
|
this.setAttribute("closebuttons", "activetab");
|
|
break;
|
|
case 1:
|
|
try {
|
|
var width = this.firstChild.boxObject.width;
|
|
// 0 width is an invalid value and indicates
|
|
// an item without display, so ignore.
|
|
if (width > this.mTabClipWidth || width == 0)
|
|
this.setAttribute("closebuttons", "alltabs");
|
|
else
|
|
this.setAttribute("closebuttons", "activetab");
|
|
}
|
|
catch (e) {
|
|
// XXXzeniko what error are we catching here?
|
|
}
|
|
break;
|
|
case 2:
|
|
case 3:
|
|
this.setAttribute("closebuttons", "noclose");
|
|
break;
|
|
}
|
|
this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
|
|
]]></body>
|
|
</method>
|
|
|
|
<field name="_mPrefs">null</field>
|
|
<property name="mPrefs" readonly="true">
|
|
<getter>
|
|
<![CDATA[
|
|
if (!this._mPrefs) {
|
|
this._mPrefs =
|
|
Components.classes['@mozilla.org/preferences-service;1'].
|
|
getService(Components.interfaces.nsIPrefBranch2);
|
|
}
|
|
return this._mPrefs;
|
|
]]>
|
|
</getter>
|
|
</property>
|
|
|
|
<method name="_handleTabSelect">
|
|
<body><![CDATA[
|
|
this.mTabstrip.scrollBoxObject
|
|
.ensureElementIsVisible(this.selectedItem);
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="handleEvent">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
switch (aEvent.type) {
|
|
case "overflow":
|
|
this.setAttribute("overflow", "true");
|
|
this.mTabstrip.scrollBoxObject
|
|
.ensureElementIsVisible(this.selectedItem);
|
|
break;
|
|
case "underflow":
|
|
this.removeAttribute("overflow");
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<field name="mAllTabsPopup">
|
|
document.getAnonymousElementByAttribute(this,
|
|
"anonid", "alltabs-popup");
|
|
</field>
|
|
|
|
<field name="mAllTabsBox">
|
|
document.getAnonymousElementByAttribute(this,
|
|
"anonid", "alltabs-box");
|
|
</field>
|
|
|
|
<field name="mAllTabsButton">
|
|
document.getAnonymousElementByAttribute(this,
|
|
"anonid", "alltabs-button");
|
|
</field>
|
|
|
|
<field name="_animateTimer">null</field>
|
|
<field name="_animateStep">-1</field>
|
|
<field name="_animateDelay">25</field>
|
|
<field name="_animatePercents">
|
|
[1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
|
|
0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
|
|
0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
|
|
0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
|
|
0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
|
|
0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
|
|
</field>
|
|
|
|
<method name="_stopAnimation">
|
|
<body><![CDATA[
|
|
if (this._animateStep != -1) {
|
|
if (this._animateTimer)
|
|
this._animateTimer.cancel();
|
|
|
|
this._animateStep = -1;
|
|
this.mAllTabsBox.style.opacity = 0.0;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="_notifyBackgroundTab">
|
|
<parameter name="aTab"/>
|
|
<body><![CDATA[
|
|
var tsbo = this.mTabstrip.scrollBoxObject;
|
|
var tsboStart = tsbo.screenX;
|
|
var tsboEnd = tsboStart + tsbo.width;
|
|
|
|
var ctbo = aTab.boxObject;
|
|
var ctboStart = ctbo.screenX;
|
|
var ctboEnd = ctboStart + ctbo.width;
|
|
|
|
// only start the flash timer if the new tab (which was loaded in
|
|
// the background) is not completely visible
|
|
if (tsboStart > ctboStart || ctboEnd > tsboEnd) {
|
|
this._animateStep = 0;
|
|
|
|
if (!this._animateTimer)
|
|
this._animateTimer =
|
|
Components.classes["@mozilla.org/timer;1"]
|
|
.createInstance(Components.interfaces.nsITimer);
|
|
else
|
|
this._animateTimer.cancel();
|
|
|
|
this._animateTimer.initWithCallback(this,
|
|
this._animateDelay,
|
|
Components.interfaces.nsITimer.TYPE_REPEATING_SLACK);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="notify">
|
|
<parameter name="aTimer"/>
|
|
<body><![CDATA[
|
|
if (!document)
|
|
aTimer.cancel();
|
|
|
|
var percent = this._animatePercents[this._animateStep];
|
|
this.mAllTabsBox.style.opacity = percent;
|
|
|
|
if (this._animateStep < (this._animatePercents.length - 1))
|
|
this._animateStep++;
|
|
else
|
|
this._stopAnimation();
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
<handlers>
|
|
<handler event="TabSelect" action="this._handleTabSelect();"/>
|
|
<handler event="mouseover"><![CDATA[
|
|
if (event.originalTarget == this.mAllTabsButton) {
|
|
this.mAllTabsButton
|
|
.setAttribute("tooltiptext",
|
|
this.mAllTabsButton.getAttribute("tooltipstring"));
|
|
}
|
|
else
|
|
this.mAllTabsButton.removeAttribute("tooltiptext");
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<!-- alltabs-popup binding
|
|
This binding relies on the structure of the tabbrowser binding.
|
|
Therefore it should only be used as a child of the tabs element.
|
|
This binding is exposed as a pseudo-public-API so themes can customize
|
|
the tabbar appearance without having to be scriptable
|
|
(see globalBindings.xml in Pinstripe for example).
|
|
-->
|
|
<binding id="tabbrowser-alltabs-popup"
|
|
extends="chrome://global/content/bindings/popup.xml#popup">
|
|
<implementation implements="nsIDOMEventListener">
|
|
<method name="_menuItemOnCommand">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
// note, the tab may not be valid (if after we built the popup
|
|
// the tab was closed. but selectedItem setter handles that
|
|
// gracefully.
|
|
var tabcontainer = document.getBindingParent(this);
|
|
tabcontainer.selectedItem = aEvent.target.tab;
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="_tabOnAttrModified">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
var menuItem = aEvent.target.mCorrespondingMenuitem;
|
|
if (menuItem) {
|
|
var attrName = aEvent.attrName;
|
|
switch (attrName) {
|
|
case "label":
|
|
case "crop":
|
|
case "busy":
|
|
case "image":
|
|
if (aEvent.attrChange == aEvent.REMOVAL)
|
|
menuItem.removeAttribute(attrName);
|
|
else
|
|
menuItem.setAttribute(attrName, aEvent.newValue);
|
|
}
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="handleEvent">
|
|
<parameter name="aEvent"/>
|
|
<body><![CDATA[
|
|
if (!aEvent.isTrusted)
|
|
return;
|
|
|
|
switch (aEvent.type) {
|
|
case "command":
|
|
this._menuItemOnCommand(aEvent);
|
|
break;
|
|
case "DOMAttrModified":
|
|
this._tabOnAttrModified(aEvent);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="_onHidingAllTabsPopup">
|
|
<body><![CDATA[
|
|
// clear out the menu popup and remove the listeners
|
|
while (this.hasChildNodes()) {
|
|
var menuItem = this.lastChild;
|
|
menuItem.removeEventListener("command", this, false);
|
|
menuItem.tab.removeEventListener("DOMAttrModified", this, false);
|
|
menuItem.tab.mCorrespondingMenuitem = null;
|
|
this.removeChild(menuItem);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<method name="_onShowingAllTabsPopup">
|
|
<body><![CDATA[
|
|
// set up the menu popup
|
|
var tabcontainer = document.getBindingParent(this);
|
|
var tabs = tabcontainer.childNodes;
|
|
|
|
// if an animation is in progress and the user
|
|
// clicks on the "all tabs" button, stop the animation
|
|
tabcontainer._stopAnimation();
|
|
|
|
for (var i = 0; i < tabs.length; i++) {
|
|
var menuItem = document.createElementNS(
|
|
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
|
"menuitem");
|
|
var curTab = tabs[i];
|
|
|
|
if (curTab.selected)
|
|
menuItem.setAttribute("selected", "true");
|
|
menuItem.setAttribute("class", "menuitem-iconic alltabs-item");
|
|
|
|
menuItem.setAttribute("label", curTab.label);
|
|
menuItem.setAttribute("crop", curTab.getAttribute("crop"));
|
|
menuItem.setAttribute("image", curTab.getAttribute("image"));
|
|
if (curTab.hasAttribute("busy"))
|
|
menuItem.setAttribute("busy", curTab.getAttribute("busy"));
|
|
|
|
// XXX todo
|
|
// statustext not working yet, since I don't have a menubar
|
|
// reuse the menubar statustext logic
|
|
var URI = curTab.linkedBrowser.currentURI.spec;
|
|
menuItem.setAttribute("statustext", URI);
|
|
|
|
// Keep some attributes of the menuitem in sync with its
|
|
// corresponding tab (e.g. the tab label)
|
|
curTab.mCorrespondingMenuitem = menuItem;
|
|
curTab.addEventListener("DOMAttrModified", this, false);
|
|
|
|
menuItem.tab = curTab;
|
|
menuItem.addEventListener("command", this, false);
|
|
this.appendChild(menuItem);
|
|
}
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
<handlers>
|
|
<handler event="popupshowing"><![CDATA[
|
|
if (event.target == this)
|
|
this._onShowingAllTabsPopup();
|
|
]]></handler>
|
|
<handler event="popuphiding"><![CDATA[
|
|
if (event.target == this)
|
|
this._onHidingAllTabsPopup();
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<!-- close-tab-button binding
|
|
This binding relies on the structure of the tabbrowser binding.
|
|
Therefore it should only be used as a child of the tab or the tabs
|
|
element (in both cases, when they are anonymous nodes of <tabbrowser>).
|
|
This binding is exposed as a pseudo-public-API so themes can customize
|
|
the tabbar appearance without having to be scriptable
|
|
(see globalBindings.xml in Pinstripe for example).
|
|
-->
|
|
<binding id="tabbrowser-close-tab-button"
|
|
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
|
|
<handlers>
|
|
<handler event="click"><![CDATA[
|
|
var bindingParent = document.getBindingParent(this);
|
|
if (bindingParent) {
|
|
var tabbedBrowser = document.getBindingParent(bindingParent);
|
|
if (bindingParent.localName == "tab") {
|
|
/* The only sequence in which a second click event (i.e. dblclik)
|
|
* can be dispatched on an in-tab close button is when it is shown
|
|
* after the first click (i.e. the first click event was dispatched
|
|
* on the tab). This happens when we show the close button only on
|
|
* the active tab; in which case, it is most likely that the close
|
|
* button area has been accidentally clicked, therefore we do not
|
|
* close the tab. See bug 352021 for no details.
|
|
*/
|
|
if (event.detail == 2)
|
|
return;
|
|
|
|
tabbedBrowser.removeTab(bindingParent);
|
|
tabbedBrowser._blockDblClick = true;
|
|
|
|
/* XXXmano hack (see bug 343628):
|
|
* Since we're removing the event target, if the user
|
|
* double-clicks this button, the dblclick event will be dispatched
|
|
* with the tabbar as its event target (and explicit/originalTarget),
|
|
* which treats that as a mouse gesture for opening a new tab.
|
|
* In this context, there is no way to prevent the dispatching
|
|
* of the dblclick event, so we're manually blocking it (see
|
|
* onTabBarDblClick) until the mouse is moved.
|
|
*/
|
|
function mouseMoveHandler() {
|
|
tabbedBrowser._blockDblClick = false;
|
|
tabbedBrowser.removeEventListener("mousemove", mouseMoveHandler, false);
|
|
}
|
|
tabbedBrowser.addEventListener("mousemove", mouseMoveHandler, false);
|
|
}
|
|
else // "tabs"
|
|
tabbedBrowser.removeCurrentTab();
|
|
}
|
|
]]></handler>
|
|
<handler event="dblclick" phase="capturing">
|
|
// for the one-close-button case
|
|
event.stopPropagation();
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="tabbrowser-tab" display="xul:box"
|
|
extends="chrome://global/content/bindings/tabbox.xml#tab">
|
|
<content chromedir="&locale.dir;"
|
|
closetabtext="&closeTab.label;">
|
|
<xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
|
|
<xul:image class="tab-icon" xbl:inherits="validate,src=image"/>
|
|
<xul:label class="tab-text" xbl:inherits="value=label,accesskey,crop,disabled" flex="1"/>
|
|
</xul:hbox>
|
|
<xul:image anonid="close-button-placeholder" class="tab-close-button-placeholder"/>
|
|
<xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button"/>
|
|
</content>
|
|
|
|
<implementation>
|
|
<field name="mOverCloseButton">false</field>
|
|
<field name="mCorrespondingMenuitem">null</field>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="mouseover">
|
|
var anonid = event.originalTarget.getAttribute("anonid");
|
|
if (anonid == "close-button")
|
|
this.mOverCloseButton = true;
|
|
</handler>
|
|
<handler event="mouseout">
|
|
var anonid = event.originalTarget.getAttribute("anonid");
|
|
if (anonid == "close-button")
|
|
this.mOverCloseButton = false;
|
|
</handler>
|
|
<handler event="mousedown" button="0" phase="capturing">
|
|
<![CDATA[
|
|
if (this.mOverCloseButton)
|
|
event.stopPropagation();
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
</bindings>
|