зеркало из https://github.com/mozilla/pjs.git
3634 строки
131 KiB
XML
3634 строки
131 KiB
XML
<?xml version="1.0"?>
|
||
|
||
<!-- ***** BEGIN LICENSE BLOCK *****
|
||
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
-
|
||
- The contents of this file are subject to the Mozilla Public License Version
|
||
- 1.1 (the "License"); you may not use this file except in compliance with
|
||
- the License. You may obtain a copy of the License at
|
||
- http://www.mozilla.org/MPL/
|
||
-
|
||
- Software distributed under the License is distributed on an "AS IS" basis,
|
||
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
- for the specific language governing rights and limitations under the
|
||
- License.
|
||
-
|
||
- The Original Code is this file as it was released on March 28, 2001.
|
||
-
|
||
- The Initial Developer of the Original Code is
|
||
- David Hyatt.
|
||
- Portions created by the Initial Developer are Copyright (C) 2001
|
||
- the Initial Developer. All Rights Reserved.
|
||
-
|
||
- Contributor(s):
|
||
- David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
|
||
- Mike Connor <mconnor@steelgryphon.com>
|
||
- Peter Parente <parente@cs.unc.edu>
|
||
- Giorgio Maone <g.maone@informaction.com>
|
||
- Asaf Romano <mozilla.mano@sent.com>
|
||
- Seth Spitzer <sspitzer@mozilla.org>
|
||
- Simon Bünzli <zeniko@gmail.com>
|
||
- Michael Ventnor <ventnor.bugzilla@yahoo.com.au>
|
||
- Mark Pilgrim <pilgrim@gmail.com>
|
||
- Dão Gottwald <dao@mozilla.com>
|
||
- Paul O’Shannessy <paul@oshannessy.com>
|
||
- Rob Arnold <tellrob@gmail.com>
|
||
-
|
||
- Alternatively, the contents of this file may be used under the terms of
|
||
- either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
- in which case the provisions of the GPL or the LGPL are applicable instead
|
||
- of those above. If you wish to allow use of your version of this file only
|
||
- under the terms of either the GPL or the LGPL, and not to allow others to
|
||
- use your version of this file under the terms of the MPL, indicate your
|
||
- decision by deleting the provisions above and replace them with the notice
|
||
- and other provisions required by the GPL or the LGPL. If you do not delete
|
||
- the provisions above, a recipient may use your version of this file under
|
||
- the terms of any one of the MPL, the GPL or the LGPL.
|
||
-
|
||
- ***** END LICENSE BLOCK ***** -->
|
||
|
||
<!DOCTYPE bindings [
|
||
<!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
|
||
%tabBrowserDTD;
|
||
]>
|
||
|
||
<bindings id="tabBrowserBindings"
|
||
xmlns="http://www.mozilla.org/xbl"
|
||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||
|
||
<binding id="tabbrowser">
|
||
<resources>
|
||
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
|
||
</resources>
|
||
|
||
<content>
|
||
<xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
|
||
<xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
|
||
flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
|
||
onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
|
||
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
|
||
<xul:notificationbox flex="1">
|
||
<xul:stack flex="1" anonid="browserStack">
|
||
<xul:browser type="content-primary" message="true" disablehistory="true"
|
||
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
|
||
</xul:stack>
|
||
</xul:notificationbox>
|
||
</xul:tabpanels>
|
||
</xul:tabbox>
|
||
<children/>
|
||
</content>
|
||
<implementation implements="nsIDOMEventListener">
|
||
|
||
<property name="tabContextMenu" readonly="true"
|
||
onget="return this.tabContainer.contextMenu;"/>
|
||
|
||
<field name="tabContainer" readonly="true">
|
||
document.getElementById(this.getAttribute("tabcontainer"));
|
||
</field>
|
||
<field name="tabs" readonly="true">
|
||
this.tabContainer.childNodes;
|
||
</field>
|
||
<property name="visibleTabs" readonly="true">
|
||
<getter><![CDATA[
|
||
return Array.filter(this.tabs, function(tab) {
|
||
return !tab.hidden && this._removingTabs.indexOf(tab) == -1;
|
||
}, this);
|
||
]]></getter>
|
||
</property>
|
||
<field name="mURIFixup" readonly="true">
|
||
Components.classes["@mozilla.org/docshell/urifixup;1"]
|
||
.getService(Components.interfaces.nsIURIFixup);
|
||
</field>
|
||
<field name="mFaviconService" readonly="true">
|
||
Components.classes["@mozilla.org/browser/favicon-service;1"]
|
||
.getService(Components.interfaces.nsIFaviconService);
|
||
</field>
|
||
<field name="mBrowserHistory" readonly="true">
|
||
Components.classes["@mozilla.org/browser/nav-history-service;1"]
|
||
.getService(Components.interfaces.nsIBrowserHistory);
|
||
</field>
|
||
<field name="mTabBox" readonly="true">
|
||
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
|
||
</field>
|
||
<field name="mPanelContainer" readonly="true">
|
||
document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
|
||
</field>
|
||
<field name="mStringBundle">
|
||
document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
|
||
</field>
|
||
<field name="mCurrentTab">
|
||
null
|
||
</field>
|
||
<field name="_lastRelatedTab">
|
||
null
|
||
</field>
|
||
<field name="mCurrentBrowser">
|
||
null
|
||
</field>
|
||
<field name="mProgressListeners">
|
||
[]
|
||
</field>
|
||
<field name="mTabsProgressListeners">
|
||
[]
|
||
</field>
|
||
<field name="mTabListeners">
|
||
new Array()
|
||
</field>
|
||
<field name="mTabFilters">
|
||
new Array()
|
||
</field>
|
||
<field name="mTabbedMode">
|
||
false
|
||
</field>
|
||
<field name="mIsBusy">
|
||
false
|
||
</field>
|
||
<field name="arrowKeysShouldWrap" readonly="true">
|
||
#ifdef XP_MACOSX
|
||
true
|
||
#else
|
||
false
|
||
#endif
|
||
</field>
|
||
<field name="mAddProgressListenerWasCalled">
|
||
false
|
||
</field>
|
||
<field name="_browsers">
|
||
null
|
||
</field>
|
||
|
||
<field name="_autoScrollPopup">
|
||
null
|
||
</field>
|
||
|
||
<field name="_previewMode">
|
||
false
|
||
</field>
|
||
|
||
<property name="_numPinnedTabs" readonly="true">
|
||
<getter><![CDATA[
|
||
for (var i = 0; i < this.tabs.length; i++) {
|
||
if (!this.tabs[i].pinned)
|
||
break;
|
||
}
|
||
return i;
|
||
]]></getter>
|
||
</property>
|
||
|
||
<method name="pinTab">
|
||
<parameter name="aTab"/>
|
||
<body><![CDATA[
|
||
if (aTab.pinned)
|
||
return;
|
||
|
||
if (aTab.hidden)
|
||
this.showTab(aTab);
|
||
|
||
this.moveTabTo(aTab, this._numPinnedTabs);
|
||
aTab.setAttribute("pinned", "true");
|
||
this.tabContainer._positionPinnedTabs();
|
||
this.tabContainer.adjustTabstrip();
|
||
|
||
this.getBrowserForTab(aTab).docShell.isAppTab = true;
|
||
|
||
let event = document.createEvent("Events");
|
||
event.initEvent("TabPinned", true, false);
|
||
aTab.dispatchEvent(event);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="unpinTab">
|
||
<parameter name="aTab"/>
|
||
<body><![CDATA[
|
||
if (!aTab.pinned)
|
||
return;
|
||
|
||
this.moveTabTo(aTab, this._numPinnedTabs - 1);
|
||
aTab.setAttribute("fadein", "true");
|
||
aTab.removeAttribute("pinned");
|
||
aTab.style.MozMarginStart = "";
|
||
this.tabContainer._positionPinnedTabs();
|
||
this.tabContainer.adjustTabstrip();
|
||
|
||
this.getBrowserForTab(aTab).docShell.isAppTab = false;
|
||
|
||
let event = document.createEvent("Events");
|
||
event.initEvent("TabUnpinned", true, false);
|
||
aTab.dispatchEvent(event);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="previewTab">
|
||
<parameter name="aTab"/>
|
||
<parameter name="aCallback"/>
|
||
<body>
|
||
<![CDATA[
|
||
let currentTab = this.selectedTab;
|
||
try {
|
||
// Suppress focus, ownership and selected tab changes
|
||
this._previewMode = true;
|
||
this.selectedTab = aTab;
|
||
aCallback();
|
||
} finally {
|
||
this.selectedTab = currentTab;
|
||
this._previewMode = false;
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getBrowserAtIndex">
|
||
<parameter name="aIndex"/>
|
||
<body>
|
||
<![CDATA[
|
||
return this.browsers[aIndex];
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getBrowserIndexForDocument">
|
||
<parameter name="aDocument"/>
|
||
<body>
|
||
<![CDATA[
|
||
var tab = this._getTabForContentWindow(aDocument.defaultView);
|
||
return tab ? tab._tPos : -1;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getBrowserForDocument">
|
||
<parameter name="aDocument"/>
|
||
<body>
|
||
<![CDATA[
|
||
var tab = this._getTabForContentWindow(aDocument.defaultView);
|
||
return tab ? tab.linkedBrowser : null;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="_getTabForContentWindow">
|
||
<parameter name="aWindow"/>
|
||
<body>
|
||
<![CDATA[
|
||
for (let i = 0; i < this.browsers.length; i++) {
|
||
if (this.browsers[i].contentWindow == aWindow)
|
||
return this.tabs[i];
|
||
}
|
||
return null;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getNotificationBox">
|
||
<parameter name="aBrowser"/>
|
||
<body>
|
||
<![CDATA[
|
||
return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getTabModalPromptBox">
|
||
<parameter name="aBrowser"/>
|
||
<body>
|
||
<![CDATA[
|
||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||
let browser = (aBrowser || this.mCurrentBrowser);
|
||
let stack = browser.parentNode;
|
||
let self = this;
|
||
|
||
let promptBox = {
|
||
appendPrompt : function(args, onCloseCallback) {
|
||
let count = browser.getAttribute("tabmodalPromptShowing");
|
||
if (count)
|
||
count = parseInt(count) + 1;
|
||
else
|
||
count = 1;
|
||
browser.setAttribute("tabmodalPromptShowing", count);
|
||
|
||
let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
|
||
stack.appendChild(newPrompt);
|
||
newPrompt.clientTop; // style flush to assure binding is attached
|
||
|
||
let tab = self._getTabForContentWindow(browser.contentWindow);
|
||
newPrompt.init(args, tab, onCloseCallback);
|
||
return newPrompt;
|
||
},
|
||
|
||
removePrompt : function(aPrompt) {
|
||
let count = parseInt(browser.getAttribute("tabmodalPromptShowing"));
|
||
count--;
|
||
if (count)
|
||
browser.setAttribute("tabmodalPromptShowing", count);
|
||
else
|
||
browser.removeAttribute("tabmodalPromptShowing");
|
||
stack.removeChild(aPrompt);
|
||
},
|
||
|
||
listPrompts : function(aPrompt) {
|
||
let prompts = [];
|
||
let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
|
||
// NodeList --> real JS array
|
||
for (let i = 0; i < els.length; i++)
|
||
prompts.push(els[i]);
|
||
return prompts;
|
||
},
|
||
};
|
||
|
||
return promptBox;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="_callProgressListeners">
|
||
<parameter name="aBrowser"/>
|
||
<parameter name="aMethod"/>
|
||
<parameter name="aArguments"/>
|
||
<parameter name="aCallGlobalListeners"/>
|
||
<parameter name="aCallTabsListeners"/>
|
||
<body><![CDATA[
|
||
var rv = true;
|
||
|
||
if (!aBrowser)
|
||
aBrowser = this.mCurrentBrowser;
|
||
|
||
if (aCallGlobalListeners != false &&
|
||
aBrowser == this.mCurrentBrowser) {
|
||
this.mProgressListeners.forEach(function (p) {
|
||
if (aMethod in p) {
|
||
try {
|
||
if (!p[aMethod].apply(p, aArguments))
|
||
rv = false;
|
||
} catch (e) {
|
||
// don't inhibit other listeners
|
||
Components.utils.reportError(e);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
if (aCallTabsListeners != false) {
|
||
aArguments.unshift(aBrowser);
|
||
|
||
this.mTabsProgressListeners.forEach(function (p) {
|
||
if (aMethod in p) {
|
||
try {
|
||
if (!p[aMethod].apply(p, aArguments))
|
||
rv = false;
|
||
} catch (e) {
|
||
// don't inhibit other listeners
|
||
Components.utils.reportError(e);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
return rv;
|
||
]]></body>
|
||
</method>
|
||
|
||
<!-- A web progress listener object definition for a given tab. -->
|
||
<method name="mTabProgressListener">
|
||
<parameter name="aTab"/>
|
||
<parameter name="aBrowser"/>
|
||
<parameter name="aStartsBlank"/>
|
||
<body>
|
||
<![CDATA[
|
||
return ({
|
||
mTabBrowser: this,
|
||
mTab: aTab,
|
||
mBrowser: aBrowser,
|
||
mBlank: aStartsBlank,
|
||
|
||
// cache flags for correct status UI update after tab switching
|
||
mStateFlags: 0,
|
||
mStatus: 0,
|
||
mMessage: "",
|
||
mTotalProgress: 0,
|
||
|
||
// count of open requests (should always be 0 or 1)
|
||
mRequestCount: 0,
|
||
|
||
destroy: function () {
|
||
delete this.mTab;
|
||
delete this.mBrowser;
|
||
delete this.mTabBrowser;
|
||
},
|
||
|
||
_callProgressListeners: function () {
|
||
Array.unshift(arguments, this.mBrowser);
|
||
return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
|
||
},
|
||
|
||
onProgressChange: function (aWebProgress, aRequest,
|
||
aCurSelfProgress, aMaxSelfProgress,
|
||
aCurTotalProgress, aMaxTotalProgress) {
|
||
this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
|
||
|
||
if (this.mBlank)
|
||
return;
|
||
|
||
if (this.mTotalProgress)
|
||
this.mTab.setAttribute("progress", "true");
|
||
|
||
this._callProgressListeners("onProgressChange",
|
||
[aWebProgress, aRequest,
|
||
aCurSelfProgress, aMaxSelfProgress,
|
||
aCurTotalProgress, aMaxTotalProgress]);
|
||
},
|
||
|
||
onProgressChange64: function (aWebProgress, aRequest,
|
||
aCurSelfProgress, aMaxSelfProgress,
|
||
aCurTotalProgress, aMaxTotalProgress) {
|
||
return this.onProgressChange(aWebProgress, aRequest,
|
||
aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
|
||
aMaxTotalProgress);
|
||
},
|
||
|
||
onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
|
||
if (!aRequest)
|
||
return;
|
||
|
||
var oldBlank = this.mBlank;
|
||
|
||
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
|
||
const nsIChannel = Components.interfaces.nsIChannel;
|
||
|
||
if (aStateFlags & nsIWebProgressListener.STATE_START) {
|
||
this.mRequestCount++;
|
||
}
|
||
else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
|
||
const NS_ERROR_UNKNOWN_HOST = 2152398878;
|
||
if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
|
||
// to prevent bug 235825: wait for the request handled
|
||
// by the automatic keyword resolver
|
||
return;
|
||
}
|
||
// since we (try to) only handle STATE_STOP of the last request,
|
||
// the count of open requests should now be 0
|
||
this.mRequestCount = 0;
|
||
}
|
||
|
||
if (aStateFlags & nsIWebProgressListener.STATE_START &&
|
||
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
|
||
// It's okay to clear what the user typed when we start
|
||
// loading a document. If the user types, this counter gets
|
||
// set to zero, if the document load ends without an
|
||
// onLocationChange, this counter gets decremented
|
||
// (so we keep it while switching tabs after failed loads)
|
||
// We need to add 2 because loadURIWithFlags may have
|
||
// cancelled a pending load which would have cleared
|
||
// its anchor scroll detection temporary increment.
|
||
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
|
||
this.mBrowser.userTypedClear += 2;
|
||
|
||
if (!this.mBlank) {
|
||
if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
|
||
this.mTab.setAttribute("busy", "true");
|
||
this.mTabBrowser.setTabTitleLoading(this.mTab);
|
||
}
|
||
|
||
if (this.mTab.selected)
|
||
this.mTabBrowser.mIsBusy = true;
|
||
}
|
||
}
|
||
else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
|
||
aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
|
||
if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
|
||
// The document is done loading, we no longer want the
|
||
// value cleared.
|
||
if (this.mBrowser.userTypedClear > 1)
|
||
this.mBrowser.userTypedClear -= 2;
|
||
else if (this.mBrowser.userTypedClear > 0)
|
||
this.mBrowser.userTypedClear--;
|
||
|
||
if (!this.mBrowser.mIconURL)
|
||
this.mTabBrowser.useDefaultIcon(this.mTab);
|
||
}
|
||
|
||
if (this.mBlank)
|
||
this.mBlank = false;
|
||
|
||
this.mTab.removeAttribute("busy");
|
||
this.mTab.removeAttribute("progress");
|
||
|
||
var location = aRequest.QueryInterface(nsIChannel).URI;
|
||
|
||
// For keyword URIs clear the user typed value since they will be changed into real URIs
|
||
if (location.scheme == "keyword")
|
||
this.mBrowser.userTypedValue = null;
|
||
|
||
if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
|
||
this.mTabBrowser.setTabTitle(this.mTab);
|
||
|
||
if (this.mTab.selected)
|
||
this.mTabBrowser.mIsBusy = false;
|
||
}
|
||
|
||
if (oldBlank) {
|
||
this._callProgressListeners("onUpdateCurrentBrowser",
|
||
[aStateFlags, aStatus, "", 0],
|
||
true, false);
|
||
} else {
|
||
this._callProgressListeners("onStateChange",
|
||
[aWebProgress, aRequest, aStateFlags, aStatus],
|
||
true, false);
|
||
}
|
||
|
||
this._callProgressListeners("onStateChange",
|
||
[aWebProgress, aRequest, aStateFlags, aStatus],
|
||
false);
|
||
|
||
if (aStateFlags & (nsIWebProgressListener.STATE_START |
|
||
nsIWebProgressListener.STATE_STOP)) {
|
||
// reset cached temporary values at beginning and end
|
||
this.mMessage = "";
|
||
this.mTotalProgress = 0;
|
||
}
|
||
this.mStateFlags = aStateFlags;
|
||
this.mStatus = aStatus;
|
||
},
|
||
|
||
onLocationChange: function (aWebProgress, aRequest, aLocation) {
|
||
// OnLocationChange is called for both the top-level content
|
||
// and the subframes.
|
||
let topLevel = aWebProgress.DOMWindow == this.mBrowser.contentWindow;
|
||
|
||
if (topLevel) {
|
||
// The document loaded correctly, clear the value if we should
|
||
if (this.mBrowser.userTypedClear > 0)
|
||
this.mBrowser.userTypedValue = null;
|
||
|
||
// Clear out the missing plugins list since it's related to the
|
||
// previous location.
|
||
this.mBrowser.missingPlugins = null;
|
||
|
||
// Don't clear the favicon if this onLocationChange was
|
||
// triggered by a pushState or a replaceState. See bug 550565.
|
||
if (aWebProgress.isLoadingDocument &&
|
||
!(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
|
||
this.mBrowser.mIconURL = null;
|
||
|
||
let browserHistory = this.mTabBrowser.mBrowserHistory;
|
||
if (this.mBrowser.registeredOpenURI) {
|
||
browserHistory.unregisterOpenPage(this.mBrowser.registeredOpenURI);
|
||
delete this.mBrowser.registeredOpenURI;
|
||
}
|
||
if (aLocation.spec != "about:blank") {
|
||
browserHistory.registerOpenPage(aLocation);
|
||
this.mBrowser.registeredOpenURI = aLocation;
|
||
}
|
||
}
|
||
|
||
if (!this.mBlank) {
|
||
this._callProgressListeners("onLocationChange",
|
||
[aWebProgress, aRequest, aLocation]);
|
||
}
|
||
|
||
if (topLevel)
|
||
this.mBrowser.lastURI = aLocation;
|
||
},
|
||
|
||
onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
|
||
if (this.mBlank)
|
||
return;
|
||
|
||
this._callProgressListeners("onStatusChange",
|
||
[aWebProgress, aRequest, aStatus, aMessage]);
|
||
|
||
this.mMessage = aMessage;
|
||
},
|
||
|
||
onSecurityChange: function (aWebProgress, aRequest, aState) {
|
||
this._callProgressListeners("onSecurityChange",
|
||
[aWebProgress, aRequest, aState]);
|
||
},
|
||
|
||
onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
|
||
return this._callProgressListeners("onRefreshAttempted",
|
||
[aWebProgress, aURI, aDelay, aSameURI]);
|
||
},
|
||
|
||
QueryInterface: function (aIID) {
|
||
if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
|
||
aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
|
||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
|
||
aIID.equals(Components.interfaces.nsISupports))
|
||
return this;
|
||
throw Components.results.NS_NOINTERFACE;
|
||
}
|
||
});
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="setIcon">
|
||
<parameter name="aTab"/>
|
||
<parameter name="aURI"/>
|
||
<body>
|
||
<![CDATA[
|
||
var browser = this.getBrowserForTab(aTab);
|
||
browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
|
||
|
||
if (aURI && this.mFaviconService) {
|
||
if (!(aURI instanceof Ci.nsIURI))
|
||
aURI = makeURI(aURI);
|
||
this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI,
|
||
aURI, false);
|
||
}
|
||
|
||
if ((browser.mIconURL || "") != aTab.getAttribute("image")) {
|
||
if (browser.mIconURL)
|
||
aTab.setAttribute("image", browser.mIconURL);
|
||
else
|
||
aTab.removeAttribute("image");
|
||
this._tabAttrModified(aTab);
|
||
}
|
||
|
||
this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getIcon">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
|
||
return browser.mIconURL;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="shouldLoadFavIcon">
|
||
<parameter name="aURI"/>
|
||
<body>
|
||
<![CDATA[
|
||
return (aURI &&
|
||
Services.prefs.getBoolPref("browser.chrome.site_icons") &&
|
||
Services.prefs.getBoolPref("browser.chrome.favicons") &&
|
||
("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="useDefaultIcon">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
var browser = this.getBrowserForTab(aTab);
|
||
var docURIObject = browser.contentDocument.documentURIObject;
|
||
var icon = null;
|
||
if (browser.contentDocument instanceof ImageDocument) {
|
||
if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
|
||
let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
|
||
try {
|
||
let req = browser.contentDocument.imageRequest;
|
||
if (req &&
|
||
req.image &&
|
||
req.image.width <= sz &&
|
||
req.image.height <= sz)
|
||
icon = browser.currentURI;
|
||
} catch (e) { }
|
||
}
|
||
}
|
||
// Use documentURIObject in the check for shouldLoadFavIcon so that we
|
||
// do the right thing with about:-style error pages. Bug 453442
|
||
else if (this.shouldLoadFavIcon(docURIObject)) {
|
||
let url = docURIObject.prePath + "/favicon.ico";
|
||
if (!this.isFailedIcon(url))
|
||
icon = url;
|
||
}
|
||
this.setIcon(aTab, icon);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="isFailedIcon">
|
||
<parameter name="aURI"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (this.mFaviconService) {
|
||
if (!(aURI instanceof Ci.nsIURI))
|
||
aURI = makeURI(aURI);
|
||
return this.mFaviconService.isFailedFavicon(aURI);
|
||
}
|
||
return null;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getWindowTitleForBrowser">
|
||
<parameter name="aBrowser"/>
|
||
<body>
|
||
<![CDATA[
|
||
var newTitle = "";
|
||
var docTitle;
|
||
var docElement = this.ownerDocument.documentElement;
|
||
var sep = docElement.getAttribute("titlemenuseparator");
|
||
|
||
if (aBrowser.docShell.contentViewer)
|
||
docTitle = aBrowser.contentTitle;
|
||
|
||
if (!docTitle)
|
||
docTitle = docElement.getAttribute("titledefault");
|
||
|
||
var modifier = docElement.getAttribute("titlemodifier");
|
||
if (docTitle) {
|
||
newTitle += docElement.getAttribute("titlepreface");
|
||
newTitle += docTitle;
|
||
if (modifier)
|
||
newTitle += sep;
|
||
}
|
||
newTitle += modifier;
|
||
|
||
// If location bar is hidden and the URL type supports a host,
|
||
// add the scheme and host to the title to prevent spoofing.
|
||
// XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
|
||
try {
|
||
if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
|
||
var uri = this.mURIFixup.createExposableURI(
|
||
aBrowser.currentURI);
|
||
if (uri.scheme == "about")
|
||
newTitle = uri.spec + sep + newTitle;
|
||
else
|
||
newTitle = uri.prePath + sep + newTitle;
|
||
}
|
||
} catch (e) {}
|
||
|
||
if (window.TabView) {
|
||
let groupName = TabView.getActiveGroupName();
|
||
if (groupName)
|
||
newTitle = groupName + sep + newTitle;
|
||
}
|
||
|
||
return newTitle;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="updateTitlebar">
|
||
<body>
|
||
<![CDATA[
|
||
if (window.TabView && TabView.isVisible()) {
|
||
// ToDo: this will be removed when we gain ability to draw to the menu bar.
|
||
// Bug 586175
|
||
this.ownerDocument.title = TabView.windowTitle;
|
||
}
|
||
else {
|
||
this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="updateCurrentBrowser">
|
||
<parameter name="aForceUpdate"/>
|
||
<body>
|
||
<![CDATA[
|
||
var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
|
||
if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
|
||
return;
|
||
|
||
var oldTab = this.mCurrentTab;
|
||
|
||
// Preview mode should not reset the owner
|
||
if (!this._previewMode && oldTab != this.selectedTab)
|
||
oldTab.owner = null;
|
||
|
||
if (this._lastRelatedTab) {
|
||
if (this._lastRelatedTab != this.selectedTab)
|
||
this._lastRelatedTab.owner = null;
|
||
this._lastRelatedTab = null;
|
||
}
|
||
|
||
var oldBrowser = this.mCurrentBrowser;
|
||
if (oldBrowser) {
|
||
oldBrowser.setAttribute("type", "content-targetable");
|
||
oldBrowser.docShell.isActive = false;
|
||
}
|
||
|
||
var updatePageReport = false;
|
||
if (!oldBrowser ||
|
||
(oldBrowser.pageReport && !newBrowser.pageReport) ||
|
||
(!oldBrowser.pageReport && newBrowser.pageReport))
|
||
updatePageReport = true;
|
||
|
||
newBrowser.setAttribute("type", "content-primary");
|
||
newBrowser.docShell.isActive = true;
|
||
this.mCurrentBrowser = newBrowser;
|
||
this.mCurrentTab = this.selectedTab;
|
||
this.showTab(this.mCurrentTab);
|
||
|
||
if (updatePageReport)
|
||
this.mCurrentBrowser.updatePageReport();
|
||
|
||
// Update the URL bar.
|
||
var loc = this.mCurrentBrowser.currentURI;
|
||
|
||
var webProgress = this.mCurrentBrowser.webProgress;
|
||
var securityUI = this.mCurrentBrowser.securityUI;
|
||
|
||
this._callProgressListeners(null, "onLocationChange",
|
||
[webProgress, null, loc], true, false);
|
||
|
||
if (securityUI) {
|
||
this._callProgressListeners(null, "onSecurityChange",
|
||
[webProgress, null, securityUI.state], true, false);
|
||
}
|
||
|
||
var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
|
||
if (listener && listener.mStateFlags) {
|
||
this._callProgressListeners(null, "onUpdateCurrentBrowser",
|
||
[listener.mStateFlags, listener.mStatus,
|
||
listener.mMessage, listener.mTotalProgress],
|
||
true, false);
|
||
}
|
||
|
||
// Don't switch the fast find or update the titlebar (bug 540248) - this tab switch is temporary
|
||
if (!this._previewMode) {
|
||
this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
|
||
|
||
this.updateTitlebar();
|
||
|
||
this.mCurrentTab.removeAttribute("titlechanged");
|
||
}
|
||
|
||
// If the new tab is busy, and our current state is not busy, then
|
||
// we need to fire a start to all progress listeners.
|
||
const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
|
||
if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
|
||
this.mIsBusy = true;
|
||
this._callProgressListeners(null, "onStateChange",
|
||
[webProgress, null,
|
||
nsIWebProgressListener.STATE_START |
|
||
nsIWebProgressListener.STATE_IS_NETWORK, 0],
|
||
true, false);
|
||
}
|
||
|
||
// If the new tab is not busy, and our current state is busy, then
|
||
// we need to fire a stop to all progress listeners.
|
||
if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
|
||
this.mIsBusy = false;
|
||
this._callProgressListeners(null, "onStateChange",
|
||
[webProgress, null,
|
||
nsIWebProgressListener.STATE_STOP |
|
||
nsIWebProgressListener.STATE_IS_NETWORK, 0],
|
||
true, false);
|
||
}
|
||
|
||
// TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
|
||
// that might rely upon the other changes suppressed.
|
||
// Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
|
||
if (!this._previewMode) {
|
||
// We've selected the new tab, so go ahead and notify listeners.
|
||
let event = document.createEvent("Events");
|
||
event.initEvent("TabSelect", true, false);
|
||
this.mCurrentTab.dispatchEvent(event);
|
||
|
||
this._tabAttrModified(oldTab);
|
||
this._tabAttrModified(this.mCurrentTab);
|
||
|
||
// Adjust focus
|
||
do {
|
||
// Focus the location bar if it was previously focused for that tab.
|
||
// In full screen mode, only bother making the location bar visible
|
||
// if the tab is a blank one.
|
||
oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
|
||
if (newBrowser._urlbarFocused && gURLBar) {
|
||
if (!window.fullScreen) {
|
||
gURLBar.focus();
|
||
break;
|
||
} else if (isTabEmpty(this.mCurrentTab)) {
|
||
focusAndSelectUrlBar();
|
||
break;
|
||
}
|
||
}
|
||
|
||
// If the find bar is focused, keep it focused.
|
||
if (gFindBarInitialized &&
|
||
!gFindBar.hidden &&
|
||
gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
|
||
break;
|
||
|
||
// Otherwise, focus the content area.
|
||
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
|
||
let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
|
||
|
||
// for anchors, use FLAG_SHOWRING so that it is clear what link was
|
||
// last clicked when switching back to that tab
|
||
let focusFlags = fm.FLAG_NOSCROLL;
|
||
if (newFocusedElement &&
|
||
(newFocusedElement instanceof HTMLAnchorElement ||
|
||
newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
|
||
focusFlags |= fm.FLAG_SHOWRING;
|
||
fm.setFocus(newBrowser, focusFlags);
|
||
} while (false);
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="_tabAttrModified">
|
||
<parameter name="aTab"/>
|
||
<body><![CDATA[
|
||
if (this._removingTabs.indexOf(aTab) > -1)
|
||
return;
|
||
|
||
// This event should be dispatched when any of these attributes change:
|
||
// label, crop, busy, image, selected
|
||
var event = document.createEvent("Events");
|
||
event.initEvent("TabAttrModified", true, false);
|
||
aTab.dispatchEvent(event);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="setTabTitleLoading">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
aTab.label = this.mStringBundle.getString("tabs.connecting");
|
||
aTab.crop = "end";
|
||
this._tabAttrModified(aTab);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="setTabTitle">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
var browser = this.getBrowserForTab(aTab);
|
||
var crop = "end";
|
||
var title = browser.contentTitle;
|
||
|
||
if (!title) {
|
||
if (browser.currentURI.spec) {
|
||
try {
|
||
title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
|
||
} catch(ex) {
|
||
title = browser.currentURI.spec;
|
||
}
|
||
}
|
||
|
||
if (title && title != "about:blank") {
|
||
// At this point, we now have a URI.
|
||
// Let's try to unescape it using a character set
|
||
// in case the URI is not ASCII.
|
||
try {
|
||
var characterSet = browser.contentDocument.characterSet;
|
||
const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
|
||
.getService(Components.interfaces.nsITextToSubURI);
|
||
title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
|
||
} catch(ex) { /* Do nothing. */ }
|
||
|
||
crop = "center";
|
||
|
||
} else // Still no title? Fall back to our untitled string.
|
||
title = this.mStringBundle.getString("tabs.emptyTabTitle");
|
||
}
|
||
|
||
if (aTab.label == title &&
|
||
aTab.crop == crop)
|
||
return false;
|
||
|
||
aTab.label = title;
|
||
aTab.crop = crop;
|
||
this._tabAttrModified(aTab);
|
||
|
||
if (aTab.selected)
|
||
this.updateTitlebar();
|
||
|
||
return true;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="enterTabbedMode">
|
||
<body>
|
||
<![CDATA[
|
||
if (this.mTabbedMode)
|
||
return;
|
||
|
||
this.mTabbedMode = true; // Welcome to multi-tabbed mode.
|
||
|
||
if (XULBrowserWindow.isBusy) {
|
||
this.mCurrentTab.setAttribute("busy", "true");
|
||
this.mIsBusy = true;
|
||
this.setTabTitleLoading(this.mCurrentTab);
|
||
} else {
|
||
this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
|
||
}
|
||
|
||
var filter;
|
||
if (this.mTabFilters.length > 0) {
|
||
// Use the filter hooked up in our addProgressListener
|
||
filter = this.mTabFilters[0];
|
||
} else {
|
||
// create a filter and hook it up to our first browser
|
||
filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
|
||
.createInstance(Components.interfaces.nsIWebProgress);
|
||
this.mTabFilters[0] = filter;
|
||
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
}
|
||
|
||
// Remove all our progress listeners from the active browser's filter.
|
||
this.mProgressListeners.forEach(filter.removeProgressListener, filter);
|
||
|
||
// Wire up a progress listener to our filter.
|
||
const listener = this.mTabProgressListener(this.mCurrentTab,
|
||
this.mCurrentBrowser,
|
||
false);
|
||
filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
this.mTabListeners[0] = listener;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="loadOneTab">
|
||
<parameter name="aURI"/>
|
||
<parameter name="aReferrerURI"/>
|
||
<parameter name="aCharset"/>
|
||
<parameter name="aPostData"/>
|
||
<parameter name="aLoadInBackground"/>
|
||
<parameter name="aAllowThirdPartyFixup"/>
|
||
<body>
|
||
<![CDATA[
|
||
var aFromExternal;
|
||
var aRelatedToCurrent;
|
||
if (arguments.length == 2 &&
|
||
typeof arguments[1] == "object" &&
|
||
!(arguments[1] instanceof Ci.nsIURI)) {
|
||
let params = arguments[1];
|
||
aReferrerURI = params.referrerURI;
|
||
aCharset = params.charset;
|
||
aPostData = params.postData;
|
||
aLoadInBackground = params.inBackground;
|
||
aAllowThirdPartyFixup = params.allowThirdPartyFixup;
|
||
aFromExternal = params.fromExternal;
|
||
aRelatedToCurrent = params.relatedToCurrent;
|
||
}
|
||
|
||
var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
|
||
Services.prefs.getBoolPref("browser.tabs.loadInBackground");
|
||
var owner = bgLoad ? null : this.selectedTab;
|
||
var tab = this.addTab(aURI, {
|
||
referrerURI: aReferrerURI,
|
||
charset: aCharset,
|
||
postData: aPostData,
|
||
ownerTab: owner,
|
||
allowThirdPartyFixup: aAllowThirdPartyFixup,
|
||
fromExternal: aFromExternal,
|
||
relatedToCurrent: aRelatedToCurrent});
|
||
if (!bgLoad)
|
||
this.selectedTab = tab;
|
||
|
||
return tab;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="loadTabs">
|
||
<parameter name="aURIs"/>
|
||
<parameter name="aLoadInBackground"/>
|
||
<parameter name="aReplace"/>
|
||
<body><![CDATA[
|
||
if (!aURIs.length)
|
||
return;
|
||
|
||
// The tab selected after this new tab is closed (i.e. the new tab's
|
||
// "owner") is the next adjacent tab (i.e. not the previously viewed tab)
|
||
// when several urls are opened here (i.e. closing the first should select
|
||
// the next of many URLs opened) or if the pref to have UI links opened in
|
||
// the background is set (i.e. the link is not being opened modally)
|
||
//
|
||
// i.e.
|
||
// Number of URLs Load UI Links in BG Focus Last Viewed?
|
||
// == 1 false YES
|
||
// == 1 true NO
|
||
// > 1 false/true NO
|
||
var multiple = aURIs.length > 1;
|
||
var owner = multiple || aLoadInBackground ? null : this.selectedTab;
|
||
var firstTabAdded = null;
|
||
|
||
if (aReplace) {
|
||
try {
|
||
this.loadURI(aURIs[0], null, null);
|
||
} catch (e) {
|
||
// Ignore failure in case a URI is wrong, so we can continue
|
||
// opening the next ones.
|
||
}
|
||
}
|
||
else
|
||
firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
|
||
|
||
var tabNum = this.tabContainer.selectedIndex;
|
||
for (let i = 1; i < aURIs.length; ++i) {
|
||
let tab = this.addTab(aURIs[i], {skipAnimation: true});
|
||
if (aReplace)
|
||
this.moveTabTo(tab, ++tabNum);
|
||
}
|
||
|
||
if (!aLoadInBackground) {
|
||
if (firstTabAdded) {
|
||
// .selectedTab setter focuses the content area
|
||
this.selectedTab = firstTabAdded;
|
||
}
|
||
else
|
||
window.content.focus();
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="addTab">
|
||
<parameter name="aURI"/>
|
||
<parameter name="aReferrerURI"/>
|
||
<parameter name="aCharset"/>
|
||
<parameter name="aPostData"/>
|
||
<parameter name="aOwner"/>
|
||
<parameter name="aAllowThirdPartyFixup"/>
|
||
<body>
|
||
<![CDATA[
|
||
var aFromExternal;
|
||
var aRelatedToCurrent;
|
||
var aSkipAnimation;
|
||
if (arguments.length == 2 &&
|
||
typeof arguments[1] == "object" &&
|
||
!(arguments[1] instanceof Ci.nsIURI)) {
|
||
let params = arguments[1];
|
||
aReferrerURI = params.referrerURI;
|
||
aCharset = params.charset;
|
||
aPostData = params.postData;
|
||
aOwner = params.ownerTab;
|
||
aAllowThirdPartyFixup = params.allowThirdPartyFixup;
|
||
aFromExternal = params.fromExternal;
|
||
aRelatedToCurrent = params.relatedToCurrent;
|
||
aSkipAnimation = params.skipAnimation;
|
||
}
|
||
|
||
this._browsers = null; // invalidate cache
|
||
|
||
this.enterTabbedMode();
|
||
|
||
// if we're adding tabs, we're past interrupt mode, ditch the owner
|
||
if (this.mCurrentTab.owner)
|
||
this.mCurrentTab.owner = null;
|
||
|
||
var t = document.createElementNS(
|
||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||
"tab");
|
||
|
||
var blank = !aURI || (aURI == "about:blank");
|
||
|
||
if (blank)
|
||
t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
|
||
else
|
||
t.setAttribute("label", aURI);
|
||
|
||
t.setAttribute("crop", "end");
|
||
t.setAttribute("validate", "never");
|
||
t.setAttribute("onerror", "this.removeAttribute('image');");
|
||
t.className = "tabbrowser-tab";
|
||
|
||
// When overflowing, new tabs are scrolled into view smoothly, which
|
||
// doesn't go well together with the width transition. So we skip the
|
||
// transition in that case.
|
||
if (aSkipAnimation ||
|
||
this.tabContainer.getAttribute("overflow") == "true" ||
|
||
!Services.prefs.getBoolPref("browser.tabs.animate")) {
|
||
t.setAttribute("fadein", "true");
|
||
setTimeout(function (tabContainer) {
|
||
tabContainer._handleNewTab(t);
|
||
}, 0, this.tabContainer);
|
||
} else {
|
||
setTimeout(function (tabContainer) {
|
||
if (t.pinned)
|
||
tabContainer._handleNewTab(t);
|
||
else
|
||
t.setAttribute("fadein", "true");
|
||
}, 0, this.tabContainer);
|
||
}
|
||
|
||
this.tabContainer.appendChild(t);
|
||
|
||
if (this.tabContainer.mTabstrip._isRTLScrollbox) {
|
||
/* In RTL UI, the tab is visually added to the left side of the
|
||
* tabstrip. This means the tabstip has to be scrolled back in
|
||
* order to make sure the same set of tabs is visible before and
|
||
* after the new tab is added. See bug 508816. */
|
||
|
||
this.tabContainer.mTabstrip.scrollByPixels(t.clientWidth);
|
||
}
|
||
|
||
// invalidate cache, because tabContainer is about to change
|
||
this._browsers = null;
|
||
|
||
// If this new tab is owned by another, assert that relationship
|
||
if (aOwner)
|
||
t.owner = aOwner;
|
||
|
||
var b = document.createElementNS(
|
||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||
"browser");
|
||
b.setAttribute("type", "content-targetable");
|
||
b.setAttribute("message", "true");
|
||
b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
|
||
b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
|
||
if (this.hasAttribute("autocompletepopup"))
|
||
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
|
||
b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
||
|
||
// Create the browserStack container
|
||
var stack = document.createElementNS(
|
||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||
"stack");
|
||
stack.setAttribute("anonid", "browserStack");
|
||
stack.appendChild(b);
|
||
stack.setAttribute("flex", "1");
|
||
|
||
// Add the Message and the Browser to the box
|
||
var notificationbox = document.createElementNS(
|
||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||
"notificationbox");
|
||
notificationbox.setAttribute("flex", "1");
|
||
notificationbox.appendChild(stack);
|
||
|
||
var position = this.tabs.length - 1;
|
||
var uniqueId = "panel" + Date.now() + position;
|
||
notificationbox.id = uniqueId;
|
||
t.linkedPanel = uniqueId;
|
||
t.linkedBrowser = b;
|
||
t._tPos = position;
|
||
if (t.previousSibling.selected)
|
||
t.setAttribute("afterselected", true);
|
||
|
||
// NB: this appendChild call causes us to run constructors for the
|
||
// browser element, which fires off a bunch of notifications. Some
|
||
// of those notifications can cause code to run that inspects our
|
||
// state, so it is important that the tab element is fully
|
||
// initialized by this point.
|
||
this.mPanelContainer.appendChild(notificationbox);
|
||
|
||
this.tabContainer.updateVisibility();
|
||
|
||
// wire up a progress listener for the new browser object.
|
||
var tabListener = this.mTabProgressListener(t, b, blank);
|
||
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
|
||
.createInstance(Components.interfaces.nsIWebProgress);
|
||
filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
this.mTabListeners[position] = tabListener;
|
||
this.mTabFilters[position] = filter;
|
||
|
||
b._fastFind = this.fastFind;
|
||
b.droppedLinkHandler = handleDroppedLink;
|
||
|
||
// Dispatch a new tab notification. We do this once we're
|
||
// entirely done, so that things are in a consistent state
|
||
// even if the event listener opens or closes tabs.
|
||
var evt = document.createEvent("Events");
|
||
evt.initEvent("TabOpen", true, false);
|
||
t.dispatchEvent(evt);
|
||
|
||
if (!blank) {
|
||
// Stop the existing about:blank load. Otherwise, if aURI
|
||
// doesn't stop in-progress loads on its own, we'll get into
|
||
// trouble with multiple parallel loads running at once.
|
||
b.stop();
|
||
|
||
// pretend the user typed this so it'll be available till
|
||
// the document successfully loads
|
||
b.userTypedValue = aURI;
|
||
|
||
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
||
if (aAllowThirdPartyFixup)
|
||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||
if (aFromExternal)
|
||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
|
||
try {
|
||
b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
|
||
|
||
// We start our browsers out as inactive, and then maintain
|
||
// activeness in the tab switcher.
|
||
b.docShell.isActive = false;
|
||
}
|
||
catch (ex) { }
|
||
}
|
||
|
||
// Check if we're opening a tab related to the current tab and
|
||
// move it to after the current tab.
|
||
// aReferrerURI is null or undefined if the tab is opened from
|
||
// an external application or bookmark, i.e. somewhere other
|
||
// than the current tab.
|
||
if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
|
||
Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
|
||
let newTabPos = (this._lastRelatedTab ||
|
||
this.selectedTab)._tPos + 1;
|
||
if (this._lastRelatedTab)
|
||
this._lastRelatedTab.owner = null;
|
||
else
|
||
t.owner = this.selectedTab;
|
||
this.moveTabTo(t, newTabPos);
|
||
this._lastRelatedTab = t;
|
||
}
|
||
|
||
return t;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="warnAboutClosingTabs">
|
||
<parameter name="aAll"/>
|
||
<body>
|
||
<![CDATA[
|
||
var tabsToClose = (aAll ? this.tabs.length : this.visibleTabs.length - 1)
|
||
- gBrowser._numPinnedTabs;
|
||
if (tabsToClose <= 1)
|
||
return true;
|
||
|
||
const pref = "browser.tabs.warnOnClose";
|
||
var shouldPrompt = Services.prefs.getBoolPref(pref);
|
||
|
||
if (!shouldPrompt)
|
||
return true;
|
||
|
||
var ps = Services.prompt;
|
||
|
||
// default to true: if it were false, we wouldn't get this far
|
||
var warnOnClose = { value: true };
|
||
var bundle = this.mStringBundle;
|
||
|
||
// focus the window before prompting.
|
||
// this will raise any minimized window, which will
|
||
// make it obvious which window the prompt is for and will
|
||
// solve the problem of windows "obscuring" the prompt.
|
||
// see bug #350299 for more details
|
||
window.focus();
|
||
var buttonPressed =
|
||
ps.confirmEx(window,
|
||
bundle.getString("tabs.closeWarningTitle"),
|
||
bundle.getFormattedString("tabs.closeWarningMultipleTabs",
|
||
[tabsToClose]),
|
||
(ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
|
||
+ (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
|
||
bundle.getString("tabs.closeButtonMultiple"),
|
||
null, null,
|
||
bundle.getString('tabs.closeWarningPromptMe'),
|
||
warnOnClose);
|
||
var reallyClose = (buttonPressed == 0);
|
||
// don't set the pref unless they press OK and it's false
|
||
if (reallyClose && !warnOnClose.value)
|
||
Services.prefs.setBoolPref(pref, false);
|
||
|
||
return reallyClose;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="removeAllTabsBut">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (aTab.pinned)
|
||
return;
|
||
|
||
if (this.warnAboutClosingTabs(false)) {
|
||
let tabs = this.visibleTabs;
|
||
this.selectedTab = aTab;
|
||
|
||
for (let i = tabs.length - 1; i >= 0; --i) {
|
||
if (tabs[i] != aTab && !tabs[i].pinned)
|
||
this.removeTab(tabs[i]);
|
||
}
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="removeCurrentTab">
|
||
<parameter name="aParams"/>
|
||
<body>
|
||
<![CDATA[
|
||
this.removeTab(this.mCurrentTab, aParams);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<field name="_removingTabs">
|
||
[]
|
||
</field>
|
||
|
||
<method name="removeTab">
|
||
<parameter name="aTab"/>
|
||
<parameter name="aParams"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (aParams)
|
||
var animate = aParams.animate;
|
||
|
||
// Handle requests for synchronously removing an already
|
||
// asynchronously closing tab.
|
||
if (!animate &&
|
||
this._removingTabs.indexOf(aTab) > -1) {
|
||
this._endRemoveTab(aTab);
|
||
return;
|
||
}
|
||
|
||
var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
|
||
|
||
if (!this._beginRemoveTab(aTab, false, null, true))
|
||
return;
|
||
|
||
if (!animate /* the caller didn't opt in */ ||
|
||
isLastTab ||
|
||
aTab.pinned ||
|
||
this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
|
||
aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
|
||
window.getComputedStyle(aTab).maxWidth == "1px" /* fade-in transition hasn't moved yet */ ||
|
||
!Services.prefs.getBoolPref("browser.tabs.animate")) {
|
||
this._endRemoveTab(aTab);
|
||
return;
|
||
}
|
||
|
||
this._blurTab(aTab);
|
||
aTab.removeAttribute("fadein");
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<!-- Tab close requests are ignored if the window is closing anyway,
|
||
e.g. when holding Ctrl+W. -->
|
||
<field name="_windowIsClosing">
|
||
false
|
||
</field>
|
||
|
||
<method name="_beginRemoveTab">
|
||
<parameter name="aTab"/>
|
||
<parameter name="aTabWillBeMoved"/>
|
||
<parameter name="aCloseWindowWithLastTab"/>
|
||
<parameter name="aCloseWindowFastpath"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (this._removingTabs.indexOf(aTab) > -1 || this._windowIsClosing)
|
||
return false;
|
||
|
||
var browser = this.getBrowserForTab(aTab);
|
||
|
||
if (!aTabWillBeMoved) {
|
||
let ds = browser.docShell;
|
||
if (ds && ds.contentViewer && !ds.contentViewer.permitUnload())
|
||
return false;
|
||
}
|
||
|
||
var closeWindow = false;
|
||
var newTab = false;
|
||
if (this.tabs.length - this._removingTabs.length == 1) {
|
||
closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
|
||
!window.toolbar.visible ||
|
||
this.tabContainer._closeWindowWithLastTab;
|
||
|
||
// Closing the tab and replacing it with a blank one is notably slower
|
||
// than closing the window right away. If the caller opts in, take
|
||
// the fast path.
|
||
if (closeWindow &&
|
||
aCloseWindowFastpath &&
|
||
this._removingTabs.length == 0 &&
|
||
(this._windowIsClosing = window.closeWindow(true)))
|
||
return null;
|
||
|
||
newTab = true;
|
||
}
|
||
|
||
this._removingTabs.push(aTab);
|
||
|
||
this.tabContainer.updateVisibility();
|
||
|
||
// We're committed to closing the tab now.
|
||
// Dispatch a notification.
|
||
// We dispatch it before any teardown so that event listeners can
|
||
// inspect the tab that's about to close.
|
||
var evt = document.createEvent("UIEvent");
|
||
evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
|
||
aTab.dispatchEvent(evt);
|
||
|
||
// Remove the tab's filter and progress listener.
|
||
const filter = this.mTabFilters[aTab._tPos];
|
||
browser.webProgress.removeProgressListener(filter);
|
||
filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
|
||
this.mTabListeners[aTab._tPos].destroy();
|
||
|
||
if (browser.registeredOpenURI && !aTabWillBeMoved) {
|
||
this.mBrowserHistory.unregisterOpenPage(browser.registeredOpenURI);
|
||
delete browser.registeredOpenURI;
|
||
}
|
||
|
||
// We are no longer the primary content area.
|
||
browser.setAttribute("type", "content-targetable");
|
||
|
||
// Remove this tab as the owner of any other tabs, since it's going away.
|
||
Array.forEach(this.tabs, function (tab) {
|
||
if ("owner" in tab && tab.owner == aTab)
|
||
// |tab| is a child of the tab we're removing, make it an orphan
|
||
tab.owner = null;
|
||
});
|
||
|
||
aTab._endRemoveArgs = [closeWindow, newTab];
|
||
return true;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="_endRemoveTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (!aTab || !aTab._endRemoveArgs)
|
||
return;
|
||
|
||
var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
|
||
aTab._endRemoveArgs = null;
|
||
|
||
if (this._windowIsClosing) {
|
||
aCloseWindow = false;
|
||
aNewTab = false;
|
||
}
|
||
|
||
this._lastRelatedTab = null;
|
||
|
||
// update the UI early for responsiveness
|
||
aTab.collapsed = true;
|
||
if (aNewTab)
|
||
this.addTab("about:blank", {skipAnimation: true});
|
||
this.tabContainer._fillTrailingGap();
|
||
this._blurTab(aTab);
|
||
|
||
this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
|
||
|
||
if (aCloseWindow) {
|
||
this._windowIsClosing = true;
|
||
while (this._removingTabs.length)
|
||
this._endRemoveTab(this._removingTabs[0]);
|
||
} else if (!this._windowIsClosing) {
|
||
if (aNewTab)
|
||
focusAndSelectUrlBar();
|
||
|
||
// workaround for bug 345399
|
||
this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
|
||
}
|
||
|
||
// We're going to remove the tab and the browser now.
|
||
// Clean up mTabFilters and mTabListeners now rather than in
|
||
// _beginRemoveTab, so that their size is always in sync with the
|
||
// number of tabs and browsers (the xbl destructor depends on this).
|
||
this.mTabFilters.splice(aTab._tPos, 1);
|
||
this.mTabListeners.splice(aTab._tPos, 1);
|
||
|
||
var browser = this.getBrowserForTab(aTab);
|
||
|
||
// Because of the way XBL works (fields just set JS
|
||
// properties on the element) and the code we have in place
|
||
// to preserve the JS objects for any elements that have
|
||
// JS properties set on them, the browser element won't be
|
||
// destroyed until the document goes away. So we force a
|
||
// cleanup ourselves.
|
||
// This has to happen before we remove the child so that the
|
||
// XBL implementation of nsIObserver still works.
|
||
browser.destroy();
|
||
|
||
if (browser == this.mCurrentBrowser)
|
||
this.mCurrentBrowser = null;
|
||
|
||
var wasPinned = aTab.pinned;
|
||
|
||
// Invalidate browsers cache, as the tab is removed from the
|
||
// tab container.
|
||
this._browsers = null;
|
||
|
||
// Remove the tab ...
|
||
this.tabContainer.removeChild(aTab);
|
||
|
||
// ... and fix up the _tPos properties immediately.
|
||
for (let i = aTab._tPos; i < this.tabs.length; i++)
|
||
this.tabs[i]._tPos = i;
|
||
|
||
if (!this._windowIsClosing) {
|
||
if (wasPinned)
|
||
this.tabContainer._positionPinnedTabs();
|
||
|
||
// update tab close buttons state
|
||
this.tabContainer.adjustTabstrip();
|
||
}
|
||
|
||
// update first-tab/last-tab/beforeselected/afterselected attributes
|
||
this.selectedTab._selected = true;
|
||
|
||
// Removing the panel requires fixing up selectedPanel immediately
|
||
// (see below), which would be hindered by the potentially expensive
|
||
// browser removal. So we remove the browser and the panel in two
|
||
// steps.
|
||
var panel = browser.parentNode.parentNode;
|
||
|
||
// This will unload the document. An unload handler could remove
|
||
// dependant tabs, so it's important that the tabbrowser is now in
|
||
// a consistent state (tab removed, tab positions updated, etc.).
|
||
panel.removeChild(browser.parentNode);
|
||
|
||
// As the browser is removed, the removal of a dependent document can
|
||
// cause the whole window to close. So at this point, it's possible
|
||
// that the binding is destructed.
|
||
if (this.mTabBox) {
|
||
let selectedPanel = this.mTabBox.selectedPanel;
|
||
|
||
this.mPanelContainer.removeChild(panel);
|
||
|
||
// Under the hood, a selectedIndex attribute controls which panel
|
||
// is displayed. Removing a panel A which precedes the selected
|
||
// panel B makes selectedIndex point to the panel next to B. We
|
||
// need to explicitly preserve B as the selected panel.
|
||
this.mTabBox.selectedPanel = selectedPanel;
|
||
}
|
||
|
||
if (aCloseWindow)
|
||
this._windowIsClosing = closeWindow(true);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="_blurTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (this.mCurrentTab != aTab)
|
||
return;
|
||
|
||
if (aTab.owner &&
|
||
this._removingTabs.indexOf(aTab.owner) == -1 &&
|
||
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
|
||
this.selectedTab = aTab.owner;
|
||
return;
|
||
}
|
||
|
||
// Switch to a visible tab unless there aren't any others remaining
|
||
let remainingTabs = this.visibleTabs;
|
||
let numTabs = remainingTabs.length;
|
||
if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
|
||
remainingTabs = Array.filter(this.tabs, function(tab) {
|
||
return this._removingTabs.indexOf(tab) == -1;
|
||
}, this);
|
||
}
|
||
|
||
// Try to find a remaining tab that comes after the given tab
|
||
var tab = aTab;
|
||
do {
|
||
tab = tab.nextSibling;
|
||
} while (tab && remainingTabs.indexOf(tab) == -1);
|
||
|
||
if (!tab) {
|
||
tab = aTab;
|
||
|
||
do {
|
||
tab = tab.previousSibling;
|
||
} while (tab && remainingTabs.indexOf(tab) == -1);
|
||
}
|
||
|
||
this.selectedTab = tab;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="swapBrowsersAndCloseOther">
|
||
<parameter name="aOurTab"/>
|
||
<parameter name="aOtherTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
// That's gBrowser for the other window, not the tab's browser!
|
||
var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
|
||
|
||
// First, start teardown of the other browser. Make sure to not
|
||
// fire the beforeunload event in the process. Close the other
|
||
// window if this was its last tab.
|
||
if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
|
||
return;
|
||
|
||
// Unhook our progress listener
|
||
var ourIndex = aOurTab._tPos;
|
||
const filter = this.mTabFilters[ourIndex];
|
||
var tabListener = this.mTabListeners[ourIndex];
|
||
var ourBrowser = this.getBrowserForTab(aOurTab);
|
||
ourBrowser.webProgress.removeProgressListener(filter);
|
||
filter.removeProgressListener(tabListener);
|
||
var tabListenerBlank = tabListener.mBlank;
|
||
|
||
var otherBrowser = aOtherTab.linkedBrowser;
|
||
|
||
// Restore current registered open URI.
|
||
if (ourBrowser.registeredOpenURI)
|
||
this.mBrowserHistory.unregisterOpenPage(ourBrowser.registeredOpenURI);
|
||
if (otherBrowser.registeredOpenURI)
|
||
ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
|
||
|
||
// Workarounds for bug 458697
|
||
// Icon might have been set on DOMLinkAdded, don't override that.
|
||
if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
|
||
this.setIcon(aOurTab, otherBrowser.mIconURL);
|
||
var isBusy = aOtherTab.hasAttribute("busy");
|
||
if (isBusy) {
|
||
aOurTab.setAttribute("busy", "true");
|
||
this._tabAttrModified(aOurTab);
|
||
if (aOurTab == this.selectedTab)
|
||
this.mIsBusy = true;
|
||
}
|
||
|
||
// Swap the docshells
|
||
ourBrowser.swapDocShells(otherBrowser);
|
||
|
||
// Finish tearing down the tab that's going away.
|
||
remoteBrowser._endRemoveTab(aOtherTab);
|
||
|
||
// Restore the progress listener
|
||
tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
|
||
tabListenerBlank);
|
||
this.mTabListeners[ourIndex] = tabListener;
|
||
filter.addProgressListener(tabListener,
|
||
Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
|
||
ourBrowser.webProgress.addProgressListener(filter,
|
||
Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
|
||
if (isBusy)
|
||
this.setTabTitleLoading(aOurTab);
|
||
else
|
||
this.setTabTitle(aOurTab);
|
||
|
||
// If the tab was already selected (this happpens in the scenario
|
||
// of replaceTabWithWindow), notify onLocationChange, etc.
|
||
if (aOurTab == this.selectedTab)
|
||
this.updateCurrentBrowser(true);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="reloadAllTabs">
|
||
<body>
|
||
<![CDATA[
|
||
let tabs = this.visibleTabs;
|
||
let l = tabs.length;
|
||
for (var i = 0; i < l; i++) {
|
||
try {
|
||
this.getBrowserForTab(tabs[i]).reload();
|
||
} catch (e) {
|
||
// ignore failure to reload so others will be reloaded
|
||
}
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="reloadTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
this.getBrowserForTab(aTab).reload();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="addProgressListener">
|
||
<parameter name="aListener"/>
|
||
<parameter name="aMask"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (!this.mAddProgressListenerWasCalled) {
|
||
this.mAddProgressListenerWasCalled = true;
|
||
this.tabContainer.updateVisibility();
|
||
}
|
||
|
||
if (this.mProgressListeners.length == 1) {
|
||
// If we are adding a 2nd progress listener, we need to enter tabbed mode
|
||
// because the browser status filter can only handle one progress listener.
|
||
// In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
|
||
this.enterTabbedMode();
|
||
}
|
||
|
||
this.mProgressListeners.push(aListener);
|
||
|
||
if (!this.mTabbedMode) {
|
||
// If someone does this:
|
||
// addProgressListener, removeProgressListener, addProgressListener
|
||
// don't create a new filter; reuse the existing filter.
|
||
if (this.mTabFilters.length == 0) {
|
||
// hook a filter up to our first browser
|
||
const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
|
||
.createInstance(Components.interfaces.nsIWebProgress);
|
||
this.mTabFilters[0] = filter;
|
||
this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
|
||
}
|
||
|
||
// Directly hook the listener up to the filter for better performance
|
||
this.mTabFilters[0].addProgressListener(aListener, aMask);
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="removeProgressListener">
|
||
<parameter name="aListener"/>
|
||
<body>
|
||
<![CDATA[
|
||
this.mProgressListeners =
|
||
this.mProgressListeners.filter(function (l) l != aListener);
|
||
|
||
if (!this.mTabbedMode)
|
||
// Don't forget to remove it from the filter we hooked it up to
|
||
this.mTabFilters[0].removeProgressListener(aListener);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="addTabsProgressListener">
|
||
<parameter name="aListener"/>
|
||
<body>
|
||
this.enterTabbedMode();
|
||
this.mTabsProgressListeners.push(aListener);
|
||
</body>
|
||
</method>
|
||
|
||
<method name="removeTabsProgressListener">
|
||
<parameter name="aListener"/>
|
||
<body>
|
||
<![CDATA[
|
||
this.mTabsProgressListeners =
|
||
this.mTabsProgressListeners.filter(function (l) l != aListener);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="getBrowserForTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
return aTab.linkedBrowser;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="showOnlyTheseTabs">
|
||
<parameter name="aTabs"/>
|
||
<body>
|
||
<![CDATA[
|
||
Array.forEach(this.tabs, function(tab) {
|
||
if (aTabs.indexOf(tab) == -1)
|
||
this.hideTab(tab);
|
||
else
|
||
this.showTab(tab);
|
||
}, this);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="showTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (aTab.hidden) {
|
||
aTab.removeAttribute("hidden");
|
||
let event = document.createEvent("Events");
|
||
event.initEvent("TabShow", true, false);
|
||
aTab.dispatchEvent(event);
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="hideTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
|
||
this._removingTabs.indexOf(aTab) == -1) {
|
||
aTab.setAttribute("hidden", "true");
|
||
let event = document.createEvent("Events");
|
||
event.initEvent("TabHide", true, false);
|
||
aTab.dispatchEvent(event);
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="selectTabAtIndex">
|
||
<parameter name="aIndex"/>
|
||
<parameter name="aEvent"/>
|
||
<body>
|
||
<![CDATA[
|
||
let tabs = this.visibleTabs;
|
||
|
||
// count backwards for aIndex < 0
|
||
if (aIndex < 0)
|
||
aIndex += tabs.length;
|
||
|
||
if (aIndex >= 0 && aIndex < tabs.length)
|
||
this.selectedTab = tabs[aIndex];
|
||
|
||
if (aEvent) {
|
||
aEvent.preventDefault();
|
||
aEvent.stopPropagation();
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<property name="selectedTab">
|
||
<getter>
|
||
return this.mTabBox.selectedTab;
|
||
</getter>
|
||
<setter>
|
||
<![CDATA[
|
||
// Update the tab
|
||
this.mTabBox.selectedTab = val;
|
||
return val;
|
||
]]>
|
||
</setter>
|
||
</property>
|
||
|
||
<property name="selectedBrowser"
|
||
onget="return this.mCurrentBrowser;"
|
||
readonly="true"/>
|
||
|
||
<property name="browsers" readonly="true">
|
||
<getter>
|
||
<![CDATA[
|
||
return this._browsers ||
|
||
(this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
|
||
]]>
|
||
</getter>
|
||
</property>
|
||
|
||
<!-- Moves a tab to a new browser window, unless it's already the only tab
|
||
in the current window, in which case this will do nothing. -->
|
||
<method name="replaceTabWithWindow">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
if (this.tabs.length == 1)
|
||
return null;
|
||
|
||
// tell a new window to take the "dropped" tab
|
||
return window.openDialog(getBrowserURL(), "_blank", "dialog=no,all", aTab);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="moveTabTo">
|
||
<parameter name="aTab"/>
|
||
<parameter name="aIndex"/>
|
||
<body>
|
||
<![CDATA[
|
||
var oldPosition = aTab._tPos;
|
||
if (oldPosition == aIndex)
|
||
return;
|
||
|
||
// Don't allow mixing pinned and unpinned tabs.
|
||
if (aTab.pinned)
|
||
aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
|
||
else
|
||
aIndex = Math.max(aIndex, this._numPinnedTabs);
|
||
if (oldPosition == aIndex)
|
||
return;
|
||
|
||
this._lastRelatedTab = null;
|
||
|
||
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
|
||
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
|
||
|
||
aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
|
||
this.mCurrentTab._selected = false;
|
||
// use .item() instead of [] because dragging to the end of the strip goes out of
|
||
// bounds: .item() returns null (so it acts like appendChild), but [] throws
|
||
this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
|
||
// invalidate cache, because tabContainer is about to change
|
||
this._browsers = null;
|
||
|
||
for (let i = 0; i < this.tabs.length; i++) {
|
||
this.tabs[i]._tPos = i;
|
||
this.tabs[i]._selected = false;
|
||
}
|
||
this.mCurrentTab._selected = true;
|
||
this.tabContainer.mTabstrip.ensureElementIsVisible(this.mCurrentTab, false);
|
||
|
||
if (aTab.pinned)
|
||
this.tabContainer._positionPinnedTabs();
|
||
|
||
var evt = document.createEvent("UIEvents");
|
||
evt.initUIEvent("TabMove", true, false, window, oldPosition);
|
||
aTab.dispatchEvent(evt);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="moveTabForward">
|
||
<body>
|
||
<![CDATA[
|
||
var tabPos = this.mCurrentTab._tPos;
|
||
if (tabPos < this.browsers.length - 1) {
|
||
this.moveTabTo(this.mCurrentTab, tabPos + 1);
|
||
this.mCurrentTab.focus();
|
||
}
|
||
else if (this.arrowKeysShouldWrap)
|
||
this.moveTabToStart();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="moveTabBackward">
|
||
<body>
|
||
<![CDATA[
|
||
var tabPos = this.mCurrentTab._tPos;
|
||
if (tabPos > 0) {
|
||
this.moveTabTo(this.mCurrentTab, tabPos - 1);
|
||
this.mCurrentTab.focus();
|
||
}
|
||
else if (this.arrowKeysShouldWrap)
|
||
this.moveTabToEnd();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="moveTabToStart">
|
||
<body>
|
||
<![CDATA[
|
||
var tabPos = this.mCurrentTab._tPos;
|
||
if (tabPos > 0) {
|
||
this.moveTabTo(this.mCurrentTab, 0);
|
||
this.mCurrentTab.focus();
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="moveTabToEnd">
|
||
<body>
|
||
<![CDATA[
|
||
var tabPos = this.mCurrentTab._tPos;
|
||
if (tabPos < this.browsers.length - 1) {
|
||
this.moveTabTo(this.mCurrentTab,
|
||
this.browsers.length - 1);
|
||
this.mCurrentTab.focus();
|
||
}
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="moveTabOver">
|
||
<parameter name="aEvent"/>
|
||
<body>
|
||
<![CDATA[
|
||
var direction = window.getComputedStyle(this.parentNode, null).direction;
|
||
if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
|
||
(direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
|
||
this.moveTabForward();
|
||
else
|
||
this.moveTabBackward();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="duplicateTab">
|
||
<parameter name="aTab"/><!-- can be from a different window as well -->
|
||
<body>
|
||
<![CDATA[
|
||
return Cc["@mozilla.org/browser/sessionstore;1"]
|
||
.getService(Ci.nsISessionStore)
|
||
.duplicateTab(window, aTab);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
|
||
MAKE SURE TO ADD IT HERE AS WELL. -->
|
||
<property name="canGoBack"
|
||
onget="return this.mCurrentBrowser.canGoBack;"
|
||
readonly="true"/>
|
||
|
||
<property name="canGoForward"
|
||
onget="return this.mCurrentBrowser.canGoForward;"
|
||
readonly="true"/>
|
||
|
||
<method name="goBack">
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.goBack();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="goForward">
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.goForward();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="reload">
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.reload();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="reloadWithFlags">
|
||
<parameter name="aFlags"/>
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.reloadWithFlags(aFlags);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="stop">
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.stop();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<!-- throws exception for unknown schemes -->
|
||
<method name="loadURI">
|
||
<parameter name="aURI"/>
|
||
<parameter name="aReferrerURI"/>
|
||
<parameter name="aCharset"/>
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<!-- throws exception for unknown schemes -->
|
||
<method name="loadURIWithFlags">
|
||
<parameter name="aURI"/>
|
||
<parameter name="aFlags"/>
|
||
<parameter name="aReferrerURI"/>
|
||
<parameter name="aCharset"/>
|
||
<parameter name="aPostData"/>
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="goHome">
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.goHome();
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<property name="homePage">
|
||
<getter>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.homePage;
|
||
]]>
|
||
</getter>
|
||
<setter>
|
||
<![CDATA[
|
||
this.mCurrentBrowser.homePage = val;
|
||
return val;
|
||
]]>
|
||
</setter>
|
||
</property>
|
||
|
||
<method name="gotoIndex">
|
||
<parameter name="aIndex"/>
|
||
<body>
|
||
<![CDATA[
|
||
return this.mCurrentBrowser.gotoIndex(aIndex);
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<method name="attachFormFill">
|
||
<body><![CDATA[
|
||
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
|
||
var cb = this.getBrowserAtIndex(i);
|
||
cb.attachFormFill();
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="detachFormFill">
|
||
<body><![CDATA[
|
||
for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
|
||
var cb = this.getBrowserAtIndex(i);
|
||
cb.detachFormFill();
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<property name="pageReport"
|
||
onget="return this.mCurrentBrowser.pageReport;"
|
||
readonly="true"/>
|
||
|
||
<property name="currentURI"
|
||
onget="return this.mCurrentBrowser.currentURI;"
|
||
readonly="true"/>
|
||
|
||
<field name="_fastFind">null</field>
|
||
<property name="fastFind"
|
||
readonly="true">
|
||
<getter>
|
||
<![CDATA[
|
||
if (!this._fastFind) {
|
||
this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
|
||
.createInstance(Components.interfaces.nsITypeAheadFind);
|
||
this._fastFind.init(this.docShell);
|
||
}
|
||
return this._fastFind;
|
||
]]>
|
||
</getter>
|
||
</property>
|
||
|
||
<property name="docShell"
|
||
onget="return this.mCurrentBrowser.docShell"
|
||
readonly="true"/>
|
||
|
||
<property name="webNavigation"
|
||
onget="return this.mCurrentBrowser.webNavigation"
|
||
readonly="true"/>
|
||
|
||
<property name="webBrowserFind"
|
||
readonly="true"
|
||
onget="return this.mCurrentBrowser.webBrowserFind"/>
|
||
|
||
<property name="webProgress"
|
||
readonly="true"
|
||
onget="return this.mCurrentBrowser.webProgress"/>
|
||
|
||
<property name="contentWindow"
|
||
readonly="true"
|
||
onget="return this.mCurrentBrowser.contentWindow"/>
|
||
|
||
<property name="sessionHistory"
|
||
onget="return this.mCurrentBrowser.sessionHistory;"
|
||
readonly="true"/>
|
||
|
||
<property name="markupDocumentViewer"
|
||
onget="return this.mCurrentBrowser.markupDocumentViewer;"
|
||
readonly="true"/>
|
||
|
||
<property name="contentViewerEdit"
|
||
onget="return this.mCurrentBrowser.contentViewerEdit;"
|
||
readonly="true"/>
|
||
|
||
<property name="contentViewerFile"
|
||
onget="return this.mCurrentBrowser.contentViewerFile;"
|
||
readonly="true"/>
|
||
|
||
<property name="documentCharsetInfo"
|
||
onget="return this.mCurrentBrowser.documentCharsetInfo;"
|
||
readonly="true"/>
|
||
|
||
<property name="contentDocument"
|
||
onget="return this.mCurrentBrowser.contentDocument;"
|
||
readonly="true"/>
|
||
|
||
<property name="contentTitle"
|
||
onget="return this.mCurrentBrowser.contentTitle;"
|
||
readonly="true"/>
|
||
|
||
<property name="contentPrincipal"
|
||
onget="return this.mCurrentBrowser.contentPrincipal;"
|
||
readonly="true"/>
|
||
|
||
<property name="securityUI"
|
||
onget="return this.mCurrentBrowser.securityUI;"
|
||
readonly="true"/>
|
||
|
||
<method name="_handleKeyEvent">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
if (!aEvent.isTrusted) {
|
||
// Don't let untrusted events mess with tabs.
|
||
return;
|
||
}
|
||
|
||
if (aEvent.altKey)
|
||
return;
|
||
|
||
// We need to take care of FAYT-watching as long as the findbar
|
||
// isn't initialized. The checks on aEvent are copied from
|
||
// _shouldFastFind (see findbar.xml).
|
||
if (!gFindBarInitialized &&
|
||
!(aEvent.ctrlKey || aEvent.metaKey) &&
|
||
!aEvent.getPreventDefault()) {
|
||
let charCode = aEvent.charCode;
|
||
if (charCode) {
|
||
let char = String.fromCharCode(charCode);
|
||
if (char == "'" || char == "/" ||
|
||
Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
|
||
gFindBar._onBrowserKeypress(aEvent);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifdef XP_MACOSX
|
||
if (!aEvent.metaKey)
|
||
return;
|
||
|
||
var offset = 1;
|
||
switch (aEvent.charCode) {
|
||
case '}'.charCodeAt(0):
|
||
offset = -1;
|
||
case '{'.charCodeAt(0):
|
||
if (window.getComputedStyle(this, null).direction == "ltr")
|
||
offset *= -1;
|
||
this.tabContainer.advanceSelectedTab(offset, true);
|
||
aEvent.stopPropagation();
|
||
aEvent.preventDefault();
|
||
}
|
||
#else
|
||
if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
|
||
aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
|
||
this.mTabBox.handleCtrlPageUpDown) {
|
||
this.removeCurrentTab({animate: true});
|
||
aEvent.stopPropagation();
|
||
aEvent.preventDefault();
|
||
}
|
||
#endif
|
||
]]></body>
|
||
</method>
|
||
|
||
<property name="userTypedClear"
|
||
onget="return this.mCurrentBrowser.userTypedClear;"
|
||
onset="return this.mCurrentBrowser.userTypedClear = val;"/>
|
||
|
||
<property name="userTypedValue"
|
||
onget="return this.mCurrentBrowser.userTypedValue;"
|
||
onset="return this.mCurrentBrowser.userTypedValue = val;"/>
|
||
|
||
<method name="createTooltip">
|
||
<parameter name="event"/>
|
||
<body><![CDATA[
|
||
event.stopPropagation();
|
||
var tab = document.tooltipNode;
|
||
if (tab.localName != "tab") {
|
||
event.preventDefault();
|
||
return;
|
||
}
|
||
event.target.setAttribute("label", tab.mOverCloseButton ?
|
||
tab.getAttribute("closetabtext") :
|
||
tab.getAttribute("label"));
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="handleEvent">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
switch (aEvent.type) {
|
||
case "keypress":
|
||
this._handleKeyEvent(aEvent);
|
||
break;
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<constructor>
|
||
<![CDATA[
|
||
this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild.firstChild;
|
||
this.mCurrentTab = this.tabContainer.firstChild;
|
||
document.addEventListener("keypress", this, false);
|
||
|
||
var uniqueId = "panel" + Date.now();
|
||
this.mPanelContainer.childNodes[0].id = uniqueId;
|
||
this.mCurrentTab.linkedPanel = uniqueId;
|
||
this.mCurrentTab._tPos = 0;
|
||
this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
|
||
|
||
// set up the shared autoscroll popup
|
||
this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
|
||
this._autoScrollPopup.id = "autoscroller";
|
||
this.appendChild(this._autoScrollPopup);
|
||
this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
|
||
this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
|
||
]]>
|
||
</constructor>
|
||
|
||
<destructor>
|
||
<![CDATA[
|
||
for (var i = 0; i < this.mTabListeners.length; ++i) {
|
||
let browser = this.getBrowserAtIndex(i);
|
||
if (browser.registeredOpenURI) {
|
||
this.mBrowserHistory.unregisterOpenPage(browser.registeredOpenURI);
|
||
delete browser.registeredOpenURI;
|
||
}
|
||
browser.webProgress.removeProgressListener(this.mTabFilters[i]);
|
||
this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
|
||
this.mTabFilters[i] = null;
|
||
this.mTabListeners[i].destroy();
|
||
this.mTabListeners[i] = null;
|
||
}
|
||
document.removeEventListener("keypress", this, false);
|
||
]]>
|
||
</destructor>
|
||
|
||
<!-- Deprecated stuff, implemented for backwards compatibility. -->
|
||
<method name="setStripVisibilityTo">
|
||
<parameter name="aShow"/>
|
||
<body>
|
||
this.tabContainer.visible = aShow;
|
||
</body>
|
||
</method>
|
||
<method name="getStripVisibility">
|
||
<body>
|
||
return this.tabContainer.visible;
|
||
</body>
|
||
</method>
|
||
<property name="mContextTab" readonly="true"
|
||
onget="return TabContextMenu.contextTab;"/>
|
||
<property name="mPrefs" readonly="true"
|
||
onget="return Services.prefs;"/>
|
||
<property name="mTabContainer" readonly="true"
|
||
onget="return this.tabContainer;"/>
|
||
<property name="mTabs" readonly="true"
|
||
onget="return this.tabs;"/>
|
||
<!--
|
||
- Compatibility hack: several extensions depend on this property to
|
||
- access the tab context menu or tab container, so keep that working for
|
||
- now. Ideally we can remove this once extensions are using
|
||
- tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
|
||
-->
|
||
<property name="mStrip" readonly="true">
|
||
<getter>
|
||
<![CDATA[
|
||
return ({
|
||
self: this,
|
||
childNodes: [null, this.tabContextMenu, this.tabContainer],
|
||
firstChild: { nextSibling: this.tabContextMenu },
|
||
getElementsByAttribute: function (attr, attrValue) {
|
||
if (attr == "anonid" && attrValue == "tabContextMenu")
|
||
return [this.self.tabContextMenu];
|
||
return [];
|
||
},
|
||
// Also support adding event listeners (forward to the tab container)
|
||
addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
|
||
removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
|
||
});
|
||
]]>
|
||
</getter>
|
||
</property>
|
||
</implementation>
|
||
|
||
<handlers>
|
||
<handler event="DOMWindowClose" phase="capturing">
|
||
<![CDATA[
|
||
if (!event.isTrusted)
|
||
return;
|
||
|
||
if (this.tabs.length == 1)
|
||
return;
|
||
|
||
var tab = this._getTabForContentWindow(event.target);
|
||
if (tab) {
|
||
this.removeTab(tab);
|
||
event.preventDefault();
|
||
}
|
||
]]>
|
||
</handler>
|
||
<handler event="DOMWillOpenModalDialog" phase="capturing">
|
||
<![CDATA[
|
||
if (!event.isTrusted)
|
||
return;
|
||
|
||
// We're about to open a modal dialog, make sure the opening
|
||
// tab is brought to the front.
|
||
this.selectedTab = this._getTabForContentWindow(event.target.top);
|
||
]]>
|
||
</handler>
|
||
<handler event="DOMTitleChanged">
|
||
<![CDATA[
|
||
if (!event.isTrusted)
|
||
return;
|
||
|
||
var contentWin = event.target.defaultView;
|
||
if (contentWin != contentWin.top)
|
||
return;
|
||
|
||
var tab = this._getTabForContentWindow(contentWin);
|
||
var titleChanged = this.setTabTitle(tab);
|
||
if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
|
||
tab.setAttribute("titlechanged", "true");
|
||
]]>
|
||
</handler>
|
||
</handlers>
|
||
</binding>
|
||
|
||
<binding id="tabbrowser-tabbox"
|
||
extends="chrome://global/content/bindings/tabbox.xml#tabbox">
|
||
<implementation>
|
||
<property name="tabs" readonly="true"
|
||
onget="return document.getBindingParent(this).tabContainer;"/>
|
||
</implementation>
|
||
</binding>
|
||
|
||
<binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
|
||
<implementation>
|
||
<!-- Override scrollbox.xml method, since our scrollbox's children are
|
||
inherited from the binding parent -->
|
||
<method name="_getScrollableElements">
|
||
<body><![CDATA[
|
||
return Array.filter(document.getBindingParent(this).childNodes,
|
||
this._canScrollToElement, this);
|
||
]]></body>
|
||
</method>
|
||
<method name="_canScrollToElement">
|
||
<parameter name="tab"/>
|
||
<body><![CDATA[
|
||
return !tab.pinned && !tab.hidden;
|
||
]]></body>
|
||
</method>
|
||
</implementation>
|
||
|
||
<handlers>
|
||
<handler event="underflow"><![CDATA[
|
||
if (event.detail == 0)
|
||
return; // Ignore vertical events
|
||
|
||
var tabs = document.getBindingParent(this);
|
||
tabs.removeAttribute("overflow");
|
||
|
||
tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
|
||
tabs.tabbrowser);
|
||
|
||
tabs._positionPinnedTabs();
|
||
]]></handler>
|
||
<handler event="overflow"><![CDATA[
|
||
if (event.detail == 0)
|
||
return; // Ignore vertical events
|
||
|
||
var tabs = document.getBindingParent(this);
|
||
tabs.setAttribute("overflow", "true");
|
||
tabs._positionPinnedTabs();
|
||
]]></handler>
|
||
</handlers>
|
||
</binding>
|
||
|
||
<binding id="tabbrowser-tabs"
|
||
extends="chrome://global/content/bindings/tabbox.xml#tabs">
|
||
<resources>
|
||
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
|
||
</resources>
|
||
|
||
<content>
|
||
<xul:hbox align="start">
|
||
<xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
|
||
</xul:hbox>
|
||
<xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
|
||
style="min-width: 1px;"
|
||
#ifndef XP_MACOSX
|
||
clicktoscroll="true"
|
||
#endif
|
||
class="tabbrowser-arrowscrollbox">
|
||
# This is a hack to circumvent bug 472020, otherwise the tabs show up on the
|
||
# right of the newtab button.
|
||
<children includes="tab"/>
|
||
# This is to ensure anything extensions put here will go before the newtab
|
||
# button, necessary due to the previous hack.
|
||
<children/>
|
||
<xul:toolbarbutton class="tabs-newtab-button"
|
||
command="cmd_newNavigatorTab"
|
||
onclick="checkForMiddleClick(this, event);"
|
||
tooltiptext="&newTabButton.tooltip;"/>
|
||
</xul:arrowscrollbox>
|
||
</content>
|
||
|
||
<implementation implements="nsIDOMEventListener">
|
||
<constructor>
|
||
<![CDATA[
|
||
this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
|
||
this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
|
||
this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
|
||
|
||
var tab = this.firstChild;
|
||
tab.setAttribute("label",
|
||
this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
|
||
tab.setAttribute("crop", "end");
|
||
tab.setAttribute("validate", "never");
|
||
tab.setAttribute("onerror", "this.removeAttribute('image');");
|
||
this.adjustTabstrip();
|
||
|
||
Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
|
||
window.addEventListener("resize", this, false);
|
||
]]>
|
||
</constructor>
|
||
|
||
<destructor>
|
||
<![CDATA[
|
||
Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
|
||
]]>
|
||
</destructor>
|
||
|
||
<field name="tabbrowser" readonly="true">
|
||
document.getElementById(this.getAttribute("tabbrowser"));
|
||
</field>
|
||
|
||
<field name="tabbox" readonly="true">
|
||
this.tabbrowser.mTabBox;
|
||
</field>
|
||
|
||
<field name="contextMenu" readonly="true">
|
||
document.getElementById("tabContextMenu");
|
||
</field>
|
||
|
||
<field name="mTabstripWidth">0</field>
|
||
|
||
<field name="mTabstrip">
|
||
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
|
||
</field>
|
||
|
||
<field name="_prefObserver"><![CDATA[({
|
||
tabContainer: this,
|
||
|
||
observe: function (subject, topic, data) {
|
||
switch (data) {
|
||
case "browser.tabs.closeButtons":
|
||
this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
|
||
this.tabContainer.adjustTabstrip();
|
||
break;
|
||
case "browser.tabs.autoHide":
|
||
this.tabContainer.updateVisibility();
|
||
break;
|
||
case "browser.tabs.closeWindowWithLastTab":
|
||
this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
|
||
this.tabContainer.adjustTabstrip();
|
||
break;
|
||
}
|
||
}
|
||
});]]></field>
|
||
<field name="_blockDblClick">false</field>
|
||
|
||
<field name="_tabDropIndicator">
|
||
document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
|
||
</field>
|
||
|
||
<field name="_dragOverDelay">350</field>
|
||
<field name="_dragTime">0</field>
|
||
|
||
<field name="_container" readonly="true"><![CDATA[
|
||
this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
|
||
]]></field>
|
||
|
||
<property name="visible"
|
||
onget="return !this._container.collapsed;">
|
||
<setter><![CDATA[
|
||
this._container.collapsed = !val;
|
||
|
||
if (val)
|
||
this.tabbrowser.enterTabbedMode();
|
||
|
||
document.getElementById("menu_closeWindow").hidden = !val;
|
||
document.getElementById("menu_close").setAttribute("label",
|
||
this.tabbrowser.mStringBundle.getString(val ? "tabs.closeTab" : "tabs.close"));
|
||
|
||
return val;
|
||
]]></setter>
|
||
</property>
|
||
|
||
<method name="updateVisibility">
|
||
<body><![CDATA[
|
||
if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1 &&
|
||
window.toolbar.visible)
|
||
this.visible = !Services.prefs.getBoolPref("browser.tabs.autoHide");
|
||
else
|
||
this.visible = true;
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="adjustTabstrip">
|
||
<body><![CDATA[
|
||
// modes for tabstrip
|
||
// 0 - activetab = close button on active tab only
|
||
// 1 - alltabs = close buttons on all tabs
|
||
// 2 - noclose = no close buttons at all
|
||
// 3 - closeatend = close button at the end of the tabstrip
|
||
switch (this.mCloseButtons) {
|
||
case 0:
|
||
if (this.childNodes.length == 1 && this._closeWindowWithLastTab)
|
||
this.setAttribute("closebuttons", "noclose");
|
||
else
|
||
this.setAttribute("closebuttons", "activetab");
|
||
break;
|
||
case 1:
|
||
if (this.childNodes.length == 1) {
|
||
if (this._closeWindowWithLastTab)
|
||
this.setAttribute("closebuttons", "noclose");
|
||
else
|
||
this.setAttribute("closebuttons", "alltabs");
|
||
} else {
|
||
let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
|
||
if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
|
||
this.setAttribute("closebuttons", "alltabs");
|
||
else
|
||
this.setAttribute("closebuttons", "activetab");
|
||
}
|
||
break;
|
||
case 2:
|
||
case 3:
|
||
this.setAttribute("closebuttons", "noclose");
|
||
break;
|
||
}
|
||
var tabstripClosebutton = document.getElementById("tabs-closebutton");
|
||
if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
|
||
tabstripClosebutton.collapsed = this.mCloseButtons != 3;
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_handleTabSelect">
|
||
<body><![CDATA[
|
||
this.mTabstrip.ensureElementIsVisible(this.selectedItem);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_fillTrailingGap">
|
||
<body><![CDATA[
|
||
try {
|
||
// if we're at the right side (and not the logical end,
|
||
// which is why this works for both LTR and RTL)
|
||
// of the tabstrip, we need to ensure that we stay
|
||
// completely scrolled to the right side
|
||
var tabStrip = this.mTabstrip;
|
||
if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
|
||
tabStrip.scrollSize)
|
||
tabStrip.scrollByPixels(-1);
|
||
} catch (e) {}
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_positionPinnedTabs">
|
||
<body><![CDATA[
|
||
var width = 0;
|
||
var pinnedOnly = (this.tabbrowser._numPinnedTabs == this.tabbrowser.visibleTabs.length);
|
||
|
||
if (pinnedOnly)
|
||
this.tabbrowser.tabContainer.setAttribute("pinnedonly", "true");
|
||
else
|
||
this.tabbrowser.tabContainer.removeAttribute("pinnedonly");
|
||
|
||
var scrollButtonWidth = (this.getAttribute("overflow") != "true" || pinnedOnly) ? 0 :
|
||
this.mTabstrip._scrollButtonDown.scrollWidth;
|
||
var paddingStart = this.mTabstrip.scrollboxPaddingStart;
|
||
|
||
for (var i = this.tabbrowser._numPinnedTabs - 1; i >= 0; i--) {
|
||
let tab = this.childNodes[i];
|
||
width += pinnedOnly ? 0 : tab.scrollWidth;
|
||
if (this.getAttribute("overflow") != "true")
|
||
tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
|
||
else
|
||
tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
|
||
}
|
||
if (width == 0 || this.getAttribute("overflow") != "true")
|
||
this.style.MozMarginStart = width + "px";
|
||
else
|
||
this.style.MozMarginStart = width + paddingStart + "px";
|
||
this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="handleEvent">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
switch (aEvent.type) {
|
||
case "resize":
|
||
if (aEvent.target != window)
|
||
break;
|
||
var width = this.mTabstrip.boxObject.width;
|
||
if (width != this.mTabstripWidth) {
|
||
this.adjustTabstrip();
|
||
this._fillTrailingGap();
|
||
this._handleTabSelect();
|
||
this.mTabstripWidth = width;
|
||
}
|
||
break;
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<field name="_animateElement">
|
||
this.mTabstrip._scrollButtonDown;
|
||
</field>
|
||
|
||
<method name="_notifyBackgroundTab">
|
||
<parameter name="aTab"/>
|
||
<body><![CDATA[
|
||
if (aTab.pinned)
|
||
return;
|
||
|
||
var scrollRect = this.mTabstrip.scrollClientRect;
|
||
var tab = aTab.getBoundingClientRect();
|
||
|
||
// Is the new tab already completely visible?
|
||
if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
|
||
return;
|
||
|
||
if (this.mTabstrip.smoothScroll) {
|
||
let selected = !this.selectedItem.pinned &&
|
||
this.selectedItem.getBoundingClientRect();
|
||
|
||
// Can we make both the new tab and the selected tab completely visible?
|
||
if (!selected ||
|
||
Math.max(tab.right - selected.left, selected.right - tab.left) <=
|
||
scrollRect.width) {
|
||
this.mTabstrip.ensureElementIsVisible(aTab);
|
||
return;
|
||
}
|
||
|
||
this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
|
||
selected.right - scrollRect.right :
|
||
selected.left - scrollRect.left);
|
||
}
|
||
|
||
if (!this._animateElement.hasAttribute("notifybgtab")) {
|
||
this._animateElement.setAttribute("notifybgtab", "true");
|
||
setTimeout(function (ele) {
|
||
ele.removeAttribute("notifybgtab");
|
||
}, 150, this._animateElement);
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_getDragTargetTab">
|
||
<parameter name="event"/>
|
||
<body><![CDATA[
|
||
let tab = event.target.localName == "tab" ? event.target : null;
|
||
if (tab &&
|
||
(event.type == "drop" || event.type == "dragover") &&
|
||
event.dataTransfer.dropEffect == "link") {
|
||
let boxObject = tab.boxObject;
|
||
if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
|
||
event.screenX > boxObject.screenX + boxObject.width * .75)
|
||
return null;
|
||
}
|
||
return tab;
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_getDropIndex">
|
||
<parameter name="event"/>
|
||
<body><![CDATA[
|
||
var tabs = this.childNodes;
|
||
var tab = this._getDragTargetTab(event);
|
||
if (window.getComputedStyle(this, null).direction == "ltr") {
|
||
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
|
||
if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
|
||
return i;
|
||
} else {
|
||
for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
|
||
if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
|
||
return i;
|
||
}
|
||
return tabs.length;
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_setEffectAllowedForDataTransfer">
|
||
<parameter name="event"/>
|
||
<body><![CDATA[
|
||
var dt = event.dataTransfer;
|
||
// Disallow dropping multiple items
|
||
if (dt.mozItemCount > 1)
|
||
return dt.effectAllowed = "none";
|
||
|
||
var types = dt.mozTypesAt(0);
|
||
var sourceNode = null;
|
||
// tabs are always added as the first type
|
||
if (types[0] == TAB_DROP_TYPE) {
|
||
var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||
if (sourceNode instanceof XULElement &&
|
||
sourceNode.localName == "tab" &&
|
||
(sourceNode.parentNode == this ||
|
||
(sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
|
||
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
|
||
if (sourceNode.parentNode == this &&
|
||
(event.screenX >= sourceNode.boxObject.screenX &&
|
||
event.screenX <= (sourceNode.boxObject.screenX +
|
||
sourceNode.boxObject.width))) {
|
||
return dt.effectAllowed = "none";
|
||
}
|
||
|
||
return dt.effectAllowed = "copyMove";
|
||
}
|
||
}
|
||
|
||
if (browserDragAndDrop.canDropLink(event)) {
|
||
// Here we need to do this manually
|
||
return dt.effectAllowed = dt.dropEffect = "link";
|
||
}
|
||
return dt.effectAllowed = "none";
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_continueScroll">
|
||
<parameter name="event"/>
|
||
<body><![CDATA[
|
||
// Workaround for bug 481904: Dragging a tab stops scrolling at
|
||
// the tab's position when dragging to the first/last tab and back.
|
||
var t = this.selectedItem;
|
||
if (event.screenX >= t.boxObject.screenX &&
|
||
event.screenX <= t.boxObject.screenX + t.boxObject.width &&
|
||
event.screenY >= t.boxObject.screenY &&
|
||
event.screenY <= t.boxObject.screenY + t.boxObject.height)
|
||
this.mTabstrip.ensureElementIsVisible(t);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_handleNewTab">
|
||
<parameter name="tab"/>
|
||
<body><![CDATA[
|
||
if (tab.parentNode != this)
|
||
return;
|
||
|
||
this.adjustTabstrip();
|
||
|
||
if (tab.getAttribute("selected") == "true") {
|
||
this._fillTrailingGap();
|
||
this._handleTabSelect();
|
||
} else {
|
||
this._notifyBackgroundTab(tab);
|
||
}
|
||
|
||
// XXXmano: this is a temporary workaround for bug 345399
|
||
// We need to manually update the scroll buttons disabled state
|
||
// if a tab was inserted to the overflow area or removed from it
|
||
// without any scrolling and when the tabbar has already
|
||
// overflowed.
|
||
this.mTabstrip._updateScrollButtonsDisabledState();
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_canAdvanceToTab">
|
||
<parameter name="aTab"/>
|
||
<body>
|
||
<![CDATA[
|
||
return this.tabbrowser._removingTabs.indexOf(aTab) == -1;
|
||
]]>
|
||
</body>
|
||
</method>
|
||
|
||
<!-- Deprecated stuff, implemented for backwards compatibility. -->
|
||
<property name="mTabstripClosebutton" readonly="true"
|
||
onget="return document.getElementById('tabs-closebutton');"/>
|
||
<property name="mAllTabsPopup" readonly="true"
|
||
onget="return document.getElementById('alltabs-popup');"/>
|
||
</implementation>
|
||
|
||
<handlers>
|
||
<handler event="TabSelect" action="this._handleTabSelect();"/>
|
||
|
||
<handler event="transitionend"><![CDATA[
|
||
if (event.propertyName != "max-width")
|
||
return;
|
||
|
||
var tab = event.target;
|
||
|
||
if (tab.getAttribute("fadein") == "true")
|
||
this._handleNewTab(tab);
|
||
else if (this.tabbrowser._removingTabs.indexOf(tab) > -1)
|
||
this.tabbrowser._endRemoveTab(tab);
|
||
]]></handler>
|
||
|
||
<handler event="dblclick"><![CDATA[
|
||
// See hack note in the tabbrowser-close-button binding
|
||
if (!this._blockDblClick && event.button == 0 &&
|
||
event.originalTarget.localName == "box")
|
||
BrowserOpenTab();
|
||
]]></handler>
|
||
|
||
<handler event="click"><![CDATA[
|
||
if (event.button != 1)
|
||
return;
|
||
|
||
if (event.target.localName == "tab") {
|
||
if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
|
||
this.tabbrowser.removeTab(event.target, {animate: true});
|
||
} else if (event.originalTarget.localName == "box") {
|
||
BrowserOpenTab();
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
event.stopPropagation();
|
||
]]></handler>
|
||
|
||
<handler event="keypress"><![CDATA[
|
||
if (event.altKey || event.shiftKey ||
|
||
#ifdef XP_MACOSX
|
||
!event.metaKey)
|
||
#else
|
||
!event.ctrlKey || event.metaKey)
|
||
#endif
|
||
return;
|
||
|
||
switch (event.keyCode) {
|
||
case KeyEvent.DOM_VK_UP:
|
||
this.tabbrowser.moveTabBackward();
|
||
break;
|
||
case KeyEvent.DOM_VK_DOWN:
|
||
this.tabbrowser.moveTabForward();
|
||
break;
|
||
case KeyEvent.DOM_VK_RIGHT:
|
||
case KeyEvent.DOM_VK_LEFT:
|
||
this.tabbrowser.moveTabOver(event);
|
||
break;
|
||
case KeyEvent.DOM_VK_HOME:
|
||
this.tabbrowser.moveTabToStart();
|
||
break;
|
||
case KeyEvent.DOM_VK_END:
|
||
this.tabbrowser.moveTabToEnd();
|
||
break;
|
||
default:
|
||
// Stop the keypress event for the above keyboard
|
||
// shortcuts only.
|
||
return;
|
||
}
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
]]></handler>
|
||
|
||
<handler event="dragstart"><![CDATA[
|
||
var tab = this._getDragTargetTab(event);
|
||
if (!tab)
|
||
return;
|
||
|
||
let dt = event.dataTransfer;
|
||
dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
|
||
let uri = this.tabbrowser.getBrowserForTab(tab).currentURI;
|
||
let spec = uri ? uri.spec : "about:blank";
|
||
|
||
// We must not set text/x-moz-url or text/plain data here,
|
||
// otherwise trying to deatch the tab by dropping it on the desktop
|
||
// may result in an "internet shortcut"
|
||
dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
|
||
|
||
// Set the cursor to an arrow during tab drags.
|
||
dt.mozCursor = "default";
|
||
|
||
let canvas = tabPreviews.capture(tab, false);
|
||
dt.setDragImage(canvas, 0, 0);
|
||
event.stopPropagation();
|
||
]]></handler>
|
||
|
||
<handler event="dragover"><![CDATA[
|
||
var effects = this._setEffectAllowedForDataTransfer(event);
|
||
|
||
var ind = this._tabDropIndicator;
|
||
if (effects == "" || effects == "none") {
|
||
ind.collapsed = true;
|
||
this._continueScroll(event);
|
||
return;
|
||
}
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
var tabStrip = this.mTabstrip;
|
||
var ltr = (window.getComputedStyle(this, null).direction == "ltr");
|
||
|
||
// autoscroll the tab strip if we drag over the scroll
|
||
// buttons, even if we aren't dragging a tab, but then
|
||
// return to avoid drawing the drop indicator
|
||
var pixelsToScroll = 0;
|
||
if (this.getAttribute("overflow") == "true") {
|
||
var targetAnonid = event.originalTarget.getAttribute("anonid");
|
||
switch (targetAnonid) {
|
||
case "scrollbutton-up":
|
||
pixelsToScroll = tabStrip.scrollIncrement * -1;
|
||
break;
|
||
case "scrollbutton-down":
|
||
pixelsToScroll = tabStrip.scrollIncrement;
|
||
break;
|
||
}
|
||
if (pixelsToScroll)
|
||
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
|
||
}
|
||
|
||
if (effects == "link") {
|
||
let tab = this._getDragTargetTab(event);
|
||
if (tab) {
|
||
if (!this._dragTime)
|
||
this._dragTime = Date.now();
|
||
if (Date.now() >= this._dragTime + this._dragOverDelay)
|
||
this.selectedItem = tab;
|
||
ind.collapsed = true;
|
||
return;
|
||
}
|
||
}
|
||
|
||
var newIndex = this._getDropIndex(event);
|
||
var scrollRect = tabStrip.scrollClientRect;
|
||
var rect = this.getBoundingClientRect();
|
||
var minMargin = scrollRect.left - rect.left;
|
||
var maxMargin = Math.min(minMargin + scrollRect.width,
|
||
scrollRect.right);
|
||
if (!ltr)
|
||
[minMargin, maxMargin] = [this.clientWidth - maxMargin,
|
||
this.clientWidth - minMargin];
|
||
var newMargin;
|
||
if (pixelsToScroll) {
|
||
// if we are scrolling, put the drop indicator at the edge
|
||
// so that it doesn't jump while scrolling
|
||
newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
|
||
}
|
||
else {
|
||
if (newIndex == this.childNodes.length) {
|
||
let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
|
||
if (ltr)
|
||
newMargin = tabRect.right - rect.left;
|
||
else
|
||
newMargin = rect.right - tabRect.left;
|
||
}
|
||
else {
|
||
let tabRect = this.childNodes[newIndex].getBoundingClientRect();
|
||
if (ltr)
|
||
newMargin = tabRect.left - rect.left;
|
||
else
|
||
newMargin = rect.right - tabRect.right;
|
||
}
|
||
}
|
||
|
||
ind.collapsed = false;
|
||
|
||
newMargin += ind.clientWidth / 2;
|
||
if (!ltr)
|
||
newMargin *= -1;
|
||
|
||
ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)";
|
||
ind.style.MozMarginStart = (-ind.clientWidth) + "px";
|
||
ind.style.marginTop = (-ind.clientHeight) + "px";
|
||
]]></handler>
|
||
|
||
<handler event="drop"><![CDATA[
|
||
var dt = event.dataTransfer;
|
||
var dropEffect = dt.dropEffect;
|
||
var draggedTab;
|
||
if (dropEffect != "link") { // copy or move
|
||
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||
// not our drop then
|
||
if (!draggedTab)
|
||
return;
|
||
}
|
||
|
||
this._tabDropIndicator.collapsed = true;
|
||
event.stopPropagation();
|
||
|
||
if (draggedTab && (dropEffect == "copy" ||
|
||
draggedTab.parentNode == this)) {
|
||
let newIndex = this._getDropIndex(event);
|
||
if (dropEffect == "copy") {
|
||
// copy the dropped tab (wherever it's from)
|
||
let newTab = this.tabbrowser.duplicateTab(draggedTab);
|
||
this.tabbrowser.moveTabTo(newTab, newIndex);
|
||
if (draggedTab.parentNode != this || event.shiftKey)
|
||
this.selectedItem = newTab;
|
||
} else {
|
||
// move the dropped tab
|
||
if (newIndex > draggedTab._tPos)
|
||
newIndex--;
|
||
|
||
if (draggedTab.pinned) {
|
||
if (newIndex >= this.tabbrowser._numPinnedTabs)
|
||
this.tabbrowser.unpinTab(draggedTab);
|
||
} else {
|
||
if (newIndex <= this.tabbrowser._numPinnedTabs - 1)
|
||
this.tabbrowser.pinTab(draggedTab);
|
||
}
|
||
|
||
this.tabbrowser.moveTabTo(draggedTab, newIndex);
|
||
}
|
||
} else if (draggedTab) {
|
||
// swap the dropped tab with a new one we create and then close
|
||
// it in the other window (making it seem to have moved between
|
||
// windows)
|
||
let newIndex = this._getDropIndex(event);
|
||
let newTab = this.tabbrowser.addTab("about:blank");
|
||
let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
|
||
// Stop the about:blank load
|
||
newBrowser.stop();
|
||
// make sure it has a docshell
|
||
newBrowser.docShell;
|
||
|
||
this.tabbrowser.moveTabTo(newTab, newIndex);
|
||
|
||
this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
|
||
|
||
// We need to select the tab after we've done
|
||
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
|
||
// it triggers will correctly update our URL bar.
|
||
this.tabbrowser.selectedTab = newTab;
|
||
} else {
|
||
let url = browserDragAndDrop.drop(event, { });
|
||
|
||
// valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
|
||
// Also disallow dropping javascript: or data: urls--bail out
|
||
if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
|
||
/^\s*(javascript|data):/.test(url))
|
||
return;
|
||
|
||
let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
|
||
|
||
if (event.shiftKey)
|
||
bgLoad = !bgLoad;
|
||
|
||
let tab = this._getDragTargetTab(event);
|
||
if (!tab || dropEffect == "copy") {
|
||
// We're adding a new tab.
|
||
let newIndex = this._getDropIndex(event);
|
||
let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
|
||
this.tabbrowser.moveTabTo(newTab, newIndex);
|
||
} else {
|
||
// Load in an existing tab.
|
||
try {
|
||
this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
|
||
if (!bgLoad)
|
||
this.selectedItem = tab;
|
||
} catch(ex) {
|
||
// Just ignore invalid urls
|
||
}
|
||
}
|
||
}
|
||
]]></handler>
|
||
|
||
<handler event="dragend"><![CDATA[
|
||
// Note: while this case is correctly handled here, this event
|
||
// isn't dispatched when the tab is moved within the tabstrip,
|
||
// see bug 460801.
|
||
|
||
// * mozUserCancelled = the user pressed ESC to cancel the drag
|
||
var dt = event.dataTransfer;
|
||
if (dt.mozUserCancelled || dt.dropEffect != "none")
|
||
return;
|
||
|
||
// Disable detach within the browser toolbox
|
||
var eX = event.screenX;
|
||
var wX = window.screenX;
|
||
// check if the drop point is horizontally within the window
|
||
if (eX > wX && eX < (wX + window.outerWidth)) {
|
||
let bo = this.mTabstrip.boxObject;
|
||
// also avoid detaching if the the tab was dropped too close to
|
||
// the tabbar (half a tab)
|
||
let endScreenY = bo.screenY + 1.5 * bo.height;
|
||
let eY = event.screenY;
|
||
if (eY < endScreenY && eY > window.screenY)
|
||
return;
|
||
}
|
||
|
||
var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||
this.tabbrowser.replaceTabWithWindow(draggedTab);
|
||
event.stopPropagation();
|
||
]]></handler>
|
||
|
||
<handler event="dragleave"><![CDATA[
|
||
this._dragTime = 0;
|
||
|
||
// This does not work at all (see bug 458613)
|
||
var target = event.relatedTarget;
|
||
while (target && target != this)
|
||
target = target.parentNode;
|
||
if (target)
|
||
return;
|
||
|
||
this._tabDropIndicator.collapsed = true;
|
||
this._continueScroll(event);
|
||
event.stopPropagation();
|
||
]]></handler>
|
||
</handlers>
|
||
</binding>
|
||
|
||
<!-- close-tab-button binding
|
||
This binding relies on the structure of the tabbrowser binding.
|
||
Therefore it should only be used as a child of the tab or the tabs
|
||
element (in both cases, when they are anonymous nodes of <tabbrowser>).
|
||
-->
|
||
<binding id="tabbrowser-close-tab-button"
|
||
extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
|
||
<handlers>
|
||
<handler event="click" button="0"><![CDATA[
|
||
var bindingParent = document.getBindingParent(this);
|
||
var tabContainer = bindingParent.parentNode;
|
||
/* The only sequence in which a second click event (i.e. dblclik)
|
||
* can be dispatched on an in-tab close button is when it is shown
|
||
* after the first click (i.e. the first click event was dispatched
|
||
* on the tab). This happens when we show the close button only on
|
||
* the active tab. (bug 352021)
|
||
* The only sequence in which a third click event can be dispatched
|
||
* on an in-tab close button is when the tab was opened with a
|
||
* double click on the tabbar. (bug 378344)
|
||
* In both cases, it is most likely that the close button area has
|
||
* been accidentally clicked, therefore we do not close the tab.
|
||
*
|
||
* We don't want to ignore processing of more than one click event,
|
||
* though, since the user might actually be repeatedly clicking to
|
||
* close many tabs at once.
|
||
*/
|
||
if (event.detail > 1 && !this._ignoredClick) {
|
||
this._ignoredClick = true;
|
||
return;
|
||
}
|
||
|
||
// Reset the "ignored click" flag
|
||
this._ignoredClick = false;
|
||
|
||
tabContainer.tabbrowser.removeTab(bindingParent, {animate: true});
|
||
tabContainer._blockDblClick = true;
|
||
|
||
/* XXXmano hack (see bug 343628):
|
||
* Since we're removing the event target, if the user
|
||
* double-clicks this button, the dblclick event will be dispatched
|
||
* with the tabbar as its event target (and explicit/originalTarget),
|
||
* which treats that as a mouse gesture for opening a new tab.
|
||
* In this context, we're manually blocking the dblclick event
|
||
* (see dblclick handler).
|
||
*/
|
||
var clickedOnce = false;
|
||
function enableDblClick(event) {
|
||
if (event.detail == 1 && !clickedOnce) {
|
||
clickedOnce = true;
|
||
return;
|
||
}
|
||
setTimeout(function() {
|
||
tabContainer._blockDblClick = false;
|
||
}, 0);
|
||
tabContainer.removeEventListener("click", enableDblClick, false);
|
||
}
|
||
tabContainer.addEventListener("click", enableDblClick, false);
|
||
]]></handler>
|
||
|
||
<handler event="dblclick" button="0" phase="capturing">
|
||
// for the one-close-button case
|
||
event.stopPropagation();
|
||
</handler>
|
||
|
||
<handler event="dragstart">
|
||
event.stopPropagation();
|
||
</handler>
|
||
</handlers>
|
||
</binding>
|
||
|
||
<binding id="tabbrowser-tab" display="xul:hbox"
|
||
extends="chrome://global/content/bindings/tabbox.xml#tab">
|
||
<resources>
|
||
<stylesheet src="chrome://browser/content/tabbrowser.css"/>
|
||
</resources>
|
||
|
||
<content context="tabContextMenu" closetabtext="&closeTab.label;">
|
||
<xul:stack class="tab-stack" flex="1">
|
||
<xul:hbox class="tab-content" align="center">
|
||
<xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
|
||
class="tab-throbber"
|
||
role="presentation"/>
|
||
<xul:image xbl:inherits="validate,src=image,fadein,pinned,selected"
|
||
class="tab-icon-image"
|
||
role="presentation"/>
|
||
<xul:label flex="1"
|
||
xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
|
||
class="tab-text tab-label"
|
||
role="presentation"/>
|
||
<xul:toolbarbutton anonid="close-button"
|
||
xbl:inherits="fadein,pinned,selected"
|
||
tabindex="-1"
|
||
clickthrough="never"
|
||
class="tab-close-button"/>
|
||
</xul:hbox>
|
||
</xul:stack>
|
||
</content>
|
||
|
||
<implementation>
|
||
<property name="pinned" readonly="true">
|
||
<getter>
|
||
return this.getAttribute("pinned") == "true";
|
||
</getter>
|
||
</property>
|
||
<property name="hidden" readonly="true">
|
||
<getter>
|
||
return this.getAttribute("hidden") == "true";
|
||
</getter>
|
||
</property>
|
||
|
||
<field name="mOverCloseButton">false</field>
|
||
<field name="mCorrespondingMenuitem">null</field>
|
||
</implementation>
|
||
|
||
<handlers>
|
||
<handler event="mouseover">
|
||
var anonid = event.originalTarget.getAttribute("anonid");
|
||
if (anonid == "close-button")
|
||
this.mOverCloseButton = true;
|
||
</handler>
|
||
<handler event="mouseout">
|
||
var anonid = event.originalTarget.getAttribute("anonid");
|
||
if (anonid == "close-button")
|
||
this.mOverCloseButton = false;
|
||
</handler>
|
||
<handler event="dragstart" phase="capturing">
|
||
this.style.MozUserFocus = '';
|
||
</handler>
|
||
<handler event="mousedown" button="0" phase="capturing">
|
||
<![CDATA[
|
||
if (this.mOverCloseButton) {
|
||
event.stopPropagation();
|
||
}
|
||
else {
|
||
this.style.MozUserFocus = 'ignore';
|
||
this.clientTop; // just using this to flush style updates
|
||
}
|
||
]]>
|
||
</handler>
|
||
<handler event="mousedown" button="1">
|
||
this.style.MozUserFocus = 'ignore';
|
||
this.clientTop;
|
||
</handler>
|
||
<handler event="mousedown" button="2">
|
||
this.style.MozUserFocus = 'ignore';
|
||
this.clientTop;
|
||
</handler>
|
||
<handler event="mouseup">
|
||
this.style.MozUserFocus = '';
|
||
</handler>
|
||
</handlers>
|
||
</binding>
|
||
|
||
<binding id="tabbrowser-alltabs-popup"
|
||
extends="chrome://global/content/bindings/popup.xml#popup">
|
||
<implementation implements="nsIDOMEventListener">
|
||
<method name="_menuItemOnCommand">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
gBrowser.selectedTab = aEvent.target.tab;
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_tabOnAttrModified">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
var tab = aEvent.target;
|
||
this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_tabOnTabClose">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
var menuItem = aEvent.target.mCorrespondingMenuitem;
|
||
if (menuItem)
|
||
this.removeChild(menuItem);
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="handleEvent">
|
||
<parameter name="aEvent"/>
|
||
<body><![CDATA[
|
||
if (!aEvent.isTrusted)
|
||
return;
|
||
|
||
switch (aEvent.type) {
|
||
case "command":
|
||
this._menuItemOnCommand(aEvent);
|
||
break;
|
||
case "TabAttrModified":
|
||
this._tabOnAttrModified(aEvent);
|
||
break;
|
||
case "TabClose":
|
||
this._tabOnTabClose(aEvent);
|
||
break;
|
||
case "TabOpen":
|
||
this._createTabMenuItem(aEvent.originalTarget);
|
||
break;
|
||
case "scroll":
|
||
this._updateTabsVisibilityStatus();
|
||
break;
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_updateTabsVisibilityStatus">
|
||
<body><![CDATA[
|
||
var tabContainer = gBrowser.tabContainer;
|
||
// We don't want menu item decoration unless there is overflow.
|
||
if (tabContainer.getAttribute("overflow") != "true")
|
||
return;
|
||
|
||
var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
|
||
for (var i = 0; i < this.childNodes.length; i++) {
|
||
var curTabBO = this.childNodes[i].tab.boxObject;
|
||
if (curTabBO.screenX >= tabstripBO.screenX &&
|
||
curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
|
||
this.childNodes[i].setAttribute("tabIsVisible", "true");
|
||
else
|
||
this.childNodes[i].removeAttribute("tabIsVisible");
|
||
}
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_createTabMenuItem">
|
||
<parameter name="aTab"/>
|
||
<body><![CDATA[
|
||
var menuItem = document.createElementNS(
|
||
"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
|
||
"menuitem");
|
||
|
||
menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
|
||
|
||
this._setMenuitemAttributes(menuItem, aTab);
|
||
|
||
// Keep some attributes of the menuitem in sync with its
|
||
// corresponding tab (e.g. the tab label)
|
||
aTab.mCorrespondingMenuitem = menuItem;
|
||
menuItem.tab = aTab;
|
||
menuItem.addEventListener("command", this, false);
|
||
|
||
this.appendChild(menuItem);
|
||
return menuItem;
|
||
]]></body>
|
||
</method>
|
||
|
||
<method name="_setMenuitemAttributes">
|
||
<parameter name="aMenuitem"/>
|
||
<parameter name="aTab"/>
|
||
<body><![CDATA[
|
||
aMenuitem.setAttribute("label", aTab.label);
|
||
aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
|
||
aMenuitem.setAttribute("image", aTab.getAttribute("image"));
|
||
|
||
if (aTab.hasAttribute("busy"))
|
||
aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
|
||
else
|
||
aMenuitem.removeAttribute("busy");
|
||
|
||
if (aTab.selected)
|
||
aMenuitem.setAttribute("selected", "true");
|
||
else
|
||
aMenuitem.removeAttribute("selected");
|
||
]]></body>
|
||
</method>
|
||
</implementation>
|
||
|
||
<handlers>
|
||
<handler event="popupshowing">
|
||
<![CDATA[
|
||
// set up the menu popup
|
||
var tabcontainer = gBrowser.tabContainer;
|
||
let tabs = gBrowser.visibleTabs;
|
||
|
||
// Listen for changes in the tab bar.
|
||
tabcontainer.addEventListener("TabOpen", this, false);
|
||
tabcontainer.addEventListener("TabAttrModified", this, false);
|
||
tabcontainer.addEventListener("TabClose", this, false);
|
||
tabcontainer.mTabstrip.addEventListener("scroll", this, false);
|
||
|
||
for (var i = 0; i < tabs.length; i++) {
|
||
this._createTabMenuItem(tabs[i]);
|
||
}
|
||
this._updateTabsVisibilityStatus();
|
||
]]></handler>
|
||
|
||
<handler event="popuphidden">
|
||
<![CDATA[
|
||
// clear out the menu popup and remove the listeners
|
||
while (this.hasChildNodes()) {
|
||
var menuItem = this.lastChild;
|
||
menuItem.removeEventListener("command", this, false);
|
||
menuItem.tab.mCorrespondingMenuitem = null;
|
||
this.removeChild(menuItem);
|
||
}
|
||
var tabcontainer = gBrowser.tabContainer;
|
||
tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
|
||
tabcontainer.removeEventListener("TabOpen", this, false);
|
||
tabcontainer.removeEventListener("TabAttrModified", this, false);
|
||
tabcontainer.removeEventListener("TabClose", this, false);
|
||
]]></handler>
|
||
|
||
<handler event="DOMMenuItemActive">
|
||
<![CDATA[
|
||
var tab = event.target.tab;
|
||
if (tab) {
|
||
let overLink = tab.linkedBrowser.currentURI.spec;
|
||
if (overLink == "about:blank")
|
||
overLink = "";
|
||
XULBrowserWindow.setOverLink(overLink, null);
|
||
}
|
||
]]></handler>
|
||
|
||
<handler event="DOMMenuItemInactive">
|
||
<![CDATA[
|
||
XULBrowserWindow.setOverLink("", null);
|
||
]]></handler>
|
||
|
||
</handlers>
|
||
</binding>
|
||
|
||
</bindings>
|