summaryrefslogtreecommitdiffstats
path: root/comm/suite/browser/tabbrowser.xml
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/suite/browser/tabbrowser.xml3707
1 files changed, 3707 insertions, 0 deletions
diff --git a/comm/suite/browser/tabbrowser.xml b/comm/suite/browser/tabbrowser.xml
new file mode 100644
index 0000000000..390fbaa0cf
--- /dev/null
+++ b/comm/suite/browser/tabbrowser.xml
@@ -0,0 +1,3707 @@
+<?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"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tabbrowser">
+ <resources>
+ <stylesheet src="chrome://navigator/skin/tabbrowser.css"/>
+ </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="nsIDOMEventListener, 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).spec;
+ } catch (e) {
+ aURI = aURI.spec;
+ }
+
+ 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.
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
+ // (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);
+
+ var b = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "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(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "stack");
+ stack.setAttribute("anonid", "browserStack");
+ stack.appendChild(b);
+ stack.setAttribute("flex", "1");
+
+ // Add the Message and the Browser to the box
+ var n = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "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
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ 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"
+ extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <implementation>
+ <!-- Override scrollbox.xml method, since our scrollbox's children are
+ inherited from the binding parent -->
+ <method name="_getScrollableElements">
+ <body>
+ <![CDATA[
+ return Array.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"
+ extends="chrome://global/content/bindings/tabbox.xml#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 implements="nsIDOMEventListener">
+ <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"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <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(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "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.spec;
+ if (overLink == "about:blank")
+ overLink = "";
+ XULBrowserWindow.setOverLink(overLink, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive">
+ <![CDATA[
+ XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ </handlers>
+ </binding>
+</bindings>