document.getElementById(this.getAttribute("tabcontainer")); this.tabContainer.childNodes; !tab.hidden && !tab.closing); return this._visibleTabs; ]]> ({ ALL: 0, OTHER: 1, TO_END: 2 }); null Components.classes["@mozilla.org/docshell/urifixup;1"] .getService(Components.interfaces.nsIURIFixup); Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"] .getService(Components.interfaces.mozIPlacesAutoComplete); (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants; document.getAnonymousElementByAttribute(this, "anonid", "tabbox"); document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer"); document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle"); null null null [] null [] new Map() new Map() false new Map(); this.AppConstants.platform == "macosx"; null false "" 0 0 && aStatus == NS_ERROR_UNKNOWN_HOST) { // to prevent bug 235825: wait for the request handled // by the automatic keyword resolver return; } // since we (try to) only handle STATE_STOP of the last request, // the count of open requests should now be 0 this.mRequestCount = 0; } if (aStateFlags & nsIWebProgressListener.STATE_START && aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { if (aWebProgress.isTopLevel) { // Need to use originalLocation rather than location because things // like about:home and about:privatebrowsing arrive with nsIRequest // pointing to their resolved jar: or file: URIs. if (!(originalLocation && gInitialPages.includes(originalLocation.spec) && originalLocation != "about:blank" && this.mBrowser.initialPageLoadedFromURLBar != originalLocation.spec && this.mBrowser.currentURI && this.mBrowser.currentURI.spec == "about:blank")) { // Indicating that we started a load will allow the location // bar to be cleared when the load finishes. // In order to not overwrite user-typed content, we avoid it // (see if condition above) in a very specific case: // If the load is of an 'initial' page (e.g. about:privatebrowsing, // about:newtab, etc.), was not explicitly typed in the location // bar by the user, is not about:blank (because about:blank can be // loaded by websites under their principal), and the current // page in the browser is about:blank (indicating it is a newly // created or re-created browser, e.g. because it just switched // remoteness or is a new tab/window). this.mBrowser.urlbarChangeTracker.startedLoad(); } delete this.mBrowser.initialPageLoadedFromURLBar; // If the browser is loading it must not be crashed anymore this.mTab.removeAttribute("crashed"); } if (this._shouldShowProgress(aRequest)) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this.mTab.setAttribute("busy", "true"); if (aWebProgress.isTopLevel && !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD)) this.mTabBrowser.setTabTitleLoading(this.mTab); } if (this.mTab.selected) this.mTabBrowser.mIsBusy = true; } } else if (aStateFlags & nsIWebProgressListener.STATE_STOP && aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) { if (this.mTab.hasAttribute("busy")) { this.mTab.removeAttribute("busy"); this.mTabBrowser._tabAttrModified(this.mTab, ["busy"]); if (!this.mTab.selected) this.mTab.setAttribute("unread", "true"); } this.mTab.removeAttribute("progress"); if (aWebProgress.isTopLevel) { let isSuccessful = Components.isSuccessCode(aStatus); if (!isSuccessful && !isTabEmpty(this.mTab)) { // Restore the current document's location in case the // request was stopped (possibly from a content script) // before the location changed. this.mBrowser.userTypedValue = null; let inLoadURI = this.mBrowser.inLoadURI; if (this.mTab.selected && gURLBar && !inLoadURI) { URLBarSetURI(); } } else if (isSuccessful) { this.mBrowser.urlbarChangeTracker.finishedLoad(); } if (!this.mBrowser.mIconURL) this.mTabBrowser.useDefaultIcon(this.mTab); } // For keyword URIs clear the user typed value since they will be changed into real URIs if (location.scheme == "keyword") this.mBrowser.userTypedValue = null; if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting")) this.mTabBrowser.setTabTitle(this.mTab); if (this.mTab.selected) this.mTabBrowser.mIsBusy = false; } if (ignoreBlank) { this._callProgressListeners("onUpdateCurrentBrowser", [aStateFlags, aStatus, "", 0], true, false); } else { this._callProgressListeners("onStateChange", [aWebProgress, aRequest, aStateFlags, aStatus], true, false); } this._callProgressListeners("onStateChange", [aWebProgress, aRequest, aStateFlags, aStatus], false); if (aStateFlags & (nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_STOP)) { // reset cached temporary values at beginning and end this.mMessage = ""; this.mTotalProgress = 0; } this.mStateFlags = aStateFlags; this.mStatus = aStatus; }, onLocationChange: function (aWebProgress, aRequest, aLocation, aFlags) { // OnLocationChange is called for both the top-level content // and the subframes. let topLevel = aWebProgress.isTopLevel; if (topLevel) { let isSameDocument = !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT); // We need to clear the typed value // if the document failed to load, to make sure the urlbar reflects the // failed URI (particularly for SSL errors). However, don't clear the value // if the error page's URI is about:blank, because that causes complete // loss of urlbar contents for invalid URI errors (see bug 867957). // Another reason to clear the userTypedValue is if this was an anchor // navigation initiated by the user. if (this.mBrowser.didStartLoadSinceLastUserTyping() || ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) && aLocation.spec != "about:blank") || (isSameDocument && this.mBrowser.inLoadURI)) { this.mBrowser.userTypedValue = null; } // If the browser was playing audio, we should remove the playing state. if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) { clearTimeout(this.mTab._soundPlayingAttrRemovalTimer); this.mTab._soundPlayingAttrRemovalTimer = 0; this.mTab.removeAttribute("soundplaying"); this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]); } // If the browser was previously muted, we should restore the muted state. if (this.mTab.hasAttribute("muted")) { this.mTab.linkedBrowser.mute(); } if (this.mTabBrowser.isFindBarInitialized(this.mTab)) { let findBar = this.mTabBrowser.getFindBar(this.mTab); // Close the Find toolbar if we're in old-style TAF mode if (findBar.findMode != findBar.FIND_NORMAL) { findBar.close(); } } // Don't clear the favicon if this onLocationChange was // triggered by a pushState or a replaceState (bug 550565) or // a hash change (bug 408415). if (aWebProgress.isLoadingDocument && !isSameDocument) { this.mBrowser.mIconURL = null; } let unifiedComplete = this.mTabBrowser._unifiedComplete; if (this.mBrowser.registeredOpenURI) { unifiedComplete.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)) { unifiedComplete.registerOpenPage(aLocation); this.mBrowser.registeredOpenURI = aLocation; } } if (!this.mBlank) { this._callProgressListeners("onLocationChange", [aWebProgress, aRequest, aLocation, aFlags]); } if (topLevel) { this.mBrowser.lastURI = aLocation; this.mBrowser.lastLocationChange = Date.now(); } }, onStatusChange: 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; } }); ]]> Cc["@mozilla.org/network/serialization-helper;1"] .getService(Ci.nsISerializationHelper); null { if (!inPermitUnload) { return; } // Since the user is switching away from a tab that has // a beforeunload prompt active, we remove the prompt. // This prevents confusing user flows like the following: // 1. User attempts to close Firefox // 2. User switches tabs (ingoring a beforeunload prompt) // 3. User returns to tab, presses "Leave page" let promptBox = this.getTabModalPromptBox(oldBrowser); let prompts = promptBox.listPrompts(); // There might not be any prompts here if the tab was closed // while in an onbeforeunload prompt, which will have // destroyed aforementioned prompt already, so check there's // something to remove, first: if (prompts.length) { // NB: This code assumes that the beforeunload prompt // is the top-most prompt on the tab. prompts[prompts.length - 1].abortPrompt(); } }); } oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused); if (this.isFindBarInitialized(oldTab)) { let findBar = this.getFindBar(oldTab); oldTab._findBarFocused = (!findBar.hidden && findBar._findField.getAttribute("focused") == "true"); } // If focus is in the tab bar, retain it there. if (document.activeElement == oldTab) { // We need to explicitly focus the new tab, because // tabbox.xml does this only in some cases. this.mCurrentTab.focus(); } else if (gMultiProcessBrowser && document.activeElement !== newBrowser) { // Clear focus so that _adjustFocusAfterTabSwitch can detect if // some element has been focused and respect that. document.activeElement.blur(); } if (!gMultiProcessBrowser) this._adjustFocusAfterTabSwitch(this.mCurrentTab); } gIdentityHandler.updateSharingIndicator(); this.tabContainer._setPositionalAttributes(); if (!gMultiProcessBrowser) { let event = new CustomEvent("TabSwitchDone", { bubbles: true, cancelable: true }); this.dispatchEvent(event); } ]]> 1 false/true NO var multiple = aURIs.length > 1; var owner = multiple || aLoadInBackground ? null : this.selectedTab; var firstTabAdded = null; var targetTabIndex = -1; if (aReplace) { let browser; if (aTargetTab) { browser = this.getBrowserForTab(aTargetTab); targetTabIndex = aTargetTab._tPos; } else { browser = this.mCurrentBrowser; targetTabIndex = this.tabContainer.selectedIndex; } let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; if (aAllowThirdPartyFixup) { flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; } try { browser.loadURIWithFlags(aURIs[0], { flags, postData: aPostDatas[0] }); } catch (e) { // Ignore failure in case a URI is wrong, so we can continue // opening the next ones. } } else { firstTabAdded = this.addTab(aURIs[0], { ownerTab: owner, skipAnimation: multiple, allowThirdPartyFixup: aAllowThirdPartyFixup, postData: aPostDatas[0] }); if (aNewIndex !== -1) { this.moveTabTo(firstTabAdded, aNewIndex); targetTabIndex = firstTabAdded._tPos; } } let tabNum = targetTabIndex; for (let i = 1; i < aURIs.length; ++i) { let tab = this.addTab(aURIs[i], { skipAnimation: true, allowThirdPartyFixup: aAllowThirdPartyFixup, postData: aPostDatas[i] }); if (targetTabIndex !== -1) this.moveTabTo(tab, ++tabNum); } if (!aLoadInBackground) { if (firstTabAdded) { // .selectedTab setter focuses the content area this.selectedTab = firstTabAdded; } else this.selectedBrowser.focus(); } ]]> null into the DOM if necessary. if (!notificationbox.parentNode) { // NB: this appendChild call causes us to run constructors for the // browser element, which fires off a bunch of notifications. Some // of those notifications can cause code to run that inspects our // state, so it is important that the tab element is fully // initialized by this point. this.mPanelContainer.appendChild(notificationbox); } // wire up a progress listener for the new browser object. let tabListener = this.mTabProgressListener(aTab, browser, uriIsAboutBlank, usingPreloadedContent); const filter = Cc["@mozilla.org/appshell/component/browser-status-filter;1"] .createInstance(Ci.nsIWebProgress); filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL); browser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL); this._tabListeners.set(aTab, tabListener); this._tabFilters.set(aTab, filter); browser.droppedLinkHandler = handleDroppedLink; // We start our browsers out as inactive, and then maintain // activeness in the tab switcher. browser.docShellIsActive = false; // When addTab() is called with an URL that is not "about:blank" we // set the "nodefaultsrc" attribute that prevents a frameLoader // from being created as soon as the linked is inserted // into the DOM. We thus have to register the new outerWindowID // for non-remote browsers after we have called browser.loadURI(). if (!remote) { this._outerWindowIDBrowserMap.set(browser.outerWindowID, browser); } var evt = new CustomEvent("TabBrowserInserted", { bubbles: true, detail: {} }); aTab.dispatchEvent(evt); return { usingPreloadedContent: usingPreloadedContent }; ]]> = 0; --i) { tabsToEnd.push(tabs[i]); } return tabsToEnd.reverse(); ]]> = 0; --i) { this.removeTab(tabs[i], aParams); } } ]]> = 0; --i) { if (tabs[i] != aTab && !tabs[i].pinned) this.removeTab(tabs[i], {animate: true}); } } ]]> [] 3 /* don't want lots of concurrent animations */ || aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ || window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ || !Services.prefs.getBoolPref("browser.tabs.animate")) { this._endRemoveTab(aTab); return; } this.tabContainer._handleTabTelemetryStart(aTab); this._blurTab(aTab); aTab.style.maxWidth = ""; // ensure that fade-out transition happens aTab.removeAttribute("fadein"); setTimeout(function (tab, tabbrowser) { if (tab.parentNode && window.getComputedStyle(tab).maxWidth == "0.1px") { NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)"); tabbrowser._endRemoveTab(tab); } }, 3000, aTab, this); ]]> false l != aListener); ]]> this.mTabsProgressListeners.push(aListener); l != aListener); ]]> = tabs.length) { // clamp at right-most tab if out of range. aIndex = tabs.length - 1; } this.selectedTab = tabs[aIndex]; if (aEvent) { aEvent.preventDefault(); aEvent.stopPropagation(); } ]]> return this.mCurrentTab; { if (typeof name == "string" && Number.isInteger(parseInt(name))) { return (name in this.tabs); } return false; }, get: (target, name) => { if (name == "length") { return this.tabs.length; } if (typeof name == "string" && Number.isInteger(parseInt(name))) { if (!(name in this.tabs)) { return undefined; } return this.tabs[name].linkedBrowser; } return target[name]; } }); ]]> 0) this.moveTabTo(this.mCurrentTab, 0); ]]> new Set() null this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT); this.setTabState(this.requestedTab, this.STATE_LOADING); }, // This function runs before every event. It fixes up the state // to account for closed tabs. preActions: function() { this.assert(this.tabbrowser._switcher); this.assert(this.tabbrowser._switcher === this); for (let [tab, ] of this.tabState) { if (!tab.linkedBrowser) { this.tabState.delete(tab); } } if (this.lastVisibleTab && !this.lastVisibleTab.linkedBrowser) { this.lastVisibleTab = null; } if (this.spinnerTab && !this.spinnerTab.linkedBrowser) { this.spinnerHidden(); this.spinnerTab = null; } if (this.loadingTab && !this.loadingTab.linkedBrowser) { this.loadingTab = null; this.clearTimer(this.loadTimer); this.loadTimer = null; } }, // This code runs after we've responded to an event or requested a new // tab. It's expected that we've already updated all the principal // state variables. This function takes care of updating any auxilliary // state. postActions: function() { // Once we finish loading loadingTab, we null it out. So the state should // always be LOADING. this.assert(!this.loadingTab || this.getTabState(this.loadingTab) == this.STATE_LOADING); // We guarantee that loadingTab is non-null iff loadTimer is non-null. So // the timer is set only when we're loading something. this.assert(!this.loadTimer || this.loadingTab); this.assert(!this.loadingTab || this.loadTimer); // If we're not loading anything, try loading the requested tab. let requestedState = this.getTabState(this.requestedTab); if (!this.loadTimer && !this.minimized && (requestedState == this.STATE_UNLOADED || requestedState == this.STATE_UNLOADING)) { this.loadRequestedTab(); } // See how many tabs still have work to do. let numPending = 0; for (let [tab, state] of this.tabState) { // Skip print preview browsers since they shouldn't affect tab switching. if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) { continue; } if (state == this.STATE_LOADED && tab !== this.requestedTab) { numPending++; } if (state == this.STATE_LOADING || state == this.STATE_UNLOADING) { numPending++; } } this.updateDisplay(); // It's possible for updateDisplay to trigger one of our own event // handlers, which might cause finish() to already have been called. // Check for that before calling finish() again. if (!this.tabbrowser._switcher) { return; } if (numPending == 0) { this.finish(); } this.logState("done"); }, // Fires when we're ready to unload unused tabs. onUnloadTimeout: function() { this.logState("onUnloadTimeout"); this.unloadTimer = null; this.preActions(); let numPending = 0; // Unload any tabs that can be unloaded. for (let [tab, state] of this.tabState) { if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) { continue; } if (state == this.STATE_LOADED && !this.maybeVisibleTabs.has(tab) && tab !== this.lastVisibleTab && tab !== this.loadingTab && tab !== this.requestedTab) { this.setTabState(tab, this.STATE_UNLOADING); } if (state != this.STATE_UNLOADED && tab !== this.requestedTab) { numPending++; } } if (numPending) { // Keep the timer going since there may be more tabs to unload. this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY); } this.postActions(); }, // Fires when an ongoing load has taken too long. onLoadTimeout: function() { this.logState("onLoadTimeout"); this.preActions(); this.loadTimer = null; this.loadingTab = null; this.postActions(); }, // Fires when the layers become available for a tab. onLayersReady: function(browser) { let tab = this.tabbrowser.getTabForBrowser(browser); this.logState(`onLayersReady(${tab._tPos})`); this.assert(this.getTabState(tab) == this.STATE_LOADING || this.getTabState(tab) == this.STATE_LOADED); this.setTabState(tab, this.STATE_LOADED); this.maybeFinishTabSwitch(); if (this.loadingTab === tab) { this.clearTimer(this.loadTimer); this.loadTimer = null; this.loadingTab = null; } }, // Fires when we paint the screen. Any tab switches we initiated // previously are done, so there's no need to keep the old layers // around. onPaint: function() { this.maybeVisibleTabs.clear(); this.maybeFinishTabSwitch(); }, // Called when we're done clearing the layers for a tab. onLayersCleared: function(browser) { let tab = this.tabbrowser.getTabForBrowser(browser); if (tab) { this.logState(`onLayersCleared(${tab._tPos})`); this.assert(this.getTabState(tab) == this.STATE_UNLOADING || this.getTabState(tab) == this.STATE_UNLOADED); this.setTabState(tab, this.STATE_UNLOADED); } }, // Called when a tab switches from remote to non-remote. In this case // a MozLayerTreeReady notification that we requested may never fire, // so we need to simulate it. onRemotenessChange: function(tab) { this.logState(`onRemotenessChange(${tab._tPos}, ${tab.linkedBrowser.isRemoteBrowser})`); if (!tab.linkedBrowser.isRemoteBrowser) { if (this.getTabState(tab) == this.STATE_LOADING) { this.onLayersReady(tab.linkedBrowser); } else if (this.getTabState(tab) == this.STATE_UNLOADING) { this.onLayersCleared(tab.linkedBrowser); } } }, // Called when a tab has been removed, and the browser node is // about to be removed from the DOM. onTabRemoved: function(tab) { if (this.lastVisibleTab == tab) { // The browser that was being presented to the user is // going to be removed during this tick of the event loop. // This will cause us to show a tab spinner instead. this.preActions(); this.lastVisibleTab = null; this.postActions(); } }, onSizeModeChange() { if (this.minimized) { for (let [tab, state] of this.tabState) { // Skip print preview browsers since they shouldn't affect tab switching. if (this.tabbrowser._printPreviewBrowsers.has(tab.linkedBrowser)) { continue; } if (state == this.STATE_LOADING || state == this.STATE_LOADED) { this.setTabState(tab, this.STATE_UNLOADING); } } if (this.loadTimer) { this.clearTimer(this.loadTimer); this.loadTimer = null; } this.loadingTab = null; } else { // Do nothing. We'll automatically start loading the requested tab in // postActions. } }, onSwapDocShells(ourBrowser, otherBrowser) { // This event fires before the swap. ourBrowser is from // our window. We save the state of otherBrowser since ourBrowser // needs to take on that state at the end of the swap. let otherTabbrowser = otherBrowser.ownerDocument.defaultView.gBrowser; let otherState; if (otherTabbrowser && otherTabbrowser._switcher) { let otherTab = otherTabbrowser.getTabForBrowser(otherBrowser); otherState = otherTabbrowser._switcher.getTabState(otherTab); } else { otherState = (otherBrowser.docShellIsActive ? this.STATE_LOADED : this.STATE_UNLOADED); } if (!this.swapMap) { this.swapMap = new WeakMap(); } this.swapMap.set(otherBrowser, otherState); }, onEndSwapDocShells(ourBrowser, otherBrowser) { // The swap has happened. We reset the loadingTab in // case it has been swapped. We also set ourBrowser's state // to whatever otherBrowser's state was before the swap. if (this.loadTimer) { // Clearing the load timer means that we will // immediately display a spinner if ourBrowser isn't // ready yet. Typically it will already be ready // though. If it's not, we're probably in a new window, // in which case we have no other tabs to display anyway. this.clearTimer(this.loadTimer); this.loadTimer = null; } this.loadingTab = null; let otherState = this.swapMap.get(otherBrowser); this.swapMap.delete(otherBrowser); let ourTab = this.tabbrowser.getTabForBrowser(ourBrowser); if (ourTab) { this.setTabStateNoAction(ourTab, otherState); } }, shouldActivateDocShell(browser) { let tab = this.tabbrowser.getTabForBrowser(browser); let state = this.getTabState(tab); return state == this.STATE_LOADING || state == this.STATE_LOADED; }, activateBrowserForPrintPreview(browser) { let tab = this.tabbrowser.getTabForBrowser(browser); this.setTabState(tab, this.STATE_LOADING); }, // Called when the user asks to switch to a given tab. requestTab: function(tab) { if (tab === this.requestedTab) { return; } this.logState("requestTab " + this.tinfo(tab)); this.startTabSwitch(); this.requestedTab = tab; let browser = this.requestedTab.linkedBrowser; let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader; if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) { fl.tabParent.suppressDisplayport(true); this.activeSuppressDisplayport.add(fl.tabParent); } this.preActions(); if (this.unloadTimer) { this.clearTimer(this.unloadTimer); } this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY); this.postActions(); }, handleEvent: function(event, delayed = false) { if (this._processing) { this.setTimer(() => this.handleEvent(event, true), 0); return; } if (delayed && this.tabbrowser._switcher != this) { // if we delayed processing this event, we might be out of date, in which // case we drop the delayed events return; } this._processing = true; this.preActions(); if (event.type == "MozLayerTreeReady") { this.onLayersReady(event.originalTarget); } if (event.type == "MozAfterPaint") { this.onPaint(); } else if (event.type == "MozLayerTreeCleared") { this.onLayersCleared(event.originalTarget); } else if (event.type == "TabRemotenessChange") { this.onRemotenessChange(event.target); } else if (event.type == "sizemodechange") { this.onSizeModeChange(); } else if (event.type == "SwapDocShells") { this.onSwapDocShells(event.originalTarget, event.detail); } else if (event.type == "EndSwapDocShells") { this.onEndSwapDocShells(event.originalTarget, event.detail); } this.postActions(); this._processing = false; }, /* * Telemetry and Profiler related helpers for recording tab switch * timing. */ startTabSwitch: function () { this.addMarker("AsyncTabSwitch:Start"); this.switchInProgress = true; }, /** * Something has occurred that might mean that we've completed * the tab switch (layers are ready, paints are done, spinners * are hidden). This checks to make sure all conditions are * satisfied, and then records the tab switch as finished. */ maybeFinishTabSwitch: function () { if (this.switchInProgress && this.requestedTab && this.getTabState(this.requestedTab) == this.STATE_LOADED) { // After this point the tab has switched from the content thread's point of view. // The changes will be visible after the next refresh driver tick + composite. this.addMarker("AsyncTabSwitch:Finish"); this.switchInProgress = false; } }, spinnerDisplayed: function () { this.assert(!this.spinnerTab); this.addMarker("AsyncTabSwitch:SpinnerShown"); }, spinnerHidden: function () { this.assert(this.spinnerTab); this.addMarker("AsyncTabSwitch:SpinnerHidden"); // we do not get a onPaint after displaying the spinner this.maybeFinishTabSwitch(); }, addMarker: function(marker) { if (Services.profiler) { Services.profiler.AddMarker(marker); } }, /* * Debug related logging for switcher. */ _useDumpForLogging: false, _logInit: false, logging: function () { if (this._useDumpForLogging) return true; if (this._logInit) return this._shouldLog; let result = false; try { result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming"); } catch (ex) { } this._shouldLog = result; this._logInit = true; return this._shouldLog; }, tinfo: function(tab) { if (tab) { return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")"; } return "null"; }, log: function(s) { if (!this.logging()) return; if (this._useDumpForLogging) { dump(s + "\n"); } else { Services.console.logStringMessage(s); } }, logState: function(prefix) { if (!this.logging()) return; let accum = prefix + " "; for (let i = 0; i < this.tabbrowser.tabs.length; i++) { let tab = this.tabbrowser.tabs[i]; let state = this.getTabState(tab); accum += i + ":"; if (tab === this.lastVisibleTab) accum += "V"; if (tab === this.loadingTab) accum += "L"; if (tab === this.requestedTab) accum += "R"; if (state == this.STATE_LOADED) accum += "(+)"; if (state == this.STATE_LOADING) accum += "(+?)"; if (state == this.STATE_UNLOADED) accum += "(-)"; if (state == this.STATE_UNLOADING) accum += "(-?)"; accum += " "; } if (this._useDumpForLogging) { dump(accum + "\n"); } else { Services.console.logStringMessage(accum); } }, }; this._switcher = switcher; switcher.init(); return switcher; ]]> { let keyElem = document.getElementById(keyElemId); let shortcut = ShortcutUtils.prettifyShortcut(keyElem); return this.mStringBundle.getFormattedString(stringId, [shortcut]); }; var label; if (tab.mOverCloseButton) { label = tab.selected ? stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") : this.mStringBundle.getString("tabs.closeTab.tooltip"); } else if (tab._overPlayingIcon) { let stringID; if (tab.selected) { stringID = tab.linkedBrowser.audioMuted ? "tabs.unmuteAudio.tooltip" : "tabs.muteAudio.tooltip"; label = stringWithShortcut(stringID, "key_toggleMute"); } else { if (tab.linkedBrowser.audioBlocked) { stringID = "tabs.unblockAudio.tooltip"; } else { stringID = tab.linkedBrowser.audioMuted ? "tabs.unmuteAudio.background.tooltip" : "tabs.muteAudio.background.tooltip"; } label = this.mStringBundle.getString(stringID); } } else { label = tab.getAttribute("label"); } event.target.setAttribute("label", label); ]]> Services.console.logStringMessage("enterTabbedMode is an obsolete method and " + "will be removed in a future release."); true this.tabContainer.visible = aShow; return this.tabContainer.visible; 0 { tab.removeAttribute("soundplaying-scheduledremoval"); tab.removeAttribute("soundplaying"); this._tabAttrModified(tab, ["soundplaying", "soundplaying-scheduledremoval"]); }, removalDelay); } ]]> null null document.getElementById(this.getAttribute("tabbrowser")); this.tabbrowser.mTabBox; document.getElementById("tabContextMenu"); 0 document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox"); null null null null null null let root = document.documentElement; return root.getAttribute("customizing") == "true" || root.getAttribute("customize-exiting") == "true"; false document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator"); 350 0 false 2) { // This is an optimization to avoid layout flushes by calling // getBoundingClientRect() when we just opened a second tab. In // this case it's highly unlikely that the tab width is smaller // than mTabClipWidth and the tab close button obscures too much // of the tab's label. In the edge case of the window being too // narrow (or if tabClipWidth has been set to a way higher value), // we'll correct the 'closebuttons' attribute after the tabopen // animation has finished. let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs]; if (tab && tab.getBoundingClientRect().width <= this.mTabClipWidth) { this.setAttribute("closebuttons", "activetab"); return; } } this.removeAttribute("closebuttons"); ]]> tabStrip.scrollSize) tabStrip.scrollByPixels(-1); } catch (e) {} ]]> document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer"); NaN false false tabs[tabs.length-1]._tPos); var tabWidth = aTab.getBoundingClientRect().width; if (!this._tabDefaultMaxWidth) this._tabDefaultMaxWidth = parseFloat(window.getComputedStyle(aTab).maxWidth); this._lastTabClosedByMouse = true; if (this.getAttribute("overflow") == "true") { // Don't need to do anything if we're in overflow mode and aren't scrolled // all the way to the right, or if we're closing the last tab. if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled) return; // If the tab has an owner that will become the active tab, the owner will // be to the left of it, so we actually want the left tab to slide over. // This can't be done as easily in non-overflow mode, so we don't bother. if (aTab.owner) return; this._expandSpacerBy(tabWidth); } else { // non-overflow mode // Locking is neither in effect nor needed, so let tabs expand normally. if (isEndTab && !this._hasTabTempMaxWidth) return; let numPinned = this.tabbrowser._numPinnedTabs; // Force tabs to stay the same width, unless we're closing the last tab, // which case we need to let them expand just enough so that the overall // tabbar width is the same. if (isEndTab) { let numNormalTabs = tabs.length - numPinned; tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs; if (tabWidth > this._tabDefaultMaxWidth) tabWidth = this._tabDefaultMaxWidth; } tabWidth += "px"; for (let i = numPinned; i < tabs.length; i++) { let tab = tabs[i]; tab.style.setProperty("max-width", tabWidth, "important"); if (!isEndTab) { // keep tabs the same width tab.style.transition = "none"; tab.clientTop; // flush styles to skip animation; see bug 649247 tab.style.transition = ""; } } this._hasTabTempMaxWidth = true; this.tabbrowser.addEventListener("mousemove", this, false); window.addEventListener("mouseout", this, false); } ]]> 0 0; if (doPosition) { this.setAttribute("positionpinnedtabs", "true"); let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width; let paddingStart = this.mTabstrip.scrollboxPaddingStart; let width = 0; for (let i = numPinned - 1; i >= 0; i--) { let tab = this.childNodes[i]; width += tab.getBoundingClientRect().width; tab.style.marginInlineStart = - (width + scrollButtonWidth + paddingStart) + "px"; } this.style.paddingInlineStart = width + paddingStart + "px"; } else { this.removeAttribute("positionpinnedtabs"); for (let i = 0; i < numPinned; i++) { let tab = this.childNodes[i]; tab.style.marginInlineStart = ""; } this.style.paddingInlineStart = ""; } if (this._lastNumPinned != numPinned) { this._lastNumPinned = numPinned; this._handleTabSelect(false); } ]]> 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; } ]]> this.mTabstrip._scrollButtonDown; boxObject.screenX + boxObject.width * .75) return null; } return tab; ]]> tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) return i; } return tabs.length; ]]> 1) { let averageInterval = 0; for (let i = 1; i < frameCount; i++) { averageInterval += intervals[i]; } averageInterval = averageInterval / (frameCount - 1); Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval); if (aTab._recordingTabOpenPlain) { delete aTab._recordingTabOpenPlain; // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW. let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : ""; Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval); } } ]]> 1 && !target._ignoredCloseButtonClicks) { target._ignoredCloseButtonClicks = true; event.stopPropagation(); return; } else { // Reset the "ignored click" flag target._ignoredCloseButtonClicks = false; } } /* Protects from close-tab-button errant doubleclick: * Since we're removing the event target, if the user * double-clicks the button, the dblclick event will be dispatched * with the tabbar as its event target (and explicit/originalTarget), * which treats that as a mouse gesture for opening a new tab. * In this context, we're manually blocking the dblclick event * (see tabbrowser-close-tab-button dblclick handler). */ if (this._blockDblClick) { if (!("_clickedTabBarOnce" in this)) { this._clickedTabBarOnce = true; return; } delete this._clickedTabBarOnce; this._blockDblClick = false; } ]]> endOfTab) || (!ltr && event.clientX < endOfTab)) { BrowserOpenTab(); } } else { BrowserOpenTab(); } } else { return; } event.stopPropagation(); ]]> = this._dragTime + this._dragOverDelay) this.selectedItem = tab; ind.collapsed = true; return; } } var rect = tabStrip.getBoundingClientRect(); var newMargin; if (pixelsToScroll) { // if we are scrolling, put the drop indicator at the edge // so that it doesn't jump while scrolling let scrollRect = tabStrip.scrollClientRect; let minMargin = scrollRect.left - rect.left; let maxMargin = Math.min(minMargin + scrollRect.width, scrollRect.right); if (!ltr) [minMargin, maxMargin] = [this.clientWidth - maxMargin, this.clientWidth - minMargin]; newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin; } else { let newIndex = this._getDropIndex(event, effects == "link"); if (newIndex == this.childNodes.length) { let tabRect = this.childNodes[newIndex-1].getBoundingClientRect(); if (ltr) newMargin = tabRect.right - rect.left; else newMargin = rect.right - tabRect.left; } else { let tabRect = this.childNodes[newIndex].getBoundingClientRect(); if (ltr) newMargin = tabRect.left - rect.left; else newMargin = rect.right - tabRect.right; } } ind.collapsed = false; newMargin += ind.clientWidth / 2; if (!ltr) newMargin *= -1; ind.style.transform = "translate(" + Math.round(newMargin) + "px)"; ind.style.marginInlineStart = (-ind.clientWidth) + "px"; ]]> draggedTab._tPos) newIndex--; this.tabbrowser.moveTabTo(draggedTab, newIndex); } } else if (draggedTab) { let newIndex = this._getDropIndex(event, false); this.tabbrowser.adoptTab(draggedTab, newIndex, true); } else { // Pass true to disallow dropping javascript: or data: urls let links; try { links = browserDragAndDrop.dropLinks(event, true); } catch (ex) {} if (!links || links.length === 0) return; let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground"); if (event.shiftKey) inBackground = !inBackground; let targetTab = this._getDragTargetTab(event, true); let replace = !!targetTab; let newIndex = this._getDropIndex(event, true); let urls = links.map(link => link.url); this.tabbrowser.loadTabs(urls, { inBackground, replace, allowThirdPartyFixup: true, targetTab, newIndex, }); } if (draggedTab) { delete draggedTab._dragData; } ]]> wX && eX < (wX + window.outerWidth)) { let bo = this.mTabstrip.boxObject; // also avoid detaching if the the tab was dropped too close to // the tabbar (half a tab) let endScreenY = bo.screenY + 1.5 * bo.height; if (eY < endScreenY && eY > window.screenY) return; } // screen.availLeft et. al. only check the screen that this window is on, // but we want to look at the screen the tab is being dropped onto. var screen = Cc["@mozilla.org/gfx/screenmanager;1"] .getService(Ci.nsIScreenManager) .screenForRect(eX, eY, 1, 1); var fullX = {}, fullY = {}, fullWidth = {}, fullHeight = {}; var availX = {}, availY = {}, availWidth = {}, availHeight = {}; // get full screen rect and available rect, both in desktop pix screen.GetRectDisplayPix(fullX, fullY, fullWidth, fullHeight); screen.GetAvailRectDisplayPix(availX, availY, availWidth, availHeight); // scale factor to convert desktop pixels to CSS px var scaleFactor = screen.contentsScaleFactor / screen.defaultCSSScaleFactor; // synchronize CSS-px top-left coordinates with the screen's desktop-px // coordinates, to ensure uniqueness across multiple screens // (compare the equivalent adjustments in nsGlobalWindow::GetScreenXY() // and related methods) availX.value = (availX.value - fullX.value) * scaleFactor + fullX.value; availY.value = (availY.value - fullY.value) * scaleFactor + fullY.value; availWidth.value *= scaleFactor; availHeight.value *= scaleFactor; // ensure new window entirely within screen var winWidth = Math.min(window.outerWidth, availWidth.value); var winHeight = Math.min(window.outerHeight, availHeight.value); var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, availX.value), availX.value + availWidth.value - winWidth); var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, availY.value), availY.value + availHeight.value - winHeight); delete draggedTab._dragData; if (this.tabbrowser.tabs.length == 1) { // resize _before_ move to ensure the window fits the new screen. if // the window is too large for its screen, the window manager may do // automatic repositioning. window.resizeTo(winWidth, winHeight); window.moveTo(left, top); window.focus(); } else { let props = { screenX: left, screenY: top }; if (this.tabbrowser.AppConstants.platform != "win") { props.outerWidth = winWidth; props.outerHeight = winHeight; } this.tabbrowser.replaceTabWithWindow(draggedTab, props); } event.stopPropagation(); ]]> // for the one-close-button case event.stopPropagation(); event.stopPropagation(); return this.getAttribute("pinned") == "true"; return this.getAttribute("hidden") == "true"; return this.getAttribute("muted") == "true"; return this.getAttribute("blocked") == "true"; undefined return this.getAttribute("soundplaying") == "true"; return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed; Infinity false null this.style.MozUserFocus = ''; this.style.MozUserFocus = ''; = tabstripBO.screenX && curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width) this.childNodes[i].setAttribute("tabIsVisible", "true"); else this.childNodes[i].removeAttribute("tabIsVisible"); } ]]> 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); ]]> return this.hasAttribute("inactive") ? "" : this.getAttribute("label"); this._mirror(); this._mirror(); if (this.hasAttribute("mirror")) this.removeAttribute("mirror"); else this.setAttribute("mirror", "true"); if (!this.hasAttribute("sizelimit")) { this.setAttribute("sizelimit", "true"); this._calcMouseTargetRect(); } 0 = this.childNodes.length) return val; let toTab = this.getRelatedElement(this.childNodes[val]); gBrowser._getSwitcher().requestTab(toTab); var panel = this._selectedPanel; var newPanel = this.childNodes[val]; this._selectedPanel = newPanel; if (this._selectedPanel != panel) { var event = document.createEvent("Events"); event.initEvent("select", true, true); this.dispatchEvent(event); this._selectedIndex = val; } return val; ]]> null null