/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* 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/. */
/* eslint-env mozilla/browser-window */
{
// start private scope for Tabbrowser
/**
* A set of known icons to use for internal pages. These are hardcoded so we can
* start loading them faster than FaviconLoader would normally find them.
*/
const FAVICON_DEFAULTS = {
"about:newtab":
"chrome://branding/content/icon32.png",
"about:home":
"chrome://branding/content/icon32.png",
"about:welcome":
"chrome://branding/content/icon32.png",
"about:privatebrowsing":
"chrome://browser/skin/privatebrowsing/favicon.svg",
};
const {
LOAD_FLAGS_NONE,
LOAD_FLAGS_FROM_EXTERNAL,
LOAD_FLAGS_FIRST_LOAD,
LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL,
LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP,
LOAD_FLAGS_FIXUP_SCHEME_TYPOS,
LOAD_FLAGS_FORCE_ALLOW_DATA_URI,
LOAD_FLAGS_DISABLE_TRR,
} = Ci.nsIWebNavigation;
const DIRECTION_FORWARD = 1;
const DIRECTION_BACKWARD = -1;
/**
* Updates the User Context UI indicators if the browser is in a non-default context
*/
function updateUserContextUIIndicator() {
function replaceContainerClass(classType, element, value) {
let prefix =
"identity-" + classType +
"-";
if (value && element.classList.contains(prefix + value)) {
return;
}
for (let className of element.classList) {
if (className.startsWith(prefix)) {
element.classList.remove(className);
}
}
if (value) {
element.classList.add(prefix + value);
}
}
let hbox = document.getElementById(
"userContext-icons");
let userContextId = gBrowser.selectedBrowser.getAttribute(
"usercontextid");
if (!userContextId) {
replaceContainerClass(
"color", hbox,
"");
hbox.hidden =
true;
return;
}
let identity =
ContextualIdentityService.getPublicIdentityFromId(userContextId);
if (!identity) {
replaceContainerClass(
"color", hbox,
"");
hbox.hidden =
true;
return;
}
replaceContainerClass(
"color", hbox, identity.color);
let label = ContextualIdentityService.getUserContextLabel(userContextId);
document.getElementById(
"userContext-label").textContent = label;
// Also set the container label as the tooltip so we can only show the icon
// in small windows.
hbox.setAttribute(
"tooltiptext", label);
let indicator = document.getElementById(
"userContext-indicator");
replaceContainerClass(
"icon", indicator, identity.icon);
hbox.hidden =
false;
}
async
function getTotalMemoryUsage() {
const procInfo = await ChromeUtils.requestProcInfo();
let totalMemoryUsage = procInfo.memory;
for (
const child of procInfo.children) {
totalMemoryUsage += child.memory;
}
return totalMemoryUsage;
}
window.Tabbrowser =
class {
init() {
this.tabContainer = document.getElementById(
"tabbrowser-tabs");
this.tabGroupMenu = document.getElementById(
"tab-group-editor");
this.tabbox = document.getElementById(
"tabbrowser-tabbox");
this.tabpanels = document.getElementById(
"tabbrowser-tabpanels");
this.verticalPinnedTabsContainer = document.getElementById(
"vertical-pinned-tabs-container"
);
ChromeUtils.defineESModuleGetters(
this, {
AsyncTabSwitcher:
"resource:///modules/AsyncTabSwitcher.sys.mjs",
PictureInPicture:
"resource://gre/modules/PictureInPicture.sys.mjs",
UrlbarProviderOpenTabs:
"resource:///modules/UrlbarProviderOpenTabs.sys.mjs",
});
XPCOMUtils.defineLazyServiceGetters(
this, {
MacSharingService: [
"@mozilla.org/widget/macsharingservice;1",
"nsIMacSharingService",
],
});
ChromeUtils.defineLazyGetter(
this,
"tabLocalization", () => {
return new Localization(
[
"browser/tabbrowser.ftl",
"branding/brand.ftl"],
true
);
});
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_shouldExposeContentTitle",
"privacy.exposeContentTitleInWindow",
true
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_shouldExposeContentTitlePbm",
"privacy.exposeContentTitleInWindow.pbm",
true
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_showTabCardPreview",
"browser.tabs.hoverPreview.enabled",
true
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_allowTransparentBrowser",
"browser.tabs.allow_transparent_browser",
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"showPidAndActiveness",
"browser.tabs.tooltipsShowPidAndActiveness",
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_unloadTabInContextMenu",
"browser.tabs.unloadTabInContextMenu",
false
);
XPCOMUtils.defineLazyPreferenceGetter(
this,
"_notificationEnableDelay",
"security.notification_enable_delay",
500
);
if (AppConstants.MOZ_CRASHREPORTER) {
ChromeUtils.defineESModuleGetters(
this, {
TabCrashHandler:
"resource:///modules/ContentCrashHandlers.sys.mjs",
});
}
Services.obs.addObserver(
this,
"contextual-identity-updated");
document.addEventListener(
"keydown",
this, { mozSystemGroup:
true });
document.addEventListener(
"keypress",
this, { mozSystemGroup:
true });
document.addEventListener(
"visibilitychange",
this);
window.addEventListener(
"framefocusrequested",
this);
window.addEventListener(
"activate",
this);
window.addEventListener(
"deactivate",
this);
window.addEventListener(
"TabGroupCreate",
this);
this.tabContainer.init();
this._setupInitialBrowserAndTab();
if (
Services.prefs.getIntPref(
"browser.display.document_color_use") == 2
) {
this.tabpanels.style.backgroundColor = Services.prefs.getBoolPref(
"browser.display.use_system_colors"
)
?
"canvas"
: Services.prefs.getCharPref(
"browser.display.background_color");
}
this._setFindbarData();
// We take over setting the document title, so remove the l10n id to
// avoid it being re-translated and overwriting document content if
// we ever switch languages at runtime. After a language change, the
// window title will update at the next tab or location change.
document.querySelector(
"title").removeAttribute(
"data-l10n-id");
this._setupEventListeners();
this._initialized =
true;
}
ownerGlobal = window;
ownerDocument = document;
closingTabsEnum = {
ALL: 0,
OTHER: 1,
TO_START: 2,
TO_END: 3,
MULTI_SELECTED: 4,
DUPLICATES: 6,
ALL_DUPLICATES: 7,
};
_lastRelatedTabMap =
new WeakMap();
mProgressListeners = [];
mTabsProgressListeners = [];
_tabListeners =
new Map();
_tabFilters =
new Map();
_isBusy =
false;
_awaitingToggleCaretBrowsingPrompt =
false;
_previewMode =
false;
_lastFindValue =
"";
_contentWaitingCount = 0;
_tabLayerCache = [];
tabAnimationsInProgress = 0;
/**
* Binding from browser to tab
*/
_tabForBrowser =
new WeakMap();
/**
* `_createLazyBrowser` will define properties on the unbound lazy browser
* which correspond to properties defined in MozBrowser which will be bound to
* the browser when it is inserted into the document. If any of these
* properties are accessed by consumers, `_insertBrowser` is called and
* the browser is inserted to ensure that things don't break. This list
* provides the names of properties that may be called while the browser
* is in its unbound (lazy) state.
*/
_browserBindingProperties = [
"canGoBack",
"canGoForward",
"goBack",
"goForward",
"permitUnload",
"reload",
"reloadWithFlags",
"stop",
"loadURI",
"fixupAndLoadURIString",
"gotoIndex",
"currentURI",
"documentURI",
"remoteType",
"preferences",
"imageDocument",
"isRemoteBrowser",
"messageManager",
"getTabBrowser",
"finder",
"fastFind",
"sessionHistory",
"contentTitle",
"characterSet",
"fullZoom",
"textZoom",
"tabHasCustomZoom",
"webProgress",
"addProgressListener",
"removeProgressListener",
"audioPlaybackStarted",
"audioPlaybackStopped",
"resumeMedia",
"mute",
"unmute",
"blockedPopups",
"lastURI",
"purgeSessionHistory",
"stopScroll",
"startScroll",
"userTypedValue",
"userTypedClear",
"didStartLoadSinceLastUserTyping",
"audioMuted",
];
_removingTabs =
new Set();
_multiSelectedTabsSet =
new WeakSet();
_lastMultiSelectedTabRef =
null;
_clearMultiSelectionLocked =
false;
_clearMultiSelectionLockedOnce =
false;
_multiSelectChangeStarted =
false;
_multiSelectChangeAdditions =
new Set();
_multiSelectChangeRemovals =
new Set();
_multiSelectChangeSelected =
false;
/**
* Tab close requests are ignored if the window is closing anyway,
* e.g. when holding Ctrl+W.
*/
_windowIsClosing =
false;
preloadedBrowser =
null;
/**
* This defines a proxy which allows us to access browsers by
* index without actually creating a full array of browsers.
*/
browsers =
new Proxy([], {
has: (target, name) => {
if (
typeof name ==
"string" && Number.isInteger(parseInt(name))) {
return name in gBrowser.tabs;
}
return false;
},
get: (target, name) => {
if (name ==
"length") {
return gBrowser.tabs.length;
}
if (
typeof name ==
"string" && Number.isInteger(parseInt(name))) {
if (!(name in gBrowser.tabs)) {
return undefined;
}
return gBrowser.tabs[name].linkedBrowser;
}
return target[name];
},
});
/**
* List of browsers whose docshells must be active in order for print preview
* to work.
*/
_printPreviewBrowsers =
new Set();
_switcher =
null;
_soundPlayingAttrRemovalTimer = 0;
_hoverTabTimer =
null;
get _tabGroupsEnabled() {
return NimbusFeatures.tabGroups.getVariable(
"enabled");
}
get tabs() {
return this.tabContainer.allTabs;
}
get tabGroups() {
return this.tabContainer.allGroups;
}
get tabsInCollapsedTabGroups() {
return this.tabGroups
.filter(tabGroup => tabGroup.collapsed)
.flatMap(tabGroup => tabGroup.tabs)
.filter(tab => !tab.hidden && !tab.isClosing);
}
addEventListener(...args) {
this.tabpanels.addEventListener(...args);
}
removeEventListener(...args) {
this.tabpanels.removeEventListener(...args);
}
dispatchEvent(...args) {
return this.tabpanels.dispatchEvent(...args);
}
/**
* Returns all tabs in the current window, including hidden tabs and tabs
* in collapsed groups, but excluding closing tabs and the Firefox View tab.
*/
get openTabs() {
return this.tabContainer.openTabs;
}
/**
* Same as `openTabs` but excluding hidden tabs and tabs in collapsed groups.
*/
get visibleTabs() {
return this.tabContainer.visibleTabs;
}
get pinnedTabCount() {
for (
var i = 0; i <
this.tabs.length; i++) {
if (!
this.tabs[i].pinned) {
break;
}
}
return i;
}
set selectedTab(val) {
if (
gSharedTabWarning.willShowSharedTabWarning(val) ||
document.documentElement.hasAttribute(
"window-modal-open") ||
(gNavToolbox.collapsed && !
this._allowTabChange)
) {
return;
}
// Update the tab
this.tabbox.selectedTab = val;
}
get selectedTab() {
return this._selectedTab;
}
get selectedBrowser() {
return this._selectedBrowser;
}
_setupInitialBrowserAndTab() {
// See browser.js for the meaning of window.arguments.
// Bug 1485961 covers making this more sane.
let userContextId = window.arguments && window.arguments[5];
let openWindowInfo = window.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow).initialOpenWindowInfo;
if (!openWindowInfo && window.arguments && window.arguments[11]) {
openWindowInfo = window.arguments[11];
}
let tabArgument = gBrowserInit.getTabToAdopt();
// If we have a tab argument with browser, we use its remoteType. Otherwise,
// if e10s is disabled or there's a parent process opener (e.g. parent
// process about: page) for the content tab, we use a parent
// process remoteType. Otherwise, we check the URI to determine
// what to do - if there isn't one, we default to the default remote type.
//
// When adopting a tab, we'll also use that tab's browsingContextGroupId,
// if available, to ensure we don't spawn a new process.
let remoteType;
let initialBrowsingContextGroupId;
if (tabArgument && tabArgument.hasAttribute(
"usercontextid")) {
// The window's first argument is a tab if and only if we are swapping tabs.
// We must set the browser's usercontextid so that the newly created remote
// tab child has the correct usercontextid.
userContextId = parseInt(tabArgument.getAttribute(
"usercontextid"), 10);
}
if (tabArgument && tabArgument.linkedBrowser) {
remoteType = tabArgument.linkedBrowser.remoteType;
initialBrowsingContextGroupId =
tabArgument.linkedBrowser.browsingContext?.group.id;
}
else if (openWindowInfo) {
userContextId = openWindowInfo.originAttributes.userContextId;
if (openWindowInfo.isRemote) {
remoteType = E10SUtils.DEFAULT_REMOTE_TYPE;
}
else {
remoteType = E10SUtils.NOT_REMOTE;
}
}
else {
let uriToLoad = gBrowserInit.uriToLoadPromise;
if (uriToLoad && Array.isArray(uriToLoad)) {
uriToLoad = uriToLoad[0];
// we only care about the first item
}
if (uriToLoad &&
typeof uriToLoad ==
"string") {
let oa = E10SUtils.predictOriginAttributes({
window,
userContextId,
});
remoteType = E10SUtils.getRemoteTypeForURI(
uriToLoad,
gMultiProcessBrowser,
gFissionBrowser,
E10SUtils.DEFAULT_REMOTE_TYPE,
null,
oa
);
}
else {
// If we reach here, we don't have the url to load. This means that
// `uriToLoad` is most likely a promise which is waiting on SessionStore
// initialization. We can't delay setting up the browser here, as that
// would mean that `gBrowser.selectedBrowser` might not always exist,
// which is the current assumption.
// In this case we default to the privileged about process as that's
// the best guess we can make, and we'll likely need it eventually.
remoteType = E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE;
}
}
let createOptions = {
uriIsAboutBlank:
false,
userContextId,
initialBrowsingContextGroupId,
remoteType,
openWindowInfo,
};
let browser =
this.createBrowser(createOptions);
browser.setAttribute(
"primary",
"true");
if (gBrowserAllowScriptsToCloseInitialTabs) {
browser.setAttribute(
"allowscriptstoclose",
"true");
}
browser.droppedLinkHandler = handleDroppedLink;
browser.loadURI = URILoadingWrapper.loadURI.bind(
URILoadingWrapper,
browser
);
browser.fixupAndLoadURIString =
URILoadingWrapper.fixupAndLoadURIString.bind(
URILoadingWrapper,
browser
);
let uniqueId =
this._generateUniquePanelID();
let panel =
this.getPanel(browser);
panel.id = uniqueId;
this.tabpanels.appendChild(panel);
let tab =
this.tabs[0];
tab.linkedPanel = uniqueId;
this._selectedTab = tab;
this._selectedBrowser = browser;
tab.permanentKey = browser.permanentKey;
tab._tPos = 0;
tab._fullyOpen =
true;
tab.linkedBrowser = browser;
if (userContextId) {
tab.setAttribute(
"usercontextid", userContextId);
ContextualIdentityService.setTabStyle(tab);
}
this._tabForBrowser.set(browser, tab);
this._appendStatusPanel();
// This is the initial browser, so it's usually active; the default is false
// so we have to update it:
browser.docShellIsActive =
this.shouldActivateDocShell(browser);
// Hook the browser up with a progress listener.
let tabListener =
new TabProgressListener(tab, browser,
true,
false);
let filter = Cc[
"@mozilla.org/appshell/component/browser-status-filter;1"
].createInstance(Ci.nsIWebProgress);
filter.addProgressListener(tabListener, Ci.nsIWebProgress.NOTIFY_ALL);
this._tabListeners.set(tab, tabListener);
this._tabFilters.set(tab, filter);
browser.webProgress.addProgressListener(
filter,
Ci.nsIWebProgress.NOTIFY_ALL
);
}
/**
* BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
* MAKE SURE TO ADD IT HERE AS WELL.
*/
get canGoBack() {
return this.selectedBrowser.canGoBack;
}
get canGoBackIgnoringUserInteraction() {
return this.selectedBrowser.canGoBackIgnoringUserInteraction;
}
get canGoForward() {
return this.selectedBrowser.canGoForward;
}
goBack(requireUserInteraction) {
return this.selectedBrowser.goBack(requireUserInteraction);
}
goForward(requireUserInteraction) {
return this.selectedBrowser.goForward(requireUserInteraction);
}
reload() {
return this.selectedBrowser.reload();
}
reloadWithFlags(aFlags) {
return this.selectedBrowser.reloadWithFlags(aFlags);
}
stop() {
return this.selectedBrowser.stop();
}
/**
* throws exception for unknown schemes
*/
loadURI(uri, params) {
return this.selectedBrowser.loadURI(uri, params);
}
/**
* throws exception for unknown schemes
*/
fixupAndLoadURIString(uriString, params) {
return this.selectedBrowser.fixupAndLoadURIString(uriString, params);
}
gotoIndex(aIndex) {
return this.selectedBrowser.gotoIndex(aIndex);
}
get currentURI() {
return this.selectedBrowser.currentURI;
}
get finder() {
return this.selectedBrowser.finder;
}
get docShell() {
return this.selectedBrowser.docShell;
}
get webNavigation() {
return this.selectedBrowser.webNavigation;
}
get webProgress() {
return this.selectedBrowser.webProgress;
}
get contentWindow() {
return this.selectedBrowser.contentWindow;
}
get sessionHistory() {
return this.selectedBrowser.sessionHistory;
}
get contentDocument() {
return this.selectedBrowser.contentDocument;
}
get contentTitle() {
return this.selectedBrowser.contentTitle;
}
get contentPrincipal() {
return this.selectedBrowser.contentPrincipal;
}
get securityUI() {
return this.selectedBrowser.securityUI;
}
set fullZoom(val) {
this.selectedBrowser.fullZoom = val;
}
get fullZoom() {
return this.selectedBrowser.fullZoom;
}
set textZoom(val) {
this.selectedBrowser.textZoom = val;
}
get textZoom() {
return this.selectedBrowser.textZoom;
}
get isSyntheticDocument() {
return this.selectedBrowser.isSyntheticDocument;
}
set userTypedValue(val) {
this.selectedBrowser.userTypedValue = val;
}
get userTypedValue() {
return this.selectedBrowser.userTypedValue;
}
_setFindbarData() {
// Ensure we know what the find bar key is in the content process:
let { sharedData } = Services.ppmm;
if (!sharedData.has(
"Findbar:Shortcut")) {
let keyEl = document.getElementById(
"key_find");
let mods = keyEl
.getAttribute(
"modifiers")
.replace(
/accel/i,
AppConstants.platform ==
"macosx" ?
"meta" :
"control"
);
sharedData.set(
"Findbar:Shortcut", {
key: keyEl.getAttribute(
"key"),
shiftKey: mods.includes(
"shift"),
ctrlKey: mods.includes(
"control"),
altKey: mods.includes(
"alt"),
metaKey: mods.includes(
"meta"),
});
}
}
isFindBarInitialized(aTab) {
return (aTab ||
this.selectedTab)._findBar != undefined;
}
/**
* Get the already constructed findbar
*/
getCachedFindBar(aTab =
this.selectedTab) {
return aTab._findBar;
}
/**
* Get the findbar, and create it if it doesn't exist.
* @return the find bar (or null if the window or tab is closed/closing in the interim).
*/
async getFindBar(aTab =
this.selectedTab) {
let findBar =
this.getCachedFindBar(aTab);
if (findBar) {
return findBar;
}
// Avoid re-entrancy by caching the promise we're about to return.
if (!aTab._pendingFindBar) {
aTab._pendingFindBar =
this._createFindBar(aTab);
}
return aTab._pendingFindBar;
}
/**
* Create a findbar instance.
* @param aTab the tab to create the find bar for.
* @return the created findbar, or null if the window or tab is closed/closing.
*/
async _createFindBar(aTab) {
let findBar = document.createXULElement(
"findbar");
let browser =
this.getBrowserForTab(aTab);
browser.parentNode.insertAdjacentElement(
"afterend", findBar);
await
new Promise(r => requestAnimationFrame(r));
delete aTab._pendingFindBar;
if (window.closed || aTab.closing) {
return null;
}
findBar.browser = browser;
findBar._findField.value =
this._lastFindValue;
aTab._findBar = findBar;
let event = document.createEvent(
"Events");
event.initEvent(
"TabFindInitialized",
true,
false);
aTab.dispatchEvent(event);
return findBar;
}
_appendStatusPanel() {
this.selectedBrowser.insertAdjacentElement(
"afterend", StatusPanel.panel);
}
_updateTabBarForPinnedTabs() {
this.tabContainer._unlockTabSizing();
this.tabContainer._positionPinnedTabs();
this.tabContainer._updateCloseButtons();
}
_notifyPinnedStatus(aTab) {
// browsingContext is expected to not be defined on discarded tabs.
if (aTab.linkedBrowser.browsingContext) {
aTab.linkedBrowser.browsingContext.isAppTab = aTab.pinned;
}
let event = document.createEvent(
"Events");
event.initEvent(aTab.pinned ?
"TabPinned" :
"TabUnpinned",
true,
false);
aTab.dispatchEvent(event);
}
pinTab(aTab) {
if (aTab.pinned) {
return;
}
this.showTab(aTab);
this.ungroupTab(aTab);
if (
this.tabContainer.verticalMode) {
this._handleTabMove(aTab, () =>
this.verticalPinnedTabsContainer.appendChild(aTab)
);
}
else {
this.moveTabTo(aTab,
this.pinnedTabCount, { forceStandaloneTab:
true });
}
aTab.setAttribute(
"pinned",
"true");
this._updateTabBarForPinnedTabs();
this._notifyPinnedStatus(aTab);
}
unpinTab(aTab) {
if (!aTab.pinned) {
return;
}
if (
this.tabContainer.verticalMode) {
this._handleTabMove(aTab, () => {
// we remove this attribute first, so that allTabs represents
// the moving of a tab from the vertical pinned tabs container
// and back into arrowscrollbox.
aTab.removeAttribute(
"pinned");
this.tabContainer.arrowScrollbox.prepend(aTab);
});
}
else {
this.moveTabTo(aTab,
this.pinnedTabCount - 1, {
forceStandaloneTab:
true,
});
aTab.removeAttribute(
"pinned");
}
aTab.style.marginInlineStart =
"";
aTab._pinnedUnscrollable =
false;
this._updateTabBarForPinnedTabs();
this._notifyPinnedStatus(aTab);
}
previewTab(aTab, aCallback) {
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;
}
}
getBrowserAtIndex(aIndex) {
return this.browsers[aIndex];
}
getBrowserForOuterWindowID(aID) {
for (let b of
this.browsers) {
if (b.outerWindowID == aID) {
return b;
}
}
return null;
}
getTabForBrowser(aBrowser) {
return this._tabForBrowser.get(aBrowser);
}
getPanel(aBrowser) {
return this.getBrowserContainer(aBrowser).parentNode;
}
getBrowserContainer(aBrowser) {
return (aBrowser ||
this.selectedBrowser).parentNode.parentNode;
}
getTabNotificationDeck() {
if (!
this._tabNotificationDeck) {
let template = document.getElementById(
"tab-notification-deck-template"
);
template.replaceWith(template.content);
this._tabNotificationDeck = document.getElementById(
"tab-notification-deck"
);
}
return this._tabNotificationDeck;
}
_nextNotificationBoxId = 0;
getNotificationBox(aBrowser) {
let browser = aBrowser ||
this.selectedBrowser;
if (!browser._notificationBox) {
browser._notificationBox =
new MozElements.NotificationBox(element => {
element.setAttribute(
"notificationside",
"top");
element.setAttribute(
"name",
`tab-notification-box-${
this._nextNotificationBoxId++}`
);
this.getTabNotificationDeck().append(element);
if (browser ==
this.selectedBrowser) {
this._updateVisibleNotificationBox(browser);
}
},
this._notificationEnableDelay);
}
return browser._notificationBox;
}
readNotificationBox(aBrowser) {
let browser = aBrowser ||
this.selectedBrowser;
return browser._notificationBox ||
null;
}
_updateVisibleNotificationBox(aBrowser) {
if (!
this._tabNotificationDeck) {
// If the deck hasn't been created we don't need to create it here.
return;
}
let notificationBox =
this.readNotificationBox(aBrowser);
this.getTabNotificationDeck().selectedViewName = notificationBox
? notificationBox.stack.getAttribute(
"name")
:
"";
}
getTabDialogBox(aBrowser) {
if (!aBrowser) {
throw new Error(
"aBrowser is required");
}
if (!aBrowser.tabDialogBox) {
aBrowser.tabDialogBox =
new TabDialogBox(aBrowser);
}
return aBrowser.tabDialogBox;
}
getTabFromAudioEvent(aEvent) {
if (!aEvent.isTrusted) {
return null;
}
var browser = aEvent.originalTarget;
var tab =
this.getTabForBrowser(browser);
return tab;
}
_callProgressListeners(
aBrowser,
aMethod,
aArguments,
aCallGlobalListeners =
true,
aCallTabsListeners =
true
) {
var rv =
true;
function callListeners(listeners, args) {
for (let p of listeners) {
if (aMethod in p) {
try {
if (!p[aMethod].apply(p, args)) {
rv =
false;
}
}
catch (e) {
// don't inhibit other listeners
console.error(e);
}
}
}
}
aBrowser = aBrowser ||
this.selectedBrowser;
if (aCallGlobalListeners && aBrowser ==
this.selectedBrowser) {
callListeners(
this.mProgressListeners, aArguments);
}
if (aCallTabsListeners) {
aArguments.unshift(aBrowser);
callListeners(
this.mTabsProgressListeners, aArguments);
}
return rv;
}
/**
* Sets an icon for the tab if the URI is defined in FAVICON_DEFAULTS.
*/
setDefaultIcon(aTab, aURI) {
if (aURI && aURI.spec in FAVICON_DEFAULTS) {
this.setIcon(aTab, FAVICON_DEFAULTS[aURI.spec]);
}
}
setIcon(
aTab,
aIconURL =
"",
aOriginalURL = aIconURL,
aLoadingPrincipal =
null,
aClearImageFirst =
false
) {
let makeString = url => (url
instanceof Ci.nsIURI ? url.spec : url);
aIconURL = makeString(aIconURL);
aOriginalURL = makeString(aOriginalURL);
let LOCAL_PROTOCOLS = [
"chrome:",
"about:",
"resource:",
"data:"];
if (
aIconURL &&
!aLoadingPrincipal &&
!LOCAL_PROTOCOLS.some(protocol => aIconURL.startsWith(protocol))
) {
console.error(
`Attempt to set a remote URL ${aIconURL} as a tab icon without a loading principal.`
);
return;
}
let browser =
this.getBrowserForTab(aTab);
browser.mIconURL = aIconURL;
if (aIconURL != aTab.getAttribute(
"image")) {
if (aClearImageFirst) {
aTab.removeAttribute(
"image");
}
if (aIconURL) {
if (aLoadingPrincipal) {
aTab.setAttribute(
"iconloadingprincipal", aLoadingPrincipal);
}
else {
aTab.removeAttribute(
"iconloadingprincipal");
}
aTab.setAttribute(
"image", aIconURL);
}
else {
aTab.removeAttribute(
"image");
aTab.removeAttribute(
"iconloadingprincipal");
}
this._tabAttrModified(aTab, [
"image"]);
}
// The aOriginalURL argument is currently only used by tests.
this._callProgressListeners(browser,
"onLinkIconAvailable", [
aIconURL,
aOriginalURL,
]);
}
getIcon(aTab) {
let browser = aTab ?
this.getBrowserForTab(aTab) :
this.selectedBrowser;
return browser.mIconURL;
}
setPageInfo(aURL, aDescription, aPreviewImage) {
if (aURL) {
let pageInfo = {
url: aURL,
description: aDescription,
previewImageURL: aPreviewImage,
};
PlacesUtils.history.update(pageInfo).
catch(console.error);
}
}
getWindowTitleForBrowser(aBrowser) {
let docElement = document.documentElement;
let title =
"";
let dataSuffix =
docElement.getAttribute(
"privatebrowsingmode") ==
"temporary"
?
"Private"
:
"Default";
if (
SelectableProfileService?.isEnabled &&
SelectableProfileService.currentProfile
) {
dataSuffix +=
"WithProfile";
}
let defaultTitle = docElement.dataset[
"title" + dataSuffix].replace(
"PROFILENAME",
() => SelectableProfileService.currentProfile.name.replace(/\0/g,
"")
);
if (
!
this._shouldExposeContentTitle ||
(PrivateBrowsingUtils.isWindowPrivate(window) &&
!
this._shouldExposeContentTitlePbm)
) {
return defaultTitle;
}
// 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")) {
const uri = Services.io.createExposableURI(aBrowser.currentURI);
let prefix = uri.prePath;
if (uri.scheme ==
"about") {
prefix = uri.spec;
}
else if (uri.scheme ==
"moz-extension") {
const ext = WebExtensionPolicy.getByHostname(uri.host);
if (ext && ext.name) {
let extensionLabel = document.getElementById(
"urlbar-label-extension"
);
prefix = `${extensionLabel.value} (${ext.name})`;
}
}
title = prefix +
" - ";
}
}
catch (e) {
// ignored
}
if (docElement.hasAttribute(
"titlepreface")) {
title += docElement.getAttribute(
"titlepreface");
}
let tab =
this.getTabForBrowser(aBrowser);
if (tab._labelIsContentTitle) {
// Strip out any null bytes in the content title, since the
// underlying widget implementations of nsWindow::SetTitle pass
// null-terminated strings to system APIs.
title += tab.getAttribute(
"label").replace(/\0/g,
"");
}
if (title) {
// We're using a function rather than just using `title` as the
// new substring to avoid `$$`, `$'` etc. having a special
// meaning to `replace`.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter
// and the documentation for functions for more info about this.
return docElement.dataset[
"contentTitle" + dataSuffix]
.replace(
"CONTENTTITLE", () => title)
.replace(
"PROFILENAME",
() =>
SelectableProfileService?.currentProfile?.name.replace(
/\0/g,
""
) ??
""
);
}
return defaultTitle;
}
updateTitlebar() {
document.title =
this.getWindowTitleForBrowser(
this.selectedBrowser);
}
updateCurrentBrowser(aForceUpdate) {
let newBrowser =
this.getBrowserAtIndex(
this.tabContainer.selectedIndex);
if (
this.selectedBrowser == newBrowser && !aForceUpdate) {
return;
}
let oldBrowser =
this.selectedBrowser;
// Once the async switcher starts, it's unpredictable when it will touch
// the address bar, thus we store its state immediately.
gURLBar?.saveSelectionStateForBrowser(oldBrowser);
let newTab =
this.getTabForBrowser(newBrowser);
if (!aForceUpdate) {
TelemetryStopwatch.start(
"FX_TAB_SWITCH_UPDATE_MS");
if (gMultiProcessBrowser) {
this._asyncTabSwitching =
true;
this._getSwitcher().requestTab(newTab);
this._asyncTabSwitching =
false;
}
document.commandDispatcher.lock();
}
let oldTab =
this.selectedTab;
// Preview mode should not reset the owner
if (!
this._previewMode && !oldTab.selected) {
oldTab.owner =
null;
}
let lastRelatedTab =
this._lastRelatedTabMap.get(oldTab);
if (lastRelatedTab) {
if (!lastRelatedTab.selected) {
lastRelatedTab.owner =
null;
}
}
this._lastRelatedTabMap =
new WeakMap();
if (!gMultiProcessBrowser) {
oldBrowser.removeAttribute(
"primary");
oldBrowser.docShellIsActive =
false;
newBrowser.setAttribute(
"primary",
"true");
newBrowser.docShellIsActive = !document.hidden;
}
this._selectedBrowser = newBrowser;
this._selectedTab = newTab;
this.showTab(newTab);
this._appendStatusPanel();
this._updateVisibleNotificationBox(newBrowser);
let oldBrowserPopupsBlocked =
oldBrowser.popupBlocker.getBlockedPopupCount();
let newBrowserPopupsBlocked =
newBrowser.popupBlocker.getBlockedPopupCount();
if (oldBrowserPopupsBlocked != newBrowserPopupsBlocked) {
newBrowser.popupBlocker.updateBlockedPopupsUI();
}
// Update the URL bar.
let webProgress = newBrowser.webProgress;
this._callProgressListeners(
null,
"onLocationChange",
[webProgress,
null, newBrowser.currentURI, 0,
true],
true,
false
);
let securityUI = newBrowser.securityUI;
if (securityUI) {
this._callProgressListeners(
null,
"onSecurityChange",
[webProgress,
null, securityUI.state],
true,
false
);
// Include the true final argument to indicate that this event is
// simulated (instead of being observed by the webProgressListener).
this._callProgressListeners(
null,
"onContentBlockingEvent",
[webProgress,
null, newBrowser.getContentBlockingEvents(),
true],
true,
false
);
}
let listener =
this._tabListeners.get(newTab);
if (listener && listener.mStateFlags) {
this._callProgressListeners(
null,
"onUpdateCurrentBrowser",
[
listener.mStateFlags,
listener.mStatus,
listener.mMessage,
listener.mTotalProgress,
],
true,
false
);
}
if (!
this._previewMode) {
newTab.recordTimeFromUnloadToReload();
newTab.updateLastAccessed();
oldTab.updateLastAccessed();
// if this is the foreground window, update the last-seen timestamps.
if (
this.ownerGlobal == BrowserWindowTracker.getTopWindow()) {
newTab.updateLastSeenActive();
oldTab.updateLastSeenActive();
}
let oldFindBar = oldTab._findBar;
if (
oldFindBar &&
oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
!oldFindBar.hidden
) {
this._lastFindValue = oldFindBar._findField.value;
}
this.updateTitlebar();
newTab.removeAttribute(
"titlechanged");
newTab.attention =
false;
// The tab has been selected, it's not unselected anymore.
// Call the current browser's unselectedTabHover() with false
// to dispatch an event.
newBrowser.unselectedTabHover(
false);
}
// If the new tab is busy, and our current state is not busy, then
// we need to fire a start to all progress listeners.
if (newTab.hasAttribute(
"busy") && !
this._isBusy) {
this._isBusy =
true;
this._callProgressListeners(
null,
"onStateChange",
[
webProgress,
null,
Ci.nsIWebProgressListener.STATE_START |
Ci.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 (!newTab.hasAttribute(
"busy") &&
this._isBusy) {
this._isBusy =
false;
this._callProgressListeners(
null,
"onStateChange",
[
webProgress,
null,
Ci.nsIWebProgressListener.STATE_STOP |
Ci.nsIWebProgressListener.STATE_IS_NETWORK,
0,
],
true,
false
);
}
// 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,
},
});
newTab.dispatchEvent(event);
this._tabAttrModified(oldTab, [
"selected"]);
this._tabAttrModified(newTab, [
"selected"]);
this._startMultiSelectChange();
this._multiSelectChangeSelected =
true;
this.clearMultiSelectedTabs();
if (
this._multiSelectChangeAdditions.size) {
// Some tab has been multiselected just before switching tabs.
// The tab that was selected at that point should also be multiselected.
this.addToMultiSelectedTabs(oldTab);
}
if (!gMultiProcessBrowser) {
this._adjustFocusBeforeTabSwitch(oldTab, newTab);
this._adjustFocusAfterTabSwitch(newTab);
}
// Bug 1781806 - A forced update can indicate the tab was already
// selected. To ensure the internal state of the Urlbar is kept in
// sync, notify it as if focus changed. Alternatively, if there is no
// force update but the load context is not using remote tabs, there
// can be a focus change due to the _adjustFocus above.
if (aForceUpdate || !gMultiProcessBrowser) {
gURLBar.afterTabSwitchFocusChange();
}
}
updateUserContextUIIndicator();
gPermissionPanel.updateSharingIndicator();
// Enable touch events to start a native dragging
// session to allow the user to easily drag the selected tab.
// This is currently only supported on Windows.
oldTab.removeAttribute(
"touchdownstartsdrag");
newTab.setAttribute(
"touchdownstartsdrag",
"true");
if (!gMultiProcessBrowser) {
document.commandDispatcher.unlock();
let event =
new CustomEvent(
"TabSwitchDone", {
bubbles:
true,
cancelable:
true,
});
this.dispatchEvent(event);
}
if (!aForceUpdate) {
TelemetryStopwatch.finish(
"FX_TAB_SWITCH_UPDATE_MS");
}
}
_adjustFocusBeforeTabSwitch(oldTab, newTab) {
if (
this._previewMode) {
return;
}
let oldBrowser = oldTab.linkedBrowser;
let newBrowser = newTab.linkedBrowser;
gURLBar.getBrowserState(oldBrowser).urlbarFocused = gURLBar.focused;
if (
this._asyncTabSwitching) {
newBrowser._userTypedValueAtBeforeTabSwitch = newBrowser.userTypedValue;
}
if (
this.isFindBarInitialized(oldTab)) {
let findBar =
this.getCachedFindBar(oldTab);
oldTab._findBarFocused =
!findBar.hidden &&
findBar._findField.getAttribute(
"focused") ==
"true";
}
let activeEl = document.activeElement;
// If focus is on the old tab, move it to the new tab.
if (activeEl == oldTab) {
newTab.focus();
}
else if (
gMultiProcessBrowser &&
activeEl != newBrowser &&
activeEl != newTab
) {
// In e10s, if focus isn't already in the tabstrip or on the new browser,
// and the new browser's previous focus wasn't in the url bar but focus is
// there now, we need to adjust focus further.
let keepFocusOnUrlBar =
newBrowser &&
gURLBar.getBrowserState(newBrowser).urlbarFocused &&
gURLBar.focused;
if (!keepFocusOnUrlBar) {
// Clear focus so that _adjustFocusAfterTabSwitch can detect if
// some element has been focused and respect that.
document.activeElement.blur();
}
}
}
_adjustFocusAfterTabSwitch(newTab) {
// Don't steal focus from the tab bar.
if (document.activeElement == newTab) {
return;
}
let newBrowser =
this.getBrowserForTab(newTab);
if (newBrowser.hasAttribute(
"tabDialogShowing")) {
newBrowser.tabDialogBox.focus();
return;
}
// Focus the location bar if it was previously focused for that tab.
// In full screen mode, only bother making the location bar visible
// if the tab is a blank one.
if (gURLBar.getBrowserState(newBrowser).urlbarFocused) {
let selectURL = () => {
if (
this._asyncTabSwitching) {
// Set _awaitingSetURI flag to suppress popup notification
// explicitly while tab switching asynchronously.
newBrowser._awaitingSetURI =
true;
// The onLocationChange event called in updateCurrentBrowser() will
// be captured in browser.js, then it calls gURLBar.setURI(). In case
// of that doing processing of here before doing above processing,
// the selection status that gURLBar.select() does will be releasing
// by gURLBar.setURI(). To resolve it, we call gURLBar.select() after
// finishing gURLBar.setURI().
const currentActiveElement = document.activeElement;
gURLBar.inputField.addEventListener(
"SetURI",
() => {
delete newBrowser._awaitingSetURI;
// If the user happened to type into the URL bar for this browser
// by the time we got here, focusing will cause the text to be
// selected which could cause them to overwrite what they've
// already typed in.
let userTypedValueAtBeforeTabSwitch =
newBrowser._userTypedValueAtBeforeTabSwitch;
delete newBrowser._userTypedValueAtBeforeTabSwitch;
if (
newBrowser.userTypedValue &&
newBrowser.userTypedValue != userTypedValueAtBeforeTabSwitch
) {
return;
}
if (currentActiveElement != document.activeElement) {
return;
}
gURLBar.restoreSelectionStateForBrowser(newBrowser);
},
{ once:
true }
);
}
else {
gURLBar.restoreSelectionStateForBrowser(newBrowser);
}
};
// This inDOMFullscreen attribute indicates that the page has something
// such as a video in fullscreen mode. Opening a new tab will cancel
// fullscreen mode, so we need to wait for that to happen and then
// select the url field.
if (window.document.documentElement.hasAttribute(
"inDOMFullscreen")) {
window.addEventListener(
"MozDOMFullscreen:Exited", selectURL, {
once:
true,
wantsUntrusted:
false,
});
return;
}
if (!window.fullScreen || newTab.isEmpty) {
selectURL();
return;
}
}
// Focus the find bar if it was previously focused for that tab.
if (
gFindBarInitialized &&
!gFindBar.hidden &&
this.selectedTab._findBarFocused
) {
gFindBar._findField.focus();
return;
}
// Don't focus the content area if something has been focused after the
// tab switch was initiated.
if (gMultiProcessBrowser && document.activeElement != document.body) {
return;
}
// We're now committed to focusing the content area.
let fm = Services.focus;
let focusFlags = fm.FLAG_NOSCROLL;
if (!gMultiProcessBrowser) {
let newFocusedElement = fm.getFocusedElementForWindow(
window.content,
true,
{}
);
// for anchors, use FLAG_SHOWRING so that it is clear what link was
// last clicked when switching back to that tab
if (
newFocusedElement &&
(HTMLAnchorElement.isInstance(newFocusedElement) ||
newFocusedElement.getAttributeNS(
"http://www.w3.org/1999/xlink",
"type"
) ==
"simple")
) {
focusFlags |= fm.FLAG_SHOWRING;
}
}
fm.setFocus(newBrowser, focusFlags);
}
_tabAttrModified(aTab, aChanged) {
if (aTab.closing) {
return;
}
let event =
new CustomEvent(
"TabAttrModified", {
bubbles:
true,
cancelable:
false,
detail: {
changed: aChanged,
},
});
aTab.dispatchEvent(event);
}
resetBrowserSharing(aBrowser) {
let tab =
this.getTabForBrowser(aBrowser);
if (!tab) {
return;
}
// If WebRTC was used, leave object to enable tracking of grace periods.
tab._sharingState = tab._sharingState?.webRTC ? { webRTC: {} } : {};
tab.removeAttribute(
"sharing");
this._tabAttrModified(tab, [
"sharing"]);
if (aBrowser ==
this.selectedBrowser) {
gPermissionPanel.updateSharingIndicator();
}
}
updateBrowserSharing(aBrowser, aState) {
let tab =
this.getTabForBrowser(aBrowser);
if (!tab) {
return;
}
if (tab._sharingState ==
null) {
tab._sharingState = {};
}
tab._sharingState = Object.assign(tab._sharingState, aState);
if (
"webRTC" in aState) {
if (tab._sharingState.webRTC?.sharing) {
if (tab._sharingState.webRTC.paused) {
tab.removeAttribute(
"sharing");
}
else {
tab.setAttribute(
"sharing", aState.webRTC.sharing);
}
}
else {
tab.removeAttribute(
"sharing");
}
this._tabAttrModified(tab, [
"sharing"]);
}
if (aBrowser ==
this.selectedBrowser) {
gPermissionPanel.updateSharingIndicator();
}
}
getTabSharingState(aTab) {
// Normalize the state object for consumers (ie.extensions).
let state = Object.assign(
{},
aTab._sharingState && aTab._sharingState.webRTC
);
return {
camera: !!state.camera,
microphone: !!state.microphone,
screen: state.screen && state.screen.replace(
"Paused",
""),
};
}
setInitialTabTitle(aTab, aTitle, aOptions = {}) {
// Convert some non-content title (actually a url) to human readable title
if (!aOptions.isContentTitle && isBlankPageURL(aTitle)) {
aTitle =
this.tabContainer.emptyTabTitle;
}
if (aTitle) {
if (!aTab.getAttribute(
"label")) {
aTab._labelIsInitialTitle =
true;
}
this._setTabLabel(aTab, aTitle, aOptions);
}
}
_dataURLRegEx = /^data:[^,]+;base64,/i;
// Regex to test if a string (potential tab label) consists of only non-
// printable characters. We consider Unicode categories Separator
// (spaces & line-breaks) and Other (control chars, private use, non-
// character codepoints) to be unprintable, along with a few specific
// characters whose expected rendering is blank:
// U+2800 BRAILLE PATTERN BLANK (category So)
// U+115F HANGUL CHOSEONG FILLER (category Lo)
// U+1160 HANGUL JUNGSEONG FILLER (category Lo)
// U+3164 HANGUL FILLER (category Lo)
// U+FFA0 HALFWIDTH HANGUL FILLER (category Lo)
// We also ignore combining marks, as in the absence of a printable base
// character they are unlikely to be usefully rendered, and may well be
// clipped away entirely.
_nonPrintingRegEx =
/^[\p{Z}\p{C}\p{M}\u{115f}\u{1160}\u{2800}\u{3164}\u{ffa0}]*$/u;
setTabTitle(aTab) {
var browser =
this.getBrowserForTab(aTab);
var title = browser.contentTitle;
if (aTab.hasAttribute(
"customizemode")) {
title =
this.tabLocalization.formatValueSync(
"tabbrowser-customizemode-tab-title"
);
}
// Don't replace an initially set label with the URL while the tab
// is loading.
if (aTab._labelIsInitialTitle) {
if (!title) {
return false;
}
delete aTab._labelIsInitialTitle;
}
let isURL =
false;
// Trim leading and trailing whitespace from the title.
title = title.trim();
// If the title contains only non-printing characters (or only combining
// marks, but no base character for them), we won't use it.
if (
this._nonPrintingRegEx.test(title)) {
title =
"";
}
let isContentTitle = !!title;
if (!title) {
// See if we can use the URI as the title.
if (browser.currentURI.displaySpec) {
try {
title = Services.io.createExposableURI(
browser.currentURI
).displaySpec;
}
catch (ex) {
title = browser.currentURI.displaySpec;
}
}
if (title && !isBlankPageURL(title)) {
isURL =
true;
if (title.length <= 500 || !
this._dataURLRegEx.test(title)) {
// Try to unescape not-ASCII URIs using the current character set.
try {
let characterSet = browser.characterSet;
title = Services.textToSubURI.unEscapeNonAsciiURI(
characterSet,
title
);
}
catch (ex) {
/* Do nothing. */
}
}
}
else {
// No suitable URI? Fall back to our untitled string.
title =
this.tabContainer.emptyTabTitle;
}
}
return this._setTabLabel(aTab, title, { isContentTitle, isURL });
}
// While an auth prompt from a base domain different than the current sites is open, we do not want to show the tab title of the current site,
// but of the origin that is requesting authentication.
// This is to prevent possible auth spoofing scenarios.
// See bug 791594 for reference.
setTabLabelForAuthPrompts(aTab, aLabel) {
return this._setTabLabel(aTab, aLabel);
}
_setTabLabel(aTab, aLabel, { beforeTabOpen, isContentTitle, isURL } = {}) {
if (!aLabel || aLabel.includes(
"about:reader?")) {
return false;
}
// If it's a long data: URI that uses base64 encoding, truncate to a
// reasonable length rather than trying to display the entire thing,
// which can hang or crash the browser.
// We can't shorten arbitrary URIs like this, as bidi etc might mean
// we need the trailing characters for display. But a base64-encoded
// data-URI is plain ASCII, so this is OK for tab-title display.
// (See bug 1408854.)
if (isURL && aLabel.length > 500 &&
this._dataURLRegEx.test(aLabel)) {
aLabel = aLabel.substring(0, 500) +
"\u2026";
}
aTab._fullLabel = aLabel;
if (!isContentTitle) {
// Remove protocol and "www."
if (!(
"_regex_shortenURLForTabLabel" in
this)) {
this._regex_shortenURLForTabLabel = /^[^:]+:\/\/(?:www\.)?/;
}
aLabel = aLabel.replace(
this._regex_shortenURLForTabLabel,
"");
}
aTab._labelIsContentTitle = isContentTitle;
if (aTab.getAttribute(
"label") == aLabel) {
return false;
}
let dwu = window.windowUtils;
let isRTL =
dwu.getDirectionFromText(aLabel) == Ci.nsIDOMWindowUtils.DIRECTION_RTL;
aTab.setAttribute(
"label", aLabel);
aTab.setAttribute(
"labeldirection", isRTL ?
"rtl" :
"ltr");
aTab.toggleAttribute(
"labelendaligned", isRTL != (document.dir ==
"rtl"));
// Dispatch TabAttrModified event unless we're setting the label
// before the TabOpen event was dispatched.
if (!beforeTabOpen) {
this._tabAttrModified(aTab, [
"label"]);
}
if (aTab.selected) {
this.updateTitlebar();
}
return true;
}
loadTabs(
aURIs,
{
allowInheritPrincipal,
allowThirdPartyFixup,
inBackground,
newIndex,
postDatas,
replace,
targetTab,
triggeringPrincipal,
csp,
userContextId,
fromExternal,
} = {}
) {
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 || inBackground ?
null :
this.selectedTab;
var firstTabAdded =
null;
var targetTabIndex = -1;
if (
typeof newIndex !=
"number") {
newIndex = -1;
}
// When bulk opening tabs, such as from a bookmark folder, we want to insertAfterCurrent
// if necessary, but we also will set the bulkOrderedOpen flag so that the bookmarks
// open in the same order they are in the folder.
if (
multiple &&
newIndex < 0 &&
Services.prefs.getBoolPref(
"browser.tabs.insertAfterCurrent")
) {
newIndex =
this.selectedTab._tPos + 1;
}
if (replace) {
let browser;
if (targetTab) {
browser =
this.getBrowserForTab(targetTab);
targetTabIndex = targetTab._tPos;
}
else {
browser =
this.selectedBrowser;
targetTabIndex =
this.tabContainer.selectedIndex;
}
let loadFlags = LOAD_FLAGS_NONE;
if (allowThirdPartyFixup) {
loadFlags |=
LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
}
if (!allowInheritPrincipal) {
loadFlags |= LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
if (fromExternal) {
loadFlags |= LOAD_FLAGS_FROM_EXTERNAL;
}
try {
browser.fixupAndLoadURIString(aURIs[0], {
loadFlags,
postData: postDatas && postDatas[0],
triggeringPrincipal,
csp,
});
}
catch (e) {
// Ignore failure in case a URI is wrong, so we can continue
// opening the next ones.
}
}
else {
let params = {
allowInheritPrincipal,
ownerTab: owner,
skipAnimation: multiple,
allowThirdPartyFixup,
postData: postDatas && postDatas[0],
userContextId,
triggeringPrincipal,
bulkOrderedOpen: multiple,
csp,
fromExternal,
};
if (newIndex > -1) {
params.index = newIndex;
}
firstTabAdded =
this.addTab(aURIs[0], params);
if (newIndex > -1) {
targetTabIndex = firstTabAdded._tPos;
}
}
let tabNum = targetTabIndex;
for (let i = 1; i < aURIs.length; ++i) {
let params = {
allowInheritPrincipal,
skipAnimation:
true,
allowThirdPartyFixup,
postData: postDatas && postDatas[i],
userContextId,
triggeringPrincipal,
bulkOrderedOpen:
true,
csp,
fromExternal,
};
if (targetTabIndex > -1) {
params.index = ++tabNum;
}
this.addTab(aURIs[i], params);
}
if (firstTabAdded && !inBackground) {
this.selectedTab = firstTabAdded;
}
}
updateBrowserRemoteness(aBrowser, { newFrameloader, remoteType } = {}) {
let isRemote = aBrowser.getAttribute(
"remote") ==
"true";
// We have to be careful with this here, as the "no remote type" is null,
// not a string. Make sure to check only for undefined, since null is
// allowed.
if (remoteType === undefined) {
throw new Error(
"Remote type must be set!");
}
let shouldBeRemote = remoteType !== E10SUtils.NOT_REMOTE;
if (!gMultiProcessBrowser && shouldBeRemote) {
throw new Error(
"Cannot switch to remote browser in a window " +
"without the remote tabs load context."
);
}
// Abort if we're not going to change anything
let oldRemoteType = aBrowser.remoteType;
if (
isRemote == shouldBeRemote &&
!newFrameloader &&
(!isRemote || oldRemoteType == remoteType)
) {
return false;
}
let tab =
this.getTabForBrowser(aBrowser);
// aBrowser needs to be inserted now if it hasn't been already.
this._insertBrowser(tab);
let evt = document.createEvent(
"Events");
evt.initEvent(
"BeforeTabRemotenessChange",
true,
false);
tab.dispatchEvent(evt);
// Unhook our progress listener.
let filter =
this._tabFilters.get(tab);
let listener =
this._tabListeners.get(tab);
aBrowser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(listener);
// We'll be creating a new listener, so destroy the old one.
listener.destroy();
let oldDroppedLinkHandler = aBrowser.droppedLinkHandler;
let oldUserTypedValue = aBrowser.userTypedValue;
let hadStartedLoad = aBrowser.didStartLoadSinceLastUserTyping();
// Change the "remote" attribute.
// Make sure the browser is destroyed so it unregisters from observer notifications
aBrowser.destroy();
if (shouldBeRemote) {
aBrowser.setAttribute(
"remote",
"true");
aBrowser.setAttribute(
"remoteType", remoteType);
}
else {
aBrowser.setAttribute(
"remote",
"false");
aBrowser.removeAttribute(
"remoteType");
}
// This call actually switches out our frameloaders. Do this as late as
// possible before rebuilding the browser, as we'll need the new browser
// state set up completely first.
aBrowser.changeRemoteness({
remoteType,
});
// Once we have new frameloaders, this call sets the browser back up.
aBrowser.construct();
aBrowser.userTypedValue = oldUserTypedValue;
if (hadStartedLoad) {
aBrowser.urlbarChangeTracker.startedLoad();
}
aBrowser.droppedLinkHandler = oldDroppedLinkHandler;
// This shouldn't really be necessary, however, this has the side effect
// of sending MozLayerTreeReady / MozLayerTreeCleared events for remote
// frames, which the tab switcher depends on.
//
// eslint-disable-next-line no-self-assign
aBrowser.docShellIsActive = aBrowser.docShellIsActive;
// Create a new tab progress listener for the new browser we just injected,
// since tab progress listeners have logic for handling the initial about:blank
// load
listener =
new TabProgressListener(tab, aBrowser,
true,
false);
this._tabListeners.set(tab, listener);
filter.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
// Restore the progress listener.
aBrowser.webProgress.addProgressListener(
filter,
Ci.nsIWebProgress.NOTIFY_ALL
);
// Restore the securityUI state.
let securityUI = aBrowser.securityUI;
let state = securityUI
? securityUI.state
: Ci.nsIWebProgressListener.STATE_IS_INSECURE;
this._callProgressListeners(
aBrowser,
"onSecurityChange",
[aBrowser.webProgress,
null, state],
true,
false
);
let event = aBrowser.getContentBlockingEvents();
// Include the true final argument to indicate that this event is
// simulated (instead of being observed by the webProgressListener).
this._callProgressListeners(
aBrowser,
"onContentBlockingEvent",
[aBrowser.webProgress,
null, event,
true],
true,
false
);
if (shouldBeRemote) {
// Switching the browser to be remote will connect to a new child
// process so the browser can no longer be considered to be
// crashed.
tab.removeAttribute(
"crashed");
}
// If the findbar has been initialised, reset its browser reference.
if (
this.isFindBarInitialized(tab)) {
this.getCachedFindBar(tab).browser = aBrowser;
}
evt = document.createEvent(
"Events");
evt.initEvent(
"TabRemotenessChange",
true,
false);
tab.dispatchEvent(evt);
return true;
}
updateBrowserRemotenessByURL(aBrowser, aURL, aOptions = {}) {
if (!gMultiProcessBrowser) {
return this.updateBrowserRemoteness(aBrowser, {
remoteType: E10SUtils.NOT_REMOTE,
});
}
let oldRemoteType = aBrowser.remoteType;
let oa = E10SUtils.predictOriginAttributes({ browser: aBrowser });
aOptions.remoteType = E10SUtils.getRemoteTypeForURI(
aURL,
gMultiProcessBrowser,
gFissionBrowser,
oldRemoteType,
aBrowser.currentURI,
oa
);
// If this URL can't load in the current browser then flip it to the
// correct type.
if (oldRemoteType != aOptions.remoteType || aOptions.newFrameloader) {
return this.updateBrowserRemoteness(aBrowser, aOptions);
}
return false;
}
createBrowser({
isPreloadBrowser,
name,
openWindowInfo,
remoteType,
initialBrowsingContextGroupId,
uriIsAboutBlank,
userContextId,
skipLoad,
} = {}) {
let b = document.createXULElement(
"browser");
// Use the JSM global to create the permanentKey, so that if the
// permanentKey is held by something after this window closes, it
// doesn't keep the window alive.
--> --------------------
--> maximum size reached
--> --------------------