this._tabMinWidth = newValue, newValue => { const LIMIT = 50; return Math.max(newValue, LIMIT); }, ); this._tabMinWidth = this._tabMinWidthPref; this._setPositionalAttributes(); CustomizableUI.addListener(this); this._updateNewTabVisibility(); XPCOMUtils.defineLazyPreferenceGetter(this, "_closeTabByDblclick", "browser.tabs.closeTabByDblclick", false); ]]> document.getElementById("tabbrowser-tabbox"); document.getElementById("tabContextMenu"); document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox"); null null null null null null this.style.setProperty("--tab-min-width", val + "px"); return val; 2)) { containersEnabled = false; } const newTab = document.getElementById("new-tab-button"); const newTab2 = document.getAnonymousElementByAttribute(this, "anonid", "tabs-newtab-button"); for (let parent of [newTab, newTab2]) { if (!parent) continue; gClickAndHoldListenersOnElement.remove(parent); parent.removeAttribute("type"); if (parent.firstChild) { parent.firstChild.remove(); } if (containersEnabled) { let popup = document.createElementNS( "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menupopup"); if (parent.id) { popup.id = "newtab-popup"; } else { popup.setAttribute("anonid", "newtab-popup"); } popup.className = "new-tab-popup"; popup.setAttribute("position", "after_end"); parent.appendChild(popup); // longPressBehavior == 2 means that the menu is shown after X // millisecs. Otherwise, with 1, the menu is open immediatelly. if (longPressBehavior == 2) { gClickAndHoldListenersOnElement.add(parent); } parent.setAttribute("type", "menu"); } } break; } ]]> false document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator"); 350 0 false { window.requestAnimationFrame(() => { this._closeButtonsUpdatePending = false; // The scrollbox may have started overflowing since we checked // overflow earlier, so check again. if (this.getAttribute("overflow") == "true") { this.setAttribute("closebuttons", "activetab"); return; } // Check if tab widths are below the threshold where we want to // remove close buttons from background tabs so that people don't // accidentally close tabs by selecting them. let rect = ele => { return window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils) .getBoundsWithoutFlushing(ele); }; let tab = this._getVisibleTabs()[gBrowser._numPinnedTabs]; if (tab && rect(tab).width <= this._tabClipWidth) { this.setAttribute("closebuttons", "activetab"); } else { this.removeAttribute("closebuttons"); } }); }); ]]> 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.arrowScrollbox._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 = gBrowser._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; gBrowser.addEventListener("mousemove", this); window.addEventListener("mouseout", this); } ]]> 0 null numPinned && numPinned > 0; if (doPosition) { this.setAttribute("positionpinnedtabs", "true"); let layoutData = this._pinnedTabsLayoutCache; let uiDensity = document.documentElement.getAttribute("uidensity"); if (!layoutData || layoutData.uiDensity != uiDensity) { let arrowScrollbox = this.arrowScrollbox; layoutData = this._pinnedTabsLayoutCache = { uiDensity, pinnedTabWidth: this.childNodes[0].getBoundingClientRect().width, scrollButtonWidth: arrowScrollbox._scrollButtonDown.getBoundingClientRect().width }; } let width = 0; for (let i = numPinned - 1; i >= 0; i--) { let tab = this.childNodes[i]; width += layoutData.pinnedTabWidth; tab.style.marginInlineStart = -(width + layoutData.scrollButtonWidth) + "px"; tab._pinnedUnscrollable = true; } this.style.paddingInlineStart = width + "px"; } else { this.removeAttribute("positionpinnedtabs"); for (let i = 0; i < numPinned; i++) { let tab = this.childNodes[i]; tab.style.marginInlineStart = ""; tab._pinnedUnscrollable = false; } this.style.paddingInlineStart = ""; } if (this._lastNumPinned != numPinned) { this._lastNumPinned = numPinned; this._handleTabSelect(true); } ]]> high) break; let boxObject = tabs[mid].boxObject; screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex); if (screenX > tabCenter) { high = mid - 1; } else if (screenX + boxObject.width < tabCenter) { low = mid + 1; } else { newIndex = tabs[mid]._tPos; break; } } if (newIndex >= oldIndex) newIndex++; if (newIndex < 0 || newIndex == oldIndex) return; draggedTab._dragData.animDropIndex = newIndex; // Shift background tabs to leave a gap where the dragged tab // would currently be dropped. for (let tab of tabs) { if (tab != draggedTab) { let shift = getTabShift(tab, newIndex); tab.style.transform = shift ? "translateX(" + shift + "px)" : ""; } } function getTabShift(tab, dropIndex) { if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex) return rtl ? -tabWidth : tabWidth; if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex) return rtl ? tabWidth : -tabWidth; return 0; } ]]> this.arrowScrollbox._scrollButtonDown; boxObject.screenX + boxObject.width * .75) return null; } return tab; ]]> tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) return i; } return tabs.length; ]]> n.parentNode.localName == "toolbarpaletteitem" ? n.parentNode : n; let unwrap = n => n && n.localName == "toolbarpaletteitem" ? n.firstElementChild : n; // Starting from the tabs element, find the next sibling that: // - isn't hidden; and // - isn't one of the titlebar placeholder elements; and // - isn't the all-tabs button. // If it's the new tab button, consider the new tab button adjacent to the tabs. // If the new tab button is marked as adjacent and the tabstrip doesn't // overflow, we'll display the 'new tab' button inline in the tabstrip. // In all other cases, the separate new tab button is displayed in its // customized location. let sib = this; do { sib = unwrap(wrap(sib).nextElementSibling); } while (sib && (sib.hidden || sib.getAttribute("skipintoolbarset") == "true" || sib.id == "alltabs-button")); const kAttr = "hasadjacentnewtabbutton"; if (sib && sib.id == "new-tab-button") { this.setAttribute(kAttr, "true"); } else { this.removeAttribute(kAttr); } ]]> 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. */ 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 = arrowScrollbox.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 = arrowScrollbox.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"; ]]> 0 && translateOffset > tabWidth / 2) { newTranslateX += tabWidth; } else if (oldTranslateX < 0 && -translateOffset > tabWidth / 2) { newTranslateX -= tabWidth; } let dropIndex = "animDropIndex" in draggedTab._dragData && draggedTab._dragData.animDropIndex; if (dropIndex && dropIndex > draggedTab._tPos) dropIndex--; let animate = gBrowser.animationsEnabled; if (oldTranslateX && oldTranslateX != newTranslateX && animate) { draggedTab.setAttribute("tabdrop-samewindow", "true"); draggedTab.style.transform = "translateX(" + newTranslateX + "px)"; let onTransitionEnd = transitionendEvent => { if (transitionendEvent.propertyName != "transform" || transitionendEvent.originalTarget != draggedTab) { return; } draggedTab.removeEventListener("transitionend", onTransitionEnd); draggedTab.removeAttribute("tabdrop-samewindow"); this._finishAnimateTabMove(); if (dropIndex !== false) { gBrowser.moveTabTo(draggedTab, dropIndex); } gBrowser.syncThrobberAnimations(draggedTab); }; draggedTab.addEventListener("transitionend", onTransitionEnd); } else { this._finishAnimateTabMove(); if (dropIndex !== false) { gBrowser.moveTabTo(draggedTab, dropIndex); } } } else if (draggedTab) { let newIndex = this._getDropIndex(event, false); gBrowser.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); let triggeringPrincipal = browserDragAndDrop.getTriggeringPrincipal(event); (async () => { if (urls.length >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) { // Sync dialog cannot be used inside drop event handler. let answer = await OpenInTabsUtils.promiseConfirmOpenInTabs(urls.length, window); if (!answer) { return; } } gBrowser.loadTabs(urls, { inBackground, replace, allowThirdPartyFixup: true, targetTab, newIndex, userContextId, triggeringPrincipal, }); })(); } if (draggedTab) { delete draggedTab._dragData; } ]]> wX && eX < (wX + window.outerWidth)) { let bo = this.arrowScrollbox.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 (gBrowser.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, suppressanimation: 1 }; if (AppConstants.platform != "win") { props.outerWidth = winWidth; props.outerHeight = winHeight; } gBrowser.replaceTabWithWindow(draggedTab, props); } event.stopPropagation(); ]]> false return this.getAttribute("pinned") == "true"; return this.getAttribute("hidden") == "true"; return this.getAttribute("muted") == "true"; return this.getAttribute("multiselected") == "true"; return this.getAttribute("before-multiselected") == "true"; undefined return this.hasAttribute("usercontextid") ? parseInt(this.getAttribute("usercontextid")) : 0; return this.getAttribute("soundplaying") == "true"; return this.getAttribute("activemedia-blocked") == "true"; return this._lastAccessed == Infinity ? Date.now() : this._lastAccessed; false null { if (TelemetryStopwatch.running("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this)) { TelemetryStopwatch.cancel("HOVER_UNTIL_UNSELECTED_TAB_OPENED", this); } }, 100); ]]> this.style.MozUserFocus = ""; this.style.MozUserFocus = ""; 0 && !overCloseButton && !this._overPlayingIcon) { // Tabs were previously multi-selected and user clicks on a tab // without holding Ctrl/Cmd Key // Force positional attributes to update when the // target (of the click) is the "active" tab. let updatePositionalAttr = gBrowser.selectedTab == this; gBrowser.clearMultiSelectedTabs(updatePositionalAttr); } } if (this._overPlayingIcon) { if (this.multiselected) { gBrowser.toggleMuteAudioOnMultiSelectedTabs(this); } else { this.toggleMuteAudio(); } return; } if (event.originalTarget.getAttribute("anonid") == "close-button") { if (this.multiselected) { gBrowser.removeMultiSelectedTabs(); } else { gBrowser.removeTab(this, { animate: true, byMouse: event.mozInputSource == MouseEvent.MOZ_SOURCE_MOUSE, }); } // This enables double-click protection for the tab container // (see tabbrowser-tabs 'click' handler). gBrowser.tabContainer._blockDblClick = true; } ]]> 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