summaryrefslogtreecommitdiffstats
path: root/application/basilisk/base/content/tabbrowser.xml
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-02-02 05:06:10 -0500
committerMatt A. Tobin <email@mattatobin.com>2018-02-02 05:06:10 -0500
commit6d614170cbfa958564eb5f824234ad5a9e484344 (patch)
tree3e1eb384382f30987cb2e64bd654afa8b74fd06b /application/basilisk/base/content/tabbrowser.xml
parent2a6b605d64b19411a300efdbbd7f78c349f90206 (diff)
downloadUXP-6d614170cbfa958564eb5f824234ad5a9e484344.tar
UXP-6d614170cbfa958564eb5f824234ad5a9e484344.tar.gz
UXP-6d614170cbfa958564eb5f824234ad5a9e484344.tar.lz
UXP-6d614170cbfa958564eb5f824234ad5a9e484344.tar.xz
UXP-6d614170cbfa958564eb5f824234ad5a9e484344.zip
Revert "Add Basilisk"
This reverts commit e72ef92b5bdc43cd2584198e2e54e951b70299e8.
Diffstat (limited to 'application/basilisk/base/content/tabbrowser.xml')
-rw-r--r--application/basilisk/base/content/tabbrowser.xml7631
1 files changed, 0 insertions, 7631 deletions
diff --git a/application/basilisk/base/content/tabbrowser.xml b/application/basilisk/base/content/tabbrowser.xml
deleted file mode 100644
index 695e14c16..000000000
--- a/application/basilisk/base/content/tabbrowser.xml
+++ /dev/null
@@ -1,7631 +0,0 @@
-<?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/. -->
-
-<bindings id="tabBrowserBindings"
- xmlns="http://www.mozilla.org/xbl"
- xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- xmlns:xbl="http://www.mozilla.org/xbl">
-
- <binding id="tabbrowser">
- <resources>
- <stylesheet src="chrome://browser/content/tabbrowser.css"/>
- </resources>
-
- <content>
- <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
- <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
- flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
- onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
- <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
- <xul:notificationbox flex="1" notificationside="top">
- <xul:hbox flex="1" class="browserSidebarContainer">
- <xul:vbox flex="1" class="browserContainer">
- <xul:stack flex="1" class="browserStack" anonid="browserStack">
- <xul:browser anonid="initialBrowser" type="content" message="true" messagemanagergroup="browsers"
- primary="true"
- xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectmenulist,datetimepicker"/>
- </xul:stack>
- </xul:vbox>
- </xul:hbox>
- </xul:notificationbox>
- </xul:tabpanels>
- </xul:tabbox>
- <children/>
- </content>
- <implementation implements="nsIDOMEventListener, nsIMessageListener, nsIObserver">
-
- <property name="tabContextMenu" readonly="true"
- onget="return this.tabContainer.contextMenu;"/>
-
- <field name="tabContainer" readonly="true">
- document.getElementById(this.getAttribute("tabcontainer"));
- </field>
- <field name="tabs" readonly="true">
- this.tabContainer.childNodes;
- </field>
-
- <property name="visibleTabs" readonly="true">
- <getter><![CDATA[
- if (!this._visibleTabs)
- this._visibleTabs = Array.filter(this.tabs,
- tab => !tab.hidden && !tab.closing);
- return this._visibleTabs;
- ]]></getter>
- </property>
-
- <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
-
- <field name="_visibleTabs">null</field>
-
- <field name="mURIFixup" readonly="true">
- Components.classes["@mozilla.org/docshell/urifixup;1"]
- .getService(Components.interfaces.nsIURIFixup);
- </field>
- <field name="_unifiedComplete" readonly="true">
- Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
- .getService(Components.interfaces.mozIPlacesAutoComplete);
- </field>
- <field name="mTabBox" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
- </field>
- <field name="mPanelContainer" readonly="true">
- document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
- </field>
- <field name="mStringBundle">
- document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
- </field>
- <field name="mCurrentTab">
- null
- </field>
- <field name="_lastRelatedTab">
- null
- </field>
- <field name="mCurrentBrowser">
- null
- </field>
- <field name="mProgressListeners">
- []
- </field>
- <field name="mTabsProgressListeners">
- []
- </field>
- <field name="_tabListeners">
- new Map()
- </field>
- <field name="_tabFilters">
- new Map()
- </field>
- <field name="mIsBusy">
- false
- </field>
- <field name="_outerWindowIDBrowserMap">
- new Map();
- </field>
- <field name="arrowKeysShouldWrap" readonly="true">
-#ifdef XP_MACOSX
- true
-#else
- false
-#endif
- </field>
-
- <field name="_autoScrollPopup">
- null
- </field>
-
- <field name="_previewMode">
- false
- </field>
-
- <field name="_lastFindValue">
- ""
- </field>
-
- <field name="_contentWaitingCount">
- 0
- </field>
-
- <property name="_numPinnedTabs" readonly="true">
- <getter><![CDATA[
- for (var i = 0; i < this.tabs.length; i++) {
- if (!this.tabs[i].pinned)
- break;
- }
- return i;
- ]]></getter>
- </property>
-
- <property name="popupAnchor" readonly="true">
- <getter><![CDATA[
- if (this.mCurrentTab._popupAnchor) {
- return this.mCurrentTab._popupAnchor;
- }
- 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="isFindBarInitialized">
- <parameter name="aTab"/>
- <body><![CDATA[
- return (aTab || this.selectedTab)._findBar != undefined;
- ]]></body>
- </method>
-
- <method name="getFindBar">
- <parameter name="aTab"/>
- <body><![CDATA[
- if (!aTab)
- aTab = this.selectedTab;
-
- if (aTab._findBar)
- return aTab._findBar;
-
- let findBar = document.createElementNS(this.namespaceURI, "findbar");
- let browser = this.getBrowserForTab(aTab);
- let browserContainer = this.getBrowserContainer(browser);
- browserContainer.appendChild(findBar);
-
- // Force a style flush to ensure that our binding is attached.
- findBar.clientTop;
-
- findBar.browser = browser;
- findBar._findField.value = this._lastFindValue;
-
- aTab._findBar = findBar;
-
- let event = document.createEvent("Events");
- event.initEvent("TabFindInitialized", true, false);
- aTab.dispatchEvent(event);
-
- return findBar;
- ]]></body>
- </method>
-
- <method name="getStatusPanel">
- <body><![CDATA[
- if (!this._statusPanel) {
- this._statusPanel = document.createElementNS(this.namespaceURI, "statuspanel");
- this._statusPanel.setAttribute("inactive", "true");
- this._statusPanel.setAttribute("layer", "true");
- this._appendStatusPanel();
- }
- return this._statusPanel;
- ]]></body>
- </method>
-
- <method name="_appendStatusPanel">
- <body><![CDATA[
- if (this._statusPanel) {
- let browser = this.selectedBrowser;
- let browserContainer = this.getBrowserContainer(browser);
- browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
- }
- ]]></body>
- </method>
-
- <method name="_setCloseKeyState">
- <parameter name="aEnabled"/>
- <body><![CDATA[
- let keyClose = document.getElementById("key_close");
- let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
- if (closeKeyEnabled == aEnabled)
- return;
-
- if (aEnabled)
- keyClose.removeAttribute("disabled");
- else
- keyClose.setAttribute("disabled", "true");
-
- // We also want to remove the keyboard shortcut from the file menu
- // when the shortcut is disabled, and bring it back when it's
- // renabled.
- //
- // Fixing bug 630826 could make that happen automatically.
- // Fixing bug 630830 could avoid the ugly hack below.
-
- let closeMenuItem = document.getElementById("menu_close");
- let parentPopup = closeMenuItem.parentNode;
- let nextItem = closeMenuItem.nextSibling;
- let clonedItem = closeMenuItem.cloneNode(true);
-
- parentPopup.removeChild(closeMenuItem);
-
- if (aEnabled)
- clonedItem.setAttribute("key", "key_close");
- else
- clonedItem.removeAttribute("key");
-
- parentPopup.insertBefore(clonedItem, nextItem);
- ]]></body>
- </method>
-
- <method name="pinTab">
- <parameter name="aTab"/>
- <body><![CDATA[
- if (aTab.pinned)
- return;
-
- if (aTab.hidden)
- this.showTab(aTab);
-
- this.moveTabTo(aTab, this._numPinnedTabs);
- aTab.setAttribute("pinned", "true");
- this.tabContainer._unlockTabSizing();
- this.tabContainer._positionPinnedTabs();
- this.tabContainer.adjustTabstrip();
-
- this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: true })
-
- if (aTab.selected)
- this._setCloseKeyState(false);
-
- let event = document.createEvent("Events");
- event.initEvent("TabPinned", true, false);
- aTab.dispatchEvent(event);
- ]]></body>
- </method>
-
- <method name="unpinTab">
- <parameter name="aTab"/>
- <body><![CDATA[
- if (!aTab.pinned)
- return;
-
- this.moveTabTo(aTab, this._numPinnedTabs - 1);
- aTab.removeAttribute("pinned");
- aTab.style.marginInlineStart = "";
- this.tabContainer._unlockTabSizing();
- this.tabContainer._positionPinnedTabs();
- this.tabContainer.adjustTabstrip();
-
- this.getBrowserForTab(aTab).messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: false })
-
- if (aTab.selected)
- this._setCloseKeyState(true);
-
- let event = document.createEvent("Events");
- event.initEvent("TabUnpinned", true, false);
- aTab.dispatchEvent(event);
- ]]></body>
- </method>
-
- <method name="previewTab">
- <parameter name="aTab"/>
- <parameter name="aCallback"/>
- <body>
- <![CDATA[
- let currentTab = this.selectedTab;
- try {
- // Suppress focus, ownership and selected tab changes
- this._previewMode = true;
- this.selectedTab = aTab;
- aCallback();
- } finally {
- this.selectedTab = currentTab;
- this._previewMode = false;
- }
- ]]>
- </body>
- </method>
-
- <method name="getBrowserAtIndex">
- <parameter name="aIndex"/>
- <body>
- <![CDATA[
- return this.browsers[aIndex];
- ]]>
- </body>
- </method>
-
- <method name="getBrowserIndexForDocument">
- <parameter name="aDocument"/>
- <body>
- <![CDATA[
- var tab = this._getTabForContentWindow(aDocument.defaultView);
- return tab ? tab._tPos : -1;
- ]]>
- </body>
- </method>
-
- <method name="getBrowserForDocument">
- <parameter name="aDocument"/>
- <body>
- <![CDATA[
- var tab = this._getTabForContentWindow(aDocument.defaultView);
- return tab ? tab.linkedBrowser : null;
- ]]>
- </body>
- </method>
-
- <method name="getBrowserForContentWindow">
- <parameter name="aWindow"/>
- <body>
- <![CDATA[
- var tab = this._getTabForContentWindow(aWindow);
- return tab ? tab.linkedBrowser : null;
- ]]>
- </body>
- </method>
-
- <method name="getBrowserForOuterWindowID">
- <parameter name="aID"/>
- <body>
- <![CDATA[
- return this._outerWindowIDBrowserMap.get(aID);
- ]]>
- </body>
- </method>
-
- <method name="_getTabForContentWindow">
- <parameter name="aWindow"/>
- <body>
- <![CDATA[
- // When not using remote browsers, we can take a fast path by getting
- // directly from the content window to the browser without looping
- // over all browsers.
- if (!gMultiProcessBrowser) {
- let browser = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell)
- .chromeEventHandler;
- return this.getTabForBrowser(browser);
- }
-
- for (let i = 0; i < this.browsers.length; i++) {
- // NB: We use contentWindowAsCPOW so that this code works both
- // for remote browsers as well. aWindow may be a CPOW.
- if (this.browsers[i].contentWindowAsCPOW == aWindow)
- return this.tabs[i];
- }
- return null;
- ]]>
- </body>
- </method>
-
- <!-- Binding from browser to tab -->
- <field name="_tabForBrowser" readonly="true">
- <![CDATA[
- new WeakMap();
- ]]>
- </field>
-
- <method name="_getTabForBrowser">
- <parameter name="aBrowser" />
- <body>
- <![CDATA[
- let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
- let text = "_getTabForBrowser` is now deprecated, please use `getTabForBrowser";
- let url = "https://developer.mozilla.org/docs/Mozilla/Tech/XUL/Method/getTabForBrowser";
- Deprecated.warning(text, url);
- return this.getTabForBrowser(aBrowser);
- ]]>
- </body>
- </method>
-
- <method name="getTabForBrowser">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- return this._tabForBrowser.get(aBrowser);
- ]]>
- </body>
- </method>
-
- <method name="getNotificationBox">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- return this.getSidebarContainer(aBrowser).parentNode;
- ]]>
- </body>
- </method>
-
- <method name="getSidebarContainer">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- return this.getBrowserContainer(aBrowser).parentNode;
- ]]>
- </body>
- </method>
-
- <method name="getBrowserContainer">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
- ]]>
- </body>
- </method>
-
- <method name="getTabModalPromptBox">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- let browser = (aBrowser || this.mCurrentBrowser);
- if (!browser.tabModalPromptBox) {
- browser.tabModalPromptBox = new TabModalPromptBox(browser);
- }
- return browser.tabModalPromptBox;
- ]]>
- </body>
- </method>
-
- <method name="getTabFromAudioEvent">
- <parameter name="aEvent"/>
- <body>
- <![CDATA[
- if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
- !aEvent.isTrusted) {
- return null;
- }
-
- var browser = aEvent.originalTarget;
- var tab = this.getTabForBrowser(browser);
- return tab;
- ]]>
- </body>
- </method>
-
- <method name="_callProgressListeners">
- <parameter name="aBrowser"/>
- <parameter name="aMethod"/>
- <parameter name="aArguments"/>
- <parameter name="aCallGlobalListeners"/>
- <parameter name="aCallTabsListeners"/>
- <body><![CDATA[
- var rv = true;
-
- function callListeners(listeners, args) {
- for (let p of listeners) {
- if (aMethod in p) {
- try {
- if (!p[aMethod].apply(p, args))
- rv = false;
- } catch (e) {
- // don't inhibit other listeners
- Components.utils.reportError(e);
- }
- }
- }
- }
-
- if (!aBrowser)
- aBrowser = this.mCurrentBrowser;
-
- if (aCallGlobalListeners != false &&
- aBrowser == this.mCurrentBrowser) {
- callListeners(this.mProgressListeners, aArguments);
- }
-
- if (aCallTabsListeners != false) {
- aArguments.unshift(aBrowser);
-
- callListeners(this.mTabsProgressListeners, aArguments);
- }
-
- return rv;
- ]]></body>
- </method>
-
- <!-- A web progress listener object definition for a given tab. -->
- <method name="mTabProgressListener">
- <parameter name="aTab"/>
- <parameter name="aBrowser"/>
- <parameter name="aStartsBlank"/>
- <parameter name="aWasPreloadedBrowser"/>
- <parameter name="aOrigStateFlags"/>
- <body>
- <![CDATA[
- let stateFlags = aOrigStateFlags || 0;
- // Initialize mStateFlags to non-zero e.g. when creating a progress
- // listener for preloaded browsers as there was no progress listener
- // around when the content started loading. If the content didn't
- // quite finish loading yet, mStateFlags will very soon be overridden
- // with the correct value and end up at STATE_STOP again.
- if (aWasPreloadedBrowser) {
- stateFlags = Ci.nsIWebProgressListener.STATE_STOP |
- Ci.nsIWebProgressListener.STATE_IS_REQUEST;
- }
-
- return ({
- mTabBrowser: this,
- mTab: aTab,
- mBrowser: aBrowser,
- mBlank: aStartsBlank,
-
- // cache flags for correct status UI update after tab switching
- mStateFlags: stateFlags,
- mStatus: 0,
- mMessage: "",
- mTotalProgress: 0,
-
- // count of open requests (should always be 0 or 1)
- mRequestCount: 0,
-
- destroy() {
- delete this.mTab;
- delete this.mBrowser;
- delete this.mTabBrowser;
- },
-
- _callProgressListeners() {
- Array.unshift(arguments, this.mBrowser);
- return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
- },
-
- _shouldShowProgress(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;
- },
-
- _isForInitialAboutBlank(aWebProgress, aLocation) {
- if (!this.mBlank || !aWebProgress.isTopLevel) {
- return false;
- }
-
- let location = aLocation ? aLocation.spec : "";
- return location == "about:blank";
- },
-
- onProgressChange(aWebProgress, aRequest,
- aCurSelfProgress, aMaxSelfProgress,
- aCurTotalProgress, aMaxTotalProgress) {
- this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
-
- if (!this._shouldShowProgress(aRequest))
- return;
-
- if (this.mTotalProgress)
- this.mTab.setAttribute("progress", "true");
-
- this._callProgressListeners("onProgressChange",
- [aWebProgress, aRequest,
- aCurSelfProgress, aMaxSelfProgress,
- aCurTotalProgress, aMaxTotalProgress]);
- },
-
- onProgressChange64(aWebProgress, aRequest,
- aCurSelfProgress, aMaxSelfProgress,
- aCurTotalProgress, aMaxTotalProgress) {
- return this.onProgressChange(aWebProgress, aRequest,
- aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
- aMaxTotalProgress);
- },
-
- onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
- if (!aRequest)
- return;
-
- const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
- const nsIChannel = Components.interfaces.nsIChannel;
- let location, originalLocation;
- try {
- aRequest.QueryInterface(nsIChannel)
- location = aRequest.URI;
- originalLocation = aRequest.originalURI;
- } catch (ex) {}
-
- let ignoreBlank = this._isForInitialAboutBlank(aWebProgress, location);
- // If we were ignoring some messages about the initial about:blank, and we
- // got the STATE_STOP for it, we'll want to pay attention to those messages
- // from here forward. Similarly, if we conclude that this state change
- // is one that we shouldn't be ignoring, then stop ignoring.
- if ((ignoreBlank &&
- aStateFlags & nsIWebProgressListener.STATE_STOP &&
- aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) ||
- !ignoreBlank && this.mBlank) {
- this.mBlank = false;
- }
-
- if (aStateFlags & nsIWebProgressListener.STATE_START) {
- this.mRequestCount++;
- } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
- const NS_ERROR_UNKNOWN_HOST = 2152398878;
- if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
- // to prevent bug 235825: wait for the request handled
- // by the automatic keyword resolver
- return;
- }
- // since we (try to) only handle STATE_STOP of the last request,
- // the count of open requests should now be 0
- this.mRequestCount = 0;
- }
-
- if (aStateFlags & nsIWebProgressListener.STATE_START &&
- aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
- if (aWebProgress.isTopLevel) {
- // Need to use originalLocation rather than location because things
- // like about:home and about:privatebrowsing arrive with nsIRequest
- // pointing to their resolved jar: or file: URIs.
- if (!(originalLocation && gInitialPages.includes(originalLocation.spec) &&
- originalLocation != "about:blank" &&
- this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec &&
- this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) {
- // Indicating that we started a load will allow the location
- // bar to be cleared when the load finishes.
- // In order to not overwrite user-typed content, we avoid it
- // (see if condition above) in a very specific case:
- // If the load is of an 'initial' page (e.g. about:privatebrowsing,
- // about:newtab, etc.), was not explicitly typed in the location
- // bar by the user, is not about:blank (because about:blank can be
- // loaded by websites under their principal), and the current
- // page in the browser is about:blank (indicating it is a newly
- // created or re-created browser, e.g. because it just switched
- // remoteness or is a new tab/window).
- this.mBrowser.urlbarChangeTracker.startedLoad();
- }
- delete this.mBrowser.initialPageLoadedFromURLBar;
- // If the browser is loading it must not be crashed anymore
- this.mTab.removeAttribute("crashed");
- }
-
- if (this._shouldShowProgress(aRequest)) {
- if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
- this.mTab.setAttribute("busy", "true");
-
- if (aWebProgress.isTopLevel &&
- !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
- this.mTabBrowser.setTabTitleLoading(this.mTab);
- }
-
- if (this.mTab.selected)
- this.mTabBrowser.mIsBusy = true;
- }
- } else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
- aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
-
- if (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);
- }
-
- // For keyword URIs clear the user typed value since they will be changed into real URIs
- if (location.scheme == "keyword")
- this.mBrowser.userTypedValue = null;
-
- if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
- this.mTabBrowser.setTabTitle(this.mTab);
-
- if (this.mTab.selected)
- this.mTabBrowser.mIsBusy = false;
- }
-
- if (ignoreBlank) {
- this._callProgressListeners("onUpdateCurrentBrowser",
- [aStateFlags, aStatus, "", 0],
- true, false);
- } else {
- this._callProgressListeners("onStateChange",
- [aWebProgress, aRequest, aStateFlags, aStatus],
- true, false);
- }
-
- this._callProgressListeners("onStateChange",
- [aWebProgress, aRequest, aStateFlags, aStatus],
- false);
-
- if (aStateFlags & (nsIWebProgressListener.STATE_START |
- nsIWebProgressListener.STATE_STOP)) {
- // reset cached temporary values at beginning and end
- this.mMessage = "";
- this.mTotalProgress = 0;
- }
- this.mStateFlags = aStateFlags;
- this.mStatus = aStatus;
- },
-
- onLocationChange(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;
- }
-
- // If the browser was playing audio, we should remove the playing state.
- if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
- clearTimeout(this.mTab._soundPlayingAttrRemovalTimer);
- this.mTab._soundPlayingAttrRemovalTimer = 0;
- this.mTab.removeAttribute("soundplaying");
- this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
- }
-
- // If the browser was previously muted, we should restore the muted state.
- if (this.mTab.hasAttribute("muted")) {
- this.mTab.linkedBrowser.mute();
- }
-
- if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
- let findBar = this.mTabBrowser.getFindBar(this.mTab);
-
- // Close the Find toolbar if we're in old-style TAF mode
- if (findBar.findMode != findBar.FIND_NORMAL) {
- findBar.close();
- }
- }
-
- // 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;
- }
-
- let unifiedComplete = this.mTabBrowser._unifiedComplete;
- let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
- if (this.mBrowser.registeredOpenURI) {
- unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI,
- userContextId);
- delete this.mBrowser.registeredOpenURI;
- }
- // Tabs in private windows aren't registered as "Open" so
- // that they don't appear as switch-to-tab candidates.
- if (!isBlankPageURL(aLocation.spec) &&
- (!PrivateBrowsingUtils.isWindowPrivate(window) ||
- PrivateBrowsingUtils.permanentPrivateBrowsing)) {
- unifiedComplete.registerOpenPage(aLocation, userContextId);
- this.mBrowser.registeredOpenURI = aLocation;
- }
- }
-
- if (!this.mBlank) {
- this._callProgressListeners("onLocationChange",
- [aWebProgress, aRequest, aLocation,
- aFlags]);
- }
-
- if (topLevel) {
- this.mBrowser.lastURI = aLocation;
- this.mBrowser.lastLocationChange = Date.now();
- }
- },
-
- onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
- if (this.mBlank)
- return;
-
- this._callProgressListeners("onStatusChange",
- [aWebProgress, aRequest, aStatus, aMessage]);
-
- this.mMessage = aMessage;
- },
-
- onSecurityChange(aWebProgress, aRequest, aState) {
- this._callProgressListeners("onSecurityChange",
- [aWebProgress, aRequest, aState]);
- },
-
- onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI) {
- return this._callProgressListeners("onRefreshAttempted",
- [aWebProgress, aURI, aDelay, aSameURI]);
- },
-
- QueryInterface(aIID) {
- if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
- aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
- aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
- aIID.equals(Components.interfaces.nsISupports))
- return this;
- throw Components.results.NS_NOINTERFACE;
- }
- });
- ]]>
- </body>
- </method>
-
- <field name="serializationHelper">
- Cc["@mozilla.org/network/serialization-helper;1"]
- .getService(Ci.nsISerializationHelper);
- </field>
-
- <field name="mIconLoadingPrincipal">
- null
- </field>
-
- <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;
- let loadingPrincipal = aLoadingPrincipal
- ? aLoadingPrincipal
- : Services.scriptSecurityManager.getSystemPrincipal();
-
- if (aURI) {
- if (!(aURI instanceof Ci.nsIURI)) {
- aURI = makeURI(aURI);
- }
- PlacesUIUtils.loadFavicon(browser, loadingPrincipal, aURI);
- }
-
- let sizedIconUrl = browser.mIconURL || "";
- if (sizedIconUrl != aTab.getAttribute("image")) {
- if (sizedIconUrl) {
- aTab.setAttribute("image", sizedIconUrl);
- if (!browser.mIconLoadingPrincipal ||
- !browser.mIconLoadingPrincipal.equals(loadingPrincipal)) {
- aTab.setAttribute("iconLoadingPrincipal",
- this.serializationHelper.serializeToString(loadingPrincipal));
- browser.mIconLoadingPrincipal = loadingPrincipal;
- }
- } else {
- aTab.removeAttribute("image");
- aTab.removeAttribute("iconLoadingPrincipal");
- delete browser.mIconLoadingPrincipal;
- }
- this._tabAttrModified(aTab, ["image"]);
- }
-
- this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
- ]]>
- </body>
- </method>
-
- <method name="getIcon">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
- return browser.mIconURL;
- ]]>
- </body>
- </method>
-
- <method name="shouldLoadFavIcon">
- <parameter name="aURI"/>
- <body>
- <![CDATA[
- return (aURI &&
- Services.prefs.getBoolPref("browser.chrome.site_icons") &&
- Services.prefs.getBoolPref("browser.chrome.favicons") &&
- ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
- ]]>
- </body>
- </method>
-
- <method name="useDefaultIcon">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- var browser = this.getBrowserForTab(aTab);
- var 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="getWindowTitleForBrowser">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- var newTitle = "";
- var docElement = this.ownerDocument.documentElement;
- var sep = docElement.getAttribute("titlemenuseparator");
-
- // Strip out any null bytes in the content title, since the
- // underlying widget implementations of nsWindow::SetTitle pass
- // null-terminated strings to system APIs.
- var docTitle = aBrowser.contentTitle.replace(/\0/g, "");
-
- if (!docTitle)
- docTitle = docElement.getAttribute("titledefault");
-
- var modifier = docElement.getAttribute("titlemodifier");
- if (docTitle) {
- newTitle += docElement.getAttribute("titlepreface");
- newTitle += docTitle;
- if (modifier)
- newTitle += sep;
- }
- newTitle += modifier;
-
- // If location bar is hidden and the URL type supports a host,
- // add the scheme and host to the title to prevent spoofing.
- // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
- try {
- if (docElement.getAttribute("chromehidden").includes("location")) {
- var uri = this.mURIFixup.createExposableURI(
- aBrowser.currentURI);
- if (uri.scheme == "about")
- newTitle = uri.spec + sep + newTitle;
- else
- newTitle = uri.prePath + sep + newTitle;
- }
- } catch (e) {}
-
- return newTitle;
- ]]>
- </body>
- </method>
-
- <method name="updateTitlebar">
- <body>
- <![CDATA[
- this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
- ]]>
- </body>
- </method>
-
- <!-- Holds a unique ID for the tab change that's currently being timed.
- Used to make sure that multiple, rapid tab switches do not try to
- create overlapping timers. -->
- <field name="_tabSwitchID">null</field>
-
- <method name="updateCurrentBrowser">
- <parameter name="aForceUpdate"/>
- <body>
- <![CDATA[
- var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
- if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
- return;
-
- if (!aForceUpdate) {
- TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
- if (!gMultiProcessBrowser) {
- // old way of measuring tab paint which is not valid with e10s.
- // Waiting until the next MozAfterPaint ensures that we capture
- // the time it takes to paint, upload the textures to the compositor,
- // and then composite.
- if (this._tabSwitchID) {
- TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_MS");
- }
-
- let tabSwitchID = Symbol();
-
- TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_MS");
- this._tabSwitchID = tabSwitchID;
-
- let onMozAfterPaint = () => {
- if (this._tabSwitchID === tabSwitchID) {
- TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_MS");
- this._tabSwitchID = null;
- }
- window.removeEventListener("MozAfterPaint", onMozAfterPaint);
- }
- window.addEventListener("MozAfterPaint", onMozAfterPaint);
- }
- }
-
- var oldTab = this.mCurrentTab;
-
- // Preview mode should not reset the owner
- if (!this._previewMode && !oldTab.selected)
- oldTab.owner = null;
-
- if (this._lastRelatedTab) {
- if (!this._lastRelatedTab.selected)
- this._lastRelatedTab.owner = null;
- this._lastRelatedTab = null;
- }
-
- var oldBrowser = this.mCurrentBrowser;
-
- if (!gMultiProcessBrowser) {
- oldBrowser.removeAttribute("primary");
- oldBrowser.docShellIsActive = false;
- newBrowser.setAttribute("primary", "true");
- newBrowser.docShellIsActive =
- (window.windowState != window.STATE_MINIMIZED);
- }
-
- var updateBlockedPopups = false;
- if ((oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
- (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
- updateBlockedPopups = true;
-
- this.mCurrentBrowser = newBrowser;
- this.mCurrentTab = this.tabContainer.selectedItem;
- this.showTab(this.mCurrentTab);
-
- var forwardButtonContainer = document.getElementById("urlbar-wrapper");
- if (forwardButtonContainer) {
- forwardButtonContainer.setAttribute("switchingtabs", "true");
- window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
- window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
- forwardButtonContainer.removeAttribute("switchingtabs");
- });
- }
-
- this._appendStatusPanel();
-
- if (updateBlockedPopups)
- this.mCurrentBrowser.updateBlockedPopups();
-
- // Update the URL bar.
- var loc = this.mCurrentBrowser.currentURI;
-
- var webProgress = this.mCurrentBrowser.webProgress;
- var securityUI = this.mCurrentBrowser.securityUI;
-
- this._callProgressListeners(null, "onLocationChange",
- [webProgress, null, loc, 0], true,
- false);
-
- if (securityUI) {
- // Include the true final argument to indicate that this event is
- // simulated (instead of being observed by the webProgressListener).
- this._callProgressListeners(null, "onSecurityChange",
- [webProgress, null, securityUI.state, true],
- true, false);
- }
-
- var listener = this._tabListeners.get(this.mCurrentTab);
- if (listener && listener.mStateFlags) {
- this._callProgressListeners(null, "onUpdateCurrentBrowser",
- [listener.mStateFlags, listener.mStatus,
- listener.mMessage, listener.mTotalProgress],
- true, false);
- }
-
- if (!this._previewMode) {
- this._recordTabAccess(this.mCurrentTab);
-
- this.mCurrentTab.updateLastAccessed();
- this.mCurrentTab.removeAttribute("unread");
- oldTab.updateLastAccessed();
-
- let oldFindBar = oldTab._findBar;
- if (oldFindBar &&
- oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
- !oldFindBar.hidden)
- this._lastFindValue = oldFindBar._findField.value;
-
- this.updateTitlebar();
-
- this.mCurrentTab.removeAttribute("titlechanged");
- this.mCurrentTab.removeAttribute("attention");
- }
-
- // If the new tab is busy, and our current state is not busy, then
- // we need to fire a start to all progress listeners.
- const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
- if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
- this.mIsBusy = true;
- this._callProgressListeners(null, "onStateChange",
- [webProgress, null,
- nsIWebProgressListener.STATE_START |
- nsIWebProgressListener.STATE_IS_NETWORK, 0],
- true, false);
- }
-
- // If the new tab is not busy, and our current state is busy, then
- // we need to fire a stop to all progress listeners.
- if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
- this.mIsBusy = false;
- this._callProgressListeners(null, "onStateChange",
- [webProgress, null,
- nsIWebProgressListener.STATE_STOP |
- nsIWebProgressListener.STATE_IS_NETWORK, 0],
- true, false);
- }
-
- this._setCloseKeyState(!this.mCurrentTab.pinned);
-
- // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
- // that might rely upon the other changes suppressed.
- // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
- if (!this._previewMode) {
- // We've selected the new tab, so go ahead and notify listeners.
- let event = new CustomEvent("TabSelect", {
- bubbles: true,
- cancelable: false,
- detail: {
- previousTab: oldTab
- }
- });
- this.mCurrentTab.dispatchEvent(event);
-
- this._tabAttrModified(oldTab, ["selected"]);
- this._tabAttrModified(this.mCurrentTab, ["selected"]);
-
- if (oldBrowser != newBrowser &&
- oldBrowser.getInPermitUnload) {
- oldBrowser.getInPermitUnload(inPermitUnload => {
- if (!inPermitUnload) {
- return;
- }
- // Since the user is switching away from a tab that has
- // a beforeunload prompt active, we remove the prompt.
- // This prevents confusing user flows like the following:
- // 1. User attempts to close Firefox
- // 2. User switches tabs (ingoring a beforeunload prompt)
- // 3. User returns to tab, presses "Leave page"
- let promptBox = this.getTabModalPromptBox(oldBrowser);
- let prompts = promptBox.listPrompts();
- // There might not be any prompts here if the tab was closed
- // while in an onbeforeunload prompt, which will have
- // destroyed aforementioned prompt already, so check there's
- // something to remove, first:
- if (prompts.length) {
- // NB: This code assumes that the beforeunload prompt
- // is the top-most prompt on the tab.
- prompts[prompts.length - 1].abortPrompt();
- }
- });
- }
-
- oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
- if (this.isFindBarInitialized(oldTab)) {
- let findBar = this.getFindBar(oldTab);
- oldTab._findBarFocused = (!findBar.hidden &&
- findBar._findField.getAttribute("focused") == "true");
- }
-
- // If focus is in the tab bar, retain it there.
- if (document.activeElement == oldTab) {
- // We need to explicitly focus the new tab, because
- // tabbox.xml does this only in some cases.
- this.mCurrentTab.focus();
- } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) {
- // Clear focus so that _adjustFocusAfterTabSwitch can detect if
- // some element has been focused and respect that.
- document.activeElement.blur();
- }
-
- if (!gMultiProcessBrowser)
- this._adjustFocusAfterTabSwitch(this.mCurrentTab);
- }
-
- updateUserContextUIIndicator();
- gIdentityHandler.updateSharingIndicator();
-
- this.tabContainer._setPositionalAttributes();
-
- if (!gMultiProcessBrowser) {
- let event = new CustomEvent("TabSwitchDone", {
- bubbles: true,
- cancelable: true
- });
- this.dispatchEvent(event);
- }
-
- if (!aForceUpdate)
- TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
- ]]>
- </body>
- </method>
-
- <method name="_adjustFocusAfterTabSwitch">
- <parameter name="newTab"/>
- <body><![CDATA[
- // Don't steal focus from the tab bar.
- if (document.activeElement == newTab)
- return;
-
- let newBrowser = this.getBrowserForTab(newTab);
-
- // If there's a tabmodal prompt showing, focus it.
- if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
- let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
- let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
- let prompt = prompts[prompts.length - 1];
- prompt.Dialog.setDefaultFocus();
- return;
- }
-
- // Focus the location bar if it was previously focused for that tab.
- // In full screen mode, only bother making the location bar visible
- // if the tab is a blank one.
- if (newBrowser._urlbarFocused && gURLBar) {
- // Explicitly close the popup if the URL bar retains focus
- gURLBar.closePopup();
-
- if (!window.fullScreen) {
- gURLBar.focus();
- return;
- }
-
- if (isTabEmpty(this.mCurrentTab)) {
- focusAndSelectUrlBar();
- return;
- }
- }
-
- // Focus the find bar if it was previously focused for that tab.
- if (gFindBarInitialized && !gFindBar.hidden &&
- this.selectedTab._findBarFocused) {
- gFindBar._findField.focus();
- return;
- }
-
- // Don't focus the content area if something has been focused after the
- // tab switch was initiated.
- if (gMultiProcessBrowser &&
- document.activeElement != document.documentElement)
- return;
-
- // We're now committed to focusing the content area.
- let fm = Services.focus;
- let focusFlags = fm.FLAG_NOSCROLL;
-
- if (!gMultiProcessBrowser) {
- let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
-
- // for anchors, use FLAG_SHOWRING so that it is clear what link was
- // last clicked when switching back to that tab
- if (newFocusedElement &&
- (newFocusedElement instanceof HTMLAnchorElement ||
- newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
- focusFlags |= fm.FLAG_SHOWRING;
- }
-
- fm.setFocus(newBrowser, focusFlags);
- ]]></body>
- </method>
-
- <!--
- This function assumes we have an LRU cache of tabs (either
- images of tab content or their layers). The goal is to find
- out how far into the cache we need to look in order to find
- aTab. We record this number in telemetry and also move aTab to
- the front of the cache.
-
- A newly created tab has position Infinity in the cache.
- If a tab is closed, it has no effect on the position of other
- tabs in the cache since we assume that closing a tab doesn't
- cause us to load in any other tabs.
-
- We ignore the effect of dragging tabs between windows.
- -->
- <method name="_recordTabAccess">
- <parameter name="aTab"/>
- <body><![CDATA[
- if (!Services.telemetry.canRecordExtended) {
- return;
- }
-
- let tabs = Array.from(this.visibleTabs);
-
- let pos = aTab.cachePosition;
- for (let i = 0; i < tabs.length; i++) {
- // If aTab is moving to the front, everything that was
- // previously in front of it is bumped up one position.
- if (tabs[i].cachePosition < pos) {
- tabs[i].cachePosition++;
- }
- }
- aTab.cachePosition = 0;
-
- if (isFinite(pos)) {
- Services.telemetry.getHistogramById("TAB_SWITCH_CACHE_POSITION").add(pos);
- }
- ]]></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="setBrowserSharing">
- <parameter name="aBrowser"/>
- <parameter name="aState"/>
- <body><![CDATA[
- let tab = this.getTabForBrowser(aBrowser);
- if (!tab)
- return;
-
- let sharing;
- if (aState.screen) {
- sharing = "screen";
- } else if (aState.camera) {
- sharing = "camera";
- } else if (aState.microphone) {
- sharing = "microphone";
- }
-
- if (sharing) {
- tab.setAttribute("sharing", sharing);
- tab._sharingState = aState;
- } else {
- tab.removeAttribute("sharing");
- tab._sharingState = null;
- }
- this._tabAttrModified(tab, ["sharing"]);
-
- if (aBrowser == this.mCurrentBrowser)
- gIdentityHandler.updateSharingIndicator();
- ]]></body>
- </method>
-
-
- <method name="setTabTitleLoading">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- aTab.label = this.mStringBundle.getString("tabs.connecting");
- this._tabAttrModified(aTab, ["label"]);
- ]]>
- </body>
- </method>
-
- <method name="setTabTitle">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- var browser = this.getBrowserForTab(aTab);
- var title = browser.contentTitle;
-
- if (!title) {
- if (browser.currentURI.spec) {
- try {
- title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
- } catch (ex) {
- title = browser.currentURI.spec;
- }
- }
-
- if (title && !isBlankPageURL(title)) {
- // At this point, we now have a URI.
- // Let's try to unescape it using a character set
- // in case the URI is not ASCII.
- try {
- var characterSet = browser.characterSet;
- const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
- .getService(Components.interfaces.nsITextToSubURI);
- title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
- } catch (ex) { /* Do nothing. */ }
- } else if (aTab.hasAttribute("customizemode")) {
- let brandBundle = document.getElementById("bundle_brand");
- let brandShortName = brandBundle.getString("brandShortName");
- title = gNavigatorBundle.getFormattedString("customizeMode.tabTitle",
- [ brandShortName ]);
- } else // Still no title? Fall back to our untitled string.
- title = this.mStringBundle.getString("tabs.emptyTabTitle");
- }
-
- if (aTab.label == title)
- return false;
-
- aTab.label = title;
- this._tabAttrModified(aTab, ["label"]);
-
- if (aTab.selected)
- this.updateTitlebar();
-
- return true;
- ]]>
- </body>
- </method>
-
- <method name="loadOneTab">
- <parameter name="aURI"/>
- <parameter name="aReferrerURI"/>
- <parameter name="aCharset"/>
- <parameter name="aPostData"/>
- <parameter name="aLoadInBackground"/>
- <parameter name="aAllowThirdPartyFixup"/>
- <parameter name="aIsPrerendered"/>
- <body>
- <![CDATA[
- var aTriggeringPrincipal;
- var aReferrerPolicy;
- var aFromExternal;
- var aRelatedToCurrent;
- var aAllowMixedContent;
- var aSkipAnimation;
- var aForceNotRemote;
- var aPreferredRemoteType;
- var aNoReferrer;
- var aUserContextId;
- var aRelatedBrowser;
- var aOriginPrincipal;
- var aOpener;
- if (arguments.length == 2 &&
- 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;
- aLoadInBackground = params.inBackground;
- aAllowThirdPartyFixup = params.allowThirdPartyFixup;
- aFromExternal = params.fromExternal;
- aRelatedToCurrent = params.relatedToCurrent;
- aAllowMixedContent = params.allowMixedContent;
- aSkipAnimation = params.skipAnimation;
- aForceNotRemote = params.forceNotRemote;
- aPreferredRemoteType = params.preferredRemoteType;
- aNoReferrer = params.noReferrer;
- aUserContextId = params.userContextId;
- aRelatedBrowser = params.relatedBrowser;
- aOriginPrincipal = params.originPrincipal;
- aOpener = params.opener;
- aIsPrerendered = params.isPrerendered;
- }
-
- var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
- Services.prefs.getBoolPref("browser.tabs.loadInBackground");
- var owner = bgLoad ? null : this.selectedTab;
-
- var tab = this.addTab(aURI, {
- triggeringPrincipal: aTriggeringPrincipal,
- referrerURI: aReferrerURI,
- referrerPolicy: aReferrerPolicy,
- charset: aCharset,
- postData: aPostData,
- ownerTab: owner,
- allowThirdPartyFixup: aAllowThirdPartyFixup,
- fromExternal: aFromExternal,
- relatedToCurrent: aRelatedToCurrent,
- skipAnimation: aSkipAnimation,
- allowMixedContent: aAllowMixedContent,
- forceNotRemote: aForceNotRemote,
- preferredRemoteType: aPreferredRemoteType,
- noReferrer: aNoReferrer,
- userContextId: aUserContextId,
- originPrincipal: aOriginPrincipal,
- relatedBrowser: aRelatedBrowser,
- opener: aOpener,
- isPrerendered: aIsPrerendered });
- if (!bgLoad)
- this.selectedTab = tab;
-
- return tab;
- ]]>
- </body>
- </method>
-
- <method name="loadTabs">
- <parameter name="aURIs"/>
- <parameter name="aLoadInBackground"/>
- <parameter name="aReplace"/>
- <body><![CDATA[
- let aAllowThirdPartyFixup;
- let aTargetTab;
- let aNewIndex = -1;
- let aPostDatas = [];
- let aUserContextId;
- if (arguments.length == 2 &&
- typeof arguments[1] == "object") {
- let params = arguments[1];
- aLoadInBackground = params.inBackground;
- aReplace = params.replace;
- aAllowThirdPartyFixup = params.allowThirdPartyFixup;
- aTargetTab = params.targetTab;
- aNewIndex = typeof params.newIndex === "number" ?
- params.newIndex : aNewIndex;
- aPostDatas = params.postDatas || aPostDatas;
- aUserContextId = params.userContextId;
- }
-
- 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;
- if (aTargetTab) {
- browser = this.getBrowserForTab(aTargetTab);
- targetTabIndex = aTargetTab._tPos;
- } else {
- 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]
- });
- } catch (e) {
- // Ignore failure in case a URI is wrong, so we can continue
- // opening the next ones.
- }
- } else {
- firstTabAdded = this.addTab(aURIs[0], {
- ownerTab: owner,
- skipAnimation: multiple,
- allowThirdPartyFixup: aAllowThirdPartyFixup,
- postData: aPostDatas[0],
- userContextId: aUserContextId
- });
- if (aNewIndex !== -1) {
- this.moveTabTo(firstTabAdded, aNewIndex);
- targetTabIndex = firstTabAdded._tPos;
- }
- }
-
- let tabNum = targetTabIndex;
- for (let i = 1; i < aURIs.length; ++i) {
- let tab = this.addTab(aURIs[i], {
- skipAnimation: true,
- allowThirdPartyFixup: aAllowThirdPartyFixup,
- postData: aPostDatas[i],
- userContextId: aUserContextId
- });
- 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="updateBrowserRemoteness">
- <parameter name="aBrowser"/>
- <parameter name="aShouldBeRemote"/>
- <parameter name="aOptions"/>
- <body>
- <![CDATA[
- aOptions = aOptions || {};
- let isRemote = aBrowser.getAttribute("remote") == "true";
-
- if (!gMultiProcessBrowser && aShouldBeRemote) {
- throw new Error("Cannot switch to remote browser in a window " +
- "without the remote tabs load context.");
- }
-
- // Default values for remoteType
- if (!aOptions.remoteType) {
- aOptions.remoteType = aShouldBeRemote ? E10SUtils.DEFAULT_REMOTE_TYPE : E10SUtils.NOT_REMOTE;
- }
-
- // If we are passed an opener, we must be making the browser non-remote, and
- // if the browser is _currently_ non-remote, we need the openers to match,
- // because it is already too late to change it.
- if (aOptions.opener) {
- if (aShouldBeRemote) {
- throw new Error("Cannot set an opener on a browser which should be remote!");
- }
- if (!isRemote && aBrowser.contentWindow.opener != aOptions.opener) {
- throw new Error("Cannot change opener on an already non-remote browser!");
- }
- }
-
- // Abort if we're not going to change anything
- if (isRemote == aShouldBeRemote && !aOptions.newFrameloader && !aOptions.freshProcess &&
- (!isRemote || aBrowser.getAttribute("remoteType") == aOptions.remoteType) &&
- !aBrowser.frameLoader.isFreshProcess) {
- return false;
- }
-
- let tab = this.getTabForBrowser(aBrowser);
- let evt = document.createEvent("Events");
- evt.initEvent("BeforeTabRemotenessChange", true, false);
- tab.dispatchEvent(evt);
-
- let wasActive = document.activeElement == aBrowser;
-
- // Unmap the old outerWindowID.
- this._outerWindowIDBrowserMap.delete(aBrowser.outerWindowID);
-
- // Unhook our progress listener.
- let filter = this._tabFilters.get(tab);
- let listener = this._tabListeners.get(tab);
- aBrowser.webProgress.removeProgressListener(filter);
- filter.removeProgressListener(listener);
-
- // We'll be creating a new listener, so destroy the old one.
- listener.destroy();
-
- let oldUserTypedValue = aBrowser.userTypedValue;
- let hadStartedLoad = aBrowser.didStartLoadSinceLastUserTyping();
-
- // Make sure the browser is destroyed so it unregisters from observer notifications
- aBrowser.destroy();
-
- // Make sure to restore the original droppedLinkHandler and
- // relatedBrowser.
- let droppedLinkHandler = aBrowser.droppedLinkHandler;
- let relatedBrowser = aBrowser.relatedBrowser;
-
- // Change the "remote" attribute.
- let parent = aBrowser.parentNode;
- parent.removeChild(aBrowser);
- if (aShouldBeRemote) {
- aBrowser.setAttribute("remote", "true");
- aBrowser.setAttribute("remoteType", aOptions.remoteType);
- } else {
- aBrowser.setAttribute("remote", "false");
- aBrowser.removeAttribute("remoteType");
- }
-
- // NB: This works with the hack in the browser constructor that
- // turns this normal property into a field.
- aBrowser.relatedBrowser = relatedBrowser;
-
- if (aOptions.opener) {
- // Set the opener window on the browser, such that when the frame
- // loader is created the opener is set correctly.
- aBrowser.presetOpenerWindow(aOptions.opener);
- }
-
- // Set the freshProcess attribute so that the frameloader knows to
- // create a new process
- if (aOptions.freshProcess) {
- aBrowser.setAttribute("freshProcess", "true");
- }
-
- parent.appendChild(aBrowser);
-
- // Remove the freshProcess attribute if we set it, as we don't
- // want it to apply for the next time the frameloader is created
- aBrowser.removeAttribute("freshProcess");
-
- aBrowser.userTypedValue = oldUserTypedValue;
- if (hadStartedLoad) {
- aBrowser.urlbarChangeTracker.startedLoad();
- }
-
- aBrowser.droppedLinkHandler = droppedLinkHandler;
-
- // Switching a browser's remoteness will create a new frameLoader.
- // As frameLoaders start out with an active docShell we have to
- // deactivate it if this is not the selected tab's browser or the
- // browser window is minimized.
- aBrowser.docShellIsActive = this.shouldActivateDocShell(aBrowser);
-
- // Create a new tab progress listener for the new browser we just injected,
- // since tab progress listeners have logic for handling the initial about:blank
- // load
- listener = this.mTabProgressListener(tab, aBrowser, true, false);
- this._tabListeners.set(tab, listener);
- filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
-
- // Restore the progress listener.
- aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
-
- // Restore the securityUI state.
- let securityUI = aBrowser.securityUI;
- let state = securityUI ? securityUI.state
- : Ci.nsIWebProgressListener.STATE_IS_INSECURE;
- // Include the true final argument to indicate that this event is
- // simulated (instead of being observed by the webProgressListener).
- this._callProgressListeners(aBrowser, "onSecurityChange",
- [aBrowser.webProgress, null, state, true],
- true, false);
-
- if (aShouldBeRemote) {
- // Switching the browser to be remote will connect to a new child
- // process so the browser can no longer be considered to be
- // crashed.
- tab.removeAttribute("crashed");
- } else {
- aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
-
- // Register the new outerWindowID.
- this._outerWindowIDBrowserMap.set(aBrowser.outerWindowID, aBrowser);
- }
-
- if (wasActive)
- aBrowser.focus();
-
- // If the findbar has been initialised, reset its browser reference.
- if (this.isFindBarInitialized(tab)) {
- this.getFindBar(tab).browser = aBrowser;
- }
-
- evt = document.createEvent("Events");
- evt.initEvent("TabRemotenessChange", true, false);
- tab.dispatchEvent(evt);
-
- return true;
- ]]>
- </body>
- </method>
-
- <method name="updateBrowserRemotenessByURL">
- <parameter name="aBrowser"/>
- <parameter name="aURL"/>
- <parameter name="aOptions"/>
- <body>
- <![CDATA[
- aOptions = aOptions || {};
-
- if (!gMultiProcessBrowser)
- return this.updateBrowserRemoteness(aBrowser, false);
-
- // If this URL can't load in the current browser then flip it to the
- // correct type.
- let currentRemoteType = aBrowser.remoteType;
- aOptions.remoteType =
- E10SUtils.getRemoteTypeForURI(aURL, gMultiProcessBrowser,
- currentRemoteType);
- if (currentRemoteType != aOptions.remoteType ||
- aOptions.freshProcess || aOptions.newFrameloader ||
- aBrowser.frameLoader.isFreshProcess) {
- let remote = aOptions.remoteType != E10SUtils.NOT_REMOTE;
- return this.updateBrowserRemoteness(aBrowser, remote, aOptions);
- }
-
- return false;
- ]]>
- </body>
- </method>
-
- <field name="_preloadedBrowser">null</field>
- <method name="_getPreloadedBrowser">
- <body>
- <![CDATA[
- if (!this._isPreloadingEnabled()) {
- return null;
- }
-
- // The preloaded browser might be null.
- let browser = this._preloadedBrowser;
-
- // Consume the browser.
- this._preloadedBrowser = null;
-
- // Attach the nsIFormFillController now that we know the browser
- // will be used. If we do that before and the preloaded browser
- // won't be consumed until shutdown then we leak a docShell.
- // Also, we do not need to take care of attaching nsIFormFillControllers
- // in the case that the browser is remote, as remote browsers take
- // care of that themselves.
- if (browser && this.hasAttribute("autocompletepopup")) {
- browser.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
- }
-
- return browser;
- ]]>
- </body>
- </method>
-
- <method name="_isPreloadingEnabled">
- <body>
- <![CDATA[
- // Preloading for the newtab page is enabled when the pref is true
- // and the URL is "about:newtab". We do not support preloading for
- // custom newtab URLs.
- return Services.prefs.getBoolPref("browser.newtab.preload") &&
- !aboutNewTabService.overridden;
- ]]>
- </body>
- </method>
-
- <method name="_createPreloadBrowser">
- <body>
- <![CDATA[
- // Do nothing if we have a preloaded browser already
- // or preloading of newtab pages is disabled.
- if (this._preloadedBrowser || !this._isPreloadingEnabled()) {
- return;
- }
-
- let remoteType =
- E10SUtils.getRemoteTypeForURI(BROWSER_NEW_TAB_URL,
- gMultiProcessBrowser);
- let browser = this._createBrowser({isPreloadBrowser: true, remoteType});
- this._preloadedBrowser = browser;
-
- let notificationbox = this.getNotificationBox(browser);
- this.mPanelContainer.appendChild(notificationbox);
-
- if (remoteType != E10SUtils.NOT_REMOTE) {
- // For remote browsers, we need to make sure that the webProgress is
- // instantiated, otherwise the parent won't get informed about the state
- // of the preloaded browser until it gets attached to a tab.
- browser.webProgress;
- }
-
- browser.loadURI(BROWSER_NEW_TAB_URL);
- browser.docShellIsActive = false;
- ]]>
- </body>
- </method>
-
- <method name="_createBrowser">
- <parameter name="aParams"/>
- <body>
- <![CDATA[
- // Supported parameters:
- // userContextId, remote, remoteType, isPreloadBrowser,
- // uriIsAboutBlank, permanentKey, isPrerendered
-
- const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
- let b = document.createElementNS(NS_XUL, "browser");
- b.permanentKey = aParams.permanentKey || {};
- b.setAttribute("type", "content");
- b.setAttribute("message", "true");
- b.setAttribute("messagemanagergroup", "browsers");
- b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
- b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
-
- if (aParams.isPrerendered) {
- b.setAttribute("prerendered", "true");
- }
-
- if (aParams.userContextId) {
- b.setAttribute("usercontextid", aParams.userContextId);
- }
-
- // remote parameter used by some addons, use default in this case.
- if (aParams.remote && !aParams.remoteType) {
- aParams.remoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
- }
-
- if (aParams.remoteType) {
- b.setAttribute("remoteType", aParams.remoteType);
- b.setAttribute("remote", "true");
- }
-
- if (aParams.opener) {
- if (aParams.remoteType) {
- throw new Exception("Cannot set opener window on a remote browser!");
- }
- b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aParams.opener);
- }
-
- if (!aParams.isPreloadBrowser && this.hasAttribute("autocompletepopup")) {
- b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
- }
-
- if (this.hasAttribute("selectmenulist"))
- b.setAttribute("selectmenulist", this.getAttribute("selectmenulist"));
-
- if (this.hasAttribute("datetimepicker")) {
- b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
- }
-
- b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
-
- if (aParams.relatedBrowser) {
- b.relatedBrowser = aParams.relatedBrowser;
- }
-
- // Create the browserStack container
- var stack = document.createElementNS(NS_XUL, "stack");
- stack.className = "browserStack";
- stack.appendChild(b);
- stack.setAttribute("flex", "1");
-
- // Create the browserContainer
- var browserContainer = document.createElementNS(NS_XUL, "vbox");
- browserContainer.className = "browserContainer";
- browserContainer.appendChild(stack);
- browserContainer.setAttribute("flex", "1");
-
- // Create the sidebar container
- var browserSidebarContainer = document.createElementNS(NS_XUL,
- "hbox");
- browserSidebarContainer.className = "browserSidebarContainer";
- browserSidebarContainer.appendChild(browserContainer);
- browserSidebarContainer.setAttribute("flex", "1");
-
- // Add the Message and the Browser to the box
- var notificationbox = document.createElementNS(NS_XUL,
- "notificationbox");
- notificationbox.setAttribute("flex", "1");
- notificationbox.setAttribute("notificationside", "top");
- notificationbox.appendChild(browserSidebarContainer);
-
- // Prevent the superfluous initial load of a blank document
- // if we're going to load something other than about:blank.
- if (!aParams.uriIsAboutBlank) {
- b.setAttribute("nodefaultsrc", "true");
- }
-
- return b;
- ]]>
- </body>
- </method>
-
- <method name="_linkBrowserToTab">
- <parameter name="aTab"/>
- <parameter name="aURI"/>
- <parameter name="aParams"/>
- <body>
- <![CDATA[
- "use strict";
-
- // Supported parameters:
- // forceNotRemote, preferredRemoteType, userContextId, isPrerendered
-
- let uriIsAboutBlank = !aURI || aURI == "about:blank";
-
- let remoteType =
- aParams.forceNotRemote ? E10SUtils.NOT_REMOTE
- : E10SUtils.getRemoteTypeForURI(aURI, gMultiProcessBrowser,
- aParams.preferredRemoteType);
-
- let browser;
- let usingPreloadedContent = false;
-
- // If we open a new tab with the newtab URL in the default
- // userContext, check if there is a preloaded browser ready.
- // Private windows are not included because both the label and the
- // icon for the tab would be set incorrectly (see bug 1195981).
- if (aURI == BROWSER_NEW_TAB_URL &&
- !aParams.userContextId &&
- !PrivateBrowsingUtils.isWindowPrivate(window)) {
- browser = this._getPreloadedBrowser();
- if (browser) {
- usingPreloadedContent = true;
- aTab.permanentKey = browser.permanentKey;
- }
- }
-
- if (!browser) {
- // No preloaded browser found, create one.
- browser = this._createBrowser({permanentKey: aTab.permanentKey,
- remoteType,
- uriIsAboutBlank,
- userContextId: aParams.userContextId,
- relatedBrowser: aParams.relatedBrowser,
- opener: aParams.opener,
- isPrerendered: aParams.isPrerendered});
- }
-
- let notificationbox = this.getNotificationBox(browser);
- let uniqueId = this._generateUniquePanelID();
- notificationbox.id = uniqueId;
- aTab.linkedPanel = uniqueId;
- aTab.linkedBrowser = browser;
- aTab.hasBrowser = true;
- this._tabForBrowser.set(browser, aTab);
-
- // Inject the <browser> into the DOM if necessary.
- if (!notificationbox.parentNode) {
- // NB: this appendChild call causes us to run constructors for the
- // browser element, which fires off a bunch of notifications. Some
- // of those notifications can cause code to run that inspects our
- // state, so it is important that the tab element is fully
- // initialized by this point.
- this.mPanelContainer.appendChild(notificationbox);
- }
-
- // wire up a progress listener for the new browser object.
- let tabListener = this.mTabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent);
- const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"]
- .createInstance(Ci.nsIWebProgress);
- filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
- browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
- this._tabListeners.set(aTab, tabListener);
- this._tabFilters.set(aTab, filter);
-
- browser.droppedLinkHandler = handleDroppedLink;
-
- // We start our browsers out as inactive, and then maintain
- // activeness in the tab switcher.
- browser.docShellIsActive = false;
-
- // When addTab() is called with an URL that is not "about:blank" we
- // set the "nodefaultsrc" attribute that prevents a frameLoader
- // from being created as soon as the linked <browser> is inserted
- // into the DOM. We thus have to register the new outerWindowID
- // for non-remote browsers after we have called browser.loadURI().
- if (remoteType == E10SUtils.NOT_REMOTE) {
- this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
- }
-
- var evt = new CustomEvent("TabBrowserInserted", { bubbles: true, detail: {} });
- aTab.dispatchEvent(evt);
-
- return { usingPreloadedContent };
- ]]>
- </body>
- </method>
-
- <method name="addTab">
- <parameter name="aURI"/>
- <parameter name="aReferrerURI"/>
- <parameter name="aCharset"/>
- <parameter name="aPostData"/>
- <parameter name="aOwner"/>
- <parameter name="aAllowThirdPartyFixup"/>
- <parameter name="aIsPrerendered"/>
- <body>
- <![CDATA[
- "use strict";
-
- const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
- var aTriggeringPrincipal;
- var aReferrerPolicy;
- var aFromExternal;
- var aRelatedToCurrent;
- var aSkipAnimation;
- var aAllowMixedContent;
- var aForceNotRemote;
- var aPreferredRemoteType;
- var aNoReferrer;
- var aUserContextId;
- var aEventDetail;
- var aRelatedBrowser;
- var aOriginPrincipal;
- var aDisallowInheritPrincipal;
- var aOpener;
- if (arguments.length == 2 &&
- 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;
- aAllowThirdPartyFixup = params.allowThirdPartyFixup;
- aFromExternal = params.fromExternal;
- aRelatedToCurrent = params.relatedToCurrent;
- aSkipAnimation = params.skipAnimation;
- aAllowMixedContent = params.allowMixedContent;
- aForceNotRemote = params.forceNotRemote;
- aPreferredRemoteType = params.preferredRemoteType;
- aNoReferrer = params.noReferrer;
- aUserContextId = params.userContextId;
- aEventDetail = params.eventDetail;
- aRelatedBrowser = params.relatedBrowser;
- aOriginPrincipal = params.originPrincipal;
- aDisallowInheritPrincipal = params.disallowInheritPrincipal;
- aOpener = params.opener;
- aIsPrerendered = params.isPrerendered;
- }
-
- // if we're adding tabs, we're past interrupt mode, ditch the owner
- if (this.mCurrentTab.owner)
- this.mCurrentTab.owner = null;
-
- var t = document.createElementNS(NS_XUL, "tab");
-
- var uriIsAboutBlank = !aURI || aURI == "about:blank";
-
- if (!aURI || isBlankPageURL(aURI)) {
- t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
- } else if (aURI.toLowerCase().startsWith("javascript:")) {
- // This can go away when bug 672618 or bug 55696 are fixed.
- t.setAttribute("label", aURI);
- }
-
- if (aIsPrerendered) {
- t.setAttribute("hidden", "true");
- }
-
- // Related tab inherits current tab's user context unless a different
- // usercontextid is specified
- if (aUserContextId == null &&
- (aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent)) {
- aUserContextId = this.mCurrentTab.getAttribute("usercontextid") || 0;
- }
-
- if (aUserContextId) {
- t.setAttribute("usercontextid", aUserContextId);
- ContextualIdentityService.setTabStyle(t);
- }
-
- t.setAttribute("onerror", "this.removeAttribute('image');");
- t.className = "tabbrowser-tab";
-
- this.tabContainer._unlockTabSizing();
-
- // When overflowing, new tabs are scrolled into view smoothly, which
- // doesn't go well together with the width transition. So we skip the
- // transition in that case.
- let animate = !aSkipAnimation &&
- this.tabContainer.getAttribute("overflow") != "true" &&
- Services.prefs.getBoolPref("browser.tabs.animate");
- if (!animate) {
- t.setAttribute("fadein", "true");
- setTimeout(function(tabContainer) {
- tabContainer._handleNewTab(t);
- }, 0, this.tabContainer);
- }
-
- // invalidate cache
- this._visibleTabs = null;
-
- this.tabContainer.appendChild(t);
-
- // If this new tab is owned by another, assert that relationship
- if (aOwner)
- t.owner = aOwner;
-
- var position = this.tabs.length - 1;
- t._tPos = position;
- t.permanentKey = {};
- this.tabContainer._setPositionalAttributes();
-
- this.tabContainer.updateVisibility();
-
- // If URI is about:blank and we don't have a preferred remote type,
- // then we need to use the referrer, if we have one, to get the
- // correct remote type for the new tab.
- if (uriIsAboutBlank && !aPreferredRemoteType && aReferrerURI) {
- aPreferredRemoteType =
- E10SUtils.getRemoteTypeForURI(aReferrerURI.spec,
- gMultiProcessBrowser);
- }
-
- // Currently in this incarnation of bug 906076, we are forcing the
- // browser to immediately be linked. In future incarnations of this
- // bug this will be removed so we can leave the tab in its "lazy"
- // state to be exploited for startup optimization. Note that for
- // now this must occur before "TabOpen" event is fired, as that will
- // trigger SessionStore.jsm to run code that expects the existence
- // of tab.linkedBrowser.
- let browserParams = {
- forceNotRemote: aForceNotRemote,
- preferredRemoteType: aPreferredRemoteType,
- userContextId: aUserContextId,
- relatedBrowser: aRelatedBrowser,
- opener: aOpener,
- isPrerendered: aIsPrerendered,
- };
- let { usingPreloadedContent } = this._linkBrowserToTab(t, aURI, browserParams);
- let b = t.linkedBrowser;
-
- // Dispatch a new tab notification. We do this once we're
- // entirely done, so that things are in a consistent state
- // even if the event listener opens or closes tabs.
- var detail = aEventDetail || {};
- var evt = new CustomEvent("TabOpen", { bubbles: true, detail });
- t.dispatchEvent(evt);
-
- if (!usingPreloadedContent && aOriginPrincipal) {
- b.createAboutBlankContentViewer(aOriginPrincipal);
- }
-
- // If we didn't swap docShells with a preloaded browser
- // then let's just continue loading the page normally.
- if (!usingPreloadedContent && (!uriIsAboutBlank || aDisallowInheritPrincipal)) {
- // pretend the user typed this so it'll be available till
- // the document successfully loads
- if (aURI && gInitialPages.indexOf(aURI) == -1)
- b.userTypedValue = aURI;
-
- let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
- if (aAllowThirdPartyFixup) {
- flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
- flags |= 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;
- if (aDisallowInheritPrincipal)
- flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
- try {
- b.loadURIWithFlags(aURI, {
- flags,
- triggeringPrincipal: aTriggeringPrincipal,
- referrerURI: aNoReferrer ? null : aReferrerURI,
- referrerPolicy: aReferrerPolicy,
- charset: aCharset,
- postData: aPostData,
- });
- } catch (ex) {
- Cu.reportError(ex);
- }
- }
-
- // Check if we're opening a tab related to the current tab and
- // move it to after the current tab.
- // aReferrerURI is null or undefined if the tab is opened from
- // an external application or bookmark, i.e. somewhere other
- // than the current tab.
- if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
- Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
- let newTabPos = (this._lastRelatedTab ||
- this.selectedTab)._tPos + 1;
- if (this._lastRelatedTab)
- this._lastRelatedTab.owner = null;
- else
- t.owner = this.selectedTab;
- this.moveTabTo(t, newTabPos);
- this._lastRelatedTab = t;
- }
-
- if (animate) {
- requestAnimationFrame(function() {
- this.tabContainer._handleTabTelemetryStart(t, aURI);
-
- // kick the animation off
- t.setAttribute("fadein", "true");
- }.bind(this));
- }
-
- 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 - this._removingTabs.length -
- gBrowser._numPinnedTabs;
- break;
- case this.closingTabsEnum.OTHER:
- tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
- 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.warnOnCloseOtherTabs";
- var shouldPrompt = Services.prefs.getBoolPref(pref);
- if (!shouldPrompt)
- return true;
-
- var ps = Services.prompt;
-
- // default to true: if it were false, we wouldn't get this far
- var warnOnClose = { value: true };
- var bundle = this.mStringBundle;
-
- // focus the window before prompting.
- // this will raise any minimized window, which will
- // make it obvious which window the prompt is for and will
- // solve the problem of windows "obscuring" the prompt.
- // see bug #350299 for more details
- window.focus();
- var warningMessage =
- PluralForm.get(tabsToClose, bundle.getString("tabs.closeWarningMultiple"))
- .replace("#1", tabsToClose);
- var buttonPressed =
- ps.confirmEx(window,
- bundle.getString("tabs.closeWarningTitle"),
- warningMessage,
- (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
- + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
- bundle.getString("tabs.closeButtonMultiple"),
- null, null,
- aCloseTabs == this.closingTabsEnum.ALL ?
- bundle.getString("tabs.closeWarningPromptMe") : null,
- warnOnClose);
- var reallyClose = (buttonPressed == 0);
-
- // don't set the pref unless they press OK and it's false
- if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
- Services.prefs.setBoolPref(pref, false);
-
- return reallyClose;
- ]]>
- </body>
- </method>
-
- <method name="getTabsToTheEndFrom">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- var tabsToEnd = [];
- let tabs = this.visibleTabs;
- 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"/>
- <parameter name="aParams"/>
- <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], aParams);
- }
- }
- ]]>
- </body>
- </method>
-
- <method name="removeAllTabsBut">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- if (aTab.pinned)
- return;
-
- if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
- let tabs = this.visibleTabs;
- this.selectedTab = aTab;
-
- for (let i = tabs.length - 1; i >= 0; --i) {
- if (tabs[i] != aTab && !tabs[i].pinned)
- this.removeTab(tabs[i], {animate: true});
- }
- }
- ]]>
- </body>
- </method>
-
- <method name="removeCurrentTab">
- <parameter name="aParams"/>
- <body>
- <![CDATA[
- this.removeTab(this.mCurrentTab, aParams);
- ]]>
- </body>
- </method>
-
- <field name="_removingTabs">
- []
- </field>
-
- <method name="removeTab">
- <parameter name="aTab"/>
- <parameter name="aParams"/>
- <body>
- <![CDATA[
- if (aParams) {
- var animate = aParams.animate;
- var byMouse = aParams.byMouse;
- var skipPermitUnload = aParams.skipPermitUnload;
- }
-
- // Handle requests for synchronously removing an already
- // asynchronously closing tab.
- if (!animate &&
- aTab.closing) {
- this._endRemoveTab(aTab);
- return;
- }
-
- var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
-
- if (!this._beginRemoveTab(aTab, null, null, true, skipPermitUnload))
- return;
-
- if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
- this.tabContainer._lockTabSizing(aTab);
- else
- this.tabContainer._unlockTabSizing();
-
- if (!animate /* the caller didn't opt in */ ||
- isLastTab ||
- aTab.pinned ||
- aTab.hidden ||
- this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
- aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
- window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
- !Services.prefs.getBoolPref("browser.tabs.animate")) {
- this._endRemoveTab(aTab);
- return;
- }
-
- this.tabContainer._handleTabTelemetryStart(aTab);
-
- this._blurTab(aTab);
- aTab.style.maxWidth = ""; // ensure that fade-out transition happens
- aTab.removeAttribute("fadein");
-
- setTimeout(function(tab, tabbrowser) {
- if (tab.parentNode &&
- window.getComputedStyle(tab).maxWidth == "0.1px") {
- NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
- tabbrowser._endRemoveTab(tab);
- }
- }, 3000, aTab, this);
- ]]>
- </body>
- </method>
-
- <!-- Tab close requests are ignored if the window is closing anyway,
- e.g. when holding Ctrl+W. -->
- <field name="_windowIsClosing">
- false
- </field>
-
- <method name="_beginRemoveTab">
- <parameter name="aTab"/>
- <parameter name="aAdoptedByTab"/>
- <parameter name="aCloseWindowWithLastTab"/>
- <parameter name="aCloseWindowFastpath"/>
- <parameter name="aSkipPermitUnload"/>
- <body>
- <![CDATA[
- if (aTab.closing ||
- this._windowIsClosing)
- return false;
-
- var browser = this.getBrowserForTab(aTab);
-
- if (!aTab._pendingPermitUnload && !aAdoptedByTab && !aSkipPermitUnload) {
- // We need to block while calling permitUnload() because it
- // processes the event queue and may lead to another removeTab()
- // call before permitUnload() returns.
- aTab._pendingPermitUnload = true;
- let {permitUnload, timedOut} = browser.permitUnload();
- delete aTab._pendingPermitUnload;
- // If we were closed during onbeforeunload, we return false now
- // so we don't (try to) close the same tab again. Of course, we
- // also stop if the unload was cancelled by the user:
- if (aTab.closing || (!timedOut && !permitUnload)) {
- // NB: deliberately keep the _closedDuringPermitUnload set to
- // true so we keep exiting early in case of multiple calls.
- return false;
- }
- }
-
-
- var closeWindow = false;
- var newTab = false;
- if (this.tabs.length - this._removingTabs.length == 1) {
- closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
- !window.toolbar.visible ||
- Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
-
- if (closeWindow) {
- // We've already called beforeunload on all the relevant tabs if we get here,
- // so avoid calling it again:
- window.skipNextCanClose = true;
- }
-
- // Closing the tab and replacing it with a blank one is notably slower
- // than closing the window right away. If the caller opts in, take
- // the fast path.
- if (closeWindow &&
- aCloseWindowFastpath &&
- this._removingTabs.length == 0) {
- // This call actually closes the window, unless the user
- // cancels the operation. We are finished here in both cases.
- this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
- return null;
- }
-
- newTab = true;
- }
-
- aTab.closing = true;
- this._removingTabs.push(aTab);
- this._visibleTabs = null; // invalidate cache
-
- // Invalidate hovered tab state tracking for this closing tab.
- if (this.tabContainer._hoveredTab == aTab)
- aTab._mouseleave();
-
- if (newTab)
- this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
- else
- this.tabContainer.updateVisibility();
-
- // We're committed to closing the tab now.
- // Dispatch a notification.
- // We dispatch it before any teardown so that event listeners can
- // inspect the tab that's about to close.
- var evt = new CustomEvent("TabClose", { bubbles: true, detail: { adoptedBy: aAdoptedByTab } });
- aTab.dispatchEvent(evt);
-
- if (!aAdoptedByTab && !gMultiProcessBrowser) {
- // Prevent this tab from showing further dialogs, since we're closing it
- var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
- getInterface(Ci.nsIDOMWindowUtils);
- windowUtils.disableDialogs();
- }
-
- // Remove the tab's filter and progress listener.
- const filter = this._tabFilters.get(aTab);
-
- browser.webProgress.removeProgressListener(filter);
-
- const listener = this._tabListeners.get(aTab);
- filter.removeProgressListener(listener);
- listener.destroy();
-
- if (browser.registeredOpenURI && !aAdoptedByTab) {
- this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
- browser.getAttribute("usercontextid") || 0);
- delete browser.registeredOpenURI;
- }
-
- // We are no longer the primary content area.
- browser.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;
- }
-
- aTab._endRemoveArgs = [closeWindow, newTab];
- return true;
- ]]>
- </body>
- </method>
-
- <method name="_endRemoveTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- if (!aTab || !aTab._endRemoveArgs)
- return;
-
- var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
- aTab._endRemoveArgs = null;
-
- if (this._windowIsClosing) {
- aCloseWindow = false;
- aNewTab = false;
- }
-
- this._lastRelatedTab = null;
-
- // update the UI early for responsiveness
- aTab.collapsed = true;
- this.tabContainer._fillTrailingGap();
- this._blurTab(aTab);
-
- this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
-
- if (aCloseWindow) {
- this._windowIsClosing = true;
- while (this._removingTabs.length)
- this._endRemoveTab(this._removingTabs[0]);
- } else if (!this._windowIsClosing) {
- if (aNewTab)
- focusAndSelectUrlBar();
-
- // workaround for bug 345399
- this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
- }
-
- // We're going to remove the tab and the browser now.
- this._tabFilters.delete(aTab);
- this._tabListeners.delete(aTab);
-
- var browser = this.getBrowserForTab(aTab);
- this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
-
- // Because of the way XBL works (fields just set JS
- // properties on the element) and the code we have in place
- // to preserve the JS objects for any elements that have
- // JS properties set on them, the browser element won't be
- // destroyed until the document goes away. So we force a
- // cleanup ourselves.
- // This has to happen before we remove the child so that the
- // XBL implementation of nsIObserver still works.
- browser.destroy();
-
- var wasPinned = aTab.pinned;
-
- // Remove the tab ...
- this.tabContainer.removeChild(aTab);
-
- // ... and fix up the _tPos properties immediately.
- for (let i = aTab._tPos; i < this.tabs.length; i++)
- this.tabs[i]._tPos = i;
-
- if (!this._windowIsClosing) {
- if (wasPinned)
- this.tabContainer._positionPinnedTabs();
-
- // update tab close buttons state
- this.tabContainer.adjustTabstrip();
-
- setTimeout(function(tabs) {
- tabs._lastTabClosedByMouse = false;
- }, 0, this.tabContainer);
- }
-
- // update tab positional properties and attributes
- this.selectedTab._selected = true;
- this.tabContainer._setPositionalAttributes();
-
- // Removing the panel requires fixing up selectedPanel immediately
- // (see below), which would be hindered by the potentially expensive
- // browser removal. So we remove the browser and the panel in two
- // steps.
-
- var panel = this.getNotificationBox(browser);
-
- // In the multi-process case, it's possible an asynchronous tab switch
- // is still underway. If so, then it's possible that the last visible
- // browser is the one we're in the process of removing. There's the
- // risk of displaying preloaded browsers that are at the end of the
- // deck if we remove the browser before the switch is complete, so
- // we alert the switcher in order to show a spinner instead.
- if (this._switcher) {
- this._switcher.onTabRemoved(aTab);
- }
-
- // This will unload the document. An unload handler could remove
- // dependant tabs, so it's important that the tabbrowser is now in
- // a consistent state (tab removed, tab positions updated, etc.).
- browser.parentNode.removeChild(browser);
-
- // Release the browser in case something is erroneously holding a
- // reference to the tab after its removal.
- this._tabForBrowser.delete(aTab.linkedBrowser);
- aTab.linkedBrowser = null;
-
- // As the browser is removed, the removal of a dependent document can
- // cause the whole window to close. So at this point, it's possible
- // that the binding is destructed.
- if (this.mTabBox) {
- this.mPanelContainer.removeChild(panel);
- }
-
- if (aCloseWindow)
- this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
- ]]>
- </body>
- </method>
-
- <method name="_blurTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- if (!aTab.selected)
- return;
-
- if (aTab.owner &&
- !aTab.owner.hidden &&
- !aTab.owner.closing &&
- Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
- this.selectedTab = aTab.owner;
- return;
- }
-
- // Switch to a visible tab unless there aren't any others remaining
- let remainingTabs = this.visibleTabs;
- let numTabs = remainingTabs.length;
- if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
- remainingTabs = Array.filter(this.tabs, function(tab) {
- return !tab.closing;
- }, this);
- }
-
- // Try to find a remaining tab that comes after the given tab
- var tab = aTab;
- do {
- tab = tab.nextSibling;
- } while (tab && remainingTabs.indexOf(tab) == -1);
-
- if (!tab) {
- tab = aTab;
-
- do {
- tab = tab.previousSibling;
- } while (tab && remainingTabs.indexOf(tab) == -1);
- }
-
- this.selectedTab = tab;
- ]]>
- </body>
- </method>
-
- <method name="swapBrowsersAndCloseOther">
- <parameter name="aOurTab"/>
- <parameter name="aOtherTab"/>
- <body>
- <![CDATA[
- // Do not allow transfering a private tab to a non-private window
- // and vice versa.
- if (PrivateBrowsingUtils.isWindowPrivate(window) !=
- PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
- return;
-
- let ourBrowser = this.getBrowserForTab(aOurTab);
- let otherBrowser = aOtherTab.linkedBrowser;
-
- // Can't swap between chrome and content processes.
- if (ourBrowser.isRemoteBrowser != otherBrowser.isRemoteBrowser)
- return;
-
- // Keep the userContextId if set on other browser
- if (otherBrowser.hasAttribute("usercontextid")) {
- ourBrowser.setAttribute("usercontextid", otherBrowser.getAttribute("usercontextid"));
- }
-
- // That's gBrowser for the other window, not the tab's browser!
- var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
- var isPending = aOtherTab.hasAttribute("pending");
-
- let otherTabListener = remoteBrowser._tabListeners.get(aOtherTab);
- let stateFlags = otherTabListener.mStateFlags;
-
- // Expedite the removal of the icon if it was already scheduled.
- if (aOtherTab._soundPlayingAttrRemovalTimer) {
- clearTimeout(aOtherTab._soundPlayingAttrRemovalTimer);
- aOtherTab._soundPlayingAttrRemovalTimer = 0;
- aOtherTab.removeAttribute("soundplaying");
- remoteBrowser._tabAttrModified(aOtherTab, ["soundplaying"]);
- }
-
- // First, start teardown of the other browser. Make sure to not
- // fire the beforeunload event in the process. Close the other
- // window if this was its last tab.
- if (!remoteBrowser._beginRemoveTab(aOtherTab, aOurTab, true))
- return;
-
- let modifiedAttrs = [];
- if (aOtherTab.hasAttribute("muted")) {
- aOurTab.setAttribute("muted", "true");
- aOurTab.muteReason = aOtherTab.muteReason;
- ourBrowser.mute();
- modifiedAttrs.push("muted");
- }
- if (aOtherTab.hasAttribute("soundplaying")) {
- aOurTab.setAttribute("soundplaying", "true");
- modifiedAttrs.push("soundplaying");
- }
- if (aOtherTab.hasAttribute("usercontextid")) {
- aOurTab.setUserContextId(aOtherTab.getAttribute("usercontextid"));
- modifiedAttrs.push("usercontextid");
- }
- if (aOtherTab.hasAttribute("sharing")) {
- aOurTab.setAttribute("sharing", aOtherTab.getAttribute("sharing"));
- modifiedAttrs.push("sharing");
- aOurTab._sharingState = aOtherTab._sharingState;
- webrtcUI.swapBrowserForNotification(otherBrowser, ourBrowser);
- }
-
- SitePermissions.copyTemporaryPermissions(otherBrowser, ourBrowser);
-
- // If the other tab is pending (i.e. has not been restored, yet)
- // then do not switch docShells but retrieve the other tab's state
- // and apply it to our tab.
- if (isPending) {
- SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
-
- // Make sure to unregister any open URIs.
- this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
- } else {
- // Workarounds for bug 458697
- // Icon might have been set on DOMLinkAdded, don't override that.
- if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
- this.setIcon(aOurTab, otherBrowser.mIconURL, otherBrowser.contentPrincipal);
- var isBusy = aOtherTab.hasAttribute("busy");
- if (isBusy) {
- aOurTab.setAttribute("busy", "true");
- modifiedAttrs.push("busy");
- if (aOurTab.selected)
- this.mIsBusy = true;
- }
-
- this._swapBrowserDocShells(aOurTab, otherBrowser, Ci.nsIBrowser.SWAP_DEFAULT, stateFlags);
- }
-
- // Unregister the previously opened URI
- if (otherBrowser.registeredOpenURI) {
- this._unifiedComplete.unregisterOpenPage(otherBrowser.registeredOpenURI,
- otherBrowser.getAttribute("usercontextid") || 0);
- delete otherBrowser.registeredOpenURI;
- }
-
- // Handle findbar data (if any)
- let otherFindBar = aOtherTab._findBar;
- if (otherFindBar &&
- otherFindBar.findMode == otherFindBar.FIND_NORMAL) {
- let ourFindBar = this.getFindBar(aOurTab);
- ourFindBar._findField.value = otherFindBar._findField.value;
- if (!otherFindBar.hidden)
- ourFindBar.onFindCommand();
- }
-
- // Finish tearing down the tab that's going away.
- remoteBrowser._endRemoveTab(aOtherTab);
-
- if (isBusy)
- this.setTabTitleLoading(aOurTab);
- else
- this.setTabTitle(aOurTab);
-
- // If the tab was already selected (this happpens in the scenario
- // of replaceTabWithWindow), notify onLocationChange, etc.
- if (aOurTab.selected)
- this.updateCurrentBrowser(true);
-
- if (modifiedAttrs.length) {
- this._tabAttrModified(aOurTab, modifiedAttrs);
- }
- ]]>
- </body>
- </method>
-
- <method name="swapBrowsers">
- <parameter name="aOurTab"/>
- <parameter name="aOtherTab"/>
- <parameter name="aFlags"/>
- <body>
- <![CDATA[
- let otherBrowser = aOtherTab.linkedBrowser;
- let otherTabBrowser = otherBrowser.getTabBrowser();
-
- // We aren't closing the other tab so, we also need to swap its tablisteners.
- let filter = otherTabBrowser._tabFilters.get(aOtherTab);
- let tabListener = otherTabBrowser._tabListeners.get(aOtherTab);
- otherBrowser.webProgress.removeProgressListener(filter);
- filter.removeProgressListener(tabListener);
-
- // Perform the docshell swap through the common mechanism.
- this._swapBrowserDocShells(aOurTab, otherBrowser, aFlags);
-
- // Restore the listeners for the swapped in tab.
- tabListener = otherTabBrowser.mTabProgressListener(aOtherTab, otherBrowser, false, false);
- otherTabBrowser._tabListeners.set(aOtherTab, tabListener);
-
- const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
- filter.addProgressListener(tabListener, notifyAll);
- otherBrowser.webProgress.addProgressListener(filter, notifyAll);
- ]]>
- </body>
- </method>
-
- <method name="_swapBrowserDocShells">
- <parameter name="aOurTab"/>
- <parameter name="aOtherBrowser"/>
- <parameter name="aFlags"/>
- <parameter name="aStateFlags"/>
- <body>
- <![CDATA[
- // Unhook our progress listener
- const filter = this._tabFilters.get(aOurTab);
- let tabListener = this._tabListeners.get(aOurTab);
- let ourBrowser = this.getBrowserForTab(aOurTab);
- ourBrowser.webProgress.removeProgressListener(filter);
- filter.removeProgressListener(tabListener);
-
- // Make sure to unregister any open URIs.
- this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
-
- // Unmap old outerWindowIDs.
- this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
- let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
- if (remoteBrowser) {
- remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
- }
-
- // If switcher is active, it will intercept swap events and
- // react as needed.
- if (!this._switcher) {
- aOtherBrowser.docShellIsActive = this.shouldActivateDocShell(ourBrowser);
- }
-
- // Swap the docshells
- ourBrowser.swapDocShells(aOtherBrowser);
-
- if (ourBrowser.isRemoteBrowser) {
- // Switch outerWindowIDs for remote browsers.
- let ourOuterWindowID = ourBrowser._outerWindowID;
- ourBrowser._outerWindowID = aOtherBrowser._outerWindowID;
- aOtherBrowser._outerWindowID = ourOuterWindowID;
- }
-
- // Register new outerWindowIDs.
- this._outerWindowIDBrowserMap.set(ourBrowser.outerWindowID, ourBrowser);
- if (remoteBrowser) {
- remoteBrowser._outerWindowIDBrowserMap.set(aOtherBrowser.outerWindowID, aOtherBrowser);
- }
-
- if (!(aFlags & Ci.nsIBrowser.SWAP_KEEP_PERMANENT_KEY)) {
- // Swap permanentKey properties.
- let ourPermanentKey = ourBrowser.permanentKey;
- ourBrowser.permanentKey = aOtherBrowser.permanentKey;
- aOtherBrowser.permanentKey = ourPermanentKey;
- aOurTab.permanentKey = ourBrowser.permanentKey;
- if (remoteBrowser) {
- let otherTab = remoteBrowser.getTabForBrowser(aOtherBrowser);
- if (otherTab) {
- otherTab.permanentKey = aOtherBrowser.permanentKey;
- }
- }
- }
-
- // Restore the progress listener
- tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false, false,
- aStateFlags);
- this._tabListeners.set(aOurTab, tabListener);
-
- const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
- filter.addProgressListener(tabListener, notifyAll);
- ourBrowser.webProgress.addProgressListener(filter, notifyAll);
- ]]>
- </body>
- </method>
-
- <method name="_swapRegisteredOpenURIs">
- <parameter name="aOurBrowser"/>
- <parameter name="aOtherBrowser"/>
- <body>
- <![CDATA[
- // Swap the registeredOpenURI properties of the two browsers
- let tmp = aOurBrowser.registeredOpenURI;
- delete aOurBrowser.registeredOpenURI;
- if (aOtherBrowser.registeredOpenURI) {
- aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
- delete aOtherBrowser.registeredOpenURI;
- }
- if (tmp) {
- aOtherBrowser.registeredOpenURI = tmp;
- }
- ]]>
- </body>
- </method>
-
- <method name="reloadAllTabs">
- <body>
- <![CDATA[
- let tabs = this.visibleTabs;
- let l = tabs.length;
- for (var i = 0; i < l; i++) {
- try {
- this.getBrowserForTab(tabs[i]).reload();
- } catch (e) {
- // ignore failure to reload so others will be reloaded
- }
- }
- ]]>
- </body>
- </method>
-
- <method name="reloadTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- let browser = this.getBrowserForTab(aTab);
- // Reset temporary permissions on the current tab. This is done here
- // because we only want to reset permissions on user reload.
- SitePermissions.clearTemporaryPermissions(browser);
- browser.reload();
- ]]>
- </body>
- </method>
-
- <method name="addProgressListener">
- <parameter name="aListener"/>
- <body>
- <![CDATA[
- if (arguments.length != 1) {
- Components.utils.reportError("gBrowser.addProgressListener was " +
- "called with a second argument, " +
- "which is not supported. See bug " +
- "608628. Call stack: " + new Error().stack);
- }
-
- this.mProgressListeners.push(aListener);
- ]]>
- </body>
- </method>
-
- <method name="removeProgressListener">
- <parameter name="aListener"/>
- <body>
- <![CDATA[
- this.mProgressListeners =
- this.mProgressListeners.filter(l => l != aListener);
- ]]>
- </body>
- </method>
-
- <method name="addTabsProgressListener">
- <parameter name="aListener"/>
- <body>
- this.mTabsProgressListeners.push(aListener);
- </body>
- </method>
-
- <method name="removeTabsProgressListener">
- <parameter name="aListener"/>
- <body>
- <![CDATA[
- this.mTabsProgressListeners =
- this.mTabsProgressListeners.filter(l => l != aListener);
- ]]>
- </body>
- </method>
-
- <method name="getBrowserForTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- return aTab.linkedBrowser;
- ]]>
- </body>
- </method>
-
- <method name="showOnlyTheseTabs">
- <parameter name="aTabs"/>
- <body>
- <![CDATA[
- for (let tab of this.tabs) {
- if (aTabs.indexOf(tab) == -1)
- this.hideTab(tab);
- else
- this.showTab(tab);
- }
-
- this.tabContainer._handleTabSelect(false);
- ]]>
- </body>
- </method>
-
- <method name="showTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- if (aTab.hidden) {
- aTab.removeAttribute("hidden");
- this._visibleTabs = null; // invalidate cache
-
- this.tabContainer.adjustTabstrip();
-
- this.tabContainer._setPositionalAttributes();
-
- let event = document.createEvent("Events");
- event.initEvent("TabShow", true, false);
- aTab.dispatchEvent(event);
- }
- ]]>
- </body>
- </method>
-
- <method name="hideTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
- !aTab.closing) {
- aTab.setAttribute("hidden", "true");
- this._visibleTabs = null; // invalidate cache
-
- this.tabContainer.adjustTabstrip();
-
- this.tabContainer._setPositionalAttributes();
-
- let event = document.createEvent("Events");
- event.initEvent("TabHide", true, false);
- aTab.dispatchEvent(event);
- }
- ]]>
- </body>
- </method>
-
- <method name="selectTabAtIndex">
- <parameter name="aIndex"/>
- <parameter name="aEvent"/>
- <body>
- <![CDATA[
- let tabs = this.visibleTabs;
-
- // count backwards for aIndex < 0
- if (aIndex < 0) {
- aIndex += tabs.length;
- // clamp at index 0 if still negative.
- if (aIndex < 0)
- aIndex = 0;
- } else if (aIndex >= tabs.length) {
- // clamp at right-most tab if out of range.
- aIndex = tabs.length - 1;
- }
-
- this.selectedTab = tabs[aIndex];
-
- if (aEvent) {
- aEvent.preventDefault();
- aEvent.stopPropagation();
- }
- ]]>
- </body>
- </method>
-
- <property name="selectedTab">
- <getter>
- return this.mCurrentTab;
- </getter>
- <setter>
- <![CDATA[
- if (gNavToolbox.collapsed) {
- return this.mTabBox.selectedTab;
- }
- // Update the tab
- this.mTabBox.selectedTab = val;
- return val;
- ]]>
- </setter>
- </property>
-
- <property name="selectedBrowser"
- onget="return this.mCurrentBrowser;"
- readonly="true"/>
-
- <field name="browsers" readonly="true">
- <![CDATA[
- // This defines a proxy which allows us to access browsers by
- // index without actually creating a full array of browsers.
- new Proxy([], {
- has: (target, name) => {
- if (typeof name == "string" && Number.isInteger(parseInt(name))) {
- return (name in this.tabs);
- }
- return false;
- },
- get: (target, name) => {
- if (name == "length") {
- return this.tabs.length;
- }
- if (typeof name == "string" && Number.isInteger(parseInt(name))) {
- if (!(name in this.tabs)) {
- return undefined;
- }
- return this.tabs[name].linkedBrowser;
- }
- return target[name];
- }
- });
- ]]>
- </field>
-
- <!-- Moves a tab to a new browser window, unless it's already the only tab
- in the current window, in which case this will do nothing. -->
- <method name="replaceTabWithWindow">
- <parameter name="aTab"/>
- <parameter name="aOptions"/>
- <body>
- <![CDATA[
- if (this.tabs.length == 1)
- return null;
-
- var options = "chrome,dialog=no,all";
- for (var name in aOptions)
- options += "," + name + "=" + aOptions[name];
-
- // tell a new window to take the "dropped" tab
- return window.openDialog(getBrowserURL(), "_blank", options, aTab);
- ]]>
- </body>
- </method>
-
- <!-- Opens a given tab to a non-remote window. -->
- <method name="openNonRemoteWindow">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
-#ifndef E10S_TESTING_ONLY
- throw "This method is intended only for e10s testing!";
-#endif
- let url = aTab.linkedBrowser.currentURI.spec;
- return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
- ]]>
- </body>
- </method>
-
- <method name="moveTabTo">
- <parameter name="aTab"/>
- <parameter name="aIndex"/>
- <body>
- <![CDATA[
- var oldPosition = aTab._tPos;
- if (oldPosition == aIndex)
- return;
-
- // Don't allow mixing pinned and unpinned tabs.
- if (aTab.pinned)
- aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
- else
- aIndex = Math.max(aIndex, this._numPinnedTabs);
- if (oldPosition == aIndex)
- return;
-
- this._lastRelatedTab = null;
-
- let wasFocused = (document.activeElement == this.mCurrentTab);
-
- aIndex = aIndex < aTab._tPos ? aIndex : aIndex + 1;
-
- // invalidate cache
- this._visibleTabs = 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
- this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
-
- for (let i = 0; i < this.tabs.length; i++) {
- this.tabs[i]._tPos = i;
- this.tabs[i]._selected = false;
- }
-
- // If we're in the midst of an async tab switch while calling
- // moveTabTo, we can get into a case where _visuallySelected
- // is set to true on two different tabs.
- //
- // What we want to do in moveTabTo is to remove logical selection
- // from all tabs, and then re-add logical selection to mCurrentTab
- // (and visual selection as well if we're not running with e10s, which
- // setting _selected will do automatically).
- //
- // If we're running with e10s, then the visual selection will not
- // be changed, which is fine, since if we weren't in the midst of a
- // tab switch, the previously visually selected tab should still be
- // correct, and if we are in the midst of a tab switch, then the async
- // tab switcher will set the visually selected tab once the tab switch
- // has completed.
- this.mCurrentTab._selected = true;
-
- if (wasFocused)
- this.mCurrentTab.focus();
-
- this.tabContainer._handleTabSelect(false);
-
- if (aTab.pinned)
- this.tabContainer._positionPinnedTabs();
-
- this.tabContainer._setPositionalAttributes();
-
- var evt = document.createEvent("UIEvents");
- evt.initUIEvent("TabMove", true, false, window, oldPosition);
- aTab.dispatchEvent(evt);
- ]]>
- </body>
- </method>
-
- <method name="moveTabForward">
- <body>
- <![CDATA[
- let nextTab = this.mCurrentTab.nextSibling;
- while (nextTab && nextTab.hidden)
- nextTab = nextTab.nextSibling;
-
- if (nextTab)
- this.moveTabTo(this.mCurrentTab, nextTab._tPos);
- else if (this.arrowKeysShouldWrap)
- this.moveTabToStart();
- ]]>
- </body>
- </method>
-
- <!-- Adopts a tab from another browser window, and inserts it at aIndex -->
- <method name="adoptTab">
- <parameter name="aTab"/>
- <parameter name="aIndex"/>
- <parameter name="aSelectTab"/>
- <body>
- <![CDATA[
- // Swap the dropped tab with a new one we create and then close
- // it in the other window (making it seem to have moved between
- // windows).
- let params = { eventDetail: { adoptedTab: aTab } };
- if (aTab.hasAttribute("usercontextid")) {
- // new tab must have the same usercontextid as the old one
- params.userContextId = aTab.getAttribute("usercontextid");
- }
- let newTab = this.addTab("about:blank", params);
- let newBrowser = this.getBrowserForTab(newTab);
- let newURL = aTab.linkedBrowser.currentURI.spec;
-
- // If we're an e10s browser window, an exception will be thrown
- // if we attempt to drag a non-remote browser in, so we need to
- // ensure that the remoteness of the newly created browser is
- // appropriate for the URL of the tab being dragged in.
- this.updateBrowserRemotenessByURL(newBrowser, newURL);
-
- // Stop the about:blank load.
- newBrowser.stop();
- // Make sure it has a docshell.
- newBrowser.docShell;
-
- let numPinned = this._numPinnedTabs;
- if (aIndex < numPinned || (aTab.pinned && aIndex == numPinned)) {
- this.pinTab(newTab);
- }
-
- this.moveTabTo(newTab, aIndex);
-
- // We need to select the tab before calling swapBrowsersAndCloseOther
- // so that window.content in chrome windows points to the right tab
- // when pagehide/show events are fired. This is no longer necessary
- // for any exiting browser code, but it may be necessary for add-on
- // compatibility.
- if (aSelectTab) {
- this.selectedTab = newTab;
- }
-
- aTab.parentNode._finishAnimateTabMove();
- this.swapBrowsersAndCloseOther(newTab, aTab);
-
- if (aSelectTab) {
- // Call updateCurrentBrowser to make sure the URL bar is up to date
- // for our new tab after we've done swapBrowsersAndCloseOther.
- this.updateCurrentBrowser(true);
- }
-
- return newTab;
- ]]>
- </body>
- </method>
-
-
- <method name="moveTabBackward">
- <body>
- <![CDATA[
- let previousTab = this.mCurrentTab.previousSibling;
- while (previousTab && previousTab.hidden)
- previousTab = previousTab.previousSibling;
-
- if (previousTab)
- this.moveTabTo(this.mCurrentTab, previousTab._tPos);
- else if (this.arrowKeysShouldWrap)
- this.moveTabToEnd();
- ]]>
- </body>
- </method>
-
- <method name="moveTabToStart">
- <body>
- <![CDATA[
- var tabPos = this.mCurrentTab._tPos;
- if (tabPos > 0)
- this.moveTabTo(this.mCurrentTab, 0);
- ]]>
- </body>
- </method>
-
- <method name="moveTabToEnd">
- <body>
- <![CDATA[
- var tabPos = this.mCurrentTab._tPos;
- if (tabPos < this.browsers.length - 1)
- this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
- ]]>
- </body>
- </method>
-
- <method name="moveTabOver">
- <parameter name="aEvent"/>
- <body>
- <![CDATA[
- var direction = window.getComputedStyle(this.parentNode, null).direction;
- if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
- (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
- this.moveTabForward();
- else
- this.moveTabBackward();
- ]]>
- </body>
- </method>
-
- <method name="duplicateTab">
- <parameter name="aTab"/><!-- can be from a different window as well -->
- <parameter name="aRestoreTabImmediately"/><!-- can defer loading of the tab contents -->
- <body>
- <![CDATA[
- return SessionStore.duplicateTab(window, aTab, 0, aRestoreTabImmediately);
- ]]>
- </body>
- </method>
-
- <!--
- List of browsers whose docshells must be active in order for print preview
- to work.
- -->
- <field name="_printPreviewBrowsers">
- new Set()
- </field>
-
- <method name="activateBrowserForPrintPreview">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- this._printPreviewBrowsers.add(aBrowser);
- if (this._switcher) {
- this._switcher.activateBrowserForPrintPreview(aBrowser);
- }
- aBrowser.docShellIsActive = true;
- ]]>
- </body>
- </method>
-
- <method name="deactivatePrintPreviewBrowsers">
- <body>
- <![CDATA[
- let browsers = this._printPreviewBrowsers;
- this._printPreviewBrowsers = new Set();
- for (let browser of browsers) {
- browser.docShellIsActive = this.shouldActivateDocShell(browser);
- }
- ]]>
- </body>
- </method>
-
- <!--
- Returns true if a given browser's docshell should be active.
- -->
- <method name="shouldActivateDocShell">
- <parameter name="aBrowser"/>
- <body>
- <![CDATA[
- if (this._switcher) {
- return this._switcher.shouldActivateDocShell(aBrowser);
- }
- return (aBrowser == this.selectedBrowser &&
- window.windowState != window.STATE_MINIMIZED) ||
- this._printPreviewBrowsers.has(aBrowser);
- ]]>
- </body>
- </method>
-
- <!--
- The tab switcher is responsible for asynchronously switching
- tabs in e10s. It waits until the new tab is ready (i.e., the
- layer tree is available) before switching to it. Then it
- unloads the layer tree for the old tab.
-
- The tab switcher is a state machine. For each tab, it
- maintains state about whether the layer tree for the tab is
- available, being loaded, being unloaded, or unavailable. It
- also keeps track of the tab currently being displayed, the tab
- it's trying to load, and the tab the user has asked to switch
- to. The switcher object is created upon tab switch. It is
- released when there are no pending tabs to load or unload.
-
- The following general principles have guided the design:
-
- 1. We only request one layer tree at a time. If the user
- switches to a different tab while waiting, we don't request
- the new layer tree until the old tab has loaded or timed out.
-
- 2. If loading the layers for a tab times out, we show the
- spinner and possibly request the layer tree for another tab if
- the user has requested one.
-
- 3. We discard layer trees on a delay. This way, if the user is
- switching among the same tabs frequently, we don't continually
- load the same tabs.
-
- It's important that we always show either the spinner or a tab
- whose layers are available. Otherwise the compositor will draw
- an entirely black frame, which is very jarring. To ensure this
- never happens when switching away from a tab, we assume the
- old tab might still be drawn until a MozAfterPaint event
- occurs. Because layout and compositing happen asynchronously,
- we don't have any other way of knowing when the switch
- actually takes place. Therefore, we don't unload the old tab
- until the next MozAfterPaint event.
- -->
- <field name="_switcher">null</field>
- <method name="_getSwitcher">
- <body><![CDATA[
- if (this._switcher) {
- return this._switcher;
- }
-
- let switcher = {
- // How long to wait for a tab's layers to load. After this
- // time elapses, we're free to put up the spinner and start
- // trying to load a different tab.
- TAB_SWITCH_TIMEOUT: 400 /* ms */,
-
- // When the user hasn't switched tabs for this long, we unload
- // layers for all tabs that aren't in use.
- UNLOAD_DELAY: 300 /* ms */,
-
- // The next three tabs form the principal state variables.
- // See the assertions in postActions for their invariants.
-
- // Tab the user requested most recently.
- requestedTab: this.selectedTab,
-
- // Tab we're currently trying to load.
- loadingTab: null,
-
- // We show this tab in case the requestedTab hasn't loaded yet.
- lastVisibleTab: this.selectedTab,
-
- // Auxilliary state variables:
-
- visibleTab: this.selectedTab, // Tab that's on screen.
- spinnerTab: null, // Tab showing a spinner.
- originalTab: this.selectedTab, // Tab that we started on.
-
- tabbrowser: this, // Reference to gBrowser.
- loadTimer: null, // TAB_SWITCH_TIMEOUT nsITimer instance.
- unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
-
- // Map from tabs to STATE_* (below).
- tabState: new Map(),
-
- // True if we're in the midst of switching tabs.
- switchInProgress: false,
-
- // Keep an exact list of content processes (tabParent) in which
- // we're actively suppressing the display port. This gives a robust
- // way to make sure we don't forget to un-suppress.
- activeSuppressDisplayport: new Set(),
-
- // Set of tabs that might be visible right now. We maintain
- // this set because we can't be sure when a tab is actually
- // drawn. A tab is added to this set when we ask to make it
- // visible. All tabs but the most recently shown tab are
- // removed from the set upon MozAfterPaint.
- maybeVisibleTabs: new Set([this.selectedTab]),
-
- STATE_UNLOADED: 0,
- STATE_LOADING: 1,
- STATE_LOADED: 2,
- STATE_UNLOADING: 3,
-
- // re-entrancy guard:
- _processing: false,
-
- // Wraps nsITimer. Must not use the vanilla setTimeout and
- // clearTimeout, because they will be blocked by nsIPromptService
- // dialogs.
- setTimer(callback, timeout) {
- let event = {
- notify: callback
- };
-
- var timer = Cc["@mozilla.org/timer;1"]
- .createInstance(Components.interfaces.nsITimer);
- timer.initWithCallback(event, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
- return timer;
- },
-
- clearTimer(timer) {
- timer.cancel();
- },
-
- getTabState(tab) {
- let state = this.tabState.get(tab);
- if (state === undefined) {
- return this.STATE_UNLOADED;
- }
- return state;
- },
-
- setTabStateNoAction(tab, state) {
- if (state == this.STATE_UNLOADED) {
- this.tabState.delete(tab);
- } else {
- this.tabState.set(tab, state);
- }
- },
-
- setTabState(tab, state) {
- this.setTabStateNoAction(tab, state);
-
- let browser = tab.linkedBrowser;
- let {tabParent} = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
- if (state == this.STATE_LOADING) {
- this.assert(!this.minimized);
- browser.docShellIsActive = true;
- if (!tabParent) {
- this.onLayersReady(browser);
- }
- } else if (state == this.STATE_UNLOADING) {
- browser.docShellIsActive = false;
- if (!tabParent) {
- this.onLayersCleared(browser);
- }
- }
- },
-
- get minimized() {
- return window.windowState == window.STATE_MINIMIZED;
- },
-
- init() {
- this.log("START");
-
- // If we minimized the window before the switcher was activated,
- // we might have set the preserveLayers flag for the current
- // browser. Let's clear it.
- this.tabbrowser.mCurrentBrowser.preserveLayers(false);
-
- window.addEventListener("MozAfterPaint", this);
- window.addEventListener("MozLayerTreeReady", this);
- window.addEventListener("MozLayerTreeCleared", this);
- window.addEventListener("TabRemotenessChange", this);
- window.addEventListener("sizemodechange", this);
- window.addEventListener("SwapDocShells", this, true);
- window.addEventListener("EndSwapDocShells", this, true);
- if (!this.minimized) {
- this.setTabState(this.requestedTab, this.STATE_LOADED);
- }
- },
-
- destroy() {
- if (this.unloadTimer) {
- this.clearTimer(this.unloadTimer);
- this.unloadTimer = null;
- }
- if (this.loadTimer) {
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
-
- window.removeEventListener("MozAfterPaint", this);
- window.removeEventListener("MozLayerTreeReady", this);
- window.removeEventListener("MozLayerTreeCleared", this);
- window.removeEventListener("TabRemotenessChange", this);
- window.removeEventListener("sizemodechange", this);
- window.removeEventListener("SwapDocShells", this, true);
- window.removeEventListener("EndSwapDocShells", this, true);
-
- this.tabbrowser._switcher = null;
-
- this.activeSuppressDisplayport.forEach(function(tabParent) {
- tabParent.suppressDisplayport(false);
- });
- this.activeSuppressDisplayport.clear();
- },
-
- finish() {
- this.log("FINISH");
-
- this.assert(this.tabbrowser._switcher);
- this.assert(this.tabbrowser._switcher === this);
- this.assert(!this.spinnerTab);
- this.assert(!this.loadTimer);
- this.assert(!this.loadingTab);
- this.assert(this.lastVisibleTab === this.requestedTab);
- this.assert(this.minimized || this.getTabState(this.requestedTab) == this.STATE_LOADED);
-
- this.destroy();
-
- let toBrowser = this.requestedTab.linkedBrowser;
- toBrowser.setAttribute("primary", "true");
-
- this.tabbrowser._adjustFocusAfterTabSwitch(this.requestedTab);
-
- let fromBrowser = this.originalTab.linkedBrowser;
- // It's possible that the tab we're switching from closed
- // before we were able to finalize, in which case, fromBrowser
- // doesn't exist.
- if (fromBrowser) {
- fromBrowser.removeAttribute("primary");
- }
-
- let event = new CustomEvent("TabSwitchDone", {
- bubbles: true,
- cancelable: true
- });
- this.tabbrowser.dispatchEvent(event);
- },
-
- // This function is called after all the main state changes to
- // make sure we display the right tab.
- updateDisplay() {
- // Figure out which tab we actually want visible right now.
- let showTab = null;
- if (this.getTabState(this.requestedTab) != this.STATE_LOADED &&
- this.lastVisibleTab && this.loadTimer) {
- // If we can't show the requestedTab, and lastVisibleTab is
- // available, show it.
- showTab = this.lastVisibleTab;
- } else {
- // Show the requested tab. If it's not available, we'll show the spinner.
- showTab = this.requestedTab;
- }
-
- // Show or hide the spinner as needed.
- let needSpinner = this.getTabState(showTab) != this.STATE_LOADED && !this.minimized;
- if (!needSpinner && this.spinnerTab) {
- this.spinnerHidden();
- this.tabbrowser.removeAttribute("pendingpaint");
- this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
- this.spinnerTab = null;
- } else if (needSpinner && this.spinnerTab !== showTab) {
- if (this.spinnerTab) {
- this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
- } else {
- this.spinnerDisplayed();
- }
- this.spinnerTab = showTab;
- this.tabbrowser.setAttribute("pendingpaint", "true");
- this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
- }
-
- // Switch to the tab we've decided to make visible.
- if (this.visibleTab !== showTab) {
- this.visibleTab = showTab;
-
- this.maybeVisibleTabs.add(showTab);
-
- let tabs = this.tabbrowser.mTabBox.tabs;
- let tabPanel = this.tabbrowser.mPanelContainer;
- let showPanel = tabs.getRelatedElement(showTab);
- let index = Array.indexOf(tabPanel.childNodes, showPanel);
- if (index != -1) {
- this.log(`Switch to tab ${index} - ${this.tinfo(showTab)}`);
- tabPanel.setAttribute("selectedIndex", index);
- if (showTab === this.requestedTab) {
- this.tabbrowser._adjustFocusAfterTabSwitch(showTab);
- }
- }
-
- // This doesn't necessarily exist if we're a new window and haven't switched tabs yet
- if (this.lastVisibleTab)
- this.lastVisibleTab._visuallySelected = false;
-
- this.visibleTab._visuallySelected = true;
- }
-
- this.lastVisibleTab = this.visibleTab;
- },
-
- assert(cond) {
- if (!cond) {
- dump("Assertion failure\n" + Error().stack);
-
- // Don't break a user's browser if an assertion fails.
-#ifdef DEBUG
- throw new Error("Assertion failure");
-#endif
- }
- },
-
- // We've decided to try to load requestedTab.
- loadRequestedTab() {
- this.assert(!this.loadTimer);
- this.assert(!this.minimized);
-
- // loadingTab can be non-null here if we timed out loading the current tab.
- // In that case we just overwrite it with a different tab; it's had its chance.
- this.loadingTab = this.requestedTab;
- this.log("Loading tab " + this.tinfo(this.loadingTab));
-
- this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
- this.setTabState(this.requestedTab, this.STATE_LOADING);
- },
-
- // This function runs before every event. It fixes up the state
- // to account for closed tabs.
- preActions() {
- this.assert(this.tabbrowser._switcher);
- this.assert(this.tabbrowser._switcher === this);
-
- for (let [tab, ] of this.tabState) {
- if (!tab.linkedBrowser) {
- this.tabState.delete(tab);
- }
- }
-
- if (this.lastVisibleTab && !this.lastVisibleTab.linkedBrowser) {
- this.lastVisibleTab = null;
- }
- if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
- this.spinnerHidden();
- this.spinnerTab = null;
- }
- if (this.loadingTab && !this.loadingTab.linkedBrowser) {
- this.loadingTab = null;
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
- },
-
- // This code runs after we've responded to an event or requested a new
- // tab. It's expected that we've already updated all the principal
- // state variables. This function takes care of updating any auxilliary
- // state.
- postActions() {
- // Once we finish loading loadingTab, we null it out. So the state should
- // always be LOADING.
- this.assert(!this.loadingTab ||
- this.getTabState(this.loadingTab) == this.STATE_LOADING);
-
- // We guarantee that loadingTab is non-null iff loadTimer is non-null. So
- // the timer is set only when we're loading something.
- this.assert(!this.loadTimer || this.loadingTab);
- this.assert(!this.loadingTab || this.loadTimer);
-
- // If we're not loading anything, try loading the requested tab.
- let requestedState = this.getTabState(this.requestedTab);
- if (!this.loadTimer && !this.minimized &&
- (requestedState == this.STATE_UNLOADED ||
- requestedState == this.STATE_UNLOADING)) {
- this.loadRequestedTab();
- }
-
- // See how many tabs still have work to do.
- let numPending = 0;
- for (let [tab, state] of this.tabState) {
- // Skip print preview browsers since they shouldn't affect tab switching.
- if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
- continue;
- }
-
- if (state == this.STATE_LOADED && tab !== this.requestedTab) {
- numPending++;
- }
- if (state == this.STATE_LOADING || state == this.STATE_UNLOADING) {
- numPending++;
- }
- }
-
- this.updateDisplay();
-
- // It's possible for updateDisplay to trigger one of our own event
- // handlers, which might cause finish() to already have been called.
- // Check for that before calling finish() again.
- if (!this.tabbrowser._switcher) {
- return;
- }
-
- if (numPending == 0) {
- this.finish();
- }
-
- this.logState("done");
- },
-
- // Fires when we're ready to unload unused tabs.
- onUnloadTimeout() {
- this.logState("onUnloadTimeout");
- this.unloadTimer = null;
- this.preActions();
-
- let numPending = 0;
-
- // Unload any tabs that can be unloaded.
- for (let [tab, state] of this.tabState) {
- if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
- continue;
- }
-
- if (state == this.STATE_LOADED &&
- !this.maybeVisibleTabs.has(tab) &&
- tab !== this.lastVisibleTab &&
- tab !== this.loadingTab &&
- tab !== this.requestedTab) {
- this.setTabState(tab, this.STATE_UNLOADING);
- }
-
- if (state != this.STATE_UNLOADED && tab !== this.requestedTab) {
- numPending++;
- }
- }
-
- if (numPending) {
- // Keep the timer going since there may be more tabs to unload.
- this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
- }
-
- this.postActions();
- },
-
- // Fires when an ongoing load has taken too long.
- onLoadTimeout() {
- this.logState("onLoadTimeout");
- this.preActions();
- this.loadTimer = null;
- this.loadingTab = null;
- this.postActions();
- },
-
- // Fires when the layers become available for a tab.
- onLayersReady(browser) {
- let tab = this.tabbrowser.getTabForBrowser(browser);
- this.logState(`onLayersReady(${tab._tPos})`);
-
- this.assert(this.getTabState(tab) == this.STATE_LOADING ||
- this.getTabState(tab) == this.STATE_LOADED);
- this.setTabState(tab, this.STATE_LOADED);
-
- this.maybeFinishTabSwitch();
-
- if (this.loadingTab === tab) {
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- this.loadingTab = null;
- }
- },
-
- // Fires when we paint the screen. Any tab switches we initiated
- // previously are done, so there's no need to keep the old layers
- // around.
- onPaint() {
- this.maybeVisibleTabs.clear();
- this.maybeFinishTabSwitch();
- },
-
- // Called when we're done clearing the layers for a tab.
- onLayersCleared(browser) {
- let tab = this.tabbrowser.getTabForBrowser(browser);
- if (tab) {
- this.logState(`onLayersCleared(${tab._tPos})`);
- this.assert(this.getTabState(tab) == this.STATE_UNLOADING ||
- this.getTabState(tab) == this.STATE_UNLOADED);
- this.setTabState(tab, this.STATE_UNLOADED);
- }
- },
-
- // Called when a tab switches from remote to non-remote. In this case
- // a MozLayerTreeReady notification that we requested may never fire,
- // so we need to simulate it.
- onRemotenessChange(tab) {
- this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`);
- if (!tab.linkedBrowser.isRemoteBrowser) {
- if (this.getTabState(tab) == this.STATE_LOADING) {
- this.onLayersReady(tab.linkedBrowser);
- } else if (this.getTabState(tab) == this.STATE_UNLOADING) {
- this.onLayersCleared(tab.linkedBrowser);
- }
- }
- },
-
- // Called when a tab has been removed, and the browser node is
- // about to be removed from the DOM.
- onTabRemoved(tab) {
- if (this.lastVisibleTab == tab) {
- // The browser that was being presented to the user is
- // going to be removed during this tick of the event loop.
- // This will cause us to show a tab spinner instead.
- this.preActions();
- this.lastVisibleTab = null;
- this.postActions();
- }
- },
-
- onSizeModeChange() {
- if (this.minimized) {
- for (let [tab, state] of this.tabState) {
- // Skip print preview browsers since they shouldn't affect tab switching.
- if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) {
- continue;
- }
-
- if (state == this.STATE_LOADING || state == this.STATE_LOADED) {
- this.setTabState(tab, this.STATE_UNLOADING);
- }
- }
- if (this.loadTimer) {
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
- this.loadingTab = null;
- } else {
- // Do nothing. We'll automatically start loading the requested tab in
- // postActions.
- }
- },
-
- onSwapDocShells(ourBrowser, otherBrowser) {
- // This event fires before the swap. ourBrowser is from
- // our window. We save the state of otherBrowser since ourBrowser
- // needs to take on that state at the end of the swap.
-
- let otherTabbrowser = otherBrowser.ownerDocument.defaultView.gBrowser;
- let otherState;
- if (otherTabbrowser && otherTabbrowser._switcher) {
- let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser);
- otherState = otherTabbrowser._switcher.getTabState(otherTab);
- } else {
- otherState = (otherBrowser.docShellIsActive
- ? this.STATE_LOADED
- : this.STATE_UNLOADED);
- }
-
- if (!this.swapMap) {
- this.swapMap = new WeakMap();
- }
- this.swapMap.set(otherBrowser, otherState);
- },
-
- onEndSwapDocShells(ourBrowser, otherBrowser) {
- // The swap has happened. We reset the loadingTab in
- // case it has been swapped. We also set ourBrowser's state
- // to whatever otherBrowser's state was before the swap.
-
- if (this.loadTimer) {
- // Clearing the load timer means that we will
- // immediately display a spinner if ourBrowser isn't
- // ready yet. Typically it will already be ready
- // though. If it's not, we're probably in a new window,
- // in which case we have no other tabs to display anyway.
- this.clearTimer(this.loadTimer);
- this.loadTimer = null;
- }
- this.loadingTab = null;
-
- let otherState = this.swapMap.get(otherBrowser);
- this.swapMap.delete(otherBrowser);
-
- let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser);
- if (ourTab) {
- this.setTabStateNoAction(ourTab, otherState);
- }
- },
-
- shouldActivateDocShell(browser) {
- let tab = this.tabbrowser.getTabForBrowser(browser);
- let state = this.getTabState(tab);
- return state == this.STATE_LOADING || state == this.STATE_LOADED;
- },
-
- activateBrowserForPrintPreview(browser) {
- let tab = this.tabbrowser.getTabForBrowser(browser);
- this.setTabState(tab, this.STATE_LOADING);
- },
-
- // Called when the user asks to switch to a given tab.
- requestTab(tab) {
- if (tab === this.requestedTab) {
- return;
- }
-
- this.logState("requestTab " + this.tinfo(tab));
- this.startTabSwitch();
-
- this.requestedTab = tab;
-
- let browser = this.requestedTab.linkedBrowser;
- let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
- if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
- fl.tabParent.suppressDisplayport(true);
- this.activeSuppressDisplayport.add(fl.tabParent);
- }
-
- this.preActions();
-
- if (this.unloadTimer) {
- this.clearTimer(this.unloadTimer);
- }
- this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
-
- this.postActions();
- },
-
- handleEvent(event, delayed = false) {
- if (this._processing) {
- this.setTimer(() => this.handleEvent(event, true), 0);
- return;
- }
- if (delayed && this.tabbrowser._switcher != this) {
- // if we delayed processing this event, we might be out of date, in which
- // case we drop the delayed events
- return;
- }
- this._processing = true;
- this.preActions();
-
- if (event.type == "MozLayerTreeReady") {
- this.onLayersReady(event.originalTarget);
- } if (event.type == "MozAfterPaint") {
- this.onPaint();
- } else if (event.type == "MozLayerTreeCleared") {
- this.onLayersCleared(event.originalTarget);
- } else if (event.type == "TabRemotenessChange") {
- this.onRemotenessChange(event.target);
- } else if (event.type == "sizemodechange") {
- this.onSizeModeChange();
- } else if (event.type == "SwapDocShells") {
- this.onSwapDocShells(event.originalTarget, event.detail);
- } else if (event.type == "EndSwapDocShells") {
- this.onEndSwapDocShells(event.originalTarget, event.detail);
- }
-
- this.postActions();
- this._processing = false;
- },
-
- /*
- * Telemetry and Profiler related helpers for recording tab switch
- * timing.
- */
-
- startTabSwitch() {
- TelemetryStopwatch.cancel("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
- TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
- this.addMarker("AsyncTabSwitch:Start");
- this.switchInProgress = true;
- },
-
- /**
- * Something has occurred that might mean that we've completed
- * the tab switch (layers are ready, paints are done, spinners
- * are hidden). This checks to make sure all conditions are
- * satisfied, and then records the tab switch as finished.
- */
- maybeFinishTabSwitch() {
- if (this.switchInProgress && this.requestedTab &&
- this.getTabState(this.requestedTab) == this.STATE_LOADED) {
- // After this point the tab has switched from the content thread's point of view.
- // The changes will be visible after the next refresh driver tick + composite.
- let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
- if (time != -1) {
- TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
- this.log("DEBUG: tab switch time = " + time);
- this.addMarker("AsyncTabSwitch:Finish");
- }
- this.switchInProgress = false;
- }
- },
-
- spinnerDisplayed() {
- this.assert(!this.spinnerTab);
- TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
- // We have a second, similar probe for capturing recordings of
- // when the spinner is displayed for very long periods.
- TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
- this.addMarker("AsyncTabSwitch:SpinnerShown");
- },
-
- spinnerHidden() {
- this.assert(this.spinnerTab);
- this.log("DEBUG: spinner time = " +
- TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
- TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
- TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_LONG_MS", window);
- this.addMarker("AsyncTabSwitch:SpinnerHidden");
- // we do not get a onPaint after displaying the spinner
- this.maybeFinishTabSwitch();
- },
-
- addMarker(marker) {
- if (Services.profiler) {
- Services.profiler.AddMarker(marker);
- }
- },
-
- /*
- * Debug related logging for switcher.
- */
-
- _useDumpForLogging: false,
- _logInit: false,
-
- logging() {
- if (this._useDumpForLogging)
- return true;
- if (this._logInit)
- return this._shouldLog;
- let result = false;
- try {
- result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
- } catch (ex) {
- }
- this._shouldLog = result;
- this._logInit = true;
- return this._shouldLog;
- },
-
- tinfo(tab) {
- if (tab) {
- return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
- }
- return "null";
- },
-
- log(s) {
- if (!this.logging())
- return;
- if (this._useDumpForLogging) {
- dump(s + "\n");
- } else {
- Services.console.logStringMessage(s);
- }
- },
-
- logState(prefix) {
- if (!this.logging())
- return;
-
- let accum = prefix + " ";
- for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
- let tab = this.tabbrowser.tabs[i];
- let state = this.getTabState(tab);
-
- accum += i + ":";
- if (tab === this.lastVisibleTab) accum += "V";
- if (tab === this.loadingTab) accum += "L";
- if (tab === this.requestedTab) accum += "R";
- if (state == this.STATE_LOADED) accum += "(+)";
- if (state == this.STATE_LOADING) accum += "(+?)";
- if (state == this.STATE_UNLOADED) accum += "(-)";
- if (state == this.STATE_UNLOADING) accum += "(-?)";
- accum += " ";
- }
- if (this._useDumpForLogging) {
- dump(accum + "\n");
- } else {
- Services.console.logStringMessage(accum);
- }
- },
- };
- this._switcher = switcher;
- switcher.init();
- return switcher;
- ]]></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"/>
-
- <property name="finder"
- onget="return this.mCurrentBrowser.finder"
- readonly="true"/>
-
- <property name="docShell"
- onget="return this.mCurrentBrowser.docShell"
- readonly="true"/>
-
- <property name="webNavigation"
- onget="return this.mCurrentBrowser.webNavigation"
- readonly="true"/>
-
- <property name="webBrowserFind"
- readonly="true"
- onget="return this.mCurrentBrowser.webBrowserFind"/>
-
- <property name="webProgress"
- readonly="true"
- onget="return this.mCurrentBrowser.webProgress"/>
-
- <property name="contentWindow"
- readonly="true"
- onget="return this.mCurrentBrowser.contentWindow"/>
-
- <property name="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="contentViewerEdit"
- onget="return this.mCurrentBrowser.contentViewerEdit;"
- readonly="true"/>
-
- <property name="contentViewerFile"
- onget="return this.mCurrentBrowser.contentViewerFile;"
- readonly="true"/>
-
- <property name="contentDocument"
- onget="return this.mCurrentBrowser.contentDocument;"
- readonly="true"/>
-
- <property name="contentDocumentAsCPOW"
- onget="return this.mCurrentBrowser.contentDocumentAsCPOW;"
- readonly="true"/>
-
- <property name="contentTitle"
- onget="return this.mCurrentBrowser.contentTitle;"
- readonly="true"/>
-
- <property name="contentPrincipal"
- onget="return this.mCurrentBrowser.contentPrincipal;"
- readonly="true"/>
-
- <property name="securityUI"
- onget="return this.mCurrentBrowser.securityUI;"
- readonly="true"/>
-
- <property name="fullZoom"
- onget="return this.mCurrentBrowser.fullZoom;"
- onset="this.mCurrentBrowser.fullZoom = val;"/>
-
- <property name="textZoom"
- onget="return this.mCurrentBrowser.textZoom;"
- onset="this.mCurrentBrowser.textZoom = val;"/>
-
- <property name="isSyntheticDocument"
- onget="return this.mCurrentBrowser.isSyntheticDocument;"
- readonly="true"/>
-
- <method name="_handleKeyDownEvent">
- <parameter name="aEvent"/>
- <body><![CDATA[
- if (!aEvent.isTrusted) {
- // Don't let untrusted events mess with tabs.
- return;
- }
-
- if (aEvent.altKey)
- return;
-
- // Don't check if the event was already consumed because tab
- // navigation should always work for better user experience.
-
- if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
- switch (aEvent.keyCode) {
- case aEvent.DOM_VK_PAGE_UP:
- this.moveTabBackward();
- aEvent.preventDefault();
- return;
- case aEvent.DOM_VK_PAGE_DOWN:
- this.moveTabForward();
- aEvent.preventDefault();
- return;
- }
- }
-
-#ifndef XP_MACOSX
- if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
- aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
- !this.mCurrentTab.pinned) {
- this.removeCurrentTab({animate: true});
- aEvent.preventDefault();
- }
-#endif
- ]]></body>
- </method>
-
- <method name="_handleKeyPressEventMac">
- <parameter name="aEvent"/>
- <body><![CDATA[
- if (!aEvent.isTrusted) {
- // Don't let untrusted events mess with tabs.
- return;
- }
-
- if (aEvent.altKey)
- return;
-
-#ifdef XP_MACOSX
- if (!aEvent.metaKey)
- return;
-
- var offset = 1;
- switch (aEvent.charCode) {
- case "}".charCodeAt(0):
- offset = -1;
- case "{".charCodeAt(0):
- if (window.getComputedStyle(this, null).direction == "ltr")
- offset *= -1;
- this.tabContainer.advanceSelectedTab(offset, true);
- aEvent.preventDefault();
- }
-#endif
- ]]></body>
- </method>
-
- <property name="userTypedValue"
- onget="return this.mCurrentBrowser.userTypedValue;"
- onset="return this.mCurrentBrowser.userTypedValue = val;"/>
-
- <method name="createTooltip">
- <parameter name="event"/>
- <body><![CDATA[
- event.stopPropagation();
- var tab = document.tooltipNode;
- if (tab.localName != "tab") {
- event.preventDefault();
- return;
- }
-
- let stringWithShortcut = (stringId, keyElemId) => {
- let keyElem = document.getElementById(keyElemId);
- let shortcut = ShortcutUtils.prettifyShortcut(keyElem);
- return this.mStringBundle.getFormattedString(stringId, [shortcut]);
- };
-
- var label;
- if (tab.mOverCloseButton) {
- label = tab.selected ?
- stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") :
- this.mStringBundle.getString("tabs.closeTab.tooltip");
- } else if (tab._overPlayingIcon) {
- let stringID;
- if (tab.selected) {
- stringID = tab.linkedBrowser.audioMuted ?
- "tabs.unmuteAudio.tooltip" :
- "tabs.muteAudio.tooltip";
- label = stringWithShortcut(stringID, "key_toggleMute");
- } else {
- if (tab.linkedBrowser.audioBlocked) {
- stringID = "tabs.unblockAudio.tooltip";
- } else {
- stringID = tab.linkedBrowser.audioMuted ?
- "tabs.unmuteAudio.background.tooltip" :
- "tabs.muteAudio.background.tooltip";
- }
-
- label = this.mStringBundle.getString(stringID);
- }
- } else {
-#ifdef E10S_TESTING_ONLY
- label = tab.getAttribute("label") + (tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");
-#else
- label = tab.getAttribute("label");
-#endif
- if (tab.userContextId) {
- label = this.mStringBundle.getFormattedString("tabs.containers.tooltip", [label, ContextualIdentityService.getUserContextLabel(tab.userContextId)]);
- }
- }
-
- event.target.setAttribute("label", label);
- ]]></body>
- </method>
-
- <method name="handleEvent">
- <parameter name="aEvent"/>
- <body><![CDATA[
- switch (aEvent.type) {
- case "keydown":
- this._handleKeyDownEvent(aEvent);
- break;
- case "keypress":
- this._handleKeyPressEventMac(aEvent);
- break;
- case "sizemodechange":
- if (aEvent.target == window && !this._switcher) {
- this.mCurrentBrowser.preserveLayers(window.windowState == window.STATE_MINIMIZED);
- this.mCurrentBrowser.docShellIsActive = this.shouldActivateDocShell(this.mCurrentBrowser);
- }
- break;
- }
- ]]></body>
- </method>
-
- <method name="receiveMessage">
- <parameter name="aMessage"/>
- <body><![CDATA[
- let data = aMessage.data;
- let browser = aMessage.target;
-
- switch (aMessage.name) {
- case "DOMTitleChanged": {
- let tab = this.getTabForBrowser(browser);
- if (!tab || tab.hasAttribute("pending"))
- return undefined;
- let titleChanged = this.setTabTitle(tab);
- if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
- tab.setAttribute("titlechanged", "true");
- break;
- }
- case "DOMWindowClose": {
- if (this.tabs.length == 1) {
- // We already did PermitUnload in the content process
- // for this tab (the only one in the window). So we don't
- // need to do it again for any tabs.
- window.skipNextCanClose = true;
- window.close();
- return undefined;
- }
-
- let tab = this.getTabForBrowser(browser);
- if (tab) {
- // Skip running PermitUnload since it already happened in
- // the content process.
- this.removeTab(tab, {skipPermitUnload: true});
- }
- break;
- }
- case "contextmenu": {
- let spellInfo = data.spellInfo;
- if (spellInfo)
- spellInfo.target = aMessage.target.messageManager;
- let documentURIObject = makeURI(data.docLocation,
- data.charSet,
- makeURI(data.baseURI));
- gContextMenuContentData = { isRemote: true,
- event: aMessage.objects.event,
- popupNode: aMessage.objects.popupNode,
- browser,
- editFlags: data.editFlags,
- spellInfo,
- principal: data.principal,
- customMenuItems: data.customMenuItems,
- addonInfo: data.addonInfo,
- documentURIObject,
- docLocation: data.docLocation,
- charSet: data.charSet,
- referrer: data.referrer,
- referrerPolicy: data.referrerPolicy,
- contentType: data.contentType,
- contentDisposition: data.contentDisposition,
- frameOuterWindowID: data.frameOuterWindowID,
- selectionInfo: data.selectionInfo,
- disableSetDesktopBackground: data.disableSetDesktopBg,
- loginFillInfo: data.loginFillInfo,
- parentAllowsMixedContent: data.parentAllowsMixedContent,
- userContextId: data.userContextId,
- };
- let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
- let event = gContextMenuContentData.event;
- popup.openPopupAtScreen(event.screenX, event.screenY, true);
- break;
- }
- case "DOMServiceWorkerFocusClient":
- case "DOMWebNotificationClicked": {
- let tab = this.getTabForBrowser(browser);
- if (!tab)
- return undefined;
- this.selectedTab = tab;
- window.focus();
- break;
- }
- case "Browser:Init": {
- let tab = this.getTabForBrowser(browser);
- if (!tab)
- return undefined;
-
- this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser);
- browser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
- break;
- }
- case "Browser:WindowCreated": {
- let tab = this.getTabForBrowser(browser);
- if (tab && data.userContextId) {
- ContextualIdentityService.telemetry(data.userContextId);
- tab.setUserContextId(data.userContextId);
- }
-
- // We don't want to update the container icon and identifier if
- // this is not the selected browser.
- if (browser == gBrowser.selectedBrowser) {
- updateUserContextUIIndicator();
- }
-
- break;
- }
- case "Findbar:Keypress": {
- let tab = this.getTabForBrowser(browser);
- // If the find bar for this tab is not yet alive, only initialize
- // it if there's a possibility FindAsYouType will be used.
- // There's no point in doing it for most random keypresses.
- if (!this.isFindBarInitialized(tab) &&
- data.shouldFastFind) {
- let shouldFastFind = this._findAsYouType;
- if (!shouldFastFind) {
- // Please keep in sync with toolkit/content/widgets/findbar.xml
- const FAYT_LINKS_KEY = "'";
- const FAYT_TEXT_KEY = "/";
- let charCode = data.fakeEvent.charCode;
- let key = charCode ? String.fromCharCode(charCode) : null;
- shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY;
- }
- if (shouldFastFind) {
- // Make sure we return the result.
- return this.getFindBar(tab).receiveMessage(aMessage);
- }
- }
- break;
- }
- case "RefreshBlocker:Blocked": {
- let event = new CustomEvent("RefreshBlocked", {
- bubbles: true,
- cancelable: false,
- detail: data,
- });
-
- browser.dispatchEvent(event);
-
- break;
- }
-
- case "Prerender:Request": {
- let sendCancelPrerendering = () => {
- browser.frameloader.messageManager.
- sendAsyncMessage("Prerender:Canceled", { id: data.id });
- };
-
- let tab = this.getTabForBrowser(browser);
- if (!tab) {
- // No tab?
- sendCancelPrerendering();
- break;
- }
-
- if (tab.hidden) {
- // Skip prerender on hidden tab.
- sendCancelPrerendering();
- break;
- }
-
- if (browser.canGoForward) {
- // Skip prerender on history navigation as we don't support it
- // yet. Remove this check once bug 1323650 is implemented.
- sendCancelPrerendering();
- break;
- }
-
- if (!data.href) {
- // If we don't have data.href, loadOneTab will load about:blank
- // which is meaningless for prerendering.
- sendCancelPrerendering();
- break;
- }
-
- let groupedSHistory = browser.frameLoader.ensureGroupedSHistory();
-
- let newTab = this.loadOneTab(data.href, {
- referrerURI: (data.referrer ? makeURI(data.referrer) : null),
- referrerPolicy: Ci.nsIHttpChannel.REFERRER_POLICY_UNSET,
- postData: null,
- allowThirdPartyFixup: true,
- relatedToCurrent: true,
- isPrerendered: true,
- });
- let partialSHistory = newTab.linkedBrowser.frameLoader.partialSHistory;
- groupedSHistory.addPrerenderingPartialSHistory(partialSHistory, data.id);
- break;
- }
-
- case "Prerender:Cancel": {
- let groupedSHistory = browser.frameLoader.groupedSHistory;
- if (groupedSHistory) {
- groupedSHistory.cancelPrerendering(data.id);
- }
- break;
- }
-
- case "Prerender:Swap": {
- let frameloader = browser.frameLoader;
- let groupedSHistory = browser.frameLoader.groupedSHistory;
- if (groupedSHistory) {
- groupedSHistory.activatePrerendering(data.id).then(
- () => frameloader.messageManager.sendAsyncMessage("Prerender:Swapped", data),
- () => frameloader.messageManager.sendAsyncMessage("Prerender:Canceled", data),
- );
- }
- break;
- }
-
- }
- return undefined;
- ]]></body>
- </method>
-
- <method name="observe">
- <parameter name="aSubject"/>
- <parameter name="aTopic"/>
- <parameter name="aData"/>
- <body><![CDATA[
- switch (aTopic) {
- case "contextual-identity-updated": {
- for (let tab of this.tabs) {
- if (tab.getAttribute("usercontextid") == aData) {
- ContextualIdentityService.setTabStyle(tab);
- }
- }
- break;
- }
- case "nsPref:changed": {
- // This is the only pref observed.
- this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
- break;
- }
- }
- ]]></body>
- </method>
-
- <constructor>
- <![CDATA[
- this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
- this.mCurrentBrowser.permanentKey = {};
-
- Services.obs.addObserver(this, "contextual-identity-updated", false);
-
- this.mCurrentTab = this.tabContainer.firstChild;
- const nsIEventListenerService =
- Components.interfaces.nsIEventListenerService;
- let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
- .getService(nsIEventListenerService);
- els.addSystemEventListener(document, "keydown", this, false);
-#ifdef XP_MACOSX
- els.addSystemEventListener(document, "keypress", this, false);
-#endif
- window.addEventListener("sizemodechange", this);
-
- var uniqueId = this._generateUniquePanelID();
- this.mPanelContainer.childNodes[0].id = uniqueId;
- this.mCurrentTab.linkedPanel = uniqueId;
- this.mCurrentTab.permanentKey = this.mCurrentBrowser.permanentKey;
- this.mCurrentTab._tPos = 0;
- this.mCurrentTab._fullyOpen = true;
- this.mCurrentTab.cachePosition = 0;
- this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
- this.mCurrentTab.hasBrowser = true;
- this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
-
- // set up the shared autoscroll popup
- this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
- this._autoScrollPopup.id = "autoscroller";
- this.appendChild(this._autoScrollPopup);
- this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
- this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
-
- // Hook up the event listeners to the first browser
- var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true, false);
- const nsIWebProgress = Components.interfaces.nsIWebProgress;
- const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
- .createInstance(nsIWebProgress);
- filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
- this._tabListeners.set(this.mCurrentTab, tabListener);
- this._tabFilters.set(this.mCurrentTab, filter);
- this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
-
- this.style.backgroundColor =
- Services.prefs.getBoolPref("browser.display.use_system_colors") ?
- "-moz-default-background-color" :
- Services.prefs.getCharPref("browser.display.background_color");
-
- let messageManager = window.getGroupMessageManager("browsers");
-
- let remote = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsILoadContext)
- .useRemoteTabs;
- if (remote) {
- messageManager.addMessageListener("DOMTitleChanged", this);
- messageManager.addMessageListener("DOMWindowClose", this);
- window.messageManager.addMessageListener("contextmenu", this);
- messageManager.addMessageListener("Browser:Init", this);
-
- // If this window has remote tabs, switch to our tabpanels fork
- // which does asynchronous tab switching.
- this.mPanelContainer.classList.add("tabbrowser-tabpanels");
- } else {
- this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID,
- this.mCurrentBrowser);
- }
- messageManager.addMessageListener("DOMWebNotificationClicked", this);
- messageManager.addMessageListener("DOMServiceWorkerFocusClient", this);
- messageManager.addMessageListener("RefreshBlocker:Blocked", this);
- messageManager.addMessageListener("Browser:WindowCreated", this);
-
- // To correctly handle keypresses for potential FindAsYouType, while
- // the tab's find bar is not yet initialized.
- this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
- Services.prefs.addObserver("accessibility.typeaheadfind", this, false);
- messageManager.addMessageListener("Findbar:Keypress", this);
-
- // Add listeners for prerender messages
- messageManager.addMessageListener("Prerender:Request", this);
- messageManager.addMessageListener("Prerender:Cancel", this);
- messageManager.addMessageListener("Prerender:Swap", this);
- ]]>
- </constructor>
-
- <method name="_generateUniquePanelID">
- <body><![CDATA[
- if (!this._uniquePanelIDCounter) {
- this._uniquePanelIDCounter = 0;
- }
-
- let outerID = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .outerWindowID;
-
- // We want panel IDs to be globally unique, that's why we include the
- // window ID. We switched to a monotonic counter as Date.now() lead
- // to random failures because of colliding IDs.
- return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
- ]]></body>
- </method>
-
- <destructor>
- <![CDATA[
- Services.obs.removeObserver(this, "contextual-identity-updated");
-
- for (let tab of this.tabs) {
- let browser = tab.linkedBrowser;
- if (browser.registeredOpenURI) {
- this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
- browser.getAttribute("usercontextid") || 0);
- delete browser.registeredOpenURI;
- }
- let filter = this._tabFilters.get(tab);
- let listener = this._tabListeners.get(tab);
-
- browser.webProgress.removeProgressListener(filter);
- filter.removeProgressListener(listener);
- listener.destroy();
-
- this._tabFilters.delete(tab);
- this._tabListeners.delete(tab);
- }
- const nsIEventListenerService =
- Components.interfaces.nsIEventListenerService;
- let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
- .getService(nsIEventListenerService);
- els.removeSystemEventListener(document, "keydown", this, false);
-#ifdef XP_MACOSX
- els.removeSystemEventListener(document, "keypress", this, false);
-#endif
- window.removeEventListener("sizemodechange", this);
-
- if (gMultiProcessBrowser) {
- let messageManager = window.getGroupMessageManager("browsers");
- messageManager.removeMessageListener("DOMTitleChanged", this);
- window.messageManager.removeMessageListener("contextmenu", this);
-
- if (this._switcher) {
- this._switcher.destroy();
- }
- }
-
- Services.prefs.removeObserver("accessibility.typeaheadfind", this);
- ]]>
- </destructor>
-
- <!-- Deprecated stuff, implemented for backwards compatibility. -->
- <method name="enterTabbedMode">
- <body>
- Services.console.logStringMessage("enterTabbedMode is an obsolete method and " +
- "will be removed in a future release.");
- </body>
- </method>
- <field name="mTabbedMode" readonly="true">true</field>
- <method name="setStripVisibilityTo">
- <parameter name="aShow"/>
- <body>
- this.tabContainer.visible = aShow;
- </body>
- </method>
- <method name="getStripVisibility">
- <body>
- return this.tabContainer.visible;
- </body>
- </method>
-
- <property name="mContextTab" readonly="true"
- onget="return TabContextMenu.contextTab;"/>
- <property name="mPrefs" readonly="true"
- onget="return Services.prefs;"/>
- <property name="mTabContainer" readonly="true"
- onget="return this.tabContainer;"/>
- <property name="mTabs" readonly="true"
- onget="return this.tabs;"/>
- <!--
- - Compatibility hack: several extensions depend on this property to
- - access the tab context menu or tab container, so keep that working for
- - now. Ideally we can remove this once extensions are using
- - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
- -->
- <property name="mStrip" readonly="true">
- <getter>
- <![CDATA[
- return ({
- self: this,
- childNodes: [null, this.tabContextMenu, this.tabContainer],
- firstChild: { nextSibling: this.tabContextMenu },
- getElementsByAttribute(attr, attrValue) {
- if (attr == "anonid" && attrValue == "tabContextMenu")
- return [this.self.tabContextMenu];
- return [];
- },
- // Also support adding event listeners (forward to the tab container)
- addEventListener(a, b, c) { this.self.tabContainer.addEventListener(a, b, c); },
- removeEventListener(a, b, c) { this.self.tabContainer.removeEventListener(a, b, c); }
- });
- ]]>
- </getter>
- </property>
- <field name="_soundPlayingAttrRemovalTimer">0</field>
- </implementation>
-
- <handlers>
- <handler event="DOMWindowClose" phase="capturing">
- <![CDATA[
- if (!event.isTrusted)
- return;
-
- if (this.tabs.length == 1) {
- // We already did PermitUnload in nsGlobalWindow::Close
- // for this tab. There are no other tabs we need to do
- // PermitUnload for.
- window.skipNextCanClose = true;
- return;
- }
-
- var tab = this._getTabForContentWindow(event.target);
- if (tab) {
- // Skip running PermitUnload since it already happened.
- this.removeTab(tab, {skipPermitUnload: true});
- event.preventDefault();
- }
- ]]>
- </handler>
- <handler event="DOMWillOpenModalDialog" phase="capturing">
- <![CDATA[
- if (!event.isTrusted)
- return;
-
- let targetIsWindow = event.target instanceof Window;
-
- // We're about to open a modal dialog, so figure out for which tab:
- // If this is a same-process modal dialog, then we're given its DOM
- // window as the event's target. For remote dialogs, we're given the
- // browser, but that's in the originalTarget and not the target,
- // because it's across the tabbrowser's XBL boundary.
- let tabForEvent = targetIsWindow ?
- this._getTabForContentWindow(event.target.top) :
- this.getTabForBrowser(event.originalTarget);
-
- // Focus window for beforeunload dialog so it is seen but don't
- // steal focus from other applications.
- if (event.detail &&
- event.detail.tabPrompt &&
- event.detail.inPermitUnload &&
- Services.focus.activeWindow)
- window.focus();
-
- // Don't need to act if the tab is already selected:
- if (tabForEvent.selected)
- return;
-
- // We always switch tabs for beforeunload tab-modal prompts.
- if (event.detail &&
- event.detail.tabPrompt &&
- !event.detail.inPermitUnload) {
- let docPrincipal = targetIsWindow ? event.target.document.nodePrincipal : null;
- // At least one of these should/will be non-null:
- let promptPrincipal = event.detail.promptPrincipal || docPrincipal ||
- tabForEvent.linkedBrowser.contentPrincipal;
- // For null principals, we bail immediately and don't show the checkbox:
- if (!promptPrincipal || promptPrincipal.isNullPrincipal) {
- tabForEvent.setAttribute("attention", "true");
- return;
- }
-
- // For non-system/expanded principals, we bail and show the checkbox
- if (promptPrincipal.URI &&
- !Services.scriptSecurityManager.isSystemPrincipal(promptPrincipal)) {
- let permission = Services.perms.testPermissionFromPrincipal(promptPrincipal,
- "focus-tab-by-prompt");
- if (permission != Services.perms.ALLOW_ACTION) {
- // Tell the prompt box we want to show the user a checkbox:
- let tabPrompt = this.getTabModalPromptBox(tabForEvent.linkedBrowser);
- tabPrompt.onNextPromptShowAllowFocusCheckboxFor(promptPrincipal);
- tabForEvent.setAttribute("attention", "true");
- return;
- }
- }
- // ... so system and expanded principals, as well as permitted "normal"
- // URI-based principals, always get to steal focus for the tab when prompting.
- }
-
- // If permissions/origins dictate so, bring tab to the front.
- this.selectedTab = tabForEvent;
- ]]>
- </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 || tab.hasAttribute("pending"))
- return;
-
- var titleChanged = this.setTabTitle(tab);
- if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
- tab.setAttribute("titlechanged", "true");
- ]]>
- </handler>
- <handler event="oop-browser-crashed">
- <![CDATA[
- if (!event.isTrusted)
- return;
-
- let browser = event.originalTarget;
- let icon = browser.mIconURL;
- let tab = this.getTabForBrowser(browser);
-
- if (this.selectedBrowser == browser) {
- TabCrashHandler.onSelectedBrowserCrash(browser);
- } else {
- this.updateBrowserRemoteness(browser, false);
- SessionStore.reviveCrashedTab(tab);
- }
-
- tab.removeAttribute("soundplaying");
- this.setIcon(tab, icon, browser.contentPrincipal);
- ]]>
- </handler>
- <handler event="DOMAudioPlaybackStarted">
- <![CDATA[
- var tab = getTabFromAudioEvent(event)
- if (!tab) {
- return;
- }
-
- clearTimeout(tab._soundPlayingAttrRemovalTimer);
- tab._soundPlayingAttrRemovalTimer = 0;
-
- let modifiedAttrs = [];
- if (tab.hasAttribute("soundplaying-scheduledremoval")) {
- tab.removeAttribute("soundplaying-scheduledremoval");
- modifiedAttrs.push("soundplaying-scheduledremoval");
- }
-
- if (!tab.hasAttribute("soundplaying")) {
- tab.setAttribute("soundplaying", true);
- modifiedAttrs.push("soundplaying");
- }
-
- this._tabAttrModified(tab, modifiedAttrs);
- ]]>
- </handler>
- <handler event="DOMAudioPlaybackStopped">
- <![CDATA[
- var tab = getTabFromAudioEvent(event)
- if (!tab) {
- return;
- }
-
- if (tab.hasAttribute("soundplaying")) {
- let removalDelay = Services.prefs.getIntPref("browser.tabs.delayHidingAudioPlayingIconMS");
-
- tab.style.setProperty("--soundplaying-removal-delay", `${removalDelay - 300}ms`);
- tab.setAttribute("soundplaying-scheduledremoval", "true");
- this._tabAttrModified(tab, ["soundplaying-scheduledremoval"]);
-
- tab._soundPlayingAttrRemovalTimer = setTimeout(() => {
- tab.removeAttribute("soundplaying-scheduledremoval");
- tab.removeAttribute("soundplaying");
- this._tabAttrModified(tab, ["soundplaying", "soundplaying-scheduledremoval"]);
- }, removalDelay);
- }
- ]]>
- </handler>
- <handler event="DOMAudioPlaybackBlockStarted">
- <![CDATA[
- var tab = getTabFromAudioEvent(event)
- if (!tab) {
- return;
- }
-
- if (!tab.hasAttribute("blocked")) {
- tab.setAttribute("blocked", true);
- this._tabAttrModified(tab, ["blocked"]);
- tab.startMediaBlockTimer();
- }
- ]]>
- </handler>
- <handler event="DOMAudioPlaybackBlockStopped">
- <![CDATA[
- var tab = getTabFromAudioEvent(event)
- if (!tab) {
- return;
- }
-
- if (tab.hasAttribute("blocked")) {
- tab.removeAttribute("blocked");
- this._tabAttrModified(tab, ["blocked"]);
- let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
- hist.add(2 /* unblockByVisitingTab */);
- tab.finishMediaBlockTimer();
- }
- ]]>
- </handler>
- </handlers>
- </binding>
-
- <binding id="tabbrowser-tabbox"
- extends="chrome://global/content/bindings/tabbox.xml#tabbox">
- <implementation>
- <property name="tabs" readonly="true"
- onget="return document.getBindingParent(this).tabContainer;"/>
- </implementation>
- </binding>
-
- <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
- <implementation>
- <!-- Override scrollbox.xml method, since our scrollbox's children are
- inherited from the binding parent -->
- <method name="_getScrollableElements">
- <body><![CDATA[
- return Array.filter(document.getBindingParent(this).childNodes,
- this._canScrollToElement, this);
- ]]></body>
- </method>
- <method name="_canScrollToElement">
- <parameter name="tab"/>
- <body><![CDATA[
- return !tab.pinned && !tab.hidden;
- ]]></body>
- </method>
- <field name="_tabMarginLeft">null</field>
- <field name="_tabMarginRight">null</field>
- <method name="_calcTabMargins">
- <parameter name="aTab"/>
- <body><![CDATA[
- if (this._tabMarginLeft === null || this._tabMarginRight === null) {
- let tabMiddle = document.getAnonymousElementByAttribute(aTab, "class", "tab-background-middle");
- let tabMiddleStyle = window.getComputedStyle(tabMiddle, null);
- this._tabMarginLeft = parseFloat(tabMiddleStyle.marginLeft);
- this._tabMarginRight = parseFloat(tabMiddleStyle.marginRight);
- }
- ]]></body>
- </method>
- <method name="_adjustElementStartAndEnd">
- <parameter name="aTab"/>
- <parameter name="tabStart"/>
- <parameter name="tabEnd"/>
- <body><![CDATA[
- this._calcTabMargins(aTab);
- if (this._tabMarginLeft < 0) {
- tabStart = tabStart + this._tabMarginLeft;
- }
- if (this._tabMarginRight < 0) {
- tabEnd = tabEnd - this._tabMarginRight;
- }
- return [tabStart, tabEnd];
- ]]></body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="underflow" phase="capturing"><![CDATA[
- if (event.target != this)
- return;
-
- if (event.detail == 0)
- return; // Ignore vertical events
-
- var tabs = document.getBindingParent(this);
- tabs.removeAttribute("overflow");
-
- if (tabs._lastTabClosedByMouse)
- tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
-
- for (let tab of Array.from(tabs.tabbrowser._removingTabs))
- tabs.tabbrowser.removeTab(tab);
-
- tabs._positionPinnedTabs();
- ]]></handler>
- <handler event="overflow"><![CDATA[
- if (event.target != this)
- return;
-
- if (event.detail == 0)
- return; // Ignore vertical events
-
- var tabs = document.getBindingParent(this);
- tabs.setAttribute("overflow", "true");
- tabs._positionPinnedTabs();
- tabs._handleTabSelect(false);
- ]]></handler>
- </handlers>
- </binding>
-
- <binding id="tabbrowser-tabs"
- extends="chrome://global/content/bindings/tabbox.xml#tabs">
- <resources>
- <stylesheet src="chrome://browser/content/tabbrowser.css"/>
- </resources>
-
- <content>
- <xul:hbox align="end">
- <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
- </xul:hbox>
- <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
- style="min-width: 1px;"
- class="tabbrowser-arrowscrollbox">
-<!--
- This is a hack to circumvent bug 472020, otherwise the tabs show up on the
- right of the newtab button.
--->
- <children includes="tab"/>
-<!--
- This is to ensure anything extensions put here will go before the newtab
- button, necessary due to the previous hack.
--->
- <children/>
- <xul:toolbarbutton class="tabs-newtab-button"
- anonid="tabs-newtab-button"
- command="cmd_newNavigatorTab"
- onclick="checkForMiddleClick(this, event);"
- onmouseover="document.getBindingParent(this)._enterNewTab();"
- onmouseout="document.getBindingParent(this)._leaveNewTab();"
- tooltip="dynamic-shortcut-tooltip"/>
- <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
- style="width: 0;"/>
- </xul:arrowscrollbox>
- </content>
-
- <implementation implements="nsIDOMEventListener, nsIObserver">
- <constructor>
- <![CDATA[
- this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
-
- var tab = this.firstChild;
- tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
- tab.setAttribute("onerror", "this.removeAttribute('image');");
-
- window.addEventListener("resize", this);
- window.addEventListener("load", this);
-
- try {
- this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
- } catch (ex) {
- this._tabAnimationLoggingEnabled = false;
- }
- this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
- Services.prefs.addObserver("privacy.userContext", this, false);
- this.observe(null, "nsPref:changed", "privacy.userContext.enabled");
- ]]>
- </constructor>
-
- <destructor>
- <![CDATA[
- Services.prefs.removeObserver("privacy.userContext", this);
- ]]>
- </destructor>
-
- <field name="tabbrowser" readonly="true">
- document.getElementById(this.getAttribute("tabbrowser"));
- </field>
-
- <field name="tabbox" readonly="true">
- this.tabbrowser.mTabBox;
- </field>
-
- <field name="contextMenu" readonly="true">
- document.getElementById("tabContextMenu");
- </field>
-
- <field name="mTabstripWidth">0</field>
-
- <field name="mTabstrip">
- document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
- </field>
-
- <field name="_firstTab">null</field>
- <field name="_lastTab">null</field>
- <field name="_afterSelectedTab">null</field>
- <field name="_beforeHoveredTab">null</field>
- <field name="_afterHoveredTab">null</field>
- <field name="_hoveredTab">null</field>
-
- <method name="observe">
- <parameter name="aSubject"/>
- <parameter name="aTopic"/>
- <parameter name="aData"/>
- <body><![CDATA[
- switch (aTopic) {
- case "nsPref:changed":
- // This is has to deal with changes in
- // privacy.userContext.enabled and
- // privacy.userContext.longPressBehavior.
- let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled")
- && !PrivateBrowsingUtils.isWindowPrivate(window);
-
- // This pref won't change so often, so just recreate the menu.
- let longPressBehavior = Services.prefs.getIntPref("privacy.userContext.longPressBehavior");
-
- // If longPressBehavior pref is set to 0 (or any invalid value)
- // long press menu is disabled.
- if (containersEnabled && (longPressBehavior <= 0 || longPressBehavior > 2)) {
- containersEnabled = false;
- }
-
- const newTab = document.getElementById("new-tab-button");
- const newTab2 = document.getAnonymousElementByAttribute(this, "anonid", "tabs-newtab-button")
-
- for (let parent of [newTab, newTab2]) {
- if (!parent)
- continue;
-
- gClickAndHoldListenersOnElement.remove(parent);
- parent.removeAttribute("type");
- if (parent.firstChild) {
- parent.firstChild.remove();
- }
-
- if (containersEnabled) {
- let popup = document.createElementNS(
- "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
- "menupopup");
- if (parent.id) {
- popup.id = "newtab-popup";
- } else {
- popup.setAttribute("anonid", "newtab-popup");
- }
- popup.className = "new-tab-popup";
- popup.setAttribute("position", "after_end");
- parent.appendChild(popup);
-
- // longPressBehavior == 2 means that the menu is shown after X
- // millisecs. Otherwise, with 1, the menu is open immediatelly.
- if (longPressBehavior == 2) {
- gClickAndHoldListenersOnElement.add(parent);
- }
-
- parent.setAttribute("type", "menu");
- }
- }
-
- break;
- }
- ]]></body>
- </method>
-
- <property name="_isCustomizing" readonly="true">
- <getter>
- let root = document.documentElement;
- return root.getAttribute("customizing") == "true" ||
- root.getAttribute("customize-exiting") == "true";
- </getter>
- </property>
-
- <method name="_setPositionalAttributes">
- <body><![CDATA[
- let visibleTabs = this.tabbrowser.visibleTabs;
-
- if (!visibleTabs.length)
- return;
-
- let selectedIndex = visibleTabs.indexOf(this.selectedItem);
-
- let lastVisible = visibleTabs.length - 1;
-
- if (this._afterSelectedTab)
- this._afterSelectedTab.removeAttribute("afterselected-visible");
- if (this.selectedItem.closing || selectedIndex == lastVisible) {
- this._afterSelectedTab = null;
- } else {
- this._afterSelectedTab = visibleTabs[selectedIndex + 1];
- this._afterSelectedTab.setAttribute("afterselected-visible",
- "true");
- }
-
- if (this._firstTab)
- this._firstTab.removeAttribute("first-visible-tab");
- this._firstTab = visibleTabs[0];
- this._firstTab.setAttribute("first-visible-tab", "true");
- if (this._lastTab)
- this._lastTab.removeAttribute("last-visible-tab");
- this._lastTab = visibleTabs[lastVisible];
- this._lastTab.setAttribute("last-visible-tab", "true");
-
- let hoveredTab = this._hoveredTab;
- if (hoveredTab) {
- hoveredTab._mouseleave();
- }
- hoveredTab = this.querySelector("tab:hover");
- if (hoveredTab) {
- hoveredTab._mouseenter();
- }
- ]]></body>
- </method>
-
- <field name="_blockDblClick">false</field>
-
- <field name="_tabDropIndicator">
- document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
- </field>
-
- <field name="_dragOverDelay">350</field>
- <field name="_dragTime">0</field>
-
- <field name="_container" readonly="true"><![CDATA[
- this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
- ]]></field>
-
- <field name="_propagatedVisibilityOnce">false</field>
-
- <property name="visible"
- onget="return !this._container.collapsed;">
- <setter><![CDATA[
- if (val == this.visible &&
- this._propagatedVisibilityOnce)
- return val;
-
- this._container.collapsed = !val;
-
- this._propagateVisibility();
- this._propagatedVisibilityOnce = true;
-
- return val;
- ]]></setter>
- </property>
-
- <method name="_enterNewTab">
- <body><![CDATA[
- let visibleTabs = this.tabbrowser.visibleTabs;
- let candidate = visibleTabs[visibleTabs.length - 1];
- if (!candidate.selected) {
- this._beforeHoveredTab = candidate;
- candidate.setAttribute("beforehovered", "true");
- }
- ]]></body>
- </method>
-
- <method name="_leaveNewTab">
- <body><![CDATA[
- if (this._beforeHoveredTab) {
- this._beforeHoveredTab.removeAttribute("beforehovered");
- this._beforeHoveredTab = null;
- }
- ]]></body>
- </method>
-
- <method name="_propagateVisibility">
- <body><![CDATA[
- let visible = this.visible;
-
- document.getElementById("menu_closeWindow").hidden = !visible;
- document.getElementById("menu_close").setAttribute("label",
- this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
-
- TabsInTitlebar.allowedBy("tabs-visible", visible);
- ]]></body>
- </method>
-
- <method name="updateVisibility">
- <body><![CDATA[
- if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
- this.visible = window.toolbar.visible;
- else
- this.visible = true;
- ]]></body>
- </method>
-
- <method name="adjustTabstrip">
- <body><![CDATA[
- let numTabs = this.childNodes.length -
- this.tabbrowser._removingTabs.length;
- if (numTabs > 2) {
- // This is an optimization to avoid layout flushes by calling
- // getBoundingClientRect() when we just opened a second tab. In
- // this case it's highly unlikely that the tab width is smaller
- // than mTabClipWidth and the tab close button obscures too much
- // of the tab's label. In the edge case of the window being too
- // narrow (or if tabClipWidth has been set to a way higher value),
- // we'll correct the 'closebuttons' attribute after the tabopen
- // animation has finished.
-
- let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
- if (tab && tab.getBoundingClientRect().width <= this.mTabClipWidth) {
- this.setAttribute("closebuttons", "activetab");
- return;
- }
- }
- this.removeAttribute("closebuttons");
- ]]></body>
- </method>
-
- <method name="_handleTabSelect">
- <parameter name="aSmoothScroll"/>
- <body><![CDATA[
- if (this.getAttribute("overflow") == "true")
- this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
- ]]></body>
- </method>
-
- <method name="_fillTrailingGap">
- <body><![CDATA[
- try {
- // if we're at the right side (and not the logical end,
- // which is why this works for both LTR and RTL)
- // of the tabstrip, we need to ensure that we stay
- // completely scrolled to the right side
- var tabStrip = this.mTabstrip;
- if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
- tabStrip.scrollSize)
- tabStrip.scrollByPixels(-1);
- } catch (e) {}
- ]]></body>
- </method>
-
- <field name="_closingTabsSpacer">
- document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
- </field>
-
- <field name="_tabDefaultMaxWidth">NaN</field>
- <field name="_lastTabClosedByMouse">false</field>
- <field name="_hasTabTempMaxWidth">false</field>
-
- <!-- Try to keep the active tab's close button under the mouse cursor -->
- <method name="_lockTabSizing">
- <parameter name="aTab"/>
- <body><![CDATA[
- var tabs = this.tabbrowser.visibleTabs;
- if (!tabs.length)
- return;
-
- var isEndTab = (aTab._tPos > tabs[tabs.length - 1]._tPos);
- var tabWidth = aTab.getBoundingClientRect().width;
-
- if (!this._tabDefaultMaxWidth)
- this._tabDefaultMaxWidth =
- parseFloat(window.getComputedStyle(aTab).maxWidth);
- this._lastTabClosedByMouse = true;
-
- if (this.getAttribute("overflow") == "true") {
- // Don't need to do anything if we're in overflow mode and aren't scrolled
- // all the way to the right, or if we're closing the last tab.
- if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
- return;
-
- // If the tab has an owner that will become the active tab, the owner will
- // be to the left of it, so we actually want the left tab to slide over.
- // This can't be done as easily in non-overflow mode, so we don't bother.
- if (aTab.owner)
- return;
-
- this._expandSpacerBy(tabWidth);
- } else { // non-overflow mode
- // Locking is neither in effect nor needed, so let tabs expand normally.
- if (isEndTab && !this._hasTabTempMaxWidth)
- return;
-
- let numPinned = this.tabbrowser._numPinnedTabs;
- // Force tabs to stay the same width, unless we're closing the last tab,
- // which case we need to let them expand just enough so that the overall
- // tabbar width is the same.
- if (isEndTab) {
- let numNormalTabs = tabs.length - numPinned;
- tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
- if (tabWidth > this._tabDefaultMaxWidth)
- tabWidth = this._tabDefaultMaxWidth;
- }
- tabWidth += "px";
- for (let i = numPinned; i < tabs.length; i++) {
- let tab = tabs[i];
- tab.style.setProperty("max-width", tabWidth, "important");
- if (!isEndTab) { // keep tabs the same width
- tab.style.transition = "none";
- tab.clientTop; // flush styles to skip animation; see bug 649247
- tab.style.transition = "";
- }
- }
- this._hasTabTempMaxWidth = true;
- this.tabbrowser.addEventListener("mousemove", this);
- window.addEventListener("mouseout", this);
- }
- ]]></body>
- </method>
-
- <method name="_expandSpacerBy">
- <parameter name="pixels"/>
- <body><![CDATA[
- let spacer = this._closingTabsSpacer;
- spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
- this.setAttribute("using-closing-tabs-spacer", "true");
- this.tabbrowser.addEventListener("mousemove", this);
- window.addEventListener("mouseout", this);
- ]]></body>
- </method>
-
- <method name="_unlockTabSizing">
- <body><![CDATA[
- this.tabbrowser.removeEventListener("mousemove", this);
- window.removeEventListener("mouseout", this);
-
- if (this._hasTabTempMaxWidth) {
- this._hasTabTempMaxWidth = false;
- let tabs = this.tabbrowser.visibleTabs;
- for (let i = 0; i < tabs.length; i++)
- tabs[i].style.maxWidth = "";
- }
-
- if (this.hasAttribute("using-closing-tabs-spacer")) {
- this.removeAttribute("using-closing-tabs-spacer");
- this._closingTabsSpacer.style.width = 0;
- }
- ]]></body>
- </method>
-
- <field name="_lastNumPinned">0</field>
- <method name="_positionPinnedTabs">
- <body><![CDATA[
- var numPinned = this.tabbrowser._numPinnedTabs;
- var doPosition = this.getAttribute("overflow") == "true" &&
- numPinned > 0;
-
- if (doPosition) {
- this.setAttribute("positionpinnedtabs", "true");
-
- let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
- let paddingStart = this.mTabstrip.scrollboxPaddingStart;
- let width = 0;
-
- for (let i = numPinned - 1; i >= 0; i--) {
- let tab = this.childNodes[i];
- width += tab.getBoundingClientRect().width;
- tab.style.marginInlineStart = -(width + scrollButtonWidth + paddingStart) + "px";
- }
-
- this.style.paddingInlineStart = width + paddingStart + "px";
-
- } else {
- this.removeAttribute("positionpinnedtabs");
-
- for (let i = 0; i < numPinned; i++) {
- let tab = this.childNodes[i];
- tab.style.marginInlineStart = "";
- }
-
- this.style.paddingInlineStart = "";
- }
-
- if (this._lastNumPinned != numPinned) {
- this._lastNumPinned = numPinned;
- this._handleTabSelect(false);
- }
- ]]></body>
- </method>
-
- <method name="_animateTabMove">
- <parameter name="event"/>
- <body><![CDATA[
- let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
-
- if (this.getAttribute("movingtab") != "true") {
- this.setAttribute("movingtab", "true");
- this.selectedItem = draggedTab;
- }
-
- if (!("animLastScreenX" in draggedTab._dragData))
- draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
-
- let screenX = event.screenX;
- if (screenX == draggedTab._dragData.animLastScreenX)
- return;
-
- draggedTab._dragData.animLastScreenX = screenX;
-
- let rtl = (window.getComputedStyle(this).direction == "rtl");
- let pinned = draggedTab.pinned;
- let numPinned = this.tabbrowser._numPinnedTabs;
- let tabs = this.tabbrowser.visibleTabs
- .slice(pinned ? 0 : numPinned,
- pinned ? numPinned : undefined);
- if (rtl)
- tabs.reverse();
- let tabWidth = draggedTab.getBoundingClientRect().width;
-
- // Move the dragged tab based on the mouse position.
-
- let leftTab = tabs[0];
- let rightTab = tabs[tabs.length - 1];
- let tabScreenX = draggedTab.boxObject.screenX;
- let translateX = screenX - draggedTab._dragData.screenX;
- if (!pinned)
- translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
- let leftBound = leftTab.boxObject.screenX - tabScreenX;
- let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
- (tabScreenX + tabWidth);
- translateX = Math.max(translateX, leftBound);
- translateX = Math.min(translateX, rightBound);
- draggedTab.style.transform = "translateX(" + translateX + "px)";
-
- // Determine what tab we're dragging over.
- // * Point of reference is the center of the dragged tab. If that
- // point touches a background tab, the dragged tab would take that
- // tab's position when dropped.
- // * We're doing a binary search in order to reduce the amount of
- // tabs we need to check.
-
- let tabCenter = tabScreenX + translateX + tabWidth / 2;
- let newIndex = -1;
- let oldIndex = "animDropIndex" in draggedTab._dragData ?
- draggedTab._dragData.animDropIndex : draggedTab._tPos;
- let low = 0;
- let high = tabs.length - 1;
- while (low <= high) {
- let mid = Math.floor((low + high) / 2);
- if (tabs[mid] == draggedTab &&
- ++mid > high)
- break;
- let boxObject = tabs[mid].boxObject;
- screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
- if (screenX > tabCenter) {
- high = mid - 1;
- } else if (screenX + boxObject.width < tabCenter) {
- low = mid + 1;
- } else {
- newIndex = tabs[mid]._tPos;
- break;
- }
- }
- if (newIndex >= oldIndex)
- newIndex++;
- if (newIndex < 0 || newIndex == oldIndex)
- return;
- draggedTab._dragData.animDropIndex = newIndex;
-
- // Shift background tabs to leave a gap where the dragged tab
- // would currently be dropped.
-
- for (let tab of tabs) {
- if (tab != draggedTab) {
- let shift = getTabShift(tab, newIndex);
- tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
- }
- }
-
- function getTabShift(tab, dropIndex) {
- if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
- return rtl ? -tabWidth : tabWidth;
- if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
- return rtl ? tabWidth : -tabWidth;
- return 0;
- }
- ]]></body>
- </method>
-
- <method name="_finishAnimateTabMove">
- <body><![CDATA[
- if (this.getAttribute("movingtab") != "true")
- return;
-
- for (let tab of this.tabbrowser.visibleTabs)
- tab.style.transform = "";
-
- this.removeAttribute("movingtab");
-
- this._handleTabSelect();
- ]]></body>
- </method>
-
- <method name="handleEvent">
- <parameter name="aEvent"/>
- <body><![CDATA[
- switch (aEvent.type) {
- case "load":
- this.updateVisibility();
- TabsInTitlebar.init();
- break;
- case "resize":
- if (aEvent.target != window)
- break;
-
- TabsInTitlebar.updateAppearance();
-
- var width = this.mTabstrip.boxObject.width;
- if (width != this.mTabstripWidth) {
- this.adjustTabstrip();
- this._fillTrailingGap();
- this._handleTabSelect();
- this.mTabstripWidth = width;
- }
- break;
- case "mouseout":
- // If the "related target" (the node to which the pointer went) is not
- // a child of the current document, the mouse just left the window.
- let relatedTarget = aEvent.relatedTarget;
- if (relatedTarget && relatedTarget.ownerDocument == document)
- break;
- case "mousemove":
- if (document.getElementById("tabContextMenu").state != "open")
- this._unlockTabSizing();
- break;
- }
- ]]></body>
- </method>
-
- <field name="_animateElement">
- this.mTabstrip._scrollButtonDown;
- </field>
-
- <method name="_notifyBackgroundTab">
- <parameter name="aTab"/>
- <body><![CDATA[
- if (aTab.pinned || aTab.hidden)
- return;
-
- var scrollRect = this.mTabstrip.scrollClientRect;
- var tab = aTab.getBoundingClientRect();
- this.mTabstrip._calcTabMargins(aTab);
-
- // DOMRect left/right properties are immutable.
- tab = {left: tab.left, right: tab.right};
-
- // Is the new tab already completely visible?
- if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
- return;
-
- if (this.mTabstrip.smoothScroll) {
- let selected = !this.selectedItem.pinned &&
- this.selectedItem.getBoundingClientRect();
- if (selected) {
- selected = {left: selected.left, right: selected.right};
- // Need to take in to account the width of the left/right margins on tabs.
- selected.left = selected.left + this.mTabstrip._tabMarginLeft;
- selected.right = selected.right - this.mTabstrip._tabMarginRight;
- }
-
- tab.left += this.mTabstrip._tabMarginLeft;
- tab.right -= this.mTabstrip._tabMarginRight;
-
- // Can we make both the new tab and the selected tab completely visible?
- if (!selected ||
- Math.max(tab.right - selected.left, selected.right - tab.left) <=
- scrollRect.width) {
- this.mTabstrip.ensureElementIsVisible(aTab);
- return;
- }
-
- this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
- selected.right - scrollRect.right :
- selected.left - scrollRect.left);
- }
-
- if (!this._animateElement.hasAttribute("notifybgtab")) {
- this._animateElement.setAttribute("notifybgtab", "true");
- setTimeout(function(ele) {
- ele.removeAttribute("notifybgtab");
- }, 150, this._animateElement);
- }
- ]]></body>
- </method>
-
- <method name="_getDragTargetTab">
- <parameter name="event"/>
- <parameter name="isLink"/>
- <body><![CDATA[
- let tab = event.target.localName == "tab" ? event.target : null;
- if (tab && isLink) {
- let boxObject = tab.boxObject;
- if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
- event.screenX > boxObject.screenX + boxObject.width * .75)
- return null;
- }
- return tab;
- ]]></body>
- </method>
-
- <method name="_getDropIndex">
- <parameter name="event"/>
- <parameter name="isLink"/>
- <body><![CDATA[
- var tabs = this.childNodes;
- var tab = this._getDragTargetTab(event, isLink);
- if (window.getComputedStyle(this, null).direction == "ltr") {
- for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
- if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
- return i;
- } else {
- for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
- if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
- return i;
- }
- return tabs.length;
- ]]></body>
- </method>
-
- <method name="_getDropEffectForTabDrag">
- <parameter name="event"/>
- <body><![CDATA[
- var dt = event.dataTransfer;
- if (dt.mozItemCount == 1) {
- var types = dt.mozTypesAt(0);
- // tabs are always added as the first type
- if (types[0] == TAB_DROP_TYPE) {
- let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
- if (sourceNode instanceof XULElement &&
- sourceNode.localName == "tab" &&
- sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
- sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
- sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
- // Do not allow transfering a private tab to a non-private window
- // and vice versa.
- if (PrivateBrowsingUtils.isWindowPrivate(window) !=
- PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
- return "none";
-
- if (window.gMultiProcessBrowser !=
- sourceNode.ownerDocument.defaultView.gMultiProcessBrowser)
- return "none";
-
- return dt.dropEffect == "copy" ? "copy" : "move";
- }
- }
- }
-
- if (browserDragAndDrop.canDropLink(event)) {
- return "link";
- }
- return "none";
- ]]></body>
- </method>
-
- <method name="_handleNewTab">
- <parameter name="tab"/>
- <body><![CDATA[
- if (tab.parentNode != this)
- return;
- tab._fullyOpen = true;
-
- this.adjustTabstrip();
-
- if (tab.getAttribute("selected") == "true") {
- this._fillTrailingGap();
- this._handleTabSelect();
- } else {
- this._notifyBackgroundTab(tab);
- }
-
- // XXXmano: this is a temporary workaround for bug 345399
- // We need to manually update the scroll buttons disabled state
- // if a tab was inserted to the overflow area or removed from it
- // without any scrolling and when the tabbar has already
- // overflowed.
- this.mTabstrip._updateScrollButtonsDisabledState();
-
- // Preload the next about:newtab if there isn't one already.
- this.tabbrowser._createPreloadBrowser();
- ]]></body>
- </method>
-
- <method name="_canAdvanceToTab">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- return !aTab.closing;
- ]]>
- </body>
- </method>
-
- <method name="_handleTabTelemetryStart">
- <parameter name="aTab"/>
- <parameter name="aURI"/>
- <body>
- <![CDATA[
- // Animation-smoothness telemetry/logging
- if (Services.telemetry.canRecordExtended || this._tabAnimationLoggingEnabled) {
- if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
- // Indicate newtab page animation where other tabs are unaffected
- // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
- aTab._recordingTabOpenPlain = true;
- }
- aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .startFrameTimeRecording();
- }
-
- // Overall animation duration
- aTab._animStartTime = Date.now();
- ]]>
- </body>
- </method>
-
- <method name="_handleTabTelemetryEnd">
- <parameter name="aTab"/>
- <body>
- <![CDATA[
- if (!aTab._animStartTime) {
- return;
- }
-
- aTab._animStartTime = 0;
-
- // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
- if (!("_recordingHandle" in aTab)) {
- return;
- }
-
- let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils)
- .stopFrameTimeRecording(aTab._recordingHandle);
- delete aTab._recordingHandle;
- let frameCount = intervals.length;
-
- if (this._tabAnimationLoggingEnabled) {
- let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval):\n";
- for (let i = 0; i < frameCount; i++) {
- msg += Math.round(intervals[i]) + "\n";
- }
- Services.console.logStringMessage(msg);
- }
-
- // For telemetry, the first frame interval is not useful since it may represent an interval
- // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
- if (frameCount > 1) {
- let averageInterval = 0;
- for (let i = 1; i < frameCount; i++) {
- averageInterval += intervals[i];
- }
- averageInterval = averageInterval / (frameCount - 1);
-
- Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
-
- if (aTab._recordingTabOpenPlain) {
- delete aTab._recordingTabOpenPlain;
- // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
- // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
- let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
- Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
- }
- }
- ]]>
- </body>
- </method>
-
- <!-- Deprecated stuff, implemented for backwards compatibility. -->
- <property name="mAllTabsPopup" readonly="true"
- onget="return document.getElementById('alltabs-popup');"/>
- </implementation>
-
- <handlers>
- <handler event="TabSelect" action="this._handleTabSelect();"/>
-
- <handler event="transitionend"><![CDATA[
- if (event.propertyName != "max-width")
- return;
-
- var tab = event.target;
-
- this._handleTabTelemetryEnd(tab);
-
- if (tab.getAttribute("fadein") == "true") {
- if (tab._fullyOpen)
- this.adjustTabstrip();
- else
- this._handleNewTab(tab);
- } else if (tab.closing) {
- this.tabbrowser._endRemoveTab(tab);
- }
- ]]></handler>
-
- <handler event="dblclick"><![CDATA[
- // When the tabbar has an unified appearance with the titlebar
- // and menubar, a double-click in it should have the same behavior
- // as double-clicking the titlebar
- if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
- return;
-
- if (event.button != 0 ||
- event.originalTarget.localName != "box")
- return;
-
- // See hack note in the tabbrowser-close-tab-button binding
- if (!this._blockDblClick)
- BrowserOpenTab();
-
- event.preventDefault();
- ]]></handler>
-
- <handler event="click" button="0" phase="capturing"><![CDATA[
- /* Catches extra clicks meant for the in-tab close button.
- * Placed here to avoid leaking (a temporary handler added from the
- * in-tab close button binding would close over the tab and leak it
- * until the handler itself was removed). (bug 897751)
- *
- * The only sequence in which a second click event (i.e. dblclik)
- * can be dispatched on an in-tab close button is when it is shown
- * after the first click (i.e. the first click event was dispatched
- * on the tab). This happens when we show the close button only on
- * the active tab. (bug 352021)
- * The only sequence in which a third click event can be dispatched
- * on an in-tab close button is when the tab was opened with a
- * double click on the tabbar. (bug 378344)
- * In both cases, it is most likely that the close button area has
- * been accidentally clicked, therefore we do not close the tab.
- *
- * We don't want to ignore processing of more than one click event,
- * though, since the user might actually be repeatedly clicking to
- * close many tabs at once.
- */
- let target = event.originalTarget;
- if (target.classList.contains("tab-close-button")) {
- // We preemptively set this to allow the closing-multiple-tabs-
- // in-a-row case.
- if (this._blockDblClick) {
- target._ignoredCloseButtonClicks = true;
- } else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
- target._ignoredCloseButtonClicks = true;
- event.stopPropagation();
- return;
- } else {
- // Reset the "ignored click" flag
- target._ignoredCloseButtonClicks = false;
- }
- }
-
- /* Protects from close-tab-button errant doubleclick:
- * Since we're removing the event target, if the user
- * double-clicks the button, the dblclick event will be dispatched
- * with the tabbar as its event target (and explicit/originalTarget),
- * which treats that as a mouse gesture for opening a new tab.
- * In this context, we're manually blocking the dblclick event
- * (see tabbrowser-close-tab-button dblclick handler).
- */
- if (this._blockDblClick) {
- if (!("_clickedTabBarOnce" in this)) {
- this._clickedTabBarOnce = true;
- return;
- }
- delete this._clickedTabBarOnce;
- this._blockDblClick = false;
- }
- ]]></handler>
-
- <handler event="click"><![CDATA[
- if (event.button != 1)
- return;
-
- if (event.target.localName == "tab") {
- this.tabbrowser.removeTab(event.target, {animate: true,
- byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
- } else if (event.originalTarget.localName == "box") {
- // The user middleclicked an open space on the tabstrip. This could
- // be because they intend to open a new tab, but it could also be
- // because they just removed a tab and they now middleclicked on the
- // resulting space while that tab is closing. In that case, we don't
- // want to open a tab. So if we're removing one or more tabs, and
- // the tab click is before the end of the last visible tab, we do
- // nothing.
- if (this.tabbrowser._removingTabs.length) {
- let visibleTabs = this.tabbrowser.visibleTabs;
- let ltr = (window.getComputedStyle(this, null).direction == "ltr");
- let lastTab = visibleTabs[visibleTabs.length - 1];
- let endOfTab = lastTab.getBoundingClientRect()[ltr ? "right" : "left"];
- if ((ltr && event.clientX > endOfTab) ||
- (!ltr && event.clientX < endOfTab)) {
- BrowserOpenTab();
- }
- } else {
- BrowserOpenTab();
- }
- } else {
- return;
- }
-
- event.stopPropagation();
- ]]></handler>
-
- <handler event="keydown" group="system"><![CDATA[
- if (event.altKey || event.shiftKey)
- return;
-
- let wrongModifiers;
-#ifdef XP_MACOSX
- wrongModifiers = !event.metaKey;
-#else
- wrongModifiers = !event.ctrlKey || event.metaKey;
-#endif
-
- if (wrongModifiers)
- return;
-
- // Don't check if the event was already consumed because tab navigation
- // should work always for better user experience.
-
- switch (event.keyCode) {
- case KeyEvent.DOM_VK_UP:
- this.tabbrowser.moveTabBackward();
- break;
- case KeyEvent.DOM_VK_DOWN:
- this.tabbrowser.moveTabForward();
- break;
- case KeyEvent.DOM_VK_RIGHT:
- case KeyEvent.DOM_VK_LEFT:
- this.tabbrowser.moveTabOver(event);
- break;
- case KeyEvent.DOM_VK_HOME:
- this.tabbrowser.moveTabToStart();
- break;
- case KeyEvent.DOM_VK_END:
- this.tabbrowser.moveTabToEnd();
- break;
- default:
- // Consume the keydown event for the above keyboard
- // shortcuts only.
- return;
- }
- event.preventDefault();
- ]]></handler>
-
- <handler event="dragstart"><![CDATA[
- var tab = this._getDragTargetTab(event, false);
- if (!tab || this._isCustomizing)
- return;
-
- let dt = event.dataTransfer;
- dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
- let browser = tab.linkedBrowser;
-
- // We must not set text/x-moz-url or text/plain data here,
- // otherwise trying to deatch the tab by dropping it on the desktop
- // may result in an "internet shortcut"
- dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
-
- // Set the cursor to an arrow during tab drags.
- dt.mozCursor = "default";
-
- // Set the tab as the source of the drag, which ensures we have a stable
- // node to deliver the `dragend` event. See bug 1345473.
- dt.addElement(tab);
-
- // Create a canvas to which we capture the current tab.
- // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
- // canvas size (in CSS pixels) to the window's backing resolution in order
- // to get a full-resolution drag image for use on HiDPI displays.
- let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
- let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
- let canvas = this._dndCanvas;
- if (!canvas) {
- this._dndCanvas = canvas =
- document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
- canvas.style.width = "100%";
- canvas.style.height = "100%";
- canvas.mozOpaque = true;
- }
-
- canvas.width = 160 * scale;
- canvas.height = 90 * scale;
- let toDrag = canvas;
- let dragImageOffset = -16;
- if (gMultiProcessBrowser) {
- var context = canvas.getContext("2d");
- context.fillStyle = "white";
- context.fillRect(0, 0, canvas.width, canvas.height);
-
- let captureListener;
- // On Windows and Mac we can update the drag image during a drag
- // using updateDragImage. On Linux, we can use a panel.
-#if defined(XP_WIN) || defined(XP_MACOSX)
- captureListener = function() {
- dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
- }
-#else
- // Create a panel to use it in setDragImage
- // which will tell xul to render a panel that follows
- // the pointer while a dnd session is on.
- if (!this._dndPanel) {
- this._dndCanvas = canvas;
- this._dndPanel = document.createElement("panel");
- this._dndPanel.className = "dragfeedback-tab";
- this._dndPanel.setAttribute("type", "drag");
- let wrapper = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
- wrapper.style.width = "160px";
- wrapper.style.height = "90px";
- wrapper.appendChild(canvas);
- this._dndPanel.appendChild(wrapper);
- document.documentElement.appendChild(this._dndPanel);
- }
- toDrag = this._dndPanel;
-#endif
- // PageThumb is async with e10s but that's fine
- // since we can update the image during the dnd.
- PageThumbs.captureToCanvas(browser, canvas, captureListener);
- } else {
- // For the non e10s case we can just use PageThumbs
- // sync, so let's use the canvas for setDragImage.
- PageThumbs.captureToCanvas(browser, canvas);
- dragImageOffset = dragImageOffset * scale;
- }
- dt.setDragImage(toDrag, dragImageOffset, dragImageOffset);
-
- // _dragData.offsetX/Y give the coordinates that the mouse should be
- // positioned relative to the corner of the new window created upon
- // dragend such that the mouse appears to have the same position
- // relative to the corner of the dragged tab.
- function clientX(ele) {
- return ele.getBoundingClientRect().left;
- }
- let tabOffsetX = clientX(tab) - clientX(this);
- tab._dragData = {
- offsetX: event.screenX - window.screenX - tabOffsetX,
- offsetY: event.screenY - window.screenY,
- scrollX: this.mTabstrip.scrollPosition,
- screenX: event.screenX
- };
-
- event.stopPropagation();
- ]]></handler>
-
- <handler event="dragover"><![CDATA[
- var effects = this._getDropEffectForTabDrag(event);
-
- var ind = this._tabDropIndicator;
- if (effects == "" || effects == "none") {
- ind.collapsed = true;
- return;
- }
- event.preventDefault();
- event.stopPropagation();
-
- var tabStrip = this.mTabstrip;
- var ltr = (window.getComputedStyle(this, null).direction == "ltr");
-
- // autoscroll the tab strip if we drag over the scroll
- // buttons, even if we aren't dragging a tab, but then
- // return to avoid drawing the drop indicator
- var pixelsToScroll = 0;
- if (this.getAttribute("overflow") == "true") {
- var targetAnonid = event.originalTarget.getAttribute("anonid");
- switch (targetAnonid) {
- case "scrollbutton-up":
- pixelsToScroll = tabStrip.scrollIncrement * -1;
- break;
- case "scrollbutton-down":
- pixelsToScroll = tabStrip.scrollIncrement;
- break;
- }
- if (pixelsToScroll)
- tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
- }
-
- if (effects == "move" &&
- this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
- ind.collapsed = true;
- this._animateTabMove(event);
- return;
- }
-
- this._finishAnimateTabMove();
-
- if (effects == "link") {
- let tab = this._getDragTargetTab(event, true);
- if (tab) {
- if (!this._dragTime)
- this._dragTime = Date.now();
- if (Date.now() >= this._dragTime + this._dragOverDelay)
- this.selectedItem = tab;
- ind.collapsed = true;
- return;
- }
- }
-
- var rect = tabStrip.getBoundingClientRect();
- var newMargin;
- if (pixelsToScroll) {
- // if we are scrolling, put the drop indicator at the edge
- // so that it doesn't jump while scrolling
- let scrollRect = tabStrip.scrollClientRect;
- let minMargin = scrollRect.left - rect.left;
- let maxMargin = Math.min(minMargin + scrollRect.width,
- scrollRect.right);
- if (!ltr)
- [minMargin, maxMargin] = [this.clientWidth - maxMargin,
- this.clientWidth - minMargin];
- newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
- } else {
- let newIndex = this._getDropIndex(event, effects == "link");
- if (newIndex == this.childNodes.length) {
- let tabRect = this.childNodes[newIndex - 1].getBoundingClientRect();
- if (ltr)
- newMargin = tabRect.right - rect.left;
- else
- newMargin = rect.right - tabRect.left;
- } else {
- let tabRect = this.childNodes[newIndex].getBoundingClientRect();
- if (ltr)
- newMargin = tabRect.left - rect.left;
- else
- newMargin = rect.right - tabRect.right;
- }
- }
-
- ind.collapsed = false;
-
- newMargin += ind.clientWidth / 2;
- if (!ltr)
- newMargin *= -1;
-
- ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
- ind.style.marginInlineStart = (-ind.clientWidth) + "px";
- ]]></handler>
-
- <handler event="drop"><![CDATA[
- var dt = event.dataTransfer;
- var dropEffect = dt.dropEffect;
- var draggedTab;
- if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) { // tab copy or move
- draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
- // not our drop then
- if (!draggedTab)
- return;
- }
-
- this._tabDropIndicator.collapsed = true;
- event.stopPropagation();
- if (draggedTab && dropEffect == "copy") {
- // copy the dropped tab (wherever it's from)
- let newIndex = this._getDropIndex(event, false);
- let newTab = this.tabbrowser.duplicateTab(draggedTab);
- this.tabbrowser.moveTabTo(newTab, newIndex);
- if (draggedTab.parentNode != this || event.shiftKey)
- this.selectedItem = newTab;
- } else if (draggedTab && draggedTab.parentNode == this) {
- this._finishAnimateTabMove();
-
- // actually move the dragged tab
- if ("animDropIndex" in draggedTab._dragData) {
- let newIndex = draggedTab._dragData.animDropIndex;
- if (newIndex > draggedTab._tPos)
- newIndex--;
- this.tabbrowser.moveTabTo(draggedTab, newIndex);
- }
- } else if (draggedTab) {
- let newIndex = this._getDropIndex(event, false);
- this.tabbrowser.adoptTab(draggedTab, newIndex, true);
- } else {
- // Pass true to disallow dropping javascript: or data: urls
- let links;
- try {
- links = browserDragAndDrop.dropLinks(event, true);
- } catch (ex) {}
-
- if (!links || links.length === 0)
- return;
-
- let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
-
- if (event.shiftKey)
- inBackground = !inBackground;
-
- let targetTab = this._getDragTargetTab(event, true);
- let userContextId = this.selectedItem.getAttribute("usercontextid");
- let replace = !!targetTab;
- let newIndex = this._getDropIndex(event, true);
- let urls = links.map(link => link.url);
- this.tabbrowser.loadTabs(urls, {
- inBackground,
- replace,
- allowThirdPartyFixup: true,
- targetTab,
- newIndex,
- userContextId,
- });
- }
-
- if (draggedTab) {
- delete draggedTab._dragData;
- }
- ]]></handler>
-
- <handler event="dragend"><![CDATA[
- // Note: while this case is correctly handled here, this event
- // isn't dispatched when the tab is moved within the tabstrip,
- // see bug 460801.
-
- this._finishAnimateTabMove();
-
- var dt = event.dataTransfer;
- var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
- if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
- delete draggedTab._dragData;
- return;
- }
-
- // Disable detach within the browser toolbox
- var eX = event.screenX;
- var eY = event.screenY;
- var wX = window.screenX;
- // check if the drop point is horizontally within the window
- if (eX > wX && eX < (wX + window.outerWidth)) {
- let bo = this.mTabstrip.boxObject;
- // also avoid detaching if the the tab was dropped too close to
- // the tabbar (half a tab)
- let endScreenY = bo.screenY + 1.5 * bo.height;
- if (eY < endScreenY && eY > window.screenY)
- return;
- }
-
- // screen.availLeft et. al. only check the screen that this window is on,
- // but we want to look at the screen the tab is being dropped onto.
- var screen = Cc["@mozilla.org/gfx/screenmanager;1"]
- .getService(Ci.nsIScreenManager)
- .screenForRect(eX, eY, 1, 1);
- var fullX = {}, fullY = {}, fullWidth = {}, fullHeight = {};
- var availX = {}, availY = {}, availWidth = {}, availHeight = {};
- // get full screen rect and available rect, both in desktop pix
- screen.GetRectDisplayPix(fullX, fullY, fullWidth, fullHeight);
- screen.GetAvailRectDisplayPix(availX, availY, availWidth, availHeight);
-
- // scale factor to convert desktop pixels to CSS px
- var scaleFactor =
- screen.contentsScaleFactor / screen.defaultCSSScaleFactor;
- // synchronize CSS-px top-left coordinates with the screen's desktop-px
- // coordinates, to ensure uniqueness across multiple screens
- // (compare the equivalent adjustments in nsGlobalWindow::GetScreenXY()
- // and related methods)
- availX.value = (availX.value - fullX.value) * scaleFactor + fullX.value;
- availY.value = (availY.value - fullY.value) * scaleFactor + fullY.value;
- availWidth.value *= scaleFactor;
- availHeight.value *= scaleFactor;
-
- // ensure new window entirely within screen
- var winWidth = Math.min(window.outerWidth, availWidth.value);
- var winHeight = Math.min(window.outerHeight, availHeight.value);
- var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, availX.value),
- availX.value + availWidth.value - winWidth);
- var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, availY.value),
- availY.value + availHeight.value - winHeight);
-
- delete draggedTab._dragData;
-
- if (this.tabbrowser.tabs.length == 1) {
- // resize _before_ move to ensure the window fits the new screen. if
- // the window is too large for its screen, the window manager may do
- // automatic repositioning.
- window.resizeTo(winWidth, winHeight);
- window.moveTo(left, top);
- window.focus();
- } else {
- let props = { screenX: left, screenY: top };
-#ifndef XP_WIN
- props.outerWidth = winWidth;
- props.outerHeight = winHeight;
-#endif
- this.tabbrowser.replaceTabWithWindow(draggedTab, props);
- }
- event.stopPropagation();
- ]]></handler>
-
- <handler event="dragexit"><![CDATA[
- this._dragTime = 0;
-
- // This does not work at all (see bug 458613)
- var target = event.relatedTarget;
- while (target && target != this)
- target = target.parentNode;
- if (target)
- return;
-
- this._tabDropIndicator.collapsed = true;
- event.stopPropagation();
- ]]></handler>
- </handlers>
- </binding>
-
- <!-- close-tab-button binding
- This binding relies on the structure of the tabbrowser binding.
- Therefore it should only be used as a child of the tab or the tabs
- element (in both cases, when they are anonymous nodes of <tabbrowser>).
- -->
- <binding id="tabbrowser-close-tab-button"
- extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
- <handlers>
- <handler event="click" button="0"><![CDATA[
- var bindingParent = document.getBindingParent(this);
- var tabContainer = bindingParent.parentNode;
- tabContainer.tabbrowser.removeTab(bindingParent, {animate: true,
- byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE});
- // This enables double-click protection for the tab container
- // (see tabbrowser-tabs 'click' handler).
- tabContainer._blockDblClick = true;
- ]]></handler>
-
- <handler event="dblclick" button="0" phase="capturing">
- // for the one-close-button case
- event.stopPropagation();
- </handler>
-
- <handler event="dragstart">
- event.stopPropagation();
- </handler>
- </handlers>
- </binding>
-
- <binding id="tabbrowser-tab" display="xul:hbox"
- extends="chrome://global/content/bindings/tabbox.xml#tab">
- <resources>
- <stylesheet src="chrome://browser/content/tabbrowser.css"/>
- </resources>
-
- <content context="tabContextMenu">
- <xul:stack class="tab-stack" flex="1">
- <xul:hbox xbl:inherits="pinned,selected=visuallyselected,fadein"
- class="tab-background">
- <xul:hbox xbl:inherits="pinned,selected=visuallyselected"
- class="tab-background-start"/>
- <xul:hbox xbl:inherits="pinned,selected=visuallyselected"
- class="tab-background-middle"/>
- <xul:hbox xbl:inherits="pinned,selected=visuallyselected"
- class="tab-background-end"/>
- </xul:hbox>
- <xul:hbox xbl:inherits="pinned,selected=visuallyselected,titlechanged,attention"
- class="tab-content" align="center">
- <xul:image xbl:inherits="fadein,pinned,busy,progress,selected=visuallyselected"
- class="tab-throbber"
- role="presentation"
- layer="true" />
- <xul:image xbl:inherits="src=image,loadingprincipal=iconLoadingPrincipal,fadein,pinned,selected=visuallyselected,busy,crashed,sharing"
- anonid="tab-icon-image"
- class="tab-icon-image"
- validate="never"
- role="presentation"/>
- <xul:image xbl:inherits="sharing,selected=visuallyselected"
- anonid="sharing-icon"
- class="tab-sharing-icon-overlay"
- role="presentation"/>
- <xul:image xbl:inherits="crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
- anonid="overlay-icon"
- class="tab-icon-overlay"
- role="presentation"/>
- <xul:hbox class="tab-label-container"
- xbl:inherits="pinned,selected=visuallyselected"
- onoverflow="this.setAttribute('textoverflow', 'true');"
- onunderflow="this.removeAttribute('textoverflow');"
- flex="1">
- <xul:label class="tab-text tab-label"
- xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
- role="presentation"/>
- </xul:hbox>
- <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
- anonid="soundplaying-icon"
- class="tab-icon-sound"
- role="presentation"/>
- <xul:toolbarbutton anonid="close-button"
- xbl:inherits="fadein,pinned,selected=visuallyselected"
- class="tab-close-button close-icon"/>
- </xul:hbox>
- </xul:stack>
- </content>
-
- <implementation>
- <constructor><![CDATA[
- if (!("_lastAccessed" in this)) {
- this.updateLastAccessed();
- }
- ]]></constructor>
-
- <property name="_visuallySelected">
- <setter>
- <![CDATA[
- if (val)
- this.setAttribute("visuallyselected", "true");
- else
- this.removeAttribute("visuallyselected");
- this.parentNode.tabbrowser._tabAttrModified(this, ["visuallyselected"]);
-
- this._setPositionAttributes(val);
-
- return val;
- ]]>
- </setter>
- </property>
-
- <property name="_selected">
- <setter>
- <![CDATA[
- // in e10s we want to only pseudo-select a tab before its rendering is done, so that
- // the rest of the system knows that the tab is selected, but we don't want to update its
- // visual status to selected until after we receive confirmation that its content has painted.
- if (val)
- this.setAttribute("selected", "true");
- else
- this.removeAttribute("selected");
-
- // If we're non-e10s we should update the visual selection as well at the same time,
- // *or* if we're e10s and the visually selected tab isn't changing, in which case the
- // tab switcher code won't run and update anything else (like the before- and after-
- // selected attributes).
- if (!gMultiProcessBrowser || (val && this.hasAttribute("visuallyselected"))) {
- this._visuallySelected = val;
- }
-
- return val;
- ]]>
- </setter>
- </property>
-
- <property name="pinned" readonly="true">
- <getter>
- return this.getAttribute("pinned") == "true";
- </getter>
- </property>
- <property name="hidden" readonly="true">
- <getter>
- return this.getAttribute("hidden") == "true";
- </getter>
- </property>
- <property name="muted" readonly="true">
- <getter>
- return this.getAttribute("muted") == "true";
- </getter>
- </property>
- <property name="blocked" readonly="true">
- <getter>
- return this.getAttribute("blocked") == "true";
- </getter>
- </property>
- <!--
- Describes how the tab ended up in this mute state. May be any of:
-
- - undefined: The tabs mute state has never changed.
- - null: The mute state was last changed through the UI.
- - Any string: The ID was changed through an extension API. The string
- must be the ID of the extension which changed it.
- -->
- <field name="muteReason">undefined</field>
-
- <property name="userContextId" readonly="true">
- <getter>
- return this.hasAttribute("usercontextid")
- ? parseInt(this.getAttribute("usercontextid"))
- : 0;
- </getter>
- </property>
-
- <property name="soundPlaying" readonly="true">
- <getter>
- return this.getAttribute("soundplaying") == "true";
- </getter>
- </property>
-
- <property name="soundBlocked" readonly="true">
- <getter>
- return this.getAttribute("blocked") == "true";
- </getter>
- </property>
-
- <property name="lastAccessed">
- <getter>
- return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
- </getter>
- </property>
- <method name="updateLastAccessed">
- <parameter name="aDate"/>
- <body><![CDATA[
- this._lastAccessed = this.selected ? Infinity : (aDate || Date.now());
- ]]></body>
- </method>
-
- <field name="cachePosition">Infinity</field>
-
- <field name="mOverCloseButton">false</field>
- <property name="_overPlayingIcon" readonly="true">
- <getter><![CDATA[
- let iconVisible = this.hasAttribute("soundplaying") ||
- this.hasAttribute("muted") ||
- this.hasAttribute("blocked");
- let soundPlayingIcon =
- document.getAnonymousElementByAttribute(this, "anonid", "soundplaying-icon");
- let overlayIcon =
- document.getAnonymousElementByAttribute(this, "anonid", "overlay-icon");
-
- return soundPlayingIcon && soundPlayingIcon.matches(":hover") ||
- (overlayIcon && overlayIcon.matches(":hover") && iconVisible);
- ]]></getter>
- </property>
- <field name="mCorrespondingMenuitem">null</field>
-
- <!--
- While it would make sense to track this in a field, the field will get nuked
- once the node is gone from the DOM, which causes us to think the tab is not
- closed, which causes us to make wrong decisions. So we use an expando instead.
- <field name="closing">false</field>
- -->
-
- <method name="_mouseenter">
- <body><![CDATA[
- if (this.hidden || this.closing)
- return;
-
- let tabContainer = this.parentNode;
- let visibleTabs = tabContainer.tabbrowser.visibleTabs;
- let tabIndex = visibleTabs.indexOf(this);
-
- if (this.selected)
- tabContainer._handleTabSelect();
-
- if (tabIndex == 0) {
- tabContainer._beforeHoveredTab = null;
- } else {
- let candidate = visibleTabs[tabIndex - 1];
- if (!candidate.selected) {
- tabContainer._beforeHoveredTab = candidate;
- candidate.setAttribute("beforehovered", "true");
- }
- }
-
- if (tabIndex == visibleTabs.length - 1) {
- tabContainer._afterHoveredTab = null;
- } else {
- let candidate = visibleTabs[tabIndex + 1];
- if (!candidate.selected) {
- tabContainer._afterHoveredTab = candidate;
- candidate.setAttribute("afterhovered", "true");
- }
- }
-
- tabContainer._hoveredTab = this;
- ]]></body>
- </method>
-
- <method name="_mouseleave">
- <body><![CDATA[
- let tabContainer = this.parentNode;
- if (tabContainer._beforeHoveredTab) {
- tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
- tabContainer._beforeHoveredTab = null;
- }
- if (tabContainer._afterHoveredTab) {
- tabContainer._afterHoveredTab.removeAttribute("afterhovered");
- tabContainer._afterHoveredTab = null;
- }
-
- tabContainer._hoveredTab = null;
- ]]></body>
- </method>
-
- <method name="startMediaBlockTimer">
- <body><![CDATA[
- TelemetryStopwatch.start("TAB_MEDIA_BLOCKING_TIME_MS", this);
- ]]></body>
- </method>
-
- <method name="finishMediaBlockTimer">
- <body><![CDATA[
- TelemetryStopwatch.finish("TAB_MEDIA_BLOCKING_TIME_MS", this);
- ]]></body>
- </method>
-
- <method name="toggleMuteAudio">
- <parameter name="aMuteReason"/>
- <body>
- <![CDATA[
- let tabContainer = this.parentNode;
- let browser = this.linkedBrowser;
- let modifiedAttrs = [];
- let hist = Services.telemetry.getHistogramById("TAB_AUDIO_INDICATOR_USED");
-
- if (browser.audioBlocked) {
- this.removeAttribute("blocked");
- modifiedAttrs.push("blocked");
-
- // We don't want sound icon flickering between "blocked", "none" and
- // "sound-playing", here adding the "soundplaying" is to keep the
- // transition smoothly.
- if (!this.hasAttribute("soundplaying")) {
- this.setAttribute("soundplaying", true);
- modifiedAttrs.push("soundplaying");
- }
-
- browser.resumeMedia();
- hist.add(3 /* unblockByClickingIcon */);
- this.finishMediaBlockTimer();
- } else {
- if (browser.audioMuted) {
- browser.unmute();
- this.removeAttribute("muted");
- BrowserUITelemetry.countTabMutingEvent("unmute", aMuteReason);
- hist.add(1 /* unmute */);
- } else {
- browser.mute();
- this.setAttribute("muted", "true");
- BrowserUITelemetry.countTabMutingEvent("mute", aMuteReason);
- hist.add(0 /* mute */);
- }
- this.muteReason = aMuteReason || null;
- modifiedAttrs.push("muted");
- }
- tabContainer.tabbrowser._tabAttrModified(this, modifiedAttrs);
- ]]>
- </body>
- </method>
-
- <method name="setUserContextId">
- <parameter name="aUserContextId"/>
- <body>
- <![CDATA[
- if (aUserContextId) {
- if (this.linkedBrowser) {
- this.linkedBrowser.setAttribute("usercontextid", aUserContextId);
- }
- this.setAttribute("usercontextid", aUserContextId);
- } else {
- if (this.linkedBrowser) {
- this.linkedBrowser.removeAttribute("usercontextid");
- }
- this.removeAttribute("usercontextid");
- }
-
- ContextualIdentityService.setTabStyle(this);
- ]]>
- </body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="mouseover"><![CDATA[
- let anonid = event.originalTarget.getAttribute("anonid");
- if (anonid == "close-button")
- this.mOverCloseButton = true;
-
- this._mouseenter();
- ]]></handler>
- <handler event="mouseout"><![CDATA[
- let anonid = event.originalTarget.getAttribute("anonid");
- if (anonid == "close-button")
- this.mOverCloseButton = false;
-
- this._mouseleave();
- ]]></handler>
- <handler event="dragstart" phase="capturing">
- this.style.MozUserFocus = "";
- </handler>
- <handler event="mousedown" phase="capturing">
- <![CDATA[
- if (this.selected) {
- this.style.MozUserFocus = "ignore";
- this.clientTop; // just using this to flush style updates
- } else if (this.mOverCloseButton ||
- this._overPlayingIcon) {
- // Prevent tabbox.xml from selecting the tab.
- event.stopPropagation();
- }
- ]]>
- </handler>
- <handler event="mouseup">
- this.style.MozUserFocus = "";
- </handler>
- <handler event="click">
- <![CDATA[
- if (event.button != 0) {
- return;
- }
-
- if (this._overPlayingIcon) {
- this.toggleMuteAudio();
- }
- ]]>
- </handler>
- </handlers>
- </binding>
-
- <binding id="tabbrowser-alltabs-popup"
- extends="chrome://global/content/bindings/popup.xml#popup">
- <implementation implements="nsIDOMEventListener">
- <method name="_tabOnAttrModified">
- <parameter name="aEvent"/>
- <body><![CDATA[
- var tab = aEvent.target;
- if (tab.mCorrespondingMenuitem)
- this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
- ]]></body>
- </method>
-
- <method name="_tabOnTabClose">
- <parameter name="aEvent"/>
- <body><![CDATA[
- var tab = aEvent.target;
- if (tab.mCorrespondingMenuitem)
- this.removeChild(tab.mCorrespondingMenuitem);
- ]]></body>
- </method>
-
- <method name="handleEvent">
- <parameter name="aEvent"/>
- <body><![CDATA[
- switch (aEvent.type) {
- case "TabAttrModified":
- this._tabOnAttrModified(aEvent);
- break;
- case "TabClose":
- this._tabOnTabClose(aEvent);
- break;
- case "scroll":
- this._updateTabsVisibilityStatus();
- break;
- }
- ]]></body>
- </method>
-
- <method name="_updateTabsVisibilityStatus">
- <body><![CDATA[
- var tabContainer = gBrowser.tabContainer;
- // We don't want menu item decoration unless there is overflow.
- if (tabContainer.getAttribute("overflow") != "true")
- return;
-
- var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
- for (var i = 0; i < this.childNodes.length; i++) {
- let curTab = this.childNodes[i].tab;
- if (!curTab) // "Undo close tab", menuseparator, or entries put here by addons.
- continue;
- let curTabBO = curTab.boxObject;
- if (curTabBO.screenX >= tabstripBO.screenX &&
- curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
- this.childNodes[i].setAttribute("tabIsVisible", "true");
- else
- this.childNodes[i].removeAttribute("tabIsVisible");
- }
- ]]></body>
- </method>
-
- <method name="_createTabMenuItem">
- <parameter name="aTab"/>
- <body><![CDATA[
- var menuItem = document.createElementNS(
- "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
- "menuitem");
-
- menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
-
- this._setMenuitemAttributes(menuItem, aTab);
-
- aTab.mCorrespondingMenuitem = menuItem;
- menuItem.tab = aTab;
-
- this.appendChild(menuItem);
- ]]></body>
- </method>
-
- <method name="_setMenuitemAttributes">
- <parameter name="aMenuitem"/>
- <parameter name="aTab"/>
- <body><![CDATA[
- aMenuitem.setAttribute("label", aTab.label);
- aMenuitem.setAttribute("crop", "end");
-
- if (aTab.hasAttribute("busy")) {
- aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
- aMenuitem.removeAttribute("image");
- } else {
- aMenuitem.setAttribute("image", aTab.getAttribute("image"));
- aMenuitem.removeAttribute("busy");
- }
-
- if (aTab.hasAttribute("pending"))
- aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
- else
- aMenuitem.removeAttribute("pending");
-
- if (aTab.selected)
- aMenuitem.setAttribute("selected", "true");
- else
- aMenuitem.removeAttribute("selected");
-
- function addEndImage() {
- let endImage = document.createElement("image");
- endImage.setAttribute("class", "alltabs-endimage");
- let endImageContainer = document.createElement("hbox");
- endImageContainer.setAttribute("align", "center");
- endImageContainer.setAttribute("pack", "center");
- endImageContainer.appendChild(endImage);
- aMenuitem.appendChild(endImageContainer);
- return endImage;
- }
-
- if (aMenuitem.firstChild)
- aMenuitem.firstChild.remove();
- if (aTab.hasAttribute("muted"))
- addEndImage().setAttribute("muted", "true");
- else if (aTab.hasAttribute("soundplaying"))
- addEndImage().setAttribute("soundplaying", "true");
- ]]></body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="popupshowing">
- <![CDATA[
- if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
- createUserContextMenu(event, {useAccessKeys: false});
- return;
- }
-
- let containersEnabled = Services.prefs.getBoolPref("privacy.userContext.enabled");
-
- if (event.target.getAttribute("anonid") == "newtab-popup" ||
- event.target.id == "newtab-popup") {
- createUserContextMenu(event, {useAccessKeys: false});
- } else {
- document.getElementById("alltabs-popup-separator-1").hidden = !containersEnabled;
- let containersTab = document.getElementById("alltabs_containersTab");
-
- containersTab.hidden = !containersEnabled;
- if (PrivateBrowsingUtils.isWindowPrivate(window)) {
- containersTab.setAttribute("disabled", "true");
- }
-
- document.getElementById("alltabs_undoCloseTab").disabled =
- SessionStore.getClosedTabCount(window) == 0;
-
- var tabcontainer = gBrowser.tabContainer;
-
- // Listen for changes in the tab bar.
- tabcontainer.addEventListener("TabAttrModified", this);
- tabcontainer.addEventListener("TabClose", this);
- tabcontainer.mTabstrip.addEventListener("scroll", this);
-
- let tabs = gBrowser.visibleTabs;
- for (var i = 0; i < tabs.length; i++) {
- if (!tabs[i].pinned)
- this._createTabMenuItem(tabs[i]);
- }
- this._updateTabsVisibilityStatus();
- }
- ]]></handler>
-
- <handler event="popuphidden">
- <![CDATA[
- if (event.target.getAttribute("id") == "alltabs_containersMenuTab") {
- return;
- }
-
- // clear out the menu popup and remove the listeners
- for (let i = this.childNodes.length - 1; i > 0; i--) {
- let menuItem = this.childNodes[i];
- if (menuItem.tab) {
- menuItem.tab.mCorrespondingMenuitem = null;
- this.removeChild(menuItem);
- }
- if (menuItem.hasAttribute("usercontextid")) {
- this.removeChild(menuItem);
- }
- }
- var tabcontainer = gBrowser.tabContainer;
- tabcontainer.mTabstrip.removeEventListener("scroll", this);
- tabcontainer.removeEventListener("TabAttrModified", this);
- tabcontainer.removeEventListener("TabClose", this);
- ]]></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>
-
- <handler event="command"><![CDATA[
- if (event.target.tab)
- gBrowser.selectedTab = event.target.tab;
- ]]></handler>
-
- </handlers>
- </binding>
-
- <binding id="statuspanel" display="xul:hbox">
- <content>
- <xul:hbox class="statuspanel-inner">
- <xul:label class="statuspanel-label"
- role="status"
- aria-live="off"
- xbl:inherits="value=label,crop,mirror"
- flex="1"
- crop="end"/>
- </xul:hbox>
- </content>
-
- <implementation implements="nsIDOMEventListener">
- <constructor><![CDATA[
- window.addEventListener("resize", this);
- ]]></constructor>
-
- <destructor><![CDATA[
- window.removeEventListener("resize", this);
- MousePosTracker.removeListener(this);
- ]]></destructor>
-
- <property name="label">
- <setter><![CDATA[
- if (!this.label) {
- this.removeAttribute("mirror");
- this.removeAttribute("sizelimit");
- }
-
- this.style.minWidth = this.getAttribute("type") == "status" &&
- this.getAttribute("previoustype") == "status"
- ? getComputedStyle(this).width : "";
-
- if (val) {
- this.setAttribute("label", val);
- this.removeAttribute("inactive");
- this._calcMouseTargetRect();
- MousePosTracker.addListener(this);
- } else {
- this.setAttribute("inactive", "true");
- MousePosTracker.removeListener(this);
- }
-
- return val;
- ]]></setter>
- <getter>
- return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
- </getter>
- </property>
-
- <method name="getMouseTargetRect">
- <body><![CDATA[
- return this._mouseTargetRect;
- ]]></body>
- </method>
-
- <method name="onMouseEnter">
- <body>
- this._mirror();
- </body>
- </method>
-
- <method name="onMouseLeave">
- <body>
- this._mirror();
- </body>
- </method>
-
- <method name="handleEvent">
- <parameter name="event"/>
- <body><![CDATA[
- if (!this.label)
- return;
-
- switch (event.type) {
- case "resize":
- this._calcMouseTargetRect();
- break;
- }
- ]]></body>
- </method>
-
- <method name="_calcMouseTargetRect">
- <body><![CDATA[
- let container = this.parentNode;
- let alignRight = (getComputedStyle(container).direction == "rtl");
- let panelRect = this.getBoundingClientRect();
- let containerRect = container.getBoundingClientRect();
-
- this._mouseTargetRect = {
- top: panelRect.top,
- bottom: panelRect.bottom,
- left: alignRight ? containerRect.right - panelRect.width : containerRect.left,
- right: alignRight ? containerRect.right : containerRect.left + panelRect.width
- };
- ]]></body>
- </method>
-
- <method name="_mirror">
- <body>
- if (this.hasAttribute("mirror"))
- this.removeAttribute("mirror");
- else
- this.setAttribute("mirror", "true");
-
- if (!this.hasAttribute("sizelimit")) {
- this.setAttribute("sizelimit", "true");
- this._calcMouseTargetRect();
- }
- </body>
- </method>
- </implementation>
- </binding>
-
- <binding id="tabbrowser-tabpanels"
- extends="chrome://global/content/bindings/tabbox.xml#tabpanels">
- <implementation>
- <field name="_selectedIndex">0</field>
-
- <property name="selectedIndex">
- <getter>
- <![CDATA[
- return this._selectedIndex;
- ]]>
- </getter>
-
- <setter>
- <![CDATA[
- if (val < 0 || val >= this.childNodes.length)
- return val;
-
- let toTab = this.getRelatedElement(this.childNodes[val]);
-
- gBrowser._getSwitcher().requestTab(toTab);
-
- var panel = this._selectedPanel;
- var newPanel = this.childNodes[val];
- this._selectedPanel = newPanel;
- if (this._selectedPanel != panel) {
- var event = document.createEvent("Events");
- event.initEvent("select", true, true);
- this.dispatchEvent(event);
-
- this._selectedIndex = val;
- }
-
- return val;
- ]]>
- </setter>
- </property>
- </implementation>
- </binding>
-
- <binding id="tabbrowser-browser"
- extends="chrome://global/content/bindings/browser.xml#browser">
- <implementation>
- <field name="tabModalPromptBox">null</field>
-
- <!-- 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[
- var params = arguments[1];
- if (typeof(params) == "number") {
- params = {
- flags: aFlags,
- referrerURI: aReferrerURI,
- charset: aCharset,
- postData: aPostData,
- };
- }
- _loadURIWithFlags(this, aURI, params);
- ]]>
- </body>
- </method>
- </implementation>
- </binding>
-
- <binding id="tabbrowser-remote-browser"
- extends="chrome://global/content/bindings/remote-browser.xml#remote-browser">
- <implementation>
- <field name="tabModalPromptBox">null</field>
-
- <!-- 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[
- var params = arguments[1];
- if (typeof(params) == "number") {
- params = {
- flags: aFlags,
- referrerURI: aReferrerURI,
- charset: aCharset,
- postData: aPostData,
- };
- }
- _loadURIWithFlags(this, aURI, params);
- ]]>
- </body>
- </method>
- </implementation>
- </binding>
-
-</bindings>