<?xml version="1.0"?> <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <!DOCTYPE bindings [ <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" > %tabBrowserDTD; ]> <bindings id="tabBrowserBindings" xmlns="http://www.mozilla.org/xbl" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:xbl="http://www.mozilla.org/xbl"> <binding id="tabbrowser"> <resources> <stylesheet src="chrome://browser/content/tabbrowser.css"/> </resources> <content> <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/> <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown" onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();"> <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer"> <xul:notificationbox flex="1"> <xul: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-primary" message="true" disablehistory="true" xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/> </xul:stack> </xul:vbox> </xul:hbox> </xul:notificationbox> </xul:tabpanels> </xul:tabbox> <children/> </content> <implementation implements="nsIDOMEventListener, nsIMessageListener"> <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, function (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="mFaviconService" readonly="true"> Components.classes["@mozilla.org/browser/favicon-service;1"] .getService(Components.interfaces.nsIFaviconService); </field> <field name="_placesAutocomplete" 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="mTabListeners"> [] </field> <field name="mTabFilters"> [] </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> <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="formValidationAnchor" readonly="true"> <getter><![CDATA[ if (this.mCurrentTab._formValidationAnchor) { return this.mCurrentTab._formValidationAnchor; } let stack = this.mCurrentBrowser.parentNode; // Create an anchor for the form validation popup const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; let formValidationAnchor = document.createElementNS(NS_XUL, "hbox"); formValidationAnchor.className = "form-validation-anchor"; formValidationAnchor.hidden = true; stack.appendChild(formValidationAnchor); return this.mCurrentTab._formValidationAnchor = formValidationAnchor; ]]></getter> </property> <method name="updateWindowResizers"> <body><![CDATA[ if (!window.gShowPageResizers) return; var show = document.getElementById("addon-bar").collapsed && window.windowState == window.STATE_NORMAL; for (let i = 0; i < this.browsers.length; i++) { this.browsers[i].showWindowResizer = show; } ]]></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).docShell.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.setAttribute("fadein", "true"); aTab.removeAttribute("pinned"); aTab.style.MozMarginStart = ""; this.tabContainer._unlockTabSizing(); this.tabContainer._positionPinnedTabs(); this.tabContainer.adjustTabstrip(); this.getBrowserForTab(aTab).docShell.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="getBrowserForOuterWindowID"> <parameter name="aID"/> <body> <![CDATA[ return this._outerWindowIDBrowserMap.get(aID); ]]> </body> </method> <method name="_getTabForContentWindow"> <parameter name="aWindow"/> <body> <![CDATA[ for (let i = 0; i < this.browsers.length; i++) { if (this.browsers[i].contentWindow == aWindow) return this.tabs[i]; } return null; ]]> </body> </method> <!-- 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[ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; let browser = (aBrowser || this.mCurrentBrowser); let stack = browser.parentNode; let self = this; let promptBox = { appendPrompt : function(args, onCloseCallback) { let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt"); stack.appendChild(newPrompt); browser.setAttribute("tabmodalPromptShowing", true); newPrompt.clientTop; // style flush to assure binding is attached let tab = self._getTabForContentWindow(browser.contentWindow); newPrompt.init(args, tab, onCloseCallback); return newPrompt; }, removePrompt : function(aPrompt) { stack.removeChild(aPrompt); let prompts = this.listPrompts(); if (prompts.length) { let prompt = prompts[prompts.length - 1]; prompt.Dialog.setDefaultFocus(); } else { browser.removeAttribute("tabmodalPromptShowing"); browser.focus(); } }, listPrompts : function(aPrompt) { let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt"); // NodeList --> real JS array let prompts = Array.slice(els); return prompts; }, }; return promptBox; ]]> </body> </method> <method name="_callProgressListeners"> <parameter name="aBrowser"/> <parameter name="aMethod"/> <parameter name="aArguments"/> <parameter name="aCallGlobalListeners"/> <parameter name="aCallTabsListeners"/> <body><![CDATA[ var rv = true; if (!aBrowser) aBrowser = this.mCurrentBrowser; if (aCallGlobalListeners != false && aBrowser == this.mCurrentBrowser) { this.mProgressListeners.forEach(function (p) { if (aMethod in p) { try { if (!p[aMethod].apply(p, aArguments)) rv = false; } catch (e) { // don't inhibit other listeners Components.utils.reportError(e); } } }); } if (aCallTabsListeners != false) { aArguments.unshift(aBrowser); this.mTabsProgressListeners.forEach(function (p) { if (aMethod in p) { try { if (!p[aMethod].apply(p, aArguments)) rv = false; } catch (e) { // don't inhibit other listeners Components.utils.reportError(e); } } }); } return rv; ]]></body> </method> <!-- A web progress listener object definition for a given tab. --> <method name="mTabProgressListener"> <parameter name="aTab"/> <parameter name="aBrowser"/> <parameter name="aStartsBlank"/> <body> <![CDATA[ return ({ mTabBrowser: this, mTab: aTab, mBrowser: aBrowser, mBlank: aStartsBlank, // cache flags for correct status UI update after tab switching mStateFlags: 0, mStatus: 0, mMessage: "", mTotalProgress: 0, // count of open requests (should always be 0 or 1) mRequestCount: 0, destroy: function () { delete this.mTab; delete this.mBrowser; delete this.mTabBrowser; }, _callProgressListeners: function () { Array.unshift(arguments, this.mBrowser); return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments); }, _shouldShowProgress: function (aRequest) { if (this.mBlank) return false; if (gMultiProcessBrowser) return true; // Don't show progress indicators in tabs for about: URIs // pointing to local resources. try { let channel = aRequest.QueryInterface(Ci.nsIChannel); if (channel.originalURI.schemeIs("about") && (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file"))) return false; } catch (e) {} return true; }, onProgressChange: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0; if (!this._shouldShowProgress(aRequest)) return; if (this.mTotalProgress) this.mTab.setAttribute("progress", "true"); this._callProgressListeners("onProgressChange", [aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress]); }, onProgressChange64: function (aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) { return this.onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); }, onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) { if (!aRequest) return; var oldBlank = this.mBlank; const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; const nsIChannel = Components.interfaces.nsIChannel; if (aStateFlags & nsIWebProgressListener.STATE_START) { this.mRequestCount++; } else if (aStateFlags & nsIWebProgressListener.STATE_STOP) { const NS_ERROR_UNKNOWN_HOST = 2152398878; if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) { // to prevent bug 235825: wait for the request handled // by the automatic keyword resolver return; } // since we (try to) only handle STATE_STOP of the last request, // the count of open requests should now be 0 this.mRequestCount = 0; } if (aStateFlags & nsIWebProgressListener.STATE_START && aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { // It's okay to clear what the user typed when we start // loading a document. If the user types, this counter gets // set to zero, if the document load ends without an // onLocationChange, this counter gets decremented // (so we keep it while switching tabs after failed loads) // We need to add 2 because loadURIWithFlags may have // cancelled a pending load which would have cleared // its anchor scroll detection temporary increment. if (aWebProgress.isTopLevel) this.mBrowser.userTypedClear += 2; if (this._shouldShowProgress(aRequest)) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this.mTab.setAttribute("busy", "true"); if (!gMultiProcessBrowser) { if (aWebProgress.isTopLevel && !(this.mBrowser.docShell.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); if (!this.mTab.selected) this.mTab.setAttribute("unread", "true"); } this.mTab.removeAttribute("progress"); if (aWebProgress.isTopLevel) { if (!Components.isSuccessCode(aStatus) && !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; if (this.mTab.selected && gURLBar) URLBarSetURI(); } else { // The document is done loading, we no longer want the // value cleared. if (this.mBrowser.userTypedClear > 1) this.mBrowser.userTypedClear -= 2; else if (this.mBrowser.userTypedClear > 0) this.mBrowser.userTypedClear--; } if (!this.mBrowser.mIconURL) this.mTabBrowser.useDefaultIcon(this.mTab); } if (this.mBlank) this.mBlank = false; var location = aRequest.QueryInterface(nsIChannel).URI; // For keyword URIs clear the user typed value since they will be changed into real URIs if (location.scheme == "keyword") this.mBrowser.userTypedValue = null; if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting")) this.mTabBrowser.setTabTitle(this.mTab); if (this.mTab.selected) this.mTabBrowser.mIsBusy = false; } if (oldBlank) { this._callProgressListeners("onUpdateCurrentBrowser", [aStateFlags, aStatus, "", 0], true, false); } else { this._callProgressListeners("onStateChange", [aWebProgress, aRequest, aStateFlags, aStatus], true, false); } this._callProgressListeners("onStateChange", [aWebProgress, aRequest, aStateFlags, aStatus], false); if (aStateFlags & (nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_STOP)) { // reset cached temporary values at beginning and end this.mMessage = ""; this.mTotalProgress = 0; } this.mStateFlags = aStateFlags; this.mStatus = aStatus; }, onLocationChange: function (aWebProgress, aRequest, aLocation, aFlags) { // OnLocationChange is called for both the top-level content // and the subframes. let topLevel = aWebProgress.isTopLevel; if (topLevel) { // If userTypedClear > 0, the document loaded correctly and we should be // clearing the user typed value. We also 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). if (this.mBrowser.userTypedClear > 0 || ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && aLocation.spec != "about:blank")) this.mBrowser.userTypedValue = null; // Don't clear the favicon if this onLocationChange was // triggered by a pushState or a replaceState. See bug 550565. if (!gMultiProcessBrowser) { if (aWebProgress.isLoadingDocument && !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) this.mBrowser.mIconURL = null; } let autocomplete = this.mTabBrowser._placesAutocomplete; if (this.mBrowser.registeredOpenURI) { autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI); 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)) { autocomplete.registerOpenPage(aLocation); this.mBrowser.registeredOpenURI = aLocation; } } if (!this.mBlank) { this._callProgressListeners("onLocationChange", [aWebProgress, aRequest, aLocation, aFlags]); } if (topLevel) this.mBrowser.lastURI = aLocation; }, onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) { if (this.mBlank) return; this._callProgressListeners("onStatusChange", [aWebProgress, aRequest, aStatus, aMessage]); this.mMessage = aMessage; }, onSecurityChange: function (aWebProgress, aRequest, aState) { this._callProgressListeners("onSecurityChange", [aWebProgress, aRequest, aState]); }, onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) { return this._callProgressListeners("onRefreshAttempted", [aWebProgress, aURI, aDelay, aSameURI]); }, QueryInterface: function (aIID) { if (aIID.equals(Components.interfaces.nsIWebProgressListener) || aIID.equals(Components.interfaces.nsIWebProgressListener2) || aIID.equals(Components.interfaces.nsISupportsWeakReference) || aIID.equals(Components.interfaces.nsISupports)) return this; throw Components.results.NS_NOINTERFACE; } }); ]]> </body> </method> <method name="setIcon"> <parameter name="aTab"/> <parameter name="aURI"/> <body> <![CDATA[ var browser = this.getBrowserForTab(aTab); browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI; if (aURI && this.mFaviconService) { if (!(aURI instanceof Ci.nsIURI)) aURI = makeURI(aURI); this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI, aURI, false, PrivateBrowsingUtils.isWindowPrivate(window) ? this.mFaviconService.FAVICON_LOAD_PRIVATE : this.mFaviconService.FAVICON_LOAD_NON_PRIVATE); } let sizedIconUrl = browser.mIconURL || ""; if (sizedIconUrl) { let size = Math.round(16 * window.devicePixelRatio); sizedIconUrl += (sizedIconUrl.includes("#") ? "&" : "#") + "-moz-resolution=" + size + "," + size; } if (sizedIconUrl != aTab.getAttribute("image")) { if (browser.mIconURL) //PMed aTab.setAttribute("image", sizedIconUrl); else aTab.removeAttribute("image"); this._tabAttrModified(aTab); } this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]); ]]> </body> </method> <method name="getIcon"> <parameter name="aTab"/> <body> <![CDATA[ let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser; return browser.mIconURL; ]]> </body> </method> <method name="shouldLoadFavIcon"> <parameter name="aURI"/> <body> <![CDATA[ return (aURI && Services.prefs.getBoolPref("browser.chrome.site_icons") && Services.prefs.getBoolPref("browser.chrome.favicons") && ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https"))); ]]> </body> </method> <method name="useDefaultIcon"> <parameter name="aTab"/> <body> <![CDATA[ // Bug 691610 - e10s support for useDefaultIcon if (gMultiProcessBrowser) return; var browser = this.getBrowserForTab(aTab); var docURIObject = browser.contentDocument.documentURIObject; var icon = null; <!-- Pale Moon: new image icon method, see bug #305986 --> let req = browser.contentDocument.imageRequest; let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size"); if (browser.contentDocument instanceof ImageDocument && req && req.image) { if (Services.prefs.getBoolPref("browser.chrome.site_icons") && sz) { try { <!-- Main method: draw on a hidden canvas --> var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); var tabImg = document.getAnonymousElementByAttribute(aTab, "anonid", "tab-icon"); var w = tabImg.boxObject.width; var h = tabImg.boxObject.height; canvas.width = w; canvas.height = h; var ctx = canvas.getContext('2d'); ctx.drawImage(browser.contentDocument.body.firstChild, 0, 0, w, h); icon = canvas.toDataURL(); } catch (e) { <!-- Fallback method in case canvas method fails, restricted by sz --> try { if (req && req.image && req.image.width <= sz && req.image.height <= sz) icon = browser.currentURI; } catch (e) { <!-- Both methods fail (very large or corrupt image): icon remains null --> } } } } // Use documentURIObject in the check for shouldLoadFavIcon so that we // do the right thing with about:-style error pages. Bug 453442 else if (this.shouldLoadFavIcon(docURIObject)) { let url = docURIObject.prePath + "/favicon.ico"; if (!this.isFailedIcon(url)) icon = url; } this.setIcon(aTab, icon); ]]> </body> </method> <method name="isFailedIcon"> <parameter name="aURI"/> <body> <![CDATA[ if (this.mFaviconService) { if (!(aURI instanceof Ci.nsIURI)) aURI = makeURI(aURI); return this.mFaviconService.isFailedFavicon(aURI); } return null; ]]> </body> </method> <method name="getWindowTitleForBrowser"> <parameter name="aBrowser"/> <body> <![CDATA[ var newTitle = ""; var 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="freezeTitlebar"> <parameter name="aTitle"/> <body> <![CDATA[ this._frozenTitle = aTitle || ""; this.updateTitlebar(); ]]> </body> </method> <method name="unfreezeTitlebar"> <body> <![CDATA[ this._frozenTitle = ""; this.updateTitlebar(); ]]> </body> </method> <method name="updateTitlebar"> <body> <![CDATA[ this.ownerDocument.title = this._frozenTitle || this.getWindowTitleForBrowser(this.mCurrentBrowser); ]]> </body> </method> <method name="updateCurrentBrowser"> <parameter name="aForceUpdate"/> <body> <![CDATA[ var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex); if (this.mCurrentBrowser == newBrowser && !aForceUpdate) return; if (!aForceUpdate) { window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) .beginTabSwitch(); } 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 (oldBrowser) { oldBrowser.setAttribute("type", "content-targetable"); oldBrowser.docShellIsActive = false; this.finder.mListeners.forEach(l => oldBrowser.finder.removeResultListener(l)); } var updateBlockedPopups = false; if (!oldBrowser || (oldBrowser.blockedPopups && !newBrowser.blockedPopups) || (!oldBrowser.blockedPopups && newBrowser.blockedPopups)) updateBlockedPopups = true; newBrowser.setAttribute("type", "content-primary"); newBrowser.docShellIsActive = (window.windowState != window.STATE_MINIMIZED); this.mCurrentBrowser = newBrowser; this.mCurrentTab = this.tabContainer.selectedItem; this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.addResultListener(l)); this.showTab(this.mCurrentTab); var backForwardContainer = document.getElementById("unified-back-forward-button"); if (backForwardContainer) { backForwardContainer.setAttribute("switchingtabs", "true"); window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() { window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr); backForwardContainer.removeAttribute("switchingtabs"); }); } if (updateBlockedPopups) this.mCurrentBrowser.updateBlockedPopups(); // Update the URL bar. var loc = this.mCurrentBrowser.currentURI; // Bug 666809 - SecurityUI support for e10s var webProgress = this.mCurrentBrowser.webProgress; var securityUI = this.mCurrentBrowser.securityUI; this._callProgressListeners(null, "onLocationChange", [webProgress, null, loc, 0], true, false); if (securityUI) { this._callProgressListeners(null, "onSecurityChange", [webProgress, null, securityUI.state], true, false); } var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null; if (listener && listener.mStateFlags) { this._callProgressListeners(null, "onUpdateCurrentBrowser", [listener.mStateFlags, listener.mStatus, listener.mMessage, listener.mTotalProgress], true, false); } if (!this._previewMode) { this.mCurrentTab.removeAttribute("unread"); this.selectedTab.lastAccessed = Date.now(); // Bug 666816 - TypeAheadFind support for e10s if (!gMultiProcessBrowser) this._fastFind.setDocShell(this.mCurrentBrowser.docShell); this.updateTitlebar(); this.mCurrentTab.removeAttribute("titlechanged"); } // If the new tab is busy, and our current state is not busy, then // we need to fire a start to all progress listeners. const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener; if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) { this.mIsBusy = true; this._callProgressListeners(null, "onStateChange", [webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0], true, false); } // If the new tab is not busy, and our current state is busy, then // we need to fire a stop to all progress listeners. if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) { this.mIsBusy = false; this._callProgressListeners(null, "onStateChange", [webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0], true, false); } 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); this._tabAttrModified(this.mCurrentTab); // Adjust focus oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused); do { // When 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(); break; } // 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(); break; } // 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(); break; } else if (isTabEmpty(this.mCurrentTab)) { focusAndSelectUrlBar(); break; } } // If the find bar is focused, keep it focused. if (gFindBarInitialized && !gFindBar.hidden && gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true") break; // Otherwise, focus the content area. let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); let 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); } while (false); } this.tabContainer._setPositionalAttributes(); ]]> </body> </method> <method name="_tabAttrModified"> <parameter name="aTab"/> <body><![CDATA[ if (aTab.closing) return; // This event should be dispatched when any of these attributes change: // label, crop, busy, image, selected var event = document.createEvent("Events"); event.initEvent("TabAttrModified", true, false); aTab.dispatchEvent(event); ]]></body> </method> <method name="setTabTitleLoading"> <parameter name="aTab"/> <body> <![CDATA[ aTab.label = this.mStringBundle.getString("tabs.connecting"); aTab.crop = "end"; this._tabAttrModified(aTab); ]]> </body> </method> <method name="setTabTitle"> <parameter name="aTab"/> <body> <![CDATA[ var browser = this.getBrowserForTab(aTab); var crop = "end"; var title = browser.contentTitle; if (!title) { if (browser.currentURI.spec) { try { title = this.mURIFixup.createExposableURI(browser.currentURI).spec; } catch(ex) { title = browser.currentURI.spec; } } if (title && !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. */ } crop = "center"; } else // Still no title? Fall back to our untitled string. title = this.mStringBundle.getString("tabs.emptyTabTitle"); } if (aTab.label == title && aTab.crop == crop) return false; aTab.label = title; aTab.crop = crop; this._tabAttrModified(aTab); if (aTab.selected) this.updateTitlebar(); return true; ]]> </body> </method> <method name="loadOneTab"> <parameter name="aURI"/> <parameter name="aReferrerURI"/> <parameter name="aCharset"/> <parameter name="aPostData"/> <parameter name="aLoadInBackground"/> <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ var aFromExternal; var aRelatedToCurrent; if (arguments.length == 2 && typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; aReferrerURI = params.referrerURI; aCharset = params.charset; aPostData = params.postData; aLoadInBackground = params.inBackground; aAllowThirdPartyFixup = params.allowThirdPartyFixup; aFromExternal = params.fromExternal; aRelatedToCurrent = params.relatedToCurrent; } var bgLoad = (aLoadInBackground != null) ? aLoadInBackground : Services.prefs.getBoolPref("browser.tabs.loadInBackground"); var owner = bgLoad ? null : this.selectedTab; var tab = this.addTab(aURI, { referrerURI: aReferrerURI, charset: aCharset, postData: aPostData, ownerTab: owner, allowThirdPartyFixup: aAllowThirdPartyFixup, fromExternal: aFromExternal, relatedToCurrent: aRelatedToCurrent}); if (!bgLoad) this.selectedTab = tab; return tab; ]]> </body> </method> <method name="loadTabs"> <parameter name="aURIs"/> <parameter name="aLoadInBackground"/> <parameter name="aReplace"/> <body><![CDATA[ if (!aURIs.length) return; // The tab selected after this new tab is closed (i.e. the new tab's // "owner") is the next adjacent tab (i.e. not the previously viewed tab) // when several urls are opened here (i.e. closing the first should select // the next of many URLs opened) or if the pref to have UI links opened in // the background is set (i.e. the link is not being opened modally) // // i.e. // Number of URLs Load UI Links in BG Focus Last Viewed? // == 1 false YES // == 1 true NO // > 1 false/true NO var multiple = aURIs.length > 1; var owner = multiple || aLoadInBackground ? null : this.selectedTab; var firstTabAdded = null; if (aReplace) { try { this.loadURI(aURIs[0], null, null); } catch (e) { // Ignore failure in case a URI is wrong, so we can continue // opening the next ones. } } else firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple}); var tabNum = this.tabContainer.selectedIndex; for (let i = 1; i < aURIs.length; ++i) { let tab = this.addTab(aURIs[i], {skipAnimation: true}); if (aReplace) this.moveTabTo(tab, ++tabNum); } if (!aLoadInBackground) { if (firstTabAdded) { // .selectedTab setter focuses the content area this.selectedTab = firstTabAdded; } else this.selectedBrowser.focus(); } ]]></body> </method> <method name="addTab"> <parameter name="aURI"/> <parameter name="aReferrerURI"/> <parameter name="aCharset"/> <parameter name="aPostData"/> <parameter name="aOwner"/> <parameter name="aAllowThirdPartyFixup"/> <body> <![CDATA[ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; var aFromExternal; var aRelatedToCurrent; var aSkipAnimation; if (arguments.length == 2 && typeof arguments[1] == "object" && !(arguments[1] instanceof Ci.nsIURI)) { let params = arguments[1]; aReferrerURI = params.referrerURI; aCharset = params.charset; aPostData = params.postData; aOwner = params.ownerTab; aAllowThirdPartyFixup = params.allowThirdPartyFixup; aFromExternal = params.fromExternal; aRelatedToCurrent = params.relatedToCurrent; aSkipAnimation = params.skipAnimation; } // 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 t.setAttribute("label", aURI); t.setAttribute("crop", "end"); t.setAttribute("validate", "never"); //PMed 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 caches this._browsers = null; this._visibleTabs = null; this.tabContainer.appendChild(t); // If this new tab is owned by another, assert that relationship if (aOwner) t.owner = aOwner; var b = document.createElementNS( "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser"); b.setAttribute("type", "content-targetable"); b.setAttribute("message", "true"); b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu")); b.setAttribute("tooltip", this.getAttribute("contenttooltip")); if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL && Services.prefs.getBoolPref("browser.tabs.remote")) { b.setAttribute("remote", "true"); } if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed && window.windowState == window.STATE_NORMAL) { b.setAttribute("showresizer", "true"); } if (this.hasAttribute("autocompletepopup")) b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup")); b.setAttribute("autoscrollpopup", this._autoScrollPopup.id); // 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.appendChild(browserSidebarContainer); var position = this.tabs.length - 1; var uniqueId = "panel" + Date.now() + position; notificationbox.id = uniqueId; t.linkedPanel = uniqueId; t.linkedBrowser = b; this._tabForBrowser.set(b, t); t._tPos = position; this.tabContainer._setPositionalAttributes(); // Prevent the superfluous initial load of a blank document // if we're going to load something other than about:blank. if (!uriIsAboutBlank) { b.setAttribute("nodefaultsrc", "true"); } // NB: this appendChild call causes us to run constructors for the // browser element, which fires off a bunch of notifications. Some // of those notifications can cause code to run that inspects our // state, so it is important that the tab element is fully // initialized by this point. this.mPanelContainer.appendChild(notificationbox); this.tabContainer.updateVisibility(); // wire up a progress listener for the new browser object. var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank); const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"] .createInstance(Components.interfaces.nsIWebProgress); filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL); b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL); this.mTabListeners[position] = tabListener; this.mTabFilters[position] = filter; b._fastFind = this.fastFind; b.droppedLinkHandler = handleDroppedLink; // If we just created a new tab that loads the default // newtab url, swap in a preloaded page if possible. // Do nothing if we're a private window. let docShellsSwapped = false; if (aURI == BROWSER_NEW_TAB_URL && !PrivateBrowsingUtils.isWindowPrivate(window)) { docShellsSwapped = gBrowserNewTabPreloader.newTab(t); } // Dispatch a new tab notification. We do this once we're // entirely done, so that things are in a consistent state // even if the event listener opens or closes tabs. var evt = document.createEvent("Events"); evt.initEvent("TabOpen", true, false); t.dispatchEvent(evt); // If we didn't swap docShells with a preloaded browser // then let's just continue loading the page normally. if (!docShellsSwapped && !uriIsAboutBlank) { // 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; try { b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData); } catch (ex) { Cu.reportError(ex); } } // We start our browsers out as inactive, and then maintain // activeness in the tab switcher. b.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(). // // Note: Only do this of we still have a docShell. The TabOpen // event was dispatched above and a gBrowser.removeTab() call from // one of its listeners could cause us to fail here. if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL && !Services.prefs.getBoolPref("browser.tabs.remote") && b.docShell) { this._outerWindowIDBrowserMap.set(b.outerWindowID, b); } // 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) { mozRequestAnimationFrame(function () { this.tabContainer._handleTabTelemetryStart(t, aURI); // kick the animation off t.setAttribute("fadein", "true"); // This call to adjustTabstrip is redundant but needed so that // when opening a second tab, the first tab's close buttons // appears immediately rather than when the transition ends. if (this.tabs.length - this._removingTabs.length == 2) this.tabContainer.adjustTabstrip(); }.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 buttonPressed = ps.confirmEx(window, bundle.getString("tabs.closeWarningTitle"), bundle.getFormattedString("tabs.closeWarningMultipleTabs", [tabsToClose]), (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1), bundle.getString("tabs.closeButtonMultiple"), null, null, aCloseTabs == this.closingTabsEnum.ALL ? bundle.getString("tabs.closeWarningPromptMe") : null, warnOnClose); var reallyClose = (buttonPressed == 0); // don't set the prefs unless they press OK and have unchecked the box if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value) { Services.prefs.setBoolPref("browser.tabs.warnOnClose", false); Services.prefs.setBoolPref("browser.tabs.warnOnCloseOtherTabs", 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"/> <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], {animate: true}); } } ]]> </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]); } } ]]> </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; } // 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, false, null, true)) 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"); if (this.tabs.length - this._removingTabs.length == 1) { // The second tab just got closed and we will end up with a single // one. Remove the first tab's close button immediately (if needed) // rather than after the tabclose animation ends. this.tabContainer.adjustTabstrip(); } 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="aTabWillBeMoved"/> <parameter name="aCloseWindowWithLastTab"/> <parameter name="aCloseWindowFastpath"/> <body> <![CDATA[ if (aTab.closing || aTab._pendingPermitUnload || this._windowIsClosing) return false; var browser = this.getBrowserForTab(aTab); if (!aTabWillBeMoved) { let ds = browser.docShell; if (ds && ds.contentViewer) { // We need to block while calling permitUnload() because it // processes the event queue and may lead to another removeTab() // call before permitUnload() even returned. aTab._pendingPermitUnload = true; let permitUnload = ds.contentViewer.permitUnload(); delete aTab._pendingPermitUnload; if (!permitUnload) return false; } } var closeWindow = false; var newTab = false; if (this.tabs.length - this._removingTabs.length == 1) { closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab : !window.toolbar.visible || this.tabContainer._closeWindowWithLastTab; // Closing the tab and replacing it with a blank one is notably slower // than closing the window right away. If the caller opts in, take // the fast path. if (closeWindow && aCloseWindowFastpath && this._removingTabs.length == 0 && (this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow))) return null; newTab = true; } aTab.closing = true; this._removingTabs.push(aTab); this._visibleTabs = null; // invalidate cache 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 = document.createEvent("UIEvent"); evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0); aTab.dispatchEvent(evt); if (!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.mTabFilters[aTab._tPos]; browser.webProgress.removeProgressListener(filter); filter.removeProgressListener(this.mTabListeners[aTab._tPos]); this.mTabListeners[aTab._tPos].destroy(); if (browser.registeredOpenURI && !aTabWillBeMoved) { this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI); delete browser.registeredOpenURI; } // We are no longer the primary content area. browser.setAttribute("type", "content-targetable"); // Remove this tab as the owner of any other tabs, since it's going away. Array.forEach(this.tabs, function (tab) { if ("owner" in tab && tab.owner == aTab) // |tab| is a child of the tab we're removing, make it an orphan tab.owner = null; }); aTab._endRemoveArgs = [closeWindow, newTab]; return true; ]]> </body> </method> <method name="_endRemoveTab"> <parameter name="aTab"/> <body> <![CDATA[ if (!aTab || !aTab._endRemoveArgs) return; var [aCloseWindow, aNewTab] = aTab._endRemoveArgs; aTab._endRemoveArgs = null; if (this._windowIsClosing) { aCloseWindow = false; aNewTab = false; } this._lastRelatedTab = null; // update the UI early for responsiveness aTab.collapsed = true; this.tabContainer._fillTrailingGap(); this._blurTab(aTab); this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1); if (aCloseWindow) { this._windowIsClosing = true; while (this._removingTabs.length) this._endRemoveTab(this._removingTabs[0]); } else if (!this._windowIsClosing) { if (aNewTab) focusAndSelectUrlBar(); // workaround for bug 345399 this.tabContainer.mTabstrip._updateScrollButtonsDisabledState(); } // We're going to remove the tab and the browser now. // Clean up mTabFilters and mTabListeners now rather than in // _beginRemoveTab, so that their size is always in sync with the // number of tabs and browsers (the xbl destructor depends on this). this.mTabFilters.splice(aTab._tPos, 1); this.mTabListeners.splice(aTab._tPos, 1); var browser = this.getBrowserForTab(aTab); 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(); if (browser == this.mCurrentBrowser) this.mCurrentBrowser = null; var wasPinned = aTab.pinned; // Invalidate browsers cache, as the tab is removed from the // tab container. this._browsers = null; // Remove the tab ... this.tabContainer.removeChild(aTab); // ... and fix up the _tPos properties immediately. for (let i = aTab._tPos; i < this.tabs.length; i++) this.tabs[i]._tPos = i; if (!this._windowIsClosing) { if (wasPinned) this.tabContainer._positionPinnedTabs(); // update tab close buttons state this.tabContainer.adjustTabstrip(); setTimeout(function(tabs) { tabs._lastTabClosedByMouse = false; }, 0, this.tabContainer); } // Pale Moon: if resizing immediately, select the tab immediately to the left // instead of the right (if not leftmost) to prevent focus swap and // "selected tab not under cursor" // FIXME: Tabs must be sliding in from the left for this, or it'd unfocus // in the other direction! Disabled for now. Is there an easier way? :hover? // Is this even needed when resizing immediately?... //if (Services.prefs.getBoolPref("browser.tabs.resize_immediately")) { // if (this.selectedTab._tPos > 1) { // let newPos = this.selectedTab._tPos - 1; // this.selectedTab = this.tabs[newPos]; // } //} // 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); // 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) { let selectedPanel = this.mTabBox.selectedPanel; this.mPanelContainer.removeChild(panel); // Under the hood, a selectedIndex attribute controls which panel // is displayed. Removing a panel A which precedes the selected // panel B makes selectedIndex point to the panel next to B. We // need to explicitly preserve B as the selected panel. this.mTabBox.selectedPanel = selectedPanel; } if (aCloseWindow) this._windowIsClosing = closeWindow(true, 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="swapNewTabWithBrowser"> <parameter name="aNewTab"/> <parameter name="aBrowser"/> <body> <![CDATA[ // The browser must be standalone. if (aBrowser.getTabBrowser()) throw Cr.NS_ERROR_INVALID_ARG; // The tab is definitely not loading. aNewTab.removeAttribute("busy"); if (aNewTab.selected) { this.mIsBusy = false; } this._swapBrowserDocShells(aNewTab, aBrowser); // Update the new tab's title. this.setTabTitle(aNewTab); if (aNewTab.selected) { this.updateCurrentBrowser(true); } ]]> </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; // That's gBrowser for the other window, not the tab's browser! var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser; var isPending = aOtherTab.hasAttribute("pending"); // First, start teardown of the other browser. Make sure to not // fire the beforeunload event in the process. Close the other // window if this was its last tab. if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true)) return; let ourBrowser = this.getBrowserForTab(aOurTab); let otherBrowser = aOtherTab.linkedBrowser; // 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) { let ss = Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore) ss.setTabState(aOurTab, ss.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); var isBusy = aOtherTab.hasAttribute("busy"); if (isBusy) { aOurTab.setAttribute("busy", "true"); this._tabAttrModified(aOurTab); if (aOurTab.selected) this.mIsBusy = true; } this._swapBrowserDocShells(aOurTab, otherBrowser); } // 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); ]]> </body> </method> <method name="_swapBrowserDocShells"> <parameter name="aOurTab"/> <parameter name="aOtherBrowser"/> <body> <![CDATA[ // Unhook our progress listener let index = aOurTab._tPos; const filter = this.mTabFilters[index]; let tabListener = this.mTabListeners[index]; 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); } // Swap the docshells ourBrowser.swapDocShells(aOtherBrowser); // Register new outerWindowIDs. this._outerWindowIDBrowserMap.set(ourBrowser.outerWindowID, ourBrowser); if (remoteBrowser) { remoteBrowser._outerWindowIDBrowserMap.set(aOtherBrowser.outerWindowID, aOtherBrowser); } // Restore the progress listener this.mTabListeners[index] = tabListener = this.mTabProgressListener(aOurTab, ourBrowser, false); 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[ // If the current URI is registered as open remove it from the list. if (aOurBrowser.registeredOpenURI) { this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI); delete aOurBrowser.registeredOpenURI; } // If the other/new URI is registered as open then copy it over. if (aOtherBrowser.registeredOpenURI) { aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI; delete aOtherBrowser.registeredOpenURI; } ]]> </body> </method> <method name="reloadAllTabs"> <body> <![CDATA[ let tabs = this.visibleTabs; let l = tabs.length; for (var i = 0; i < l; i++) { try { this.getBrowserForTab(tabs[i]).reload(); } catch (e) { // ignore failure to reload so others will be reloaded } } ]]> </body> </method> <method name="reloadTab"> <parameter name="aTab"/> <body> <![CDATA[ this.getBrowserForTab(aTab).reload(); ]]> </body> </method> <method name="addProgressListener"> <parameter name="aListener"/> <body> <![CDATA[ if (arguments.length != 1) { Components.utils.reportError("gBrowser.addProgressListener was " + "called with a second argument, " + "which is not supported. See bug " + "608628."); } this.mProgressListeners.push(aListener); ]]> </body> </method> <method name="removeProgressListener"> <parameter name="aListener"/> <body> <![CDATA[ this.mProgressListeners = this.mProgressListeners.filter(function (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(function (l) l != aListener); ]]> </body> </method> <method name="getBrowserForTab"> <parameter name="aTab"/> <body> <![CDATA[ return aTab.linkedBrowser; ]]> </body> </method> <method name="showOnlyTheseTabs"> <parameter name="aTabs"/> <body> <![CDATA[ Array.forEach(this.tabs, function(tab) { if (aTabs.indexOf(tab) == -1) this.hideTab(tab); else this.showTab(tab); }, this); 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; if (aIndex >= 0 && aIndex < tabs.length) 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"/> <property name="browsers" readonly="true"> <getter> <![CDATA[ return this._browsers || (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser)); ]]> </getter> </property> <field name="_browsers">null</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> <method name="moveTabTo"> <parameter name="aTab"/> <parameter name="aIndex"/> <body> <![CDATA[ var oldPosition = aTab._tPos; if (oldPosition == aIndex) return; // Don't allow mixing pinned and unpinned tabs. if (aTab.pinned) aIndex = Math.min(aIndex, this._numPinnedTabs - 1); else aIndex = Math.max(aIndex, this._numPinnedTabs); if (oldPosition == aIndex) return; this._lastRelatedTab = null; this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]); this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]); let wasFocused = (document.activeElement == this.mCurrentTab); aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1; this.mCurrentTab._selected = false; // invalidate caches this._browsers = null; 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; } 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> <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 --> <body> <![CDATA[ return Cc["@mozilla.org/browser/sessionstore;1"] .getService(Ci.nsISessionStore) .duplicateTab(window, aTab); ]]> </body> </method> <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT MAKE SURE TO ADD IT HERE AS WELL. --> <property name="canGoBack" onget="return this.mCurrentBrowser.canGoBack;" readonly="true"/> <property name="canGoForward" onget="return this.mCurrentBrowser.canGoForward;" readonly="true"/> <method name="goBack"> <body> <![CDATA[ return this.mCurrentBrowser.goBack(); ]]> </body> </method> <method name="goForward"> <body> <![CDATA[ return this.mCurrentBrowser.goForward(); ]]> </body> </method> <method name="reload"> <body> <![CDATA[ return this.mCurrentBrowser.reload(); ]]> </body> </method> <method name="reloadWithFlags"> <parameter name="aFlags"/> <body> <![CDATA[ return this.mCurrentBrowser.reloadWithFlags(aFlags); ]]> </body> </method> <method name="stop"> <body> <![CDATA[ return this.mCurrentBrowser.stop(); ]]> </body> </method> <!-- throws exception for unknown schemes --> <method name="loadURI"> <parameter name="aURI"/> <parameter name="aReferrerURI"/> <parameter name="aCharset"/> <body> <![CDATA[ return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset); ]]> </body> </method> <!-- throws exception for unknown schemes --> <method name="loadURIWithFlags"> <parameter name="aURI"/> <parameter name="aFlags"/> <parameter name="aReferrerURI"/> <parameter name="aCharset"/> <parameter name="aPostData"/> <body> <![CDATA[ return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData); ]]> </body> </method> <method name="goHome"> <body> <![CDATA[ return this.mCurrentBrowser.goHome(); ]]> </body> </method> <property name="homePage"> <getter> <![CDATA[ return this.mCurrentBrowser.homePage; ]]> </getter> <setter> <![CDATA[ this.mCurrentBrowser.homePage = val; return val; ]]> </setter> </property> <method name="gotoIndex"> <parameter name="aIndex"/> <body> <![CDATA[ return this.mCurrentBrowser.gotoIndex(aIndex); ]]> </body> </method> <method name="attachFormFill"> <body><![CDATA[ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) { var cb = this.getBrowserAtIndex(i); cb.attachFormFill(); } ]]></body> </method> <method name="detachFormFill"> <body><![CDATA[ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) { var cb = this.getBrowserAtIndex(i); cb.detachFormFill(); } ]]></body> </method> <property name="currentURI" onget="return this.mCurrentBrowser.currentURI;" readonly="true"/> <field name="_fastFind">null</field> <property name="fastFind" readonly="true"> <getter> <![CDATA[ if (!this._fastFind) { this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"] .createInstance(Components.interfaces.nsITypeAheadFind); this._fastFind.init(this.docShell); } return this._fastFind; ]]> </getter> </property> <field name="_lastSearchString">null</field> <field name="_lastSearchHighlight">false</field> <field name="finder"><![CDATA[ ({ mTabBrowser: this, mListeners: new Set(), get finder() { return this.mTabBrowser.mCurrentBrowser.finder; }, addResultListener: function(aListener) { this.mListeners.add(aListener); this.finder.addResultListener(aListener); }, removeResultListener: function(aListener) { this.mListeners.delete(aListener); this.finder.removeResultListener(aListener); }, get searchString() { return this.finder.searchString; }, get clipboardSearchString() { return this.finder.clipboardSearchString; }, set clipboardSearchString(val) { return this.finder.clipboardSearchString = val; }, set caseSensitive(val) { return this.finder.caseSensitive = val; }, fastFind: function(aSearchString, aLinksOnly, aDrawOutline) { this.finder.fastFind(aSearchString, aLinksOnly, aDrawOutline); }, findAgain: function(aFindBackwards, aLinksOnly, aDrawOutline) { this.finder.findAgain(aFindBackwards, aLinksOnly, aDrawOutline); }, setSearchStringToSelection: function() { return this.finder.setSearchStringToSelection(); }, highlight: function(aHighlight, aWord) { this.finder.highlight(aHighlight, aWord); }, getInitialSelection: function() { this.finder.getInitialSelection(); }, enableSelection: function() { this.finder.enableSelection(); }, removeSelection: function() { this.finder.removeSelection(); }, focusContent: function() { this.finder.focusContent(); }, keyPress: function(aEvent) { this.finder.keyPress(aEvent); }, requestMatchesCount: function(aWord, aMatchLimit, aLinksOnly) { this.finder.requestMatchesCount(aWord, aMatchLimit, aLinksOnly); } }) ]]></field> <property name="docShell" onget="return this.mCurrentBrowser.docShell" readonly="true"/> <property name="webNavigation" onget="return this.mCurrentBrowser.webNavigation" readonly="true"/> <property name="webBrowserFind" readonly="true" onget="return this.mCurrentBrowser.webBrowserFind"/> <property name="webProgress" readonly="true" onget="return this.mCurrentBrowser.webProgress"/> <property name="contentWindow" readonly="true" onget="return this.mCurrentBrowser.contentWindow"/> <property name="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="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="_handleKeyEvent"> <parameter name="aEvent"/> <body><![CDATA[ if (!aEvent.isTrusted) { // Don't let untrusted events mess with tabs. return; } if (aEvent.altKey) return; if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) { switch (aEvent.keyCode) { case aEvent.DOM_VK_PAGE_UP: this.moveTabBackward(); aEvent.stopPropagation(); aEvent.preventDefault(); return; case aEvent.DOM_VK_PAGE_DOWN: this.moveTabForward(); aEvent.stopPropagation(); aEvent.preventDefault(); return; } } // We need to take care of FAYT-watching as long as the findbar // isn't initialized. The checks on aEvent are copied from // _shouldFastFind (see findbar.xml). if (!gFindBarInitialized && !(aEvent.ctrlKey || aEvent.metaKey) && !aEvent.defaultPrevented) { let charCode = aEvent.charCode; if (charCode) { let char = String.fromCharCode(charCode); if (char == "'" || char == "/" || Services.prefs.getBoolPref("accessibility.typeaheadfind")) { gFindBar._onBrowserKeypress(aEvent); return; } } } #ifdef XP_MACOSX if (!aEvent.metaKey) return; var offset = 1; switch (aEvent.charCode) { case '}'.charCodeAt(0): offset = -1; case '{'.charCodeAt(0): if (window.getComputedStyle(this, null).direction == "ltr") offset *= -1; this.tabContainer.advanceSelectedTab(offset, true); aEvent.stopPropagation(); aEvent.preventDefault(); } #else if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey && aEvent.keyCode == KeyEvent.DOM_VK_F4 && !this.mCurrentTab.pinned) { this.removeCurrentTab({animate: true}); aEvent.stopPropagation(); aEvent.preventDefault(); } #endif ]]></body> </method> <property name="userTypedClear" onget="return this.mCurrentBrowser.userTypedClear;" onset="return this.mCurrentBrowser.userTypedClear = val;"/> <property name="userTypedValue" onget="return this.mCurrentBrowser.userTypedValue;" onset="return this.mCurrentBrowser.userTypedValue = val;"/> <method name="createTooltip"> <parameter name="event"/> <body><![CDATA[ event.stopPropagation(); var tab = document.tooltipNode; if (tab.localName != "tab") { event.preventDefault(); return; } event.target.setAttribute("label", tab.mOverCloseButton ? tab.getAttribute("closetabtext") : tab.getAttribute("label")); ]]></body> </method> <method name="handleEvent"> <parameter name="aEvent"/> <body><![CDATA[ switch (aEvent.type) { case "keypress": this._handleKeyEvent(aEvent); break; case "sizemodechange": if (aEvent.target == window) { this.mCurrentBrowser.docShellIsActive = (window.windowState != window.STATE_MINIMIZED); } break; } ]]></body> </method> <method name="receiveMessage"> <parameter name="aMessage"/> <body><![CDATA[ let json = aMessage.json; let browser = aMessage.target; switch (aMessage.name) { case "DOMTitleChanged": { let tab = this.getTabForBrowser(browser); if (!tab) return; let titleChanged = this.setTabTitle(tab); if (titleChanged && !tab.selected && !tab.hasAttribute("busy")) tab.setAttribute("titlechanged", "true"); break; } case "DOMWebNotificationClicked": { let tab = this.getTabForBrowser(browser); if (!tab) return; this.selectedTab = tab; window.focus(); break; } } ]]></body> </method> <constructor> <![CDATA[ let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack"); this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser"); if (Services.prefs.getBoolPref("browser.tabs.remote")) { browserStack.removeChild(this.mCurrentBrowser); this.mCurrentBrowser.setAttribute("remote", true); browserStack.appendChild(this.mCurrentBrowser); } this.mCurrentTab = this.tabContainer.firstChild; document.addEventListener("keypress", this, false); window.addEventListener("sizemodechange", this, false); var uniqueId = "panel" + Date.now(); this.mPanelContainer.childNodes[0].id = uniqueId; this.mCurrentTab.linkedPanel = uniqueId; this.mCurrentTab._tPos = 0; this.mCurrentTab._fullyOpen = true; this.mCurrentTab.linkedBrowser = this.mCurrentBrowser; 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; this.updateWindowResizers(); // Hook up the event listeners to the first browser var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true); 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.mTabListeners[0] = tabListener; this.mTabFilters[0] = filter; try { // We assume this can only fail because mCurrentBrowser's docShell // hasn't been created, yet. This may be caused by code accessing // gBrowser before the window has finished loading. this._addProgressListenerForInitialTab(); } catch (e) { // The binding was constructed too early, wait until the initial // tab's document is ready, then add the progress listener. this._waitForInitialContentDocument(); } this.style.backgroundColor = Services.prefs.getBoolPref("browser.display.use_system_colors") ? "-moz-default-background-color" : Services.prefs.getCharPref("browser.display.background_color"); if (Services.prefs.getBoolPref("browser.tabs.remote")) { messageManager.addMessageListener("DOMTitleChanged", this); } else { this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID, this.mCurrentBrowser); } messageManager.addMessageListener("DOMWebNotificationClicked", this); ]]> </constructor> <method name="_addProgressListenerForInitialTab"> <body><![CDATA[ this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL); ]]></body> </method> <method name="_waitForInitialContentDocument"> <body><![CDATA[ let obs = (subject, topic) => { if (this.browsers[0].contentWindow == subject) { Services.obs.removeObserver(obs, topic); this._addProgressListenerForInitialTab(); } }; // We use content-document-global-created as an approximation for // "docShell is initialized". We can do this because in the // mTabProgressListener we care most about the STATE_STOP notification // that will reset mBlank. That means it's important to at least add // the progress listener before the initial about:blank load stops // if we can't do it before the load starts. Services.obs.addObserver(obs, "content-document-global-created", false); ]]></body> </method> <destructor> <![CDATA[ for (var i = 0; i < this.mTabListeners.length; ++i) { let browser = this.getBrowserAtIndex(i); if (browser.registeredOpenURI) { this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI); delete browser.registeredOpenURI; } browser.webProgress.removeProgressListener(this.mTabFilters[i]); this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]); this.mTabFilters[i] = null; this.mTabListeners[i].destroy(); this.mTabListeners[i] = null; } document.removeEventListener("keypress", this, false); window.removeEventListener("sizemodechange", this, false); if (Services.prefs.getBoolPref("browser.tabs.remote")) messageManager.removeMessageListener("DOMTitleChanged", this); ]]> </destructor> <!-- Deprecated stuff, implemented for backwards compatibility. --> <method name="enterTabbedMode"> <body> Application.console.log("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: function (attr, attrValue) { if (attr == "anonid" && attrValue == "tabContextMenu") return [this.self.tabContextMenu]; return []; }, // Also support adding event listeners (forward to the tab container) addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); }, removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); } }); ]]> </getter> </property> </implementation> <handlers> <handler event="DOMWindowClose" phase="capturing"> <![CDATA[ if (!event.isTrusted) return; if (this.tabs.length == 1) return; var tab = this._getTabForContentWindow(event.target); if (tab) { this.removeTab(tab); event.preventDefault(); } ]]> </handler> <handler event="DOMWillOpenModalDialog" phase="capturing"> <![CDATA[ if (!event.isTrusted) return; // We're about to open a modal dialog, make sure the opening // tab is brought to the front. this.selectedTab = this._getTabForContentWindow(event.target.top); ]]> </handler> <handler event="DOMTitleChanged"> <![CDATA[ if (!event.isTrusted) return; var contentWin = event.target.defaultView; if (contentWin != contentWin.top) return; var tab = this._getTabForContentWindow(contentWin); if (!tab) { return; } var titleChanged = this.setTabTitle(tab); if (titleChanged && !tab.selected && !tab.hasAttribute("busy")) tab.setAttribute("titlechanged", "true"); ]]> </handler> </handlers> </binding> <binding id="tabbrowser-tabbox" extends="chrome://global/content/bindings/tabbox.xml#tabbox"> <implementation> <property name="tabs" readonly="true" onget="return document.getBindingParent(this).tabContainer;"/> </implementation> </binding> <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll"> <implementation> <!-- Override scrollbox.xml method, since our scrollbox's children are inherited from the binding parent --> <method name="_getScrollableElements"> <body><![CDATA[ return Array.filter(document.getBindingParent(this).childNodes, this._canScrollToElement, this); ]]></body> </method> <method name="_canScrollToElement"> <parameter name="tab"/> <body><![CDATA[ return !tab.pinned && !tab.hidden; ]]></body> </method> </implementation> <handlers> <handler event="underflow" phase="capturing"><![CDATA[ if (event.detail == 0) return; // Ignore vertical events var tabs = document.getBindingParent(this); tabs.removeAttribute("overflow"); if (tabs._lastTabClosedByMouse) tabs._expandSpacerBy(this._scrollButtonDown.clientWidth); tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab, tabs.tabbrowser); tabs._positionPinnedTabs(); ]]></handler> <handler event="overflow"><![CDATA[ if (event.detail == 0) return; // Ignore vertical events var tabs = document.getBindingParent(this); tabs.setAttribute("overflow", "true"); tabs._positionPinnedTabs(); 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;" #ifndef XP_MACOSX clicktoscroll="true" #endif class="tabbrowser-arrowscrollbox"> # This is a hack to circumvent bug 472020, otherwise the tabs show up on the # right of the newtab button. <children includes="tab"/> # This is to ensure anything extensions put here will go before the newtab # button, necessary due to the previous hack. <children/> <xul:toolbarbutton class="tabs-newtab-button" command="cmd_newNavigatorTab" onclick="checkForMiddleClick(this, event);" onmouseover="document.getBindingParent(this)._enterNewTab();" onmouseout="document.getBindingParent(this)._leaveNewTab();" tooltiptext="&newTabButton.tooltip;"/> <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer" style="width: 0;"/> </xul:arrowscrollbox> </content> <implementation implements="nsIDOMEventListener"> <constructor> <![CDATA[ this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth"); this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons"); this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab"); var tab = this.firstChild; tab.setAttribute("label", this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle")); tab.setAttribute("crop", "end"); tab.setAttribute("onerror", "this.removeAttribute('image');"); this.adjustTabstrip(); Services.prefs.addObserver("browser.tabs.", this._prefObserver, false); window.addEventListener("resize", this, false); window.addEventListener("load", this, false); try { this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled"); } catch (ex) { this._tabAnimationLoggingEnabled = false; } this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled"); ]]> </constructor> <destructor> <![CDATA[ Services.prefs.removeObserver("browser.tabs.", this._prefObserver); ]]> </destructor> <field name="tabbrowser" readonly="true"> document.getElementById(this.getAttribute("tabbrowser")); </field> <field name="tabbox" readonly="true"> this.tabbrowser.mTabBox; </field> <field name="contextMenu" readonly="true"> document.getElementById("tabContextMenu"); </field> <field name="mTabstripWidth">0</field> <field name="mTabstrip"> document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox"); </field> <field name="_firstTab">null</field> <field name="_lastTab">null</field> <field name="_afterSelectedTab">null</field> <field name="_beforeHoveredTab">null</field> <field name="_afterHoveredTab">null</field> <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"); ]]></body> </method> <field name="_prefObserver"><![CDATA[({ tabContainer: this, observe: function (subject, topic, data) { switch (data) { case "browser.tabs.closeButtons": this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data); this.tabContainer.adjustTabstrip(); break; case "browser.tabs.autoHide": this.tabContainer.updateVisibility(); break; case "browser.tabs.closeWindowWithLastTab": this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data); this.tabContainer.adjustTabstrip(); break; } } });]]></field> <field name="_blockDblClick">false</field> <field name="_tabDropIndicator"> document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator"); </field> <field name="_dragOverDelay">350</field> <field name="_dragTime">0</field> <field name="_container" readonly="true"><![CDATA[ this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this; ]]></field> <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")); goSetCommandEnabled("cmd_ToggleTabsOnTop", visible); TabsOnTop.syncUI(); 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 && !Services.prefs.getBoolPref("browser.tabs.autoHide"); else this.visible = true; ]]></body> </method> <method name="adjustTabstrip"> <body><![CDATA[ let numTabs = this.childNodes.length - this.tabbrowser._removingTabs.length; // modes for tabstrip // 0 - button on active tab only // 1 - close buttons on all tabs, if available space allows // 2 - no close buttons at all // 3 - close button at the end of the tabstrip switch (this.mCloseButtons) { case 0: // If we decide we want to hide the close tab button on the last tab // when closing the window with the last tab, then we should check // if (numTabs == 1 && this._closeWindowWithLastTab) here and set // this.setAttribute("closebuttons", "hidden") appropriately this.setAttribute("closebuttons", "activetab"); break; case 1: if (numTabs == 1) { // See remark about potentially hiding the close tab button, above. this.setAttribute("closebuttons", "alltabs"); } else 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. this.setAttribute("closebuttons", "alltabs"); } else { let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs]; if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth) this.setAttribute("closebuttons", "alltabs"); else this.setAttribute("closebuttons", "activetab"); } break; case 2: case 3: this.setAttribute("closebuttons", "never"); break; } var tabstripClosebutton = document.getElementById("tabs-closebutton"); if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container) tabstripClosebutton.collapsed = this.mCloseButtons != 3; ]]></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") { #ifdef XP_WIN // Don't need to do anything if we're in overflow mode and we're closing // the last tab. if (isEndTab) #else // 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) #endif 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; // Resize immediately if preffed // XXX: we may want to make this a three-state pref to disable this early // exit if people prefer a mix of behavior (don't resize in overflow, // but resize if not overflowing) if (Services.prefs.getBoolPref("browser.tabs.resize_immediately")) 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; // Resize immediately if preffed // XXX: we may want to make this a three-state pref to disable this early // exit if people prefer a mix of behavior (don't resize in overflow, // but resize if not overflowing) if (Services.prefs.getBoolPref("browser.tabs.resize_immediately")) 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, false); window.addEventListener("mouseout", this, false); } ]]></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, false); window.addEventListener("mouseout", this, false); ]]></body> </method> <method name="_unlockTabSizing"> <body><![CDATA[ this.tabbrowser.removeEventListener("mousemove", this, false); window.removeEventListener("mouseout", this, false); 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.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px"; } this.style.MozPaddingStart = width + paddingStart + "px"; } else { this.removeAttribute("positionpinnedtabs"); for (let i = 0; i < numPinned; i++) { let tab = this.childNodes[i]; tab.style.MozMarginStart = ""; } this.style.MozPaddingStart = ""; } 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; let draggingRight = screenX > draggedTab._dragData.animLastScreenX; 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; let 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(); break; case "resize": if (aEvent.target != window) break; let sizemode = document.documentElement.getAttribute("sizemode"); TabsInTitlebar.allowedBy("sizemode", sizemode == "maximized" || sizemode == "fullscreen"); var width = this.mTabstrip.boxObject.width; if (width != this.mTabstripWidth) { this.adjustTabstrip(); this._fillTrailingGap(); this._handleTabSelect(); this.mTabstripWidth = width; } this.tabbrowser.updateWindowResizers(); 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) return; var scrollRect = this.mTabstrip.scrollClientRect; var tab = aTab.getBoundingClientRect(); // Is the new tab already completely visible? if (scrollRect.left <= tab.left && tab.right <= scrollRect.right) return; if (this.mTabstrip.smoothScroll) { let selected = !this.selectedItem.pinned && this.selectedItem.getBoundingClientRect(); // Can we make both the new tab and the selected tab completely visible? if (!selected || Math.max(tab.right - selected.left, selected.right - tab.left) <= scrollRect.width) { this.mTabstrip.ensureElementIsVisible(aTab); return; } this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ? selected.right - scrollRect.right : selected.left - scrollRect.left); } if (!this._animateElement.hasAttribute("notifybgtab")) { this._animateElement.setAttribute("notifybgtab", "true"); setTimeout(function (ele) { ele.removeAttribute("notifybgtab"); }, 150, this._animateElement); } ]]></body> </method> <method name="_getDragTargetTab"> <parameter name="event"/> <body><![CDATA[ let tab = event.target.localName == "tab" ? event.target : null; if (tab && (event.type == "drop" || event.type == "dragover") && event.dataTransfer.dropEffect == "link") { let boxObject = tab.boxObject; if (event.screenX < boxObject.screenX + boxObject.width * .25 || event.screenX > boxObject.screenX + boxObject.width * .75) return null; } return tab; ]]></body> </method> <method name="_getDropIndex"> <parameter name="event"/> <body><![CDATA[ var tabs = this.childNodes; var tab = this._getDragTargetTab(event); if (window.getComputedStyle(this, null).direction == "ltr") { for (let i = tab ? tab._tPos : 0; i < tabs.length; i++) if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) return i; } else { for (let i = tab ? tab._tPos : 0; i < tabs.length; i++) if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) return i; } return tabs.length; ]]></body> </method> <method name="_setEffectAllowedForDataTransfer"> <parameter name="event"/> <body><![CDATA[ var dt = event.dataTransfer; // Disallow dropping multiple items if (dt.mozItemCount > 1) return dt.effectAllowed = "none"; var types = dt.mozTypesAt(0); var sourceNode = null; // tabs are always added as the first type if (types[0] == TAB_DROP_TYPE) { var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0); if (sourceNode instanceof XULElement && sourceNode.localName == "tab" && sourceNode.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 dt.effectAllowed = "none"; #ifdef XP_MACOSX return dt.effectAllowed = event.altKey ? "copy" : "move"; #else return dt.effectAllowed = event.ctrlKey ? "copy" : "move"; #endif } } if (browserDragAndDrop.canDropLink(event)) { // Here we need to do this manually return dt.effectAllowed = dt.dropEffect = "link"; } return dt.effectAllowed = "none"; ]]></body> </method> <method name="_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(); ]]></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 (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 paints = {}; let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils) .stopFrameTimeRecording(aTab._recordingHandle, paints); delete aTab._recordingHandle; paints = paints.value; // The result array itself. let frameCount = intervals.length; if (this._tabAnimationLoggingEnabled) { let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval / paint-processing):\n"; for (let i = 0; i < frameCount; i++) { msg += Math.round(intervals[i]) + " / " + Math.round(paints[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. // But if we recorded only 1 frame (very rare), then the first paint duration is a good // representative of the first frame interval for our cause (indicates very bad animation). // First paint duration is always useful for us. if (frameCount > 0) { let averageInterval = 0; let averagePaint = paints[0]; for (let i = 1; i < frameCount; i++) { averageInterval += intervals[i]; averagePaint += paints[i]; }; averagePaint /= frameCount; averageInterval = (frameCount == 1) ? averagePaint : averageInterval / (frameCount - 1); if (aTab._recordingTabOpenPlain) { delete aTab._recordingTabOpenPlain; } } ]]> </body> </method> <!-- Deprecated stuff, implemented for backwards compatibility. --> <property name="mTabstripClosebutton" readonly="true" onget="return document.getElementById('tabs-closebutton');"/> <property name="mAllTabsPopup" readonly="true" onget="return document.getElementById('alltabs-popup');"/> </implementation> <handlers> <handler event="TabSelect" action="this._handleTabSelect();"/> <handler event="transitionend"><![CDATA[ if (event.propertyName != "max-width") return; var tab = event.target; 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[ #ifndef XP_MACOSX // 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 || (TabsOnTop.enabled && this.parentNode._dragBindingAlive)) return; #endif 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"><![CDATA[ if (event.button != 1) return; if (event.target.localName == "tab") { if (this.childNodes.length > 1 || !this._closeWindowWithLastTab) this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true}); } else if (event.originalTarget.localName == "box") { BrowserOpenTab(); } else { return; } event.stopPropagation(); ]]></handler> <handler event="keypress"><![CDATA[ if (event.altKey || event.shiftKey || #ifdef XP_MACOSX !event.metaKey) #else !event.ctrlKey || event.metaKey) #endif return; switch (event.keyCode) { case KeyEvent.DOM_VK_UP: this.tabbrowser.moveTabBackward(); break; case KeyEvent.DOM_VK_DOWN: this.tabbrowser.moveTabForward(); break; case KeyEvent.DOM_VK_RIGHT: case KeyEvent.DOM_VK_LEFT: this.tabbrowser.moveTabOver(event); break; case KeyEvent.DOM_VK_HOME: this.tabbrowser.moveTabToStart(); break; case KeyEvent.DOM_VK_END: this.tabbrowser.moveTabToEnd(); break; default: // Stop the keypress event for the above keyboard // shortcuts only. return; } event.stopPropagation(); event.preventDefault(); ]]></handler> <handler event="dragstart"><![CDATA[ var tab = this._getDragTargetTab(event); if (!tab) return; let dt = event.dataTransfer; dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0); let 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"; // 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 = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); canvas.mozOpaque = true; canvas.width = 160 * scale; canvas.height = 90 * scale; PageThumbs.captureToCanvas(browser, canvas); dt.setDragImage(canvas, -16 * scale, -16 * scale); // _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) 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._setEffectAllowedForDataTransfer(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); 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); 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.MozMarginStart = (-ind.clientWidth) + "px"; ]]></handler> <handler event="drop"><![CDATA[ var dt = event.dataTransfer; var dropEffect = dt.dropEffect; var draggedTab; if (dropEffect != "link") { // copy or move draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); // not our drop then if (!draggedTab) return; } this._tabDropIndicator.collapsed = true; event.stopPropagation(); if (draggedTab && dropEffect == "copy") { // copy the dropped tab (wherever it's from) let newIndex = this._getDropIndex(event); 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) { // swap the dropped tab with a new one we create and then close // it in the other window (making it seem to have moved between // windows) let newIndex = this._getDropIndex(event); let newTab = this.tabbrowser.addTab("about:blank"); let newBrowser = this.tabbrowser.getBrowserForTab(newTab); // Stop the about:blank load newBrowser.stop(); // make sure it has a docshell newBrowser.docShell; let numPinned = this.tabbrowser._numPinnedTabs; if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned) this.tabbrowser.pinTab(newTab); this.tabbrowser.moveTabTo(newTab, newIndex); // 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.tabbrowser.selectedTab = newTab; draggedTab.parentNode._finishAnimateTabMove(); this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab); // Call updateCurrentBrowser to make sure the URL bar is up to date // for our new tab after we've done swapBrowsersAndCloseOther. this.tabbrowser.updateCurrentBrowser(true); } else { // Pass true to disallow dropping javascript: or data: urls let url; try { url = browserDragAndDrop.drop(event, { }, true); } catch (ex) {} // // valid urls don't contain spaces ' '; if we have a space it isn't a valid url. // if (!url || url.includes(" ")) //PMed if (!url) //FF return; let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); if (event.shiftKey) bgLoad = !bgLoad; let tab = this._getDragTargetTab(event); if (!tab || dropEffect == "copy") { // We're adding a new tab. let newIndex = this._getDropIndex(event); let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true}); this.tabbrowser.moveTabTo(newTab, newIndex); } else { // Load in an existing tab. try { let webNav = Ci.nsIWebNavigation; let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags); if (!bgLoad) this.selectedItem = tab; } catch(ex) { // Just ignore invalid urls } } } 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") { 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 sX = {}, sY = {}, sWidth = {}, sHeight = {}; Cc["@mozilla.org/gfx/screenmanager;1"] .getService(Ci.nsIScreenManager) .screenForRect(eX, eY, 1, 1) .GetAvailRect(sX, sY, sWidth, sHeight); // ensure new window entirely within screen var winWidth = Math.min(window.outerWidth, sWidth.value); var winHeight = Math.min(window.outerHeight, sHeight.value); var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value), sX.value + sWidth.value - winWidth); var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value), sY.value + sHeight.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 { this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left, screenY: top, #ifndef XP_WIN outerWidth: winWidth, outerHeight: winHeight #endif }); } 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; /* The only sequence in which a second click event (i.e. dblclik) * can be dispatched on an in-tab close button is when it is shown * after the first click (i.e. the first click event was dispatched * on the tab). This happens when we show the close button only on * the active tab. (bug 352021) * The only sequence in which a third click event can be dispatched * on an in-tab close button is when the tab was opened with a * double click on the tabbar. (bug 378344) * In both cases, it is most likely that the close button area has * been accidentally clicked, therefore we do not close the tab. * * We don't want to ignore processing of more than one click event, * though, since the user might actually be repeatedly clicking to * close many tabs at once. */ if (event.detail > 1 && !this._ignoredClick) { this._ignoredClick = true; return; } // Reset the "ignored click" flag this._ignoredClick = false; tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true}); tabContainer._blockDblClick = true; /* XXXmano hack (see bug 343628): * Since we're removing the event target, if the user * double-clicks this button, the dblclick event will be dispatched * with the tabbar as its event target (and explicit/originalTarget), * which treats that as a mouse gesture for opening a new tab. * In this context, we're manually blocking the dblclick event * (see dblclick handler). */ var clickedOnce = false; function enableDblClick(event) { var target = event.originalTarget; if (target.className == 'tab-close-button') target._ignoredClick = true; if (!clickedOnce) { clickedOnce = true; return; } tabContainer._blockDblClick = false; tabContainer.removeEventListener("click", enableDblClick, true); } tabContainer.addEventListener("click", enableDblClick, 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" closetabtext="&closeTab.label;"> <xul:stack class="tab-stack" flex="1"> <xul:hbox xbl:inherits="pinned,selected,titlechanged" class="tab-background"> <xul:hbox xbl:inherits="pinned,selected,titlechanged" class="tab-background-start"/> <xul:hbox xbl:inherits="pinned,selected,titlechanged" class="tab-background-middle"/> <xul:hbox xbl:inherits="pinned,selected,titlechanged" class="tab-background-end"/> </xul:hbox> <xul:hbox xbl:inherits="pinned,selected,titlechanged" class="tab-content" align="center"> <xul:image xbl:inherits="fadein,pinned,busy,progress,selected" class="tab-throbber" role="presentation" layer="true" /> <xul:image xbl:inherits="validate,src=image,fadein,pinned,selected" class="tab-icon-image" role="presentation" anonid="tab-icon"/> <xul:label flex="1" xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected" class="tab-text tab-label" role="presentation"/> <xul:toolbarbutton anonid="close-button" xbl:inherits="fadein,pinned,selected" class="tab-close-button close-icon"/> </xul:hbox> </xul:stack> </content> <implementation> <property name="pinned" readonly="true"> <getter> return this.getAttribute("pinned") == "true"; </getter> </property> <property name="hidden" readonly="true"> <getter> return this.getAttribute("hidden") == "true"; </getter> </property> <field name="mOverCloseButton">false</field> <field name="mCorrespondingMenuitem">null</field> <field name="closing">false</field> <field name="lastAccessed">0</field> </implementation> <handlers> <handler event="mouseover"><![CDATA[ let anonid = event.originalTarget.getAttribute("anonid"); if (anonid == "close-button") this.mOverCloseButton = true; let tab = event.target; if (tab.closing) return; let tabContainer = this.parentNode; let visibleTabs = tabContainer.tabbrowser.visibleTabs; let tabIndex = visibleTabs.indexOf(tab); 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"); } } ]]></handler> <handler event="mouseout"><![CDATA[ let anonid = event.originalTarget.getAttribute("anonid"); if (anonid == "close-button") this.mOverCloseButton = false; let tabContainer = this.parentNode; if (tabContainer._beforeHoveredTab) { tabContainer._beforeHoveredTab.removeAttribute("beforehovered"); tabContainer._beforeHoveredTab = null; } if (tabContainer._afterHoveredTab) { tabContainer._afterHoveredTab.removeAttribute("afterhovered"); tabContainer._afterHoveredTab = null; } ]]></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) { // Prevent tabbox.xml from selecting the tab. event.stopPropagation(); } ]]> </handler> <handler event="mouseup"> this.style.MozUserFocus = ''; </handler> </handlers> </binding> <binding id="tabbrowser-alltabs-popup" extends="chrome://global/content/bindings/popup.xml#popup"> <implementation implements="nsIDOMEventListener"> <method name="_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; 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); if (!aTab.mCorrespondingMenuitem) { 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", aTab.getAttribute("crop")); 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"); ]]></body> </method> </implementation> <handlers> <handler event="popupshowing"> <![CDATA[ var tabcontainer = gBrowser.tabContainer; // Listen for changes in the tab bar. tabcontainer.addEventListener("TabAttrModified", this, false); tabcontainer.addEventListener("TabClose", this, false); tabcontainer.mTabstrip.addEventListener("scroll", this, false); 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[ // 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); } } var tabcontainer = gBrowser.tabContainer; tabcontainer.mTabstrip.removeEventListener("scroll", this, false); tabcontainer.removeEventListener("TabAttrModified", this, false); tabcontainer.removeEventListener("TabClose", this, false); ]]></handler> <handler event="DOMMenuItemActive"> <![CDATA[ var tab = event.target.tab; if (tab) { let overLink = tab.linkedBrowser.currentURI.spec; if (overLink == "about:blank") overLink = ""; XULBrowserWindow.setOverLink(overLink, null); } ]]></handler> <handler event="DOMMenuItemInactive"> <![CDATA[ XULBrowserWindow.setOverLink("", null); ]]></handler> <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, false); ]]></constructor> <destructor><![CDATA[ window.removeEventListener("resize", this, false); 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 alignRight = false; if (getComputedStyle(document.documentElement).direction == "rtl") alignRight = !alignRight; let rect = this.getBoundingClientRect(); this._mouseTargetRect = { top: rect.top, bottom: rect.bottom, left: alignRight ? window.innerWidth - rect.width : 0, right: alignRight ? window.innerWidth : rect.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> </bindings>