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;
let userContextId = this.mBrowser.getAttribute("usercontextid") || 0;
if (this.mBrowser.registeredOpenURI) {
unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI,
userContextId);
delete this.mBrowser.registeredOpenURI;
}
// Tabs in private windows aren't registered as "Open" so
// that they don't appear as switch-to-tab candidates.
if (!isBlankPageURL(aLocation.spec) &&
(!PrivateBrowsingUtils.isWindowPrivate(window) ||
PrivateBrowsingUtils.permanentPrivateBrowsing)) {
unifiedComplete.registerOpenPage(aLocation, userContextId);
this.mBrowser.registeredOpenURI = aLocation;
}
}
if (!this.mBlank) {
this._callProgressListeners("onLocationChange",
[aWebProgress, aRequest, aLocation,
aFlags]);
}
if (topLevel) {
this.mBrowser.lastURI = aLocation;
this.mBrowser.lastLocationChange = Date.now();
}
},
onStatusChange: 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],
userContextId: aUserContextId
});
if (aNewIndex !== -1) {
this.moveTabTo(firstTabAdded, aNewIndex);
targetTabIndex = firstTabAdded._tPos;
}
}
let tabNum = targetTabIndex;
for (let i = 1; i < aURIs.length; ++i) {
let tab = this.addTab(aURIs[i], {
skipAnimation: true,
allowThirdPartyFixup: aAllowThirdPartyFixup,
postData: aPostDatas[i],
userContextId: aUserContextId
});
if (targetTabIndex !== -1)
this.moveTabTo(tab, ++tabNum);
}
if (!aLoadInBackground) {
if (firstTabAdded) {
// .selectedTab setter focuses the content area
this.selectedTab = firstTabAdded;
}
else
this.selectedBrowser.focus();
}
]]>
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 userContextId = this.selectedItem.getAttribute("usercontextid");
let replace = !!targetTab;
let newIndex = this._getDropIndex(event, true);
let urls = links.map(link => link.url);
this.tabbrowser.loadTabs(urls, {
inBackground,
replace,
allowThirdPartyFixup: true,
targetTab,
newIndex,
userContextId,
});
}
if (draggedTab) {
delete draggedTab._dragData;
}
]]>
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.hasAttribute("usercontextid")
? parseInt(this.getAttribute("usercontextid"))
: 0;
return this.getAttribute("soundplaying") == "true";
return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed;
Infinity
false
null
this.style.MozUserFocus = '';
this.style.MozUserFocus = '';
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