Revision control

Copy as Markdown

Other Tools

<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE bindings [
<!ENTITY % tabBrowserDTD SYSTEM "chrome://navigator/locale/tabbrowser.dtd" >
%tabBrowserDTD;
]>
<bindings id="tabBrowserBindings"
<binding id="tabbrowser">
<resources>
</resources>
<content>
<xul:stringbundle anonid="tbstringbundle" src="chrome://navigator/locale/tabbrowser.properties"/>
<xul:tabbox anonid="tabbox" flex="1" eventnode="document">
<xul:hbox class="tab-drop-indicator-bar" collapsed="true">
<xul:image class="tab-drop-indicator" mousethrough="always"/>
</xul:hbox>
<xul:hbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
anonid="strip"
ondragstart="this.parentNode.parentNode._onDragStart(event);"
ondragover="this.parentNode.parentNode._onDragOver(event);"
ondrop="this.parentNode.parentNode._onDrop(event);"
ondragleave="this.parentNode.parentNode._onDragLeave(event);">
<xul:tooltip onpopupshowing="event.stopPropagation(); return this.parentNode.parentNode.parentNode.doPreview(this);"
onpopuphiding="this.parentNode.parentNode.parentNode.resetPreview(this);" orient="vertical">
<xul:label class="tooltip-label" crop="right"/>
<xul:label class="tooltip-label" hidden="true"><html:canvas class="tab-tooltip-canvas"/></xul:label>
</xul:tooltip>
<xul:menupopup anonid="tabContextMenu" onpopupshowing="return document.getBindingParent(this).updatePopupMenu(this);">
<xul:menuitem label="&closeTab.label;" accesskey="&closeTab.accesskey;"
tbattr="tabbrowser-tab"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeTab(tabbrowser.mContextTab);"/>
<xul:menuitem label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
tbattr="tabbrowser-multiple tabbrowser-tab"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
<xul:menuitem label="&closeTabsToTheEnd.label;"
accesskey="&closeTabsToTheEnd.accesskey;"
tbattr="tabbrowser-totheend tabbrowser-tab"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.removeTabsToTheEndFrom(tabbrowser.mContextTab);"/>
<xul:menuseparator/>
<xul:menuitem label="&newTab.label;" accesskey="&newTab.accesskey;"
xbl:inherits="oncommand=onnewtab"/>
<xul:menuitem label="&undoCloseTab.label;" accesskey="&undoCloseTab.accesskey;"
tbattr="tabbrowser-undoclosetab"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.undoCloseTab(0);"/>
<xul:menuseparator/>
<xul:menuitem label="&bookmarkGroup.label;" accesskey="&bookmarkGroup.accesskey;"
tbattr="tabbrowser-multiple"
xbl:inherits="oncommand=onbookmarkgroup"/>
<xul:menuseparator/>
<xul:menuitem label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
tbattr="tabbrowser-tab"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
<xul:menuitem label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
tbattr="tabbrowser-multiple"
oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
tabbrowser.reloadAllTabs();"/>
</xul:menupopup>
<xul:tabs class="tabbrowser-tabs" flex="1"
anonid="tabcontainer"
tooltiptextnew="&newTabButton.tooltip;"
tooltiptextclose="&closeTabButton.tooltip;"
tooltiptextalltabs="&listAllTabs.tooltip;"
setfocus="false"
onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
xbl:inherits="onnewtab,onnewtabclick"
onclosetab="var node = this.parentNode;
while (node.localName != 'tabbrowser')
node = node.parentNode;
node.removeCurrentTab();">
<xul:tab selected="true" validate="never"
onerror="this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image'));
this.removeAttribute('image');"
width="0" flex="100"
class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
</xul:tabs>
</xul:hbox>
<xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
<xul:notificationbox class="browser-notificationbox" xbl:inherits="popupnotification">
<xul:stack flex="1" anonid="browserStack">
<xul:browser flex="1" type="content" primary="true"
xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,datetimepicker"/>
</xul:stack>
</xul:notificationbox>
</xul:tabpanels>
</xul:tabbox>
<children/>
</content>
<implementation implements="nsIObserver">
<field name="closingTabsEnum" readonly="true">
({ ALL: 0, OTHER: 1, TO_END: 2 });
</field>
<field name="mSessionStore" readonly="true">
Cc["@mozilla.org/suite/sessionstore;1"].getService(Ci.nsISessionStore);
</field>
<field name="mTabBox" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
</field>
<field name="mStrip" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "strip");
</field>
<field name="tabContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
</field>
<field name="mPanelContainer" readonly="true">
document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
</field>
<field name="tabs" readonly="true">
this.tabContainer.childNodes
</field>
<field name="mStringBundle">
document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
</field>
<field name="mCurrentTab">
null
</field>
<field name="mPreviousTab">
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="mLastRelatedIndex">
0
</field>
<field name="usePrivateBrowsing" readonly="true">
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext)
.usePrivateBrowsing;
</field>
<field name="mContextTab">
null
</field>
<method name="_handleKeyEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (!aEvent.isTrusted) {
// Don't let untrusted events mess with tabs.
return;
}
if (aEvent.altKey)
return;
if (AppConstants.platform == "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();
return;
}
} else {
if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
this.getStripVisibility()) {
this.removeCurrentTab();
aEvent.stopPropagation();
aEvent.preventDefault();
return;
}
if (aEvent.target == this) {
switch (aEvent.keyCode) {
case KeyEvent.DOM_VK_UP:
this.moveTabBackward();
break;
case KeyEvent.DOM_VK_DOWN:
this.moveTabForward();
break;
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_LEFT:
this.moveTabOver(aEvent);
break;
case KeyEvent.DOM_VK_HOME:
this.moveTabToStart();
break;
case KeyEvent.DOM_VK_END:
this.moveTabToEnd();
break;
default:
// Stop the keypress event for the above keyboard
// shortcuts only.
return;
}
aEvent.stopPropagation();
aEvent.preventDefault();
}
}
]]></body>
</method>
<field name="arrowKeysShouldWrap">
null
</field>
<field name="nextTabNumber">
0
</field>
<field name="_browsers">
null
</field>
<field name="savedBrowsers">
new Array()
</field>
<field name="referenceTab">
null
</field>
<method name="doPreview">
<parameter name="aPopup"/>
<body>
<![CDATA[
var tab = document.tooltipNode;
if (tab.localName != "tab")
return false;
var b = tab.linkedBrowser;
if (!b)
return false;
var label = aPopup.firstChild;
label.setAttribute("value", tab.getAttribute("label"));
var canvas = aPopup.lastChild.firstChild;
canvas.parentNode.hidden = true;
var win = b.contentWindow;
var w = win.innerWidth;
var h = win.innerHeight;
if (tab == this.mCurrentTab || h == 0 ||
!Services.prefs.getBoolPref("browser.tabs.tooltippreview.enable")) {
return true;
}
var ctx;
try {
ctx = canvas.getContext("2d");
} catch (e) {
return true;
}
label.width = 0;
aPopup.setAttribute("tabpreview", "true");
var canvasW = Services.prefs.getIntPref("browser.tabs.tooltippreview.width");
var canvasH = Math.round(canvasW * h / w);
canvas.width = canvasW;
canvas.height = canvasH;
canvas.parentNode.hidden = false;
var bgColor = Services.prefs.getBoolPref("browser.display.use_system_colors") ?
"Window" :
Services.prefs.getCharPref("browser.display.background_color");
if (b.contentDocument instanceof ImageDocument &&
!(b.contentDocument.imageRequest.imageStatus &
Ci.imgIRequest.STATUS_ERROR)) {
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, canvasW, canvasH);
var img = b.contentDocument.body.firstChild;
var ratio = img.naturalHeight / img.naturalWidth;
if (img.naturalHeight <= canvasH && img.naturalWidth <= canvasW) {
ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight);
}
else if (ratio * canvasW > canvasH) {
ctx.drawImage(img, 0, 0, canvasH / ratio, canvasH);
}
else {
ctx.drawImage(img, 0, 0, canvasW, ratio * canvasW);
}
}
else {
ctx.save();
ctx.scale(canvasW / w, canvasH / h);
ctx.drawWindow(win, win.pageXOffset, win.pageYOffset, w, h, bgColor);
ctx.restore();
}
return true;
]]>
</body>
</method>
<!-- XXXcst This should not be needed, but it seems that the tooltip
sizing is happening too early when we want to stop showing the
preview. This clears the label's width early (i.e. when the
previous preview disappears) so that when the next tooltip appears,
it doesn't start with a bad size. For now, I blame Gecko. -->
<method name="resetPreview">
<parameter name="aPopup"/>
<body>
<![CDATA[
var label = aPopup.firstChild;
// If this function is removed, these two lines need to be restored
// to the non-preview codepath above.
label.removeAttribute("width");
aPopup.removeAttribute("tabpreview");
]]>
</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 browsers = this.browsers;
for (var i = 0; i < browsers.length; i++)
if (browsers[i].contentDocument == aDocument)
return i;
return -1;
]]>
</body>
</method>
<method name="getBrowserForDocument">
<parameter name="aDocument"/>
<body>
<![CDATA[
var browsers = this.browsers;
for (var i = 0; i < browsers.length; i++)
if (browsers[i].contentDocument == aDocument)
return browsers[i];
return null;
]]>
</body>
</method>
<method name="getBrowserForContentWindow">
<parameter name="aWindow"/>
<body>
<![CDATA[
const browsers = this.browsers;
for (let browser of browsers) {
if (browser.contentWindow == aWindow)
return browser;
}
return null;
]]>
</body>
</method>
<method name="getNotificationBox">
<parameter name="aBrowser"/>
<body>
<![CDATA[
return aBrowser ? aBrowser.parentNode.parentNode
: this.mCurrentBrowser.parentNode.parentNode;
]]>
</body>
</method>
<method name="_callProgressListeners">
<parameter name="aBrowser"/>
<parameter name="aMethod"/>
<parameter name="aArguments"/>
<parameter name="aCallGlobalListeners"/>
<parameter name="aCallTabsListeners"/>
<body><![CDATA[
if (!aBrowser)
aBrowser = this.mCurrentBrowser;
if (aCallGlobalListeners != false &&
aBrowser == this.mCurrentBrowser) {
this.mProgressListeners.forEach(function (p) {
if (aMethod in p) {
try {
p[aMethod].apply(p, aArguments);
} catch (e) {
// don't inhibit other listeners
Cu.reportError(e);
}
}
});
}
if (aCallTabsListeners != false) {
aArguments.unshift(aBrowser);
this.mTabsProgressListeners.forEach(function (p) {
if (aMethod in p) {
try {
p[aMethod].apply(p, aArguments);
} catch (e) {
// don't inhibit other listeners
Cu.reportError(e);
}
}
});
}
]]></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,
mFeeds: [],
mRequest: null,
mStateFlags: 0,
mStatus: 0,
mMessage: "",
// cache flags for correct status UI update after tab switching
mTotalProgress: 0,
// count of open requests (should always be 0 or 1)
mRequestCount: 0,
_callProgressListeners: function () {
Array.prototype.unshift.call(arguments, this.mBrowser);
return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
},
_shouldShowProgress: function (aRequest) {
if (this.mBlank)
return false;
// Don't show progress indicators in tabs for about: URIs
// pointing to local resources.
if ((aRequest instanceof Ci.nsIChannel) &&
aRequest.originalURI.schemeIs("about") &&
(aRequest.URI.schemeIs("jar") || aRequest.URI.schemeIs("file")))
return false;
return true;
},
onProgressChange: function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress) {
this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
if (!this._shouldShowProgress(aRequest))
return;
if (this.mTotalProgress && this.mTab.hasAttribute("busy"))
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;
let location;
let originalLocation;
try {
aRequest.QueryInterface(Ci.nsIChannel)
location = aRequest.URI;
originalLocation = aRequest.originalURI;
} catch (ex) {}
if (aStateFlags & Ci.nsIWebProgressListener.STATE_START) {
this.mRequestCount++;
}
else if (aStateFlags & Ci.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 & Ci.nsIWebProgressListener.STATE_START &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
if (aWebProgress.isTopLevel) {
this.mFeeds = [];
// Need to use originalLocation rather than location because things
// like about:privatebrowsing arrive with nsIRequest pointing to
// their resolved jar: or file: URIs.
if (!(originalLocation && gInitialPages.has(originalLocation.spec) &&
originalLocation != "about:blank" &&
this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
// This will trigger clearing the location bar. Don't do it if
// we loaded off a blank browser and this is an initial page load
// (e.g. about:privatebrowsing, etc.) so we avoid clearing the
// location bar in case the user is typing in it.
// Loading about:blank shouldn't trigger this, either, because its
// loads are "special".
this.mBrowser.urlbarChangeTracker.startedLoad();
}
// If the browser is loading it must not be crashed anymore.
this.mTab.removeAttribute("crashed");
}
if (this._shouldShowProgress(aRequest)) {
if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING) &&
aWebProgress && aWebProgress.isTopLevel) {
this.mTab.setAttribute("busy", "true");
// Do the following only for the top frame not any subframes.
// Remove favicon. This shows busy and progress indicators even during a reload.
this.mTab.removeAttribute("image");
if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
this.mTabBrowser.setTabTitleLoading(this.mTab);
}
if (this.mTab.selected)
this.mTabBrowser.mIsBusy = true;
}
}
else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
if (this.mTab.hasAttribute("busy")) {
this.mTab.removeAttribute("busy");
this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]);
if (!this.mTab.selected)
this.mTab.setAttribute("unread", "true");
}
this.mTab.removeAttribute("progress");
if (aWebProgress.isTopLevel) {
let isSuccessful = Components.isSuccessCode(aStatus);
if (!isSuccessful && !isTabEmpty(this.mTab)) {
// Restore the current document's location in case the
// request was stopped (possibly from a content script)
// before the location changed.
this.mBrowser.userTypedValue = null;
let inLoadURI = this.mBrowser.inLoadURI;
if (this.mTab.selected && gURLBar && !inLoadURI)
URLBarSetURI();
} else if (isSuccessful) {
this.mBrowser.urlbarChangeTracker.finishedLoad();
}
if (!this.mBrowser.mIconURL)
this.mTabBrowser.useDefaultIcon(this.mTab);
}
if (this.mBlank)
this.mBlank = false;
// For keyword URIs clear the user typed value since they will be changed into real URIs.
if (location && location.scheme == "keyword")
this.mBrowser.userTypedValue = null;
if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
this.mTabBrowser.setTabTitle(this.mTab);
if (this.mTab.selected)
this.mTabBrowser.mIsBusy = false;
}
if (oldBlank) {
this._callProgressListeners("onUpdateCurrentBrowser",
[aStateFlags, aStatus, "", 0],
true, false);
} else {
this._callProgressListeners("onStateChange",
[aWebProgress, aRequest, aStateFlags, aStatus],
true, false);
}
this._callProgressListeners("onStateChange",
[aWebProgress, aRequest, aStateFlags, aStatus],
false);
if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START |
Ci.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, aFlags) {
// OnLocationChange is called for both the top-level content
// and the subframes.
let topLevel = aWebProgress.isTopLevel;
if (topLevel) {
let isSameDocument =
!!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
// We need to clear the typed value
// if the document failed to load, to make sure the urlbar reflects the
// failed URI (particularly for SSL errors). However, don't clear the value
// if the error page's URI is about:blank, because that causes complete
// loss of urlbar contents for invalid URI errors (see bug 867957).
// Another reason to clear the userTypedValue is if this was an anchor
// navigation initiated by the user.
if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
aLocation.spec != "about:blank") ||
(isSameDocument && this.mBrowser.inLoadURI))
this.mBrowser.userTypedValue = null;
// Don't clear the favicon if this onLocationChange was
// triggered by a pushState or a replaceState (bug 550565) or
// a hash change (bug 408415).
if (aWebProgress.isLoadingDocument && !isSameDocument)
this.mBrowser.mIconURL = null;
}
if (!this.mBlank)
this._callProgressListeners("onLocationChange",
[aWebProgress, aRequest, aLocation,
aFlags]);
if (topLevel) {
this.mBrowser.lastURI = aLocation;
this.mBrowser.lastLocationChange = Date.now();
}
},
onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
if (this.mBlank)
return;
this.mMessage = aMessage;
this.mTabBrowser._callProgressListeners(this.mBrowser, "onStatusChange",
[aWebProgress, aRequest, aStatus, aMessage]);
},
onSecurityChange: function (aWebProgress, aRequest, aState) {
this.mTabBrowser._callProgressListeners(this.mBrowser, "onSecurityChange",
[aWebProgress, aRequest, aState]);
},
onRefreshAttempted: function(aWebProgress, aURI, aDelay, aSameURI)
{
var allowRefresh = true;
if (this.mTabBrowser.mCurrentTab == this.mTab) {
this.mTabBrowser.mProgressListeners.forEach(
function notifyRefreshAttempted(element) {
if (element && "onRefreshAttempted" in element) {
try {
if (!element.onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI))
allowRefresh = false;
} catch (e) {
Cu.reportError(e);
}
}
}
);
}
this.mTabBrowser.mTabsProgressListeners.forEach(
function notifyRefreshAttempted(element) {
if (element && "onRefreshAttempted" in element) {
try {
if (!element.onRefreshAttempted(this.mBrowser, aWebProgress, aURI, aDelay, aSameURI))
allowRefresh = false;
} catch (e) {
Cu.reportError(e);
}
}
}
, this);
return allowRefresh;
},
addFeed: function(aLink)
{
this.mFeeds.push(aLink);
},
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsIWebProgressListener2) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_NOINTERFACE;
}
});
]]>
</body>
</method>
<method name="mInstallSH">
<parameter name="aBrowser"/>
<parameter name="aSH"/>
<body>
<![CDATA[
return ({
mBrowser: aBrowser,
mSH: aSH,
onProgressChange : function (aWebProgress, aRequest,
aCurSelfProgress, aMaxSelfProgress,
aCurTotalProgress, aMaxTotalProgress)
{
},
onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
{
if ((aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) &&
(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)) {
function refresh(closure) {
closure.mBrowser.webNavigation.sessionHistory = closure.mSH;
closure.mBrowser.webProgress.removeProgressListener(closure);
delete closure.mBrowser._SHListener;
closure.mSH.QueryInterface(Ci.nsIWebNavigation)
.gotoIndex(closure.mSH.index);
}
setTimeout(refresh, 0, this);
}
},
onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags)
{
},
onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
{
},
onSecurityChange : function(aWebProgress, aRequest, aState)
{
},
QueryInterface : function(aIID)
{
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports))
return this;
throw Cr.NS_NOINTERFACE;
}
});
]]>
</body>
</method>
<method name="setIcon">
<parameter name="aTab"/>
<parameter name="aURI"/>
<parameter name="aLoadingPrincipal"/>
<body>
<![CDATA[
let browser = this.getBrowserForTab(aTab);
browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
if (aURI) {
if (!(aURI instanceof Ci.nsIURI)) {
aURI = makeURI(aURI);
}
// We do not serialize the principal from within nsSessionStore.js,
// hence if aLoadingPrincipal is null we default to the
// systemPrincipal which will allow the favicon to load.
let loadingPrincipal = aLoadingPrincipal ||
Services.scriptSecurityManager.getSystemPrincipal();
PlacesUIUtils.loadFavicon(browser, loadingPrincipal, aURI);
}
let sizedIconUrl = browser.mIconURL || "";
if (sizedIconUrl != aTab.getAttribute("image")) {
if (sizedIconUrl)
aTab.setAttribute("image", sizedIconUrl);
else
aTab.removeAttribute("image");
this._tabAttrModified(aTab, ["image"]);
}
this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
]]>
</body>
</method>
<method name="getIcon">
<parameter name="aTab"/>
<body>
<![CDATA[
let browser = aTab ? aTab.linkedBrowser : this.selectedBrowser;
return browser.mIconURL;
]]>
</body>
</method>
<method name="buildFavIconString">
<parameter name="aURI"/>
<body>
<![CDATA[
try {
aURI = Services.uriFixup.createExposableURI(aURI);
} catch (e) {
}
return aURI.resolve("/favicon.ico");
]]>
</body>
</method>
<method name="shouldLoadFavIcon">
<parameter name="aURI"/>
<body>
<![CDATA[
try {
aURI = Services.uriFixup.createExposableURI(aURI);
} catch (e) {
}
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 documentURI = browser.documentURI;
var icon = null;
if (browser.imageDocument) {
if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
if (browser.imageDocument.width <= sz &&
browser.imageDocument.height <= sz) {
icon = browser.currentURI;
}
}
}
// Use documentURIObject in the check for shouldLoadFavIcon so that we
// do the right thing with about:-style error pages. Bug 453442
if (!icon && this.shouldLoadFavIcon(documentURI)) {
let url = documentURI.prePath + "/favicon.ico";
if (!this.isFailedIcon(url))
icon = url;
}
this.setIcon(aTab, icon, browser.contentPrincipal);
]]>
</body>
</method>
<method name="isFailedIcon">
<parameter name="aURI"/>
<body>
<![CDATA[
if (!(aURI instanceof Ci.nsIURI))
aURI = makeURI(aURI);
return PlacesUtils.favicons.isFailedFavicon(aURI);
]]>
</body>
</method>
<method name="loadFavIcon">
<parameter name="aURI"/>
<parameter name="aAttr"/>
<parameter name="aElt"/>
<parameter name="aLoadingPrincipal"/>
<body>
<![CDATA[
let iconURL = this.buildFavIconString(aURI);
let iconURI = Services.io.newURI(iconURL);
let faviconFlags = this.usePrivateBrowsing ?
PlacesUtils.favicons.FAVICON_LOAD_PRIVATE
: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
let loadingPrincipal = aLoadingPrincipal ||
Services.scriptSecurityManager.getSystemPrincipal();
PlacesUtils.favicons
.setAndFetchFaviconForPage(aURI, iconURI, false, faviconFlags,
null,
loadingPrincipal);
if (PlacesUtils.favicons.isFailedFavicon(aURI)) {
return;
}
aElt.setAttribute(aAttr, iconURL);
]]>
</body>
</method>
<method name="addToMissedIconCache">
<parameter name="aURI"/>
<body>
<![CDATA[
let uri = Services.io.newURI(aURI);
PlacesUtils.favicons.addFailedFavicon(uri);
]]>
</body>
</method>
<method name="getTitleForURI">
<parameter name="aURI"/>
<body>
<![CDATA[
try {
aURI = Services.uriFixup.createExposableURI(aURI).displaySpec;
} catch (e) {
aURI = aURI.displaySpec;
}
if (aURI == "about:blank")
return "";
// We have a URI. Let's try to unescape it using a character set
// in case the URI is not ASCII.
try {
let characterSet = this.mCurrentBrowser.contentDocument.characterSet;
let textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"]
.getService(Ci.nsITextToSubURI);
aURI = textToSubURI.unEscapeNonAsciiURI(characterSet, aURI);
} catch (e) {
// Do nothing.
}
return aURI;
]]>
</body>
</method>
<method name="updateUrlBar">
<parameter name="aWebProgress"/>
<parameter name="aRequest"/>
<parameter name="aLocation"/>
<parameter name="aFlags"/>
<parameter name="aSecurityUI"/>
<parameter name="aBrowser"/>
<parameter name="aFeeds"/>
<body>
<![CDATA[
this.mProgressListeners.forEach(
function notifyUrlBar(element) {
try {
if ("onLocationChange" in element)
element.onLocationChange(aWebProgress, aRequest, aLocation, aFlags);
// If switching tabs, the security may have changed.
if (aSecurityUI && "onSecurityChange" in element)
element.onSecurityChange(aWebProgress, null, aSecurityUI.state);
// If the document already exists, just resend cached data.
if (!aRequest && aWebProgress.isTopLevel) {
if (aBrowser.mIconURL && "onLinkIconAvailable" in element)
element.onLinkIconAvailable(aBrowser.mIconURL);
if ("onFeedAvailable" in element) {
aFeeds.forEach(
function notifyFeedAvailable(feed) {
element.onFeedAvailable(feed);
}
);
}
}
} catch (e) {
Cu.reportError(e);
}
}
);
]]>
</body>
</method>
<method name="getWindowTitleForBrowser">
<parameter name="aBrowser"/>
<body>
<![CDATA[
var newTitle = "";
var docTitle;
var docElement = this.ownerDocument.documentElement;
var sep = docElement.getAttribute("titlemenuseparator");
var modifier = docElement.getAttribute("titlemodifier");
// Strip out any null bytes in the content title, since the
// underlying widget implementations of nsWindow::SetTitle pass
// null-terminated strings to system APIs.
if (aBrowser.docShell.contentViewer)
docTitle = aBrowser.contentTitle.replace(/\0/g, "");
if (!docTitle && !modifier) {
docTitle = this.getTitleForURI(aBrowser.currentURI);
if (!docTitle) {
// Here we actually override contenttitlesetting, because we
// don't want the titledefault value.
docTitle = this.mStringBundle.getString("tabs.untitled");
}
}
if (docTitle) {
newTitle += docElement.getAttribute("titlepreface") + 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.
// (only for schemes that support a host)
try {
if (docElement.getAttribute("chromehidden").includes("location")) {
let uri = Services.uriFixup.createExposableURI(
aBrowser.currentURI);
if (uri.schemeIs("about"))
newTitle = uri.spec + sep + newTitle;
else if (uri.host)
newTitle = uri.prePath + sep + newTitle;
}
} catch (e) {
}
return newTitle;
]]>
</body>
</method>
<method name="updateTitlebar">
<body>
<![CDATA[
var newTitle = this.getWindowTitleForBrowser(this.mCurrentBrowser);
document.title = newTitle;
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIBaseWindow).title = newTitle;
]]>
</body>
</method>
<method name="updatePopupMenu">
<parameter name="aPopupMenu"/>
<body>
<![CDATA[
this.mContextTab = aPopupMenu.triggerNode;
// The user might right-click on a tab or an empty part of the tabbar.
var isTab = this.mContextTab.localName == "tab";
var isMultiple = this.tabs.length > 1;
var isAtEnd = this.getTabsToTheEndFrom(this.mContextTab).length == 0;
var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "*");
for (let menuitem of menuItems) {
let tbattr = menuitem.getAttribute("tbattr");
if (tbattr.includes("tabbrowser-undoclosetab")) {
menuitem.disabled = (this.usePrivateBrowsing ?
this.savedBrowsers.length :
this.mSessionStore.getClosedTabCount(window)) == 0;
menuitem.hidden = (this.usePrivateBrowsing ||
Services.prefs.getIntPref("browser.sessionstore.max_tabs_undo") <= 0) &&
Services.prefs.getIntPref("browser.tabs.max_tabs_undo") <= 0;
}
else
menuitem.disabled =
(tbattr.includes("tabbrowser-totheend") && isAtEnd) ||
(tbattr.includes("tabbrowser-multiple") && !isMultiple) ||
(tbattr.includes("tabbrowser-tab") && !isTab);
}
]]>
</body>
</method>
<field name="mAeroPeek">false</field>
<method name="updateCurrentBrowser">
<body>
<![CDATA[
var newBrowser = this.mPanelContainer.selectedPanel.firstChild.firstChild;
var oldBrowser = this.mCurrentBrowser;
// Transfer the dropped link handler to the new browser.
// Note: closing the current tab sets mCurrentBrowser to null
// so we use mCurrentTab.linkedBrowser instead.
newBrowser.droppedLinkHandler = this.mCurrentTab.linkedBrowser.droppedLinkHandler;
newBrowser.showWindowResizer = this.mCurrentTab.linkedBrowser.showWindowResizer;
newBrowser.docShellIsActive = this.mCurrentTab.linkedBrowser.docShellIsActive;
if (this.mCurrentBrowser) {
this.mCurrentBrowser.droppedLinkHandler = null;
this.mCurrentBrowser.docShellIsActive = false;
this.mCurrentBrowser.removeAttribute("primary");
this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.removeResultListener(l));
}
let oldTab = this.mCurrentTab;
// Preview mode should not reset the owner.
if (!this._previewMode && !oldTab.selected)
oldTab.owner = null;
let lastRelatedTab = this.mLastRelatedIndex ? this.tabs[this.mLastRelatedIndex] : null;
if (lastRelatedTab && !lastRelatedTab.selected) {
lastRelatedTab.owner = null;
}
newBrowser.setAttribute("primary", "true");
this.mCurrentBrowser = newBrowser;
this.mCurrentTab = this.selectedTab;
this.mCurrentTab.removeAttribute("unread");
this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.addResultListener(l));
var tabListener = this.mTabListeners[this.tabContainer.selectedIndex];
if (!oldBrowser ||
(!oldBrowser.blockedPopups != !newBrowser.blockedPopups))
this.mCurrentBrowser.updateBlockedPopups();
// Update the URL bar.
this.updateUrlBar(newBrowser.webProgress,
null,
newBrowser.currentURI,
0,
newBrowser.securityUI,
newBrowser,
tabListener.mFeeds);
// Send the state, status and progress to all progress listeners.
var flags = tabListener.mStateFlags &
(Ci.nsIWebProgressListener.STATE_START |
Ci.nsIWebProgressListener.STATE_STOP);
this._callProgressListeners(null, "onStateChange",
[this.mCurrentBrowser.webProgress,
tabListener.mRequest,
flags,
tabListener.mStatus],
true, false);
this._callProgressListeners(null, "onStatusChange",
[this.mCurrentBrowser.webProgress,
tabListener.mRequest,
tabListener.mStatus,
tabListener.mMessage],
true, false);
// Also send the onUpdateCurrentBrowser event for compatibility
this._callProgressListeners(null, "onUpdateCurrentBrowser",
[tabListener.mStateFlags,
tabListener.mStatus,
tabListener.mMessage,
tabListener.mTotalProgress],
true, false);
if (this.mAeroPeek)
return;
// we only want to return to the parent tab if no other
// tabs have been opened and the user hasn't switched tabs
this.mPreviousTab = null;
this.mLastRelatedIndex = 0;
// Update the window title.
this.updateTitlebar();
// FAYT
this.fastFind.setDocShell(this.mCurrentBrowser.docShell);
// We've selected the new tab, so go ahead and notify listeners
this.mCurrentTab.dispatchEvent(new Event("TabSelect",
{ bubbles: true, cancelable: false }));
if (!document.commandDispatcher.focusedElement ||
document.commandDispatcher.focusedElement.parentNode !=
this.mCurrentTab.parentNode) {
// The focus was not on one of our tabs, so focus the new browser.
newBrowser.focus();
}
]]>
</body>
</method>
<method name="onTabClick">
<parameter name="event"/>
<body>
<![CDATA[
// A middle mouse button click on a tab is a short cut for
// closing that tab.
if (event.button != 1 || event.target.localName != 'tab')
return;
this.removeTab(event.target);
event.stopPropagation();
event.preventDefault();
]]>
</body>
</method>
<method name="onLinkEvent">
<parameter name="aEvent"/>
<body>
<![CDATA[
var link = aEvent.originalTarget;
var href = link.href;
if (!href)
return;
var targetDoc = link.ownerDocument;
var index = this.getBrowserIndexForDocument(targetDoc);
if (index < 0)
return;
var rel = link.rel;
var type = link.type;
var isIcon = /(?:^|\s)icon(?:\s|$)/i.test(rel) &&
Services.prefs.getBoolPref("browser.chrome.site_icons");
if (isIcon) {
var iconUri = this.getLinkIconURI(link);
if (iconUri)
this.setIcon(this.tabs[index], iconUri,
link.nodePrincipal);
return;
}
if (aEvent.type == "DOMLinkChanged")
return;
var isFeed = /(?:^|\s)feed(?:\s|$)/i.test(rel) ||
(/(?:^|\s)alternate(?:\s|$)/i.test(rel) &&
!/(?:^|\s)stylesheet(?:\s|$)/i.test(rel) &&
/^\s*application\/(?:atom|rss)\+xml\s*$/i.test(type));
if (!isFeed)
return;
try {
let feedURI = Services.io.newURI(href, targetDoc.characterSet);
if (!/^https?$/.test(feedURI.scheme)) {
return;
}
urlSecurityCheck(feedURI, targetDoc.nodePrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
} catch(e) {
return;
}
this.mTabListeners[index].addFeed(link);
if (this.browsers[index] == this.mCurrentBrowser) {
this.mProgressListeners.forEach(
function notifyFeedAvailable(element) {
if ("onFeedAvailable" in element) {
try {
element.onFeedAvailable(link);
} catch (e) {
Cu.reportError(e);
}
}
}
);
}
]]>
</body>
</method>
<method name="getLinkIconURI">
<parameter name="aLink"/>
<body><![CDATA[
var targetDoc = aLink.ownerDocument;
// Make a URI out of our href.
var uri = Services.io.newURI(aLink.href, targetDoc.characterSet);
// Verify that the load of this icon is legal.
// Some error or special pages can load their favicon.
// To be on the safe side, only allow chrome:// favicons.
const re = /^about:(neterror|certerror|blocked)\?/;
var isAllowedPage = re.test(targetDoc.documentURI);
if (!isAllowedPage || !uri.schemeIs("chrome")) {
try {
urlSecurityCheck(uri,targetDoc.nodePrincipal,
Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
} catch(e) {
return null;
}
}
// Security says okay, now ask content policy
try {
var contentPolicy =
Cc['@mozilla.org/layout/content-policy;1']
.getService(Ci.nsIContentPolicy);
} catch (e) {
return null; // Refuse to load if we can't do a security check.
}
if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE,
uri, targetDoc.documentURIObject,
aLink, aLink.type,
null) != Ci.nsIContentPolicy.ACCEPT) {
return null;
}
return uri;
]]></body>
</method>
<method name="_tabAttrModified">
<parameter name="aTab"/>
<parameter name="aChanged"/>
<body><![CDATA[
if (aTab.closing)
return;
let event = new CustomEvent("TabAttrModified", {
bubbles: true,
cancelable: false,
detail: {
changed: aChanged,
}
});
aTab.dispatchEvent(event);
]]></body>
</method>
<method name="setTabTitleLoading">
<parameter name="aTab"/>
<body>
<![CDATA[
aTab.label = this.mStringBundle.getString("tabs.loading");
aTab.crop = "end";
this._tabAttrModified(aTab, ["label", "crop"]);
]]>
</body>
</method>
<method name="setTabTitle">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = aTab.linkedBrowser;
var title = browser.contentTitle;
var crop = "end";
if (!title) {
title = this.getTitleForURI(browser.currentURI);
if (title)
crop = "center";
else
title = this.mStringBundle.getString("tabs.untitled");
}
aTab.label = title;
aTab.crop = crop;
]]>
</body>
</method>
<method name="setStripVisibilityTo">
<parameter name="aShow"/>
<body>
<![CDATA[
this.mStrip.collapsed = !aShow;
]]>
</body>
</method>
<method name="getStripVisibility">
<body>
return !this.mStrip.collapsed;
</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 params = aReferrerURI;
if (!params || params instanceof Ci.nsIURI) {
params = {
triggeringPrincipal: Services.scriptSecurityManager
.getSystemPrincipal(),
referrerURI: aReferrerURI,
charset: aCharset,
postData: aPostData,
inBackground: aLoadInBackground,
allowThirdPartyFixup: aAllowThirdPartyFixup,
allowMixedContent: false,
userContextId: null,
opener: null,
};
}
params.focusNewTab = params.inBackground != null ?
!params.inBackground :
!Services.prefs.getBoolPref("browser.tabs.loadInBackground");
if (params.focusNewTab)
params.ownerTab = this.selectedTab;
return this.addTab(aURI, params);
]]>
</body>
</method>
<method name="loadTabs">
<parameter name="aURIs"/>
<parameter name="aLoadInBackground"/>
<parameter name="aReplace"/>
<body><![CDATA[
let aAllowThirdPartyFixup;
let aPostDatas = [];
let aUserContextId;
let aTriggeringPrincipal;
// Additional parameters are in a params object.
// Firefox uses additional parameters not supported here.
if (arguments.length == 2 &&
typeof arguments[1] == "object") {
let params = arguments[1];
aLoadInBackground = params.inBackground;
aReplace = params.replace;
aAllowThirdPartyFixup = params.allowThirdPartyFixup;
aPostDatas = params.postDatas || aPostDatas;
aUserContextId = params.userContextId;
aTriggeringPrincipal = params.triggeringPrincipal;
}
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;
var targetTabIndex = -1;
if (aReplace) {
let browser;
browser = this.mCurrentBrowser;
targetTabIndex = this.tabContainer.selectedIndex;
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (aAllowThirdPartyFixup) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
}
try {
browser.loadURIWithFlags(aURIs[0], {
flags,
postData: aPostDatas[0],
triggeringPrincipal : aTriggeringPrincipal,
});
} 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,
allowThirdPartyFixup: aAllowThirdPartyFixup,
postData: aPostDatas[0],
userContextId: aUserContextId,
triggeringPrincipal: aTriggeringPrincipal,
});
}
let tabNum = targetTabIndex;
for (let i = 1; i < aURIs.length; ++i) {
let tab = this.addTab(aURIs[i], {
allowThirdPartyFixup: aAllowThirdPartyFixup,
postData: aPostDatas[i],
userContextId: aUserContextId,
triggeringPrincipal: aTriggeringPrincipal,
});
if (targetTabIndex !== -1)
this.moveTabTo(tab, ++tabNum);
}
if (!aLoadInBackground) {
if (firstTabAdded) {
// .selectedTab setter focuses the content area
this.selectedTab = firstTabAdded;
} else
this.selectedBrowser.focus();
}
]]></body>
</method>
<method name="addTab">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<parameter name="aFocusNewTab"/>
<parameter name="aAllowThirdPartyFixup"/>
<body>
<![CDATA[
var aTriggeringPrincipal;
var aReferrerPolicy;
var aFromExternal;
var aOwner;
var aRelatedToCurrent;
var aAllowMixedContent;
var aNoReferrer;
var aUserContextId;
var aOriginPrincipal;
var aOpener;
if (arguments.length == 2 &&
arguments[1] != null &&
typeof arguments[1] == "object" &&
!(arguments[1] instanceof Ci.nsIURI)) {
let params = arguments[1];
aTriggeringPrincipal = params.triggeringPrincipal;
aReferrerURI = params.referrerURI;
aReferrerPolicy = params.referrerPolicy;
aCharset = params.charset;
aPostData = params.postData;
aOwner = params.ownerTab;
aFocusNewTab = params.focusNewTab;
aAllowThirdPartyFixup = params.allowThirdPartyFixup;
aFromExternal = params.fromExternal;
aRelatedToCurrent = params.relatedToCurrent;
aAllowMixedContent = params.allowMixedContent;
aNoReferrer = params.noReferrer;
aUserContextId = params.userContextId;
aOriginPrincipal = params.originPrincipal;
aOpener = params.opener;
}
// If we're adding tabs, we're past interrupt mode, ditch the owner.
if (this.mCurrentTab.owner)
this.mCurrentTab.owner = null;
this._browsers = null; // invalidate cache
var t = this.referenceTab.cloneNode(true);
var blank = !aURI || aURI == "about:blank";
if (!blank)
t.setAttribute("label", aURI);
this.tabContainer.appendChild(t);
"browser");
b.setAttribute("type", "content");
b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
if (this.hasAttribute("datetimepicker")) {
b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
}
// Check if we have a "parent" window which we need to set as our opener
if (aOpener) {
b.presetOpenerWindow(aOpener);
}
// Create the browserStack container
var stack = document.createElementNS(
"stack");
stack.setAttribute("anonid", "browserStack");
stack.appendChild(b);
stack.setAttribute("flex", "1");
// Add the Message and the Browser to the box
"notificationbox");
n.setAttribute("class", "browser-notificationbox");
n.setAttribute("popupnotification", this.getAttribute("popupnotification"));
n.appendChild(stack);
var uniqueId = "panel" + this.nextTabNumber++;
n.id = uniqueId;
t.linkedPanel = uniqueId;
t.linkedBrowser = b;
if (t.previousSibling.selected)
t.setAttribute("afterselected", true);
// Prevent the superfluous initial load of a blank document
// if we're going to load something other than about:blank.
if (!blank)
b.setAttribute("nodefaultsrc", "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(n);
// We start our browsers out as inactive.
b.docShellIsActive = false;
this.mStrip.collapsed = false;
Services.prefs.setBoolPref("browser.tabs.forceHide", false);
// If this new tab is owned by another, assert that relationship.
if (aOwner)
t.owner = aOwner;
// wire up a progress listener for the new browser object.
var position = this.tabs.length - 1;
var tabListener = this.mTabProgressListener(t, b, blank);
const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Ci.nsIWebProgress);
filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
b.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[position] = tabListener;
this.mTabFilters[position] = filter;
if (!blank) {
// 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 |
Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
if (aFromExternal)
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
if (aAllowMixedContent)
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
try {
b.loadURIWithFlags(aURI, {
flags,
triggeringPrincipal : aTriggeringPrincipal,
referrerURI: aNoReferrer ? null : aReferrerURI,
charset: aCharset,
referrerPolicy: aReferrerPolicy,
postData: aPostData,
});
}
catch (ex) { }
}
t.dispatchEvent(new Event("TabOpen",
{ bubbles: true, cancelable: false }));
// Check if we're opening a tab related to the current tab and
// move it to after the current tab.
// aReferrerURI is null or undefined if the tab is opened from
// an external application or bookmark, i.e. somewhere other
// than the current tab.
if ((aRelatedToCurrent || aReferrerURI ||
Services.prefs.getBoolPref("browser.tabs.insertAllTabsAfterCurrent")) &&
Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
var lastRelatedIndex = this.mLastRelatedIndex ||
this.tabContainer.selectedIndex;
if (this.mLastRelatedIndex)
this.tabs[this.mLastRelatedIndex].owner = null;
else
t.owner = this.selectedTab;
this.moveTabTo(t, ++lastRelatedIndex);
this.mLastRelatedIndex = lastRelatedIndex;
}
if (aFocusNewTab) {
var parentTab = this.selectedTab;
this.selectedTab = t;
this.mPreviousTab = parentTab;
}
else {
// The user opened a background tab, so updateCurrentBrowser
// won't be called. Explicitly clear the previous tab.
this.mPreviousTab = null;
}
this.tabContainer._handleNewTab(t);
return t;
]]>
</body>
</method>
<method name="warnAboutClosingTabs">
<parameter name="aCloseTabs"/>
<parameter name="aTab"/>
<body>
<![CDATA[
var tabsToClose;
switch (aCloseTabs) {
case this.closingTabsEnum.ALL:
tabsToClose = this.tabs.length;
break;
case this.closingTabsEnum.OTHER:
tabsToClose = this.tabs.length - 1;
break;
case this.closingTabsEnum.TO_END:
if (!aTab)
throw new Error("Required argument missing: aTab");
tabsToClose = this.getTabsToTheEndFrom(aTab).length;
break;
default:
throw new Error("Invalid argument: " + aCloseTabs);
}
if (tabsToClose <= 1)
return true;
const pref = aCloseTabs == this.closingTabsEnum.ALL ?
"browser.tabs.warnOnClose" :
"browser.tabs.warnOnCloseOther";
if (!Services.prefs.getBoolPref(pref))
return true;
//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 warningTitle;
var warningMessage;
var closeButton;
var promptMessage;
switch (aCloseTabs) {
case this.closingTabsEnum.ALL:
warningTitle = "tabs.closeWarningTitleAll";
warningMessage =
PluralForm.get(tabsToClose,
bundle.getString("tabs.closeWarningAll"));
closeButton = "tabs.closeButtonAll";
promptMessage = "tabs.closeWarningPromptMeAll";
break;
case this.closingTabsEnum.OTHER:
// fall through
case this.closingTabsEnum.TO_END:
// fall through
default:
warningTitle = "tabs.closeWarningTitle";
warningMessage =
PluralForm.get(tabsToClose,
bundle.getString("tabs.closeWarningOther"));
closeButton = "tabs.closeButton";
promptMessage = "tabs.closeWarningPromptMe";
break;
}
var ps = Services.prompt;
var buttonPressed =
ps.confirmEx(window,
bundle.getString(warningTitle),
warningMessage.replace("#1", tabsToClose),
(ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
+ (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
bundle.getString(closeButton),
null, null,
bundle.getString(promptMessage),
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="getTabsToTheEndFrom">
<parameter name="aTab"/>
<body>
<![CDATA[
let tabsToEnd = [];
let tabs = this.tabs;
for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
tabsToEnd.push(tabs[i]);
}
return tabsToEnd.reverse();
]]>
</body>
</method>
<method name="removeTabsToTheEndFrom">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
let tabs = this.getTabsToTheEndFrom(aTab);
for (let i = tabs.length - 1; i >= 0; --i) {
this.removeTab(tabs[i]);
}
}
]]>
</body>
</method>
<method name="removeAllTabsBut">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
this.selectedTab = aTab;
for (let i = this.tabs.length - 1; i >= 0; --i) {
if (this.tabs[i] != aTab)
this.removeTab(this.tabs[i]);
}
}
]]>
</body>
</method>
<method name="removeCurrentTab">
<parameter name="aParams"/>
<body>
<![CDATA[
return this.removeTab(this.mCurrentTab, aParams);
]]>
</body>
</method>
<method name="isBrowserEmpty">
<parameter name="aBrowser"/>
<body>
<![CDATA[
return aBrowser.sessionHistory.count < 2 &&
aBrowser.currentURI.spec == "about:blank" &&
!aBrowser.contentDocument.body.hasChildNodes();
]]>
</body>
</method>
<method name="getUndoList">
<body>
<![CDATA[
var tabData = this.usePrivateBrowsing ? this.savedBrowsers :
JSON.parse(this.mSessionStore.getClosedTabData(window));
return tabData.map(function(aTabData) { return aTabData.title; });
]]>
</body>
</method>
<method name="undoCloseTab">
<parameter name="aIndex"/>
<body>
<![CDATA[
if (this.usePrivateBrowsing)
return this.savedBrowsers.length ? this.restoreTab(aIndex) : null;
return this.mSessionStore.getClosedTabCount(window) ?
this.mSessionStore.undoCloseTab(window, aIndex) : null;
]]>
</body>
</method>
<method name="restoreTab">
<parameter name="aIndex"/>
<body>
<![CDATA[
if (aIndex >= this.savedBrowsers.length || aIndex < 0)
return null;
this._browsers = null;
var savedData = this.savedBrowsers.splice(aIndex, 1)[0];
var t = savedData.browserData.tab;
var b = savedData.browserData.browser;
var hist = savedData.browserData.history;
this.tabContainer.appendChild(t);
if (t.previousSibling.selected)
t.setAttribute("afterselected", true);
// navigate back to the proper page from the light page
b.stop();
b.webNavigation.gotoIndex(0);
// reattach the old history
b.webNavigation.sessionHistory = hist;
// add back the filters, security first (bug 313335)
var secFlags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
Ci.nsIWebProgress.NOTIFY_LOCATION |
Ci.nsIWebProgress.NOTIFY_SECURITY;
b.webProgress.addProgressListener(b.securityUI, secFlags);
var position = this.tabs.length - 1;
var tabListener = this.mTabProgressListener(t, b, false);
const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Ci.nsIWebProgress);
filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
b.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[position] = tabListener;
this.mTabFilters[position] = filter;
t.dispatchEvent(new Event("TabOpen",
{ bubbles: true, cancelable: false }));
if (savedData.pos < position)
this.moveTabTo(t, savedData.pos);
if (this.tabs.length == 2 && this.isBrowserEmpty(this))
this.removeCurrentTab({ disableUndo: true });
else {
this.selectedTab = t;
this.mStrip.collapsed = false;
}
this.tabContainer._handleNewTab(t);
return t;
]]>
</body>
</method>
<method name="removeBrowser">
<parameter name="aBrowser"/>
<body>
<![CDATA[
let panel = this.getNotificationBox(aBrowser);
panel.destroy();
aBrowser.destroy();
// The pagehide event that this removal triggers is safe
// because the browser is no longer current at this point.
panel.remove();
// Fix up the selected panel.
panel = this.getNotificationBox(this.selectedTab.linkedBrowser);
this.mTabBox.selectedPanel = panel;
]]>
</body>
</method>
<method name="removeTab">
<parameter name="aTab"/>
<parameter name="aParams"/>
<body>
<![CDATA[
this.mLastRelatedIndex = 0;
if (!aParams) {
aParams = {
animate: false,
disableUndo: false
};
}
var oldBrowser = aTab.linkedBrowser;
var ds = oldBrowser.docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
return;
// We're committed to closing the tab now.
var l = this.tabs.length;
switch (l) {
case 1:
// add a new blank tab to replace the one we're about to close
// (this ensures that the remaining tab is as good as new)
this.addTab("about:blank");
l++;
// fall through
case 2:
if (Services.prefs.getBoolPref("browser.tabs.autoHide"))
this.mStrip.collapsed = true;
}
// Dispatch a notification.
// We dispatch it before any teardown so that event listeners can
// inspect the tab that's about to close.
aTab.dispatchEvent(new UIEvent("TabClose",
{ bubbles: true, cancelable: false, view: window,
detail: !!aParams.disableUndo }));
var tabData = aTab.tabData || {};
tabData.pos = this.getTabIndex(aTab);
tabData.panel = this.getNotificationBox(oldBrowser).id;
tabData.title = oldBrowser.contentDocument.title ||
this.getTitleForURI(oldBrowser.currentURI) ||
this.mStringBundle.getString("tabs.untitled");
var index = this.getTabIndex(aTab);
// Remove SSL listener
oldBrowser.webProgress.removeProgressListener(oldBrowser.securityUI);
// Remove the tab's filter and progress listener.
const filter = this.mTabFilters[index];
oldBrowser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(this.mTabListeners[index]);
this.mTabFilters.splice(index, 1);
this.mTabListeners.splice(index, 1);
// We are no longer the primary content area
oldBrowser.removeAttribute("primary");
// Remove this tab as the owner of any other tabs, since it's going away.
for (let tab of this.tabs) {
if ("owner" in tab && tab.owner == aTab)
// |tab| is a child of the tab we're removing, make it an orphan.
tab.owner = null;
}
// Now select the new tab before nuking the old one.
var currentIndex = this.tabContainer.selectedIndex;
var newIndex = -1;
if (currentIndex > index)
newIndex = currentIndex - 1;
else if (currentIndex < index)
newIndex = currentIndex;
else if (index == l - 1)
newIndex = index - 1;
else
newIndex = index;
if (oldBrowser == this.mCurrentBrowser)
this.mCurrentBrowser = null;
// Invalidate browsers cache, as the tab is removed from the
// tab container.
this._browsers = null;
let owner = ("owner" in aTab) ? aTab.owner : null;
// Clean up before/after selected attributes before removing the
// tab.
aTab._selected = false;
aTab.remove();
// When the current tab is removed select a new tab
// and fire select events on tabpanels and tabs
if (owner && !owner.hidden && !owner.closing &&
Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
this.selectedTab = owner;
}
else if (this.mPreviousTab && (aTab == this.mCurrentTab))
this.selectedTab = this.mPreviousTab;
else {
this.tabContainer.selectedIndex = newIndex;
// We need to explicitly clear this, because updateCurrentBrowser
// doesn't get called for a background tab
this.mPreviousTab = null;
}
// Save the tab for undo.
// Even though we navigate to about:blank, it costs more RAM than
// really closing the tab. The pref controls how far you can undo
var maxUndoDepth = Services.prefs.getIntPref("browser.tabs.max_tabs_undo");
var oldSH = oldBrowser.webNavigation.sessionHistory;
var inOnLoad = oldBrowser.docShell.isExecutingOnLoadHandler;
var isPopup = oldBrowser.contentWindow.opener &&
!Services.prefs.getBoolPref("browser.tabs.cache_popups");
if (maxUndoDepth <= 0 || aParams.disableUndo || inOnLoad || isPopup || this.isBrowserEmpty(oldBrowser)) {
// Undo is disabled/tab is blank. Kill the browser for real.
// 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. Also fix up the selected panel in the case
// the removed browser was to the left of the current browser.
this.removeBrowser(oldBrowser);
return;
}
// preserve a pointer to the browser for undoing the close
// 1. save a copy of the session history (oldSH)
// 2. hook up a new history
// 3. add the last history entry from the old history the new
// history so we'll be able to go back from about:blank
// 4. load a light URL in the browser, pushing the current page
// into bfcache - allows for saving of JS modifications
// and also saves RAM by allowing bfcache to evict the full page
tabData.browserData = {
tab: aTab,
browser: oldBrowser,
history: oldSH,
toJSON: function() {} // hides this object from JSON.stringify
};
this.savedBrowsers.unshift(tabData);
var newSH = Cc["@mozilla.org/browser/shistory;1"]
.createInstance(Ci.nsISHistory);
oldBrowser.webNavigation.sessionHistory = newSH;
var entry = oldSH.getEntryAtIndex(oldSH.index)
.QueryInterface(Ci.nsISHEntry)
.clone();
// The bfcache entry is tightly coupled to the original shistory it
// belongs to, better to drop it.
entry.abandonBFCacheEntry();
// don't try to repost data when restoring the tab
entry.postData = null;
newSH.addEntry(entry, true);
// about:blank is light
oldBrowser.loadURI("about:blank");
// remove overflow from the undo stack
if (this.savedBrowsers.length > maxUndoDepth) {
tabData = this.savedBrowsers.pop();
var deadBrowser = tabData.browserData.browser;
delete tabData.browserData;
this.removeBrowser(deadBrowser);
}
]]>
</body>
</method>
<method name="forgetSavedBrowser">
<parameter name="aIndex"/>
<body>
<![CDATA[
if (aIndex >= this.savedBrowsers.length || aIndex < 0)
return false;
var tabData = this.savedBrowsers.splice(aIndex, 1)[0];
var deadBrowser = tabData.browserData.browser;
delete tabData.browserData;
this.removeBrowser(deadBrowser);
return true;
]]>
</body>
</method>
<method name="reloadAllTabs">
<body>
<![CDATA[
var l = this.tabs.length;
for (var i = 0; i < l; i++) {
try {
this.tabs[i].linkedBrowser.reload();
} catch (e) {
// ignore failure to reload so others will be reloaded
}
}
]]>
</body>
</method>
<method name="reloadTab">
<parameter name="aTab"/>
<body>
<![CDATA[
this.getBrowserForTab(aTab).reload();
]]>
</body>
</method>
<method name="addProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
if (!aListener)
throw Cr.NS_ERROR_INVALID_ARG;
if (this.mProgressListeners.includes(aListener))
throw Cr.NS_ERROR_FAILURE;
// push() does not disturb possibly ongoing iterations.
this.mProgressListeners.push(aListener);
]]>
</body>
</method>
<method name="removeProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
if (!this.mProgressListeners.includes(aListener))
throw Cr.NS_ERROR_FAILURE;
// Create a new array, not to disturb possibly ongoing iterations.
this.mProgressListeners =
this.mProgressListeners.filter(
function removeListener(element) {
return element != aListener;
}
);
]]>
</body>
</method>
<method name="addTabsProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
if (!aListener)
throw Cr.NS_ERROR_INVALID_ARG;
if (this.mTabsProgressListeners.includes(aListener))
throw Cr.NS_ERROR_FAILURE;
// push() does not disturb possibly ongoing iterations.
this.mTabsProgressListeners.push(aListener);
]]>
</body>
</method>
<method name="removeTabsProgressListener">
<parameter name="aListener"/>
<body>
<![CDATA[
if (!this.mTabsProgressListeners.includes(aListener))
throw Cr.NS_ERROR_FAILURE;
// Create a new array, not to disturb possibly ongoing iterations.
this.mTabsProgressListeners =
this.mTabsProgressListeners.filter(
function removeListener(element) {
return element != aListener;
}
);
]]>
</body>
</method>
<method name="_getTabForContentWindow">
<parameter name="aWindow"/>
<body>
<![CDATA[
const browsers = this.browsers;
for (var i = 0; i < browsers.length; ++i)
if (browsers[i].contentWindow == aWindow)
return this.tabs[i];
return null;
]]>
</body>
</method>
<method name="getTabForBrowser">
<parameter name="aBrowser"/>
<body>
<![CDATA[
for (var tab of this.tabs)
if (tab.linkedBrowser == aBrowser)
return tab;
return null;
]]>
</body>
</method>
<method name="getBrowserForTab">
<parameter name="aTab"/>
<body>
<![CDATA[
return aTab.linkedBrowser;
]]>
</body>
</method>
<method name="getBrowserForOuterWindowID">
<parameter name="aID"/>
<body>
<![CDATA[
for (var browser of this.browsers)
if (browser.outerWindowID == aID)
return browser;
return null;
]]>
</body>
</method>
<method name="getTabIndex">
<parameter name="aTab"/>
<body>
<![CDATA[
for (var i = 0; i < this.tabs.length; ++i)
if (this.tabs[i] == aTab)
return i;
throw Cr.NS_ERROR_ILLEGAL_VALUE;
]]>
</body>
</method>
<property name="popupAnchor" readonly="true">
<getter><![CDATA[
if (this.mCurrentTab._popupAnchor) {
return this.mCurrentTab._popupAnchor;
}
// Actually the notificationbox not the browserstack.
let stack = this.mCurrentBrowser.parentNode;
// Create an anchor for the popup
let popupAnchor = document.createElementNS(NS_XUL, "hbox");
popupAnchor.className = "popup-anchor";
popupAnchor.hidden = true;
stack.appendChild(popupAnchor);
return this.mCurrentTab._popupAnchor = popupAnchor;
]]></getter>
</property>
<method name="selectTabAtIndex">
<parameter name="aIndex"/>
<parameter name="aEvent"/>
<body>
<![CDATA[
// count backwards for aIndex < 0
if (aIndex < 0)
aIndex += this.tabs.length;
if (aIndex >= 0 &&
aIndex < this.tabs.length &&
aIndex != this.tabContainer.selectedIndex)
this.selectedTab = this.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.from(this.tabs, tab => tab.linkedBrowser));
]]>
</getter>
</property>
<!-- Drag and drop observer API -->
<method name="_onDragStart">
<parameter name="aEvent"/>
<body>
<![CDATA[
var target = aEvent.target;
if (target.localName == "tab") {
var URI = target.linkedBrowser.currentURI;
var spec = URI.spec;
var title = target.linkedBrowser.contentTitle || spec;
var dt = aEvent.dataTransfer;
dt.mozSetDataAt("text/x-moz-url", spec + "\n" + title, 0);
dt.mozSetDataAt("text/uri-list", spec, 0);
dt.mozSetDataAt("text/plain", spec, 0);
dt.mozSetDataAt("text/html", '<a href="' + spec + '">' + title + '</a>', 0);
}
aEvent.stopPropagation();
]]>
</body>
</method>
<method name="_onDragOver">
<parameter name="aEvent"/>
<body>
<![CDATA[
aEvent.preventDefault();
aEvent.stopPropagation();
var ib = document.getAnonymousElementByAttribute(this, "class", "tab-drop-indicator-bar");
// autoscroll the tab strip if we drag over the scroll buttons,
// even if we aren't dragging a tab
var pixelsToScroll = 0;
var tabStrip = this.tabContainer.arrowScrollbox;
var ltr = window.getComputedStyle(this, null).direction == "ltr";
if (this.tabContainer.getAttribute("overflow") == "true") {
var targetAnonid = aEvent.originalTarget.getAttribute("anonid");
switch (targetAnonid) {
case "scrollbutton-up":
pixelsToScroll = -tabStrip.scrollIncrement;
break;
case "scrollbutton-down":
case "alltabs-button":
pixelsToScroll = tabStrip.scrollIncrement;
break;
}
if (pixelsToScroll)
tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
}
var ind = document.getAnonymousElementByAttribute(this, "class", "tab-drop-indicator");
var draggedTab = aEvent.dataTransfer.mozSourceNode;
var within = draggedTab &&
draggedTab.parentNode == this.tabContainer;
var newIndexOn = within ? -1 : this.getDropOnIndex(aEvent);
var ltr = window.getComputedStyle(this, null).direction == "ltr";
var arrowX, tabBoxObject;
if (newIndexOn != -1) {
tabBoxObject = this.tabs[newIndexOn].boxObject;
arrowX = tabBoxObject.screenX + tabBoxObject.width / 2;
}
else {
var newIndexBetween = this.getDropIndex(aEvent);
if (within) {
var tabIndex = this.getTabIndex(draggedTab);
if (newIndexBetween == tabIndex ||
newIndexBetween == tabIndex + 1) {
ib.collapsed = true;
return;
}
}
if (newIndexBetween == this.tabs.length) {
tabBoxObject = this.tabs[this.tabs.length - 1].boxObject;
arrowX = tabBoxObject.x;
arrowX = tabBoxObject.screenX;
if (ltr) // for LTR "after" is on the right-hand side of the tab
arrowX += tabBoxObject.width;
}
else {
tabBoxObject = this.tabs[newIndexBetween].boxObject;
arrowX = tabBoxObject.screenX;
if (!ltr) // for RTL "before" is on the right-hand side of the tab
arrowX += tabBoxObject.width;
}
}
var boxObject = tabStrip.scrollBoxObject;
// Check pixelsToScroll as well to prevent noticable judder.
if (pixelsToScroll > 0 || arrowX >= boxObject.screenX + boxObject.width)
arrowX = boxObject.screenX + boxObject.width;
else if (pixelsToScroll < 0 || arrowX < boxObject.screenX)
arrowX = boxObject.screenX;
if (ltr)
ind.style.marginLeft = (arrowX - this.boxObject.screenX) + "px";
else
ind.style.marginRight = (this.boxObject.screenX + this.boxObject.width - arrowX) + "px";
ib.collapsed = false;
]]>
</body>
</method>
<method name="_onDrop">
<parameter name="aEvent"/>
<body>
<![CDATA[
document.getAnonymousElementByAttribute(this, "class",
"tab-drop-indicator-bar")
.collapsed = true;
aEvent.stopPropagation();
var newIndex = this.getDropIndex(aEvent);
var dt = aEvent.dataTransfer;
var draggedTab = dt.mozSourceNode;
if (draggedTab && draggedTab.parentNode == this.tabContainer) {
if (newIndex > this.getTabIndex(draggedTab))
newIndex--;
this.moveTabTo(draggedTab, newIndex);
return;
}
var url;
try {
// Pass true to disallow dropping javascript: or data: urls.
url = Services.droppedLinkHandler.dropLink(aEvent, {}, true);
} catch (ex) {}
// Valid urls don't contain spaces ' '; if we have a space
// it isn't a valid url.
if (!url || url.includes(" "))
return;
getShortcutOrURIAndPostData(url).then(data => {
var bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
if (aEvent.shiftKey)
bgLoad = !bgLoad;
let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(aEvent);
var tab = null;
tabIndex = this.getDropOnIndex(aEvent);
if (tabIndex != -1) {
// Load in an existing tab.
tab = this.tabs[tabIndex];
tab.linkedBrowser.loadURI(data.url, {
allowThirdPartyFixup: true,
triggeringPrincipal,
});
if (this.mCurrentTab != tab && !bgLoad)
this.selectedTab = tab;
}
else if (dt.mozSourceDocument &&
dt.mozSourceDocument.defaultView.top == content) {
// We're adding a new tab, and we may want parent-tab tracking.
tab = this.loadOneTab(data.url, {
inBackground: bgLoad,
allowThirdPartyFixup: true,
triggeringPrincipal,
});
this.moveTabTo(tab, newIndex);
}
else {
// We're adding a new tab, but do not want parent-tab tracking.
tab = this.addTab(data.url, {
allowThirdPartyFixup: true,
triggeringPrincipal,
});
this.moveTabTo(tab, newIndex);
if (this.mCurrentTab != tab && !bgLoad)
this.selectedTab = tab;
}
});
]]>
</body>
</method>
<method name="_onDragLeave">
<parameter name="aEvent"/>
<body>
<![CDATA[
var target = aEvent.relatedTarget;
while (target && target != this.mStrip)
target = target.parentNode;
if (target)
return;
document.getAnonymousElementByAttribute(this, "class",
"tab-drop-indicator-bar")
.collapsed = true;
aEvent.stopPropagation();
]]>
</body>
</method>
<method name="moveTabTo">
<parameter name="aTab"/>
<parameter name="aIndex"/>
<body>
<![CDATA[
let oldPosition;
// for compatibility with extensions
if (typeof(aTab) == "number") {
oldPosition = aTab;
aTab = this.tabs[oldPosition];
} else {
oldPosition = this.getTabIndex(aTab);
}
if (oldPosition == aIndex)
return;
this.mLastRelatedIndex = 0;
this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(oldPosition, 1)[0]);
this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(oldPosition, 1)[0]);
let wasFocused = (document.activeElement == this.mCurrentTab);
if (aIndex >= oldPosition)
++aIndex;
this.mCurrentTab._selected = false;
// invalidate cache
this._browsers = null;
// 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.
var tab = this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
this.mCurrentTab._selected = true;
if (wasFocused)
this.mCurrentTab.focus();
this.tabContainer._handleTabSelect(false);
tab.dispatchEvent(new UIEvent("TabMove",
{ bubbles: true, cancelable: false, view: window,
detail: oldPosition }));
]]>
</body>
</method>
<method name="getDropIndex">
<parameter name="aEvent"/>
<body>
<![CDATA[
for (var i = 0; i < this.tabs.length; ++i) {
var coord = this.tabs[i].boxObject.screenX +
this.tabs[i].boxObject.width / 2;
if (window.getComputedStyle(this, null).direction == "ltr") {
if (aEvent.screenX < coord)
return i;
} else {
if (aEvent.screenX > coord)
return i;
}
}
return this.tabs.length;
]]>
</body>
</method>
<method name="getDropOnIndex">
<parameter name="aEvent"/>
<body>
<![CDATA[
for (var i = 0; i < this.tabs.length; ++i) {
var tabBoxObject = this.tabs[i].boxObject;
if (aEvent.screenX > tabBoxObject.screenX + tabBoxObject.width * .25 &&
aEvent.screenX < tabBoxObject.screenX + tabBoxObject.width * .75)
return i;
}
return -1;
]]>
</body>
</method>
<!-- moveTabLeft and moveTabRight methods have been kept for backwards
compatibility for extensions. Internally moveTabOver is used. -->
<method name="moveTabLeft">
<body>
<![CDATA[
if (window.getComputedStyle(this, null).direction == "ltr")
this.moveTabBackward();
else
this.moveTabForward();
]]>
</body>
</method>
<method name="moveTabRight">
<body>
<![CDATA[
if (window.getComputedStyle(this, null).direction == "ltr")
this.moveTabForward();
else
this.moveTabBackward();
]]>
</body>
</method>
<method name="moveTabForward">
<body>
<![CDATA[
var tabPos = this.tabContainer.selectedIndex;
if (tabPos < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab, tabPos + 1);
}
else if (this.arrowKeysShouldWrap)
this.moveTabToStart();
]]>
</body>
</method>
<method name="moveTabBackward">
<body>
<![CDATA[
var tabPos = this.tabContainer.selectedIndex;
if (tabPos > 0) {
this.moveTabTo(this.mCurrentTab, tabPos - 1);
}
else if (this.arrowKeysShouldWrap)
this.moveTabToEnd();
]]>
</body>
</method>
<method name="moveTabToStart">
<body>
<![CDATA[
if (this.tabContainer.selectedIndex > 0) {
this.moveTabTo(this.mCurrentTab, 0);
}
]]>
</body>
</method>
<method name="moveTabToEnd">
<body>
<![CDATA[
if (this.tabContainer.selectedIndex < this.browsers.length - 1) {
this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
}
]]>
</body>
</method>
<method name="moveTabOver">
<parameter name="aEvent"/>
<body>
<![CDATA[
var direction = window.getComputedStyle(this, null).direction;
var keyCode = aEvent.keyCode;
if ((direction == "ltr" && keyCode == KeyEvent.DOM_VK_RIGHT) ||
(direction == "rtl" && keyCode == KeyEvent.DOM_VK_LEFT))
this.moveTabForward();
else
this.moveTabBackward();
]]>
</body>
</method>
<!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
MAKE SURE TO ADD IT HERE AS WELL. -->
<property name="canGoBack"
onget="return this.mCurrentBrowser.canGoBack;"
readonly="true"/>
<property name="canGoForward"
onget="return this.mCurrentBrowser.canGoForward;"
readonly="true"/>
<method name="goBack">
<body>
<![CDATA[
return this.mCurrentBrowser.goBack();
]]>
</body>
</method>
<method name="goForward">
<body>
<![CDATA[
return this.mCurrentBrowser.goForward();
]]>
</body>
</method>
<method name="reload">
<body>
<![CDATA[
return this.mCurrentBrowser.reload();
]]>
</body>
</method>
<method name="reloadWithFlags">
<parameter name="aFlags"/>
<body>
<![CDATA[
return this.mCurrentBrowser.reloadWithFlags(aFlags);
]]>
</body>
</method>
<method name="stop">
<body>
<![CDATA[
return this.mCurrentBrowser.stop();
]]>
</body>
</method>
<!-- throws exception for unknown schemes -->
<method name="loadURI">
<parameter name="aURI"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<body>
<![CDATA[
return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
]]>
</body>
</method>
<!-- throws exception for unknown schemes -->
<method name="loadURIWithFlags">
<parameter name="aURI"/>
<parameter name="aFlags"/>
<parameter name="aReferrerURI"/>
<parameter name="aCharset"/>
<parameter name="aPostData"/>
<body>
<![CDATA[
// Note - the callee understands both:
// (a) loadURIWithFlags(aURI, aFlags, ...)
// (b) loadURIWithFlags(aURI, { flags: aFlags, ... })
// Forwarding it as (a) here actually supports both (a) and (b),
// so you can call us either way too.
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>
<property name="currentURI"
onget="return this.mCurrentBrowser.currentURI;"
readonly="true"/>
<field name="finder"><![CDATA[
({
mTabBrowser: this,
mListeners: new Set(),
get finder() {
return this.mTabBrowser.mCurrentBrowser.finder;
},
addResultListener: function(aListener) {
this.mListeners.add(aListener);
this.finder.addResultListener(aListener);
},
removeResultListener: function(aListener) {
this.mListeners.delete(aListener);
this.finder.removeResultListener(aListener);
},
get searchString() {
return this.finder.searchString;
},
get clipboardSearchString() {
return this.finder.clipboardSearchString;
},
set clipboardSearchString(val) {
return this.finder.clipboardSearchString = val;
},
set caseSensitive(val) {
return this.finder.caseSensitive = val;
},
set entireWord(val) {
return this.finder.entireWord = val;
},
get highlighter() {
return this.finder.highlighter;
},
get matchesCountLimit() {
return this.finder.matchesCountLimit;
},
fastFind: function(aSearchString, aLinksOnly, aDrawOutline) {
this.finder.fastFind(aSearchString, aLinksOnly, aDrawOutline);
},
findAgain: function(aFindBackwards, aLinksOnly, aDrawOutline) {
this.finder.findAgain(aFindBackwards, aLinksOnly, aDrawOutline);
},
setSearchStringToSelection: function() {
return this.finder.setSearchStringToSelection();
},
highlight: function(...args) {
this.finder.highlight(...args);
},
getInitialSelection: function() {
this.finder.getInitialSelection();
},
getActiveSelectionText: function() {
return this.finder.getActiveSelectionText();
},
enableSelection: function() {
this.finder.enableSelection();
},
removeSelection: function() {
this.finder.removeSelection();
},
focusContent: function() {
this.finder.focusContent();
},
onFindbarClose: function() {
this.finder.onFindbarClose();
},
onFindbarOpen: function() {
this.finder.onFindbarOpen();
},
onModalHighlightChange: function(...args) {
return this.finder.onModalHighlightChange(...args);
},
onHighlightAllChange: function(...args) {
return this.finder.onHighlightAllChange(...args);
},
keyPress: function(aEvent) {
this.finder.keyPress(aEvent);
},
requestMatchesCount: function(...args) {
this.finder.requestMatchesCount(...args);
}
})
]]></field>
<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="contentWindowAsCPOW"
readonly="true"
onget="return this.mCurrentBrowser.contentWindowAsCPOW"/>
<property name="sessionHistory"
onget="return this.mCurrentBrowser.sessionHistory;"
readonly="true"/>
<property name="markupDocumentViewer"
onget="return this.mCurrentBrowser.markupDocumentViewer;"
readonly="true"/>
<property name="contentDocument"
onget="return this.mCurrentBrowser.contentDocument;"
readonly="true"/>
<property name="contentTitle"
onget="return this.mCurrentBrowser.contentTitle;"
readonly="true"/>
<property name="securityUI"
onget="return this.mCurrentBrowser.securityUI;"
readonly="true"/>
<property name="userTypedValue"
onget="return this.mCurrentBrowser.userTypedValue;"
onset="return this.mCurrentBrowser.userTypedValue = val;"/>
<property name="droppedLinkHandler"
onget="return this.mCurrentBrowser.droppedLinkHandler;"
onset="return this.mCurrentBrowser.droppedLinkHandler = val;"/>
<property name="showWindowResizer"
onget="return this.mCurrentBrowser.showWindowResizer;"
onset="return this.mCurrentBrowser.showWindowResizer = val;"/>
<property name="docShellIsActive"
onget="return this.mCurrentBrowser.docShellIsActive;"
onset="return this.mCurrentBrowser.docShellIsActive = val;"/>
<property name="fullZoom"
onget="return this.mCurrentBrowser.fullZoom;"
onset="return this.mCurrentBrowser.fullZoom = val;"/>
<property name="textZoom"
onget="return this.mCurrentBrowser.textZoom;"
onset="return this.mCurrentBrowser.textZoom = val;"/>
<property name="isSyntheticDocument"
onget="return this.mCurrentBrowser.isSyntheticDocument;"
readonly="true"/>
<property name="messageManager"
onget="return window.messageManager;"
readonly="true"/>
<method name="observe">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aData"/>
<body>
<![CDATA[
var maxUndoDepth = 0;
switch (aTopic) {
case "browser:purge-session-history":
break;
case "nsPref:changed":
if (aData == "browser.tabs.max_tabs_undo") {
maxUndoDepth = Math.max(0, Services.prefs.getIntPref(aData));
break;
}
default:
return;
}
// Wipe out savedBrowsers since history is gone
while (this.savedBrowsers.length > maxUndoDepth) {
var tabData = this.savedBrowsers.pop();
var deadBrowser = tabData.browserData.browser;
delete tabData.browserData;
this.removeBrowser(deadBrowser);
}
]]>
</body>
</method>
<field name="_fastFind">null</field>
<property name="fastFind" readonly="true">
<getter>
<![CDATA[
if (!this._fastFind) {
this._fastFind = Cc["@mozilla.org/typeaheadfind;1"]
.createInstance(Ci.nsITypeAheadFind);
this._fastFind.init(this.docShell);
}
return this._fastFind;
]]>
</getter>
</property>
<field name="_lastSearchString">null</field>
<field name="_lastSearchHighlight">false</field>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
switch (aEvent.type) {
case "keypress":
this._handleKeyEvent(aEvent);
break;
}
]]></body>
</method>
<constructor>
<![CDATA[
document.addEventListener("keypress", this);
this.arrowKeysShouldWrap = AppConstants.platform == "macosx";
// Bail out early if we are in tabmail. See Bug 521803.
if (!this.mPanelContainer)
return;
this.mCurrentBrowser = this.mPanelContainer.firstChild.firstChild.firstChild;
this.mCurrentTab = this.tabContainer.firstChild;
var uniqueId = "panel" + this.nextTabNumber++;
this.mPanelContainer.childNodes[0].id = uniqueId;
this.tabs[0].linkedPanel = uniqueId;
this.tabs[0].linkedBrowser = this.mCurrentBrowser;
// Ensure the browser's session history and security UI are wired up
// note that toolkit browser automatically inits its security UI
// when you get it but for xpfe you need to init it explicitly
if (!this.mCurrentBrowser.securityUI)
this.mCurrentBrowser.init();
// Wire up the tab's progress listener and filter.
var tabListener = this.mTabProgressListener(this.mCurrentTab,
this.mCurrentBrowser,
false);
var filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
.createInstance(Ci.nsIWebProgress);
filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
this.webProgress.addProgressListener(filter,
Ci.nsIWebProgress.NOTIFY_ALL);
this.mTabListeners[0] = tabListener;
this.mTabFilters[0] = filter;
if (!Services.prefs.getBoolPref("browser.tabs.autoHide") &&
!Services.prefs.getBoolPref("browser.tabs.forceHide") &&
window.toolbar.visible)
this.mStrip.collapsed = false;
var t = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "tab");
t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
t.setAttribute("crop", "end");
t.className = "tabbrowser-tab";
t.style.maxWidth = Services.prefs.getIntPref("browser.tabs.tabMaxWidth") + "px";
t.style.minWidth = Services.prefs.getIntPref("browser.tabs.tabMinWidth") + "px";
t.width = 0;
t.flex = 100;
t.setAttribute("validate", "never");
t.setAttribute("onerror", "this.parentNode.parentNode.parentNode.parentNode.addToMissedIconCache(this.getAttribute('image')); this.removeAttribute('image');");
this.referenceTab = t;
Services.obs.addObserver(this, "browser:purge-session-history");
Services.prefs.addObserver("browser.tabs.max_tabs_undo", this);
var onclick = this.getAttribute("oncontentclick");
if (onclick)
this.onContentClick = new Function("event", onclick);
]]>
</constructor>
<destructor>
<![CDATA[
document.removeEventListener("keypress", this);
// Bail out early if we are in tabmail. See Bug 521803.
if (!this.mPanelContainer)
return;
for (var i = 0; i < this.mTabListeners.length; ++i) {
this.browsers[i].webProgress.removeProgressListener(this.mTabFilters[i]);
this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
this.mTabFilters[i] = null;
this.mTabListeners[i] = null;
}
Services.obs.removeObserver(this, "browser:purge-session-history");
Services.prefs.removeObserver("browser.tabs.max_tabs_undo", this);
this.savedBrowsers.forEach(function(aTabData) {
delete aTabData.browserData;
});
]]>
</destructor>
<!-- Deprecated stuff, implemented for backwards compatibility. -->
<property name="mTabContainer" readonly="true"
onget="return this.tabContainer;"/>
<property name="mTabs" readonly="true"
onget="return this.tabs;"/>
</implementation>
<handlers>
<handler event="select" action="if (event.originalTarget == this.mPanelContainer) this.updateCurrentBrowser();"/>
<handler event="DOMLinkAdded" phase="capturing" action="this.onLinkEvent(event);"/>
<handler event="DOMLinkChanged" phase="capturing" action="this.onLinkEvent(event);"/>
<handler event="DOMWindowClose" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
if (this.tabs.length == 1)
return;
this.removeTab(this._getTabForContentWindow(event.target));
event.preventDefault();
]]>
</handler>
<handler event="DOMWebNotificationClicked" phase="capturing">
<![CDATA[
if (!event.isTrusted)
return;
// The user clicked a desktop notification; make sure its
// tab is brought to the front and then raise the window.
this.selectedTab = this._getTabForContentWindow(event.target.top);
window.focus();
]]>
</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);
if (!tab)
return;
this.setTabTitle(tab);
if (tab == this.mCurrentTab)
this.updateTitlebar();
]]>
</handler>
<handler event="click" phase="capturing" group="system">
<![CDATA[
if (this.onContentClick)
this.onContentClick(event);
]]>
</handler>
</handlers>
</binding>
<binding id="tabbrowser-arrowscrollbox"
<implementation>
<!-- Override scrollbox.xml method, since our scrollbox's children are
inherited from the binding parent -->
<method name="_getScrollableElements">
<body>
<![CDATA[
return Array.from(document.getBindingParent(this).childNodes)
.filter(this._canScrollToElement,
this);
]]>
</body>
</method>
<method name="_canScrollToElement">
<parameter name="aTab"/>
<body>
<![CDATA[
return !aTab.pinned && !aTab.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");
]]>
</handler>
<handler event="overflow">
<![CDATA[
if (event.detail == 0)
return; // Ignore vertical events
var tabs = document.getBindingParent(this);
tabs.setAttribute("overflow", true);
tabs._handleTabSelect(false);
]]>
</handler>
</handlers>
</binding>
<binding id="tabbrowser-tabs"
<content>
<xul:stack flex="1" class="tabs-stack">
<xul:vbox>
<xul:spacer flex="1"/>
<xul:hbox class="tabs-bottom" align="center"/>
</xul:vbox>
<xul:vbox>
<xul:hbox>
<xul:stack>
<xul:spacer class="tabs-left"/>
<xul:toolbarbutton class="tabs-newbutton" context=""
anonid="tabstrip-newbutton"
xbl:inherits="oncommand=onnewtab,onclick=onnewtabclick,tooltiptext=tooltiptextnew"/>
</xul:stack>
<xul:arrowscrollbox anonid="arrowscrollbox"
class="tabbrowser-arrowscrollbox"
flex="1"
xbl:inherits="smoothscroll"
orient="horizontal"
style="min-width: 1px;">
<children includes="tab"/>
<xul:spacer class="tabs-right" flex="1"/>
</xul:arrowscrollbox>
<children/>
<xul:stack>
<xul:spacer class="tabs-right"/>
<xul:hbox class="tabs-closebutton-box" align="stretch" pack="end">
<xul:toolbarbutton class="tabs-alltabs-button" context=""
anonid="alltabs-button"
type="menu"
xbl:inherits="tooltiptext=tooltiptextalltabs">
<xul:menupopup class="tabs-alltabs-popup"
anonid="alltabs-popup"
position="after_end"/>
</xul:toolbarbutton>
<xul:hbox align="center">
<xul:toolbarbutton class="tabs-closebutton close-button" context=""
anonid="tabstrip-closebutton"
xbl:inherits="disabled=disableclose,oncommand=onclosetab,tooltiptext=tooltiptextclose"/>
</xul:hbox>
</xul:hbox>
</xul:stack>
</xul:hbox>
<xul:spacer class="tabs-bottom-spacer"/>
</xul:vbox>
</xul:stack>
</content>
<implementation>
<constructor>
<![CDATA[
var tab = this.firstChild;
// set the tabstrip's minWidth too, otherwise it immediately overflows
this.arrowScrollbox.style.minWidth =
tab.style.minWidth = Services.prefs.getIntPref("browser.tabs.tabMinWidth") + "px";
tab.style.maxWidth = Services.prefs.getIntPref("browser.tabs.tabMaxWidth") + "px";
window.addEventListener("resize", this);
]]>
</constructor>
<destructor>
<![CDATA[
window.removeEventListener("resize", this);
]]>
</destructor>
<field name="arrowScrollboxWidth">0</field>
<field name="arrowScrollbox">
document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
</field>
<method name="_handleTabSelect">
<parameter name="aSmoothScroll"/>
<body>
<![CDATA[
if (this.getAttribute("overflow") == "true")
this.arrowScrollbox.ensureElementIsVisible(this.selectedItem,
aSmoothScroll);
]]>
</body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body>
<![CDATA[
switch (aEvent.type)
{
case "resize":
if (aEvent.target != window)
break;
var width = this.arrowScrollbox.boxObject.width;
if (width != this.arrowScrollboxWidth)
{
this._handleTabSelect(false);
this.arrowScrollboxWidth = width;
}
break;
}
]]>
</body>
</method>
<field name="mAllTabsPopup">
document.getAnonymousElementByAttribute(this, "anonid", "alltabs-popup");
</field>
<field name="_animateElement">
this.arrowScrollbox._scrollButtonDown;
</field>
<method name="_notifyBackgroundTab">
<parameter name="aTab"/>
<body>
<![CDATA[
var scrollRect = this.arrowScrollbox.scrollClientRect;
var tab = aTab.getBoundingClientRect();
// Is the new tab already completely visible?
if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
return;
if (this.arrowScrollbox.smoothScroll) {
let selected = 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.arrowScrollbox.ensureElementIsVisible(aTab);
return;
}
this.arrowScrollbox.scrollByPixels(this.arrowScrollbox._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="_handleNewTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (aTab.parentNode != this)
return;
if (aTab.getAttribute("selected") == "true") {
this._handleTabSelect();
} else {
this._notifyBackgroundTab(aTab);
}
/* 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.arrowScrollbox._updateScrollButtonsDisabledState();
]]>
</body>
</method>
<method name="_handleMouseScroll">
<parameter name="aEvent"/>
<body>
<![CDATA[
// Javascript does not have a logical XOR operator.
if (aEvent.shiftKey != Services.prefs.getBoolPref("browser.tabs.mouseScrollAdvancesTab")) {
this.advanceSelectedTab(aEvent.detail < 0 ? -1 : 1);
aEvent.stopPropagation();
}
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="TabSelect" action="this._handleTabSelect();"/>
<handler event="transitionend">
<![CDATA[
if (event.propertyName == "max-width")
this._handleNewTab(event.target);
]]>
</handler>
<handler event="DOMMouseScroll" phase="capturing">
<![CDATA[
this._handleMouseScroll(event);
]]>
</handler>
</handlers>
</binding>
<binding id="tabbrowser-alltabs-popup"
<implementation>
<method name="_tabOnTabClose">
<parameter name="aEvent"/>
<body>
<![CDATA[
let menuItem = aEvent.target.mCorrespondingMenuitem;
if (menuItem)
menuItem.remove();
]]>
</body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body>
<![CDATA[
switch (aEvent.type)
{
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[
let tabContainer = document.getBindingParent(this);
let tabstripBO = tabContainer.arrowScrollbox.scrollBoxObject;
for (let i = 0; i < this.childNodes.length; i++)
{
let curTabBO = this.childNodes[i].tab.boxObject;
if (curTabBO.screenX >= tabstripBO.screenX &&
curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
this.childNodes[i].removeAttribute("tabIsScrolled");
else
this.childNodes[i].setAttribute("tabIsScrolled", "true");
}
]]>
</body>
</method>
<method name="_createTabMenuItem">
<parameter name="aTabNode"/>
<body>
<![CDATA[
let menuItem = document.createElementNS(
"menuitem");
menuItem.setAttribute("class", "menuitem-iconic alltabs-item icon-holder");
menuItem.setAttribute("label", aTabNode.label);
menuItem.setAttribute("crop", aTabNode.getAttribute("crop"));
["busy", "image", "selected"].forEach(
function(attribute)
{
if (aTabNode.hasAttribute(attribute))
{
menuItem.setAttribute(attribute, aTabNode.getAttribute(attribute));
}
}
);
// Keep some attributes of the menuitem in sync with its
// corresponding tab (e.g. the tab label)
aTabNode.mCorrespondingMenuitem = menuItem;
document.addBroadcastListenerFor(aTabNode, menuItem, "label");
document.addBroadcastListenerFor(aTabNode, menuItem, "crop");
document.addBroadcastListenerFor(aTabNode, menuItem, "image");
document.addBroadcastListenerFor(aTabNode, menuItem, "busy");
document.addBroadcastListenerFor(aTabNode, menuItem, "selected");
aTabNode.addEventListener("TabClose", this);
menuItem.tab = aTabNode;
this.appendChild(menuItem);
return menuItem;
]]>
</body>
</method>
</implementation>
<handlers>
<handler event="popupshowing">
<![CDATA[
// set up the menu popup
let tabcontainer = document.getBindingParent(this);
let tabs = tabcontainer.childNodes;
// Listen for changes in the tab bar.
let tabbrowser = document.getBindingParent(tabcontainer);
tabbrowser.addEventListener("TabOpen", this);
tabcontainer.arrowScrollbox.addEventListener("scroll", this);
for (let i = 0; i < tabs.length; i++)
this._createTabMenuItem(tabs[i]);
this._updateTabsVisibilityStatus();
]]>
</handler>
<handler event="popuphiding">
<![CDATA[
// clear out the menu popup and remove the listeners
while (this.hasChildNodes())
{
let menuItem = this.lastChild;
document.removeBroadcastListenerFor(menuItem.tab, menuItem, "label");
document.removeBroadcastListenerFor(menuItem.tab, menuItem, "crop");
document.removeBroadcastListenerFor(menuItem.tab, menuItem, "image");
document.removeBroadcastListenerFor(menuItem.tab, menuItem, "busy");
document.removeBroadcastListenerFor(menuItem.tab, menuItem, "selected");
menuItem.tab.removeEventListener("TabClose", this);
menuItem.tab.mCorrespondingMenuitem = null;
menuItem.tab = null;
menuItem.remove();
}
let tabcontainer = document.getBindingParent(this);
tabcontainer.arrowScrollbox.removeEventListener("scroll", this);
document.getBindingParent(tabcontainer).removeEventListener("TabOpen", this);
]]>
</handler>
<handler event="command">
<![CDATA[
let tabcontainer = document.getBindingParent(this);
let tab = event.target.tab;
if (tabcontainer.selectedItem == tab)
tabcontainer._handleTabSelect();
else
tabcontainer.selectedItem = tab;
]]>
</handler>
<handler event="DOMMenuItemActive">
<![CDATA[
var tab = event.target.tab;
if (tab) {
let overLink = tab.linkedBrowser.currentURI.displaySpec;
if (overLink == "about:blank")
overLink = "";
XULBrowserWindow.setOverLink(overLink, null);
}
]]></handler>
<handler event="DOMMenuItemInactive">
<![CDATA[
XULBrowserWindow.setOverLink("", null);
]]></handler>
</handlers>
</binding>
</bindings>