diff options
Diffstat (limited to 'application/basilisk/base/content/tabbrowser.xml')
-rw-r--r-- | application/basilisk/base/content/tabbrowser.xml | 7631 |
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> |