- Firefox-Version
- 141.0.2
- Betriebssystem
- Win 10 Pro
Hallo liebe Fuchs Community !
Ich habe ein Problem mit meinen "angepinnten" TABs.
Wenn ich 8 offene TABs habe, dann ist das Problem nicht da, aber bei 9 wird es richtig wild.
Es ist schwer zu beschreiben, daher zeige ich es euch einfach auf ein paar Bildern.
^ so sieht es bei 8 TABs aus
^ wenn ich 9 TABs offen habe und dann die TABs anpinne, dann wird der TAB gigantisch Groß !
Ich nutze das folgende Script und ich vermute, das dort der Fehler drinnen sein wird.
MultiRowTabLiteforFx.uc.js
CSS
// ==UserScript==
// @name MultiRowTabLiteforFx.uc.js
// @namespace Based on Alice0775's zzzz-MultiRowTab_LiteforFx48.uc.js
// @description Mehrzeilige Tableiste - Experimentelle CSS Version
// @include main
// @compatibility Firefox141+
// @version 2025/06/30 16:58
// ==/UserScript==
"use strict";
MultiRowTabLiteforFx();
function MultiRowTabLiteforFx() {
if (!window.gBrowser) { return; }
// -- Config --
// Vergleichbarer CSS Code in userChrome.css Datei wird vorrangig behandelt!
const // Mehrzeilige Tableiste Ein/Aus Anzahl der Tabzeilen
MultiRowTab_OnOff_and_TabBar_Rows = -1 ,// [-1] = Mehrzeilige Tableiste aktiv unbegrenzte Anzahl von Zeilen.
// 0 = Mehrzeilige Tableiste aus.
// 1 = Mehrzeilige Tableiste aktiv. Standard = 1 Zeile. Bei Berührung
// der Tableiste mit der der Maus, werden die zweite und die folgenden
// Zeilen bis zur angegebenen Anzahl von Zeilen angezeigt.
// 2 = Mehrzeilige Tableiste aktiv. Anzahl der Tabzeilen angeben.
TabBar_Rows_on_MouseOver = 3 ,// Standard = 1 Zeile. Anzahl der Zeilen angeben, die angezeigt werden sollen,
// wenn der Mauszeiger über die Tableiste bewegt wird. Voraussetzung:
// „MultiRowTab_OnOff_and_TabBar_Rows“ auf „1“ setzen.
TabBar_DisplayTime_on_MouseOver = 1 ,// Sie können die Anzeigezeit (Sekunden) festlegen, wann die zweite und die
// folgenden Zeilen beim Mouseover angezeigt werden. Das Display zeigt den
// eingestellten Wert(Sekunden) an und kehrt dann zur ersten Zeile zurück.
// Position der Tab-Leiste.
TabBar_Position = 1 ,// [0] = Standard
// 1 = unter der Symbolleiste
// 2 = unter dem Fenster
// Positionen der Tab-Leiste und der Lesezeichen-Symbolleiste tauschen.
// sofern die Position der Tab-Leiste unterhalb der Symbolleiste festgelegt ist.
// Voraussetzung: "TabBar_Position" auf "1".
Bookmark_Toolbar_Position = true ,// [true] = Menüleiste, Navigationsleiste, Lesezeichenleiste, Tableiste
// false = Menüleiste, Navigationsleiste, Tableiste, Lesezeichensymbolleiste
// Tab-Höhe „UI-Dichte“
UI_Density_Compact = 32 ,// Standard = 29 Pixelbei Kompakt
UI_Density_Normal = 32 ,// Standard = 36 Pixel bei Normal
UI_Density_Touch = 41 ,// Standard = 41 Pixel bei Touch
// Tab-Breite
Tab_Min_Width = 165 ,// Standard - Mindestwert = 76px
Tab_Max_Width = 165 ,// Standard - Maxwert = 225px
// Bei gleichen Werten bei Min und Max, wird die Tabbreite fixiert!
// „Tab schließen“ Schaltfläche
Tab_Close_Button = 2 ,// [0] = Standard
// 1 = Ausgeblendet
// 2 = Auf allen Tabs anzeigen
// 3 = Nur bei Mausberührung anzeigen
// 4 = Aktive Tabs werden immer angezeigt, inaktive Tabs
// werden beim Mouseover angezeigt. *Standard für vertikalen Tab-Modus.
// ProtonUI Erscheinungsbild der Tabs ändern
Proton_Margins = true ,// [true] = Darstellung ProtonUI
// Die Höhe der Tab-Leiste entspricht der Höhe der UI-Dichte plus dem Leerraum darüber
// und darunter.
// false = Darstellung wie bei browser.proton.enabled auf false, was man vor Firefox 90
// noch einstellen konnte.
// Wenn der Leerraum um die Tabs auf 0 und die Höhe auf die UI-Dichte eingestellt
// ist, ist sie 4 Pixel breiter und 8 Pixel niedriger als die Standardeinstellung.
// Ränder auf der linken und rechten Seite der Tabs
Tab_Separators = false ,// [false] = Nicht anzeigen
// true = Anzeigen
// Rahmen CSS wurde extrahiert und angepasst, an Aussehen wie bei browser.proton.enabled
// auf false, was man vor Firefox 90 noch einstellen konnte.
// Voraussetzung: „TabBar_Position“ auf „0“ setzen.
TitleBar_Button_Autohide = false ,// [false] = Aktiviert
// true = Deaktiviert
// Äußeren Rahmen der Titelleistenschaltfläche [-□×] reduzieren und transparent machen.
TitleBar_Button_DisplayTime = 0.6 ,// Dauer der Anzeige in Sekunden, nach der Rückkehr zur Originalgröße und dem Aufheben
// der Transparenz per Mouseover angeben.
// Tab-Leiste von Anfang an auf die angegebene Höhe einstellen.
// Voraussetzung: „MultiRowTab_OnOff_and_TabBar_Rows“ auf „2“ oder höher setzen.
Set_the_TabBar_to_the_Specified_Height = false ,// [false] = Die Tab-Leiste wird höher, wenn der nächsten Zeile weitere Tabs hinzugefügt werden.
// true = Verwendung: Die Tab-Leiste wird von Anfang an auf die angegebene Höhe eingestellt
// „.tabDropIndicator“, der beim Ziehen und Ablegen eines Tabs angezeigt wird, ersetzen.
// Voraussetzung: „MultiRowTab_OnOff_and_TabBar_Rows“ auf einen anderen Wert als „0“ setzen.
Tab_Drop_Indicator = false ,// [false] = Stecknadel Symbol 📍
// true = Rote Linie (2px × 29px) als Symbol
// Position der angepinnten Tabs
// Voraussetzung: „MultiRowTab_OnOff_and_TabBar_Rows“ auf einen Wert ungleich „0“ setzen.
Separate_Tabs_and_PinnedTabs = false ,// [false] = Standard
// true = Angeheftete Tabs von der Tab-Leiste lösen und in die darüber liegende
// Zeile verschieben. Breite der angehefteten Tabs für die Position der
// angehefteten Tabs „true“ anpassen.
PinnedTab_Width = false ,// [false] = Kein Standard
// true = Breite angehefteter Tabs anpassen, z. B. „Tab-Breite“.
PinnedTab_Min_Width = 76 ,// Standard Mindestbreite = 76 Pixel
PinnedTab_Max_Width = 225 ,// Standard Maximalbreite = 225 Pixel
// Bei gleichen Werten ist die Breite fixiert.
// Angeheftete Tab, Schließen Schaltfläche
// Voraussetzung: „Separate_Tabs_and_PinnedTabs“ auf „true“ setzen.
PinnedTab_Close_Button = 0 ,// [0] = Standard
// 1 = auf allen Tabs sichtbar
// 2 = auf Tab bei Mouseover anzeigen
// 3 = Aktiver Tab immer sichtbar, inaktiver Tab bei Mouseover sichtbar
// *Standard für vertikalen Tab-Modus.
// Tab-Leisten-Ziehbereich
Left_Drag_Area = 0 ,// Linker Ziehbereich Breite: Standard 40 Pixel
Right_Drag_Area = 0 ,// Rechter Ziehbereich Breite: Standard 40 Pixel
Maximize_Left_Drag_Area = false ,// true = Linken Ziehbereich bei maximiertem Fenster anzeigen. Standard ausgeblendet.
Fullscreen_Drag_Area = false ,// true = Linken und rechten Ziehbereich bei Vollbild anzeigen. Standard ausgeblendet.
// Wenn die Titelleiste angezeigt wird, funktioniert sie nicht als Drag-Bereich, selbst
// wenn „.titlebar-spacer“ angezeigt wird. Daher habe ich dafür gesorgt, dass sie nichts bewirkt.
// -- Config Ende --
css = `
#TabsToolbar:not([collapsed="true"]) {
:root[uidensity="compact"] & {
--tab-min-height: ${UI_Density_Compact}px;
}
:root:not([uidensity]) & {
--tab-min-height: ${UI_Density_Normal}px;
}
:root[uidensity="touch"] & {
--tab-min-height: ${UI_Density_Touch}px;
}
&[dragtarget] {
z-index: unset !important;
position: unset !important;
pointer-events: unset !important; /* avoid blocking dragover events on scroll buttons */
}
#tabbrowser-tabs[movingtab] & {
position: unset !important;
}
#tabbrowser-tabs[movingtab] &:is(:active, [multiselected]) {
position: relative;
z-index: 2;
pointer-events: none; /* avoid blocking dragover events on scroll buttons */
}
#tabbrowser-tabs {
min-height: calc(var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px);
${MultiRowTab_OnOff_and_TabBar_Rows != 0 ? `
&[overflow] {
padding-inline: 0 !important;
& > #tabbrowser-arrowscrollbox {
& > .tabbrowser-tab[pinned] {
display: flex;
margin-inline-start: 0 !important;
position: static !important;
}
&::part(scrollbox) {
padding-inline: 0;
}
}
& + #new-tab-button {
display: none;
}
}
${Tab_Drop_Indicator ? `
& > .tab-drop-indicator {
background: url(
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAdCAIAAAAPVCo9AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAASSURBVBhXY3growJEQ5+SUQEAOb1EM8kwskcAAAAASUVORK5CYII=
) no-repeat center;
}
` : ``}
#tabbrowser-arrowscrollbox {
&::part(scrollbox) {
& > slot {
flex-wrap: wrap;
}
${MultiRowTab_OnOff_and_TabBar_Rows != -1 ? `
${MultiRowTab_OnOff_and_TabBar_Rows == 1 ? `
${TabBar_Rows_on_MouseOver == 0 || TabBar_Rows_on_MouseOver == 1 ? `
max-height: calc((var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px) * 2);
` : `
max-height: calc((var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px) * ${TabBar_Rows_on_MouseOver});
`}
&:not(:hover) {
max-height: calc(var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px) !important;
${Proton_Margins ? `scrollbar-width: none;` : ``}
transition: all 0s ease-in-out ${TabBar_DisplayTime_on_MouseOver}s;
}
` : `
${Set_the_TabBar_to_the_Specified_Height ? `
min-height: calc((var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px) * ${MultiRowTab_OnOff_and_TabBar_Rows});
& > slot {
max-height: calc(var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px);
}
` : `
max-height: calc((var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px) * ${MultiRowTab_OnOff_and_TabBar_Rows});
`}
`}
overflow: hidden auto;
& scrollbar {
-moz-window-dragging: no-drag;
}
` : ``}
}
&::part(overflow-start-indicator),
&::part(overflow-end-indicator),
&::part(scrollbutton-up),
&::part(scrollbutton-down) {
display: none;
}
${Separate_Tabs_and_PinnedTabs ? `
&:has(> .tabbrowser-tab[fadein][pinned]) {
&::part(scrollbox) {
& > slot::after {
display: flow-root list-item;
content: "";
flex-basis: -moz-available;
height: 0;
overflow: hidden;
}
}
}
.tabbrowser-tab[fadein] {
&:not([pinned]) {
#tabbrowser-tabs[haspinnedtabs] & {
&, & + :not(#tabs-newtab-button) {
order: 1;
}
}
}
&[pinned] {
.tab-background:after {
content: "📌";
font-size: 11px;
right: -2px;
position: absolute;
top: -2px;
}
${PinnedTab_Width ? `
flex: 100 100;
max-width: ${PinnedTab_Max_Width}px;
min-width: ${PinnedTab_Min_Width}px;
.tab-throbber, .tab-icon-pending, .tab-icon-image, .tab-sharing-icon-overlay, .tab-icon-overlay {
margin-inline-end: 5.5px !important;
}
${PinnedTab_Close_Button == 1 ? `
.tab-close-button {
display: flex;
}
` : PinnedTab_Close_Button == 2 ? `
.tab-close-button {
display: none;
}
&:hover .tab-close-button {
display: flex;
}
` : PinnedTab_Close_Button == 3 ? `
&:not([selected]):hover,
&[selected] {
.tab-close-button {
display: flex;
}
}
` : ``}
` : ``}
}
}
` : ``}
#tabbrowser-tabs[haspinnedtabs]:not([positionpinnedtabs]):not([orient="vertical"]) > & {
& > .tabbrowser-tab:nth-child(1 of :not([pinned], [hidden])) {
margin-inline-start: 0 !important;
}
}
}
` : ``}
}
.tabbrowser-tab[fadein]:not([pinned]) {
max-width: ${Tab_Max_Width}px;
min-width: ${Tab_Min_Width}px;
${Tab_Close_Button == 1 ? `
.tab-close-button {
display: none;
}
` : Tab_Close_Button == 2 ? `
.tab-close-button {
display: flex;
}
` : Tab_Close_Button == 3 ? `
.tab-close-button {
display: none;
}
&:hover .tab-close-button {
display: flex;
}
` : Tab_Close_Button == 4 ? `
&:not([selected]):hover {
.tab-close-button {
display: flex;
}
}
` : ``}
}
${Tab_Separators ? `
.titlebar-spacer[type="pre-tabs"] {
border-inline-end: 1px solid color-mix(in srgb, currentColor 20%, transparent);
}
.tabbrowser-tab {
&::after,
&::before {
border-left: 1px solid color-mix(in srgb, currentColor 50%, transparent);
height: calc(var(--tab-min-height) - 15%);
margin-block: auto;
}
&:hover::after,
&[multiselected]::after,
#tabbrowser-tabs:not([movingtab]) &:has(+ .tabbrowser-tab:hover)::after,
#tabbrowser-tabs:not([movingtab]) &:has(+ [multiselected])::after {
height: 100%;
}
&::after,
#tabbrowser-tabs[movingtab] &[visuallyselected]::before {
display: flex;
content: "";
}
}
` : ``}
${Proton_Margins ? `` : `
.tabbrowser-tab,
.toolbarbutton-1 {
padding: 0;
}
.tabbrowser-tab,
#tabs-newtab-button {
height: var(--tab-min-height);
}
.tabbrowser-tab {
.tab-background {
box-shadow: none;
margin-block: 0;
}
.tab-label-container {
height: var(--tab-min-height);
max-height: 24px;
}
.tab-close-button {
height: 20px !important;
padding-block: 3px !important;
}
&[usercontextid] > .tab-stack > .tab-background > .tab-context-line {
margin-block-start: 1px !important;
}
}
`}
${TabBar_Position == 0 ? `
.titlebar-buttonbox-container {
height: calc(var(--tab-min-height) + ${Proton_Margins ? 8 : 0}px);
}
${TitleBar_Button_Autohide ? `
& > .titlebar-buttonbox-container {
background-color: color-mix(in srgb, currentColor 20%, transparent);
position: fixed;
right: 0;
&:not(:hover) {
height: 6px;
.titlebar-button {
padding: 0;
}
&,& .titlebar-button {
opacity: 0;
transition: all 0s ease-in-out ${TitleBar_Button_DisplayTime}s;
}
}
}
` : ``}
}` : `
${TabBar_Position == 1 || TabBar_Position == 2 ? `
& > .titlebar-buttonbox-container {
display: none;
}}
#nav-bar {
&:not(.browser-titlebar) {
:root[customtitlebar] #toolbar-menubar[autohide="true"] ~ &,
:root[inFullscreen] #toolbar-menubar ~ & {
& > .titlebar-buttonbox-container {
display: flex;
}
}
}
.titlebar-button {
padding-block: 0;
}
}
` : ``}
body:has(> #navigator-toolbox:not([tabs-hidden])) {
${TabBar_Position == 1 ? `
script, toolbar:not(#TabsToolbar ${Bookmark_Toolbar_Position ? `` : `, #PersonalToolbar`}) {
order: -1;
}
` : TabBar_Position == 2 ? `
& > #fullscr-toggler[hidden] + tabbox,
:root[inFullscreen] & > tabbox:hover {
border-top: 0.01px solid var(--chrome-content-separator-color);
}
& > tabbox > #navigator-toolbox {
border-block: none !important;
}
:root[inFullscreen] & {
& > #navigator-toolbox {
transition: none;
&:has(~ tabbox:hover) {
margin-top: 0 !important;
}
&:hover ~ tabbox > #navigator-toolbox {
display: flex;
}
}
& > tabbox:not(:hover) {
border-top: 0.01px solid transparent;
& > #navigator-toolbox {
display: none;
}
}
}
` : ``}
}
`}
toolbar[id$="bar"].browser-titlebar {
.titlebar-spacer {
&[type="pre-tabs"] {
width: ${Left_Drag_Area}px;
}
&[type="post-tabs"] {
width: ${Right_Drag_Area}px;
}
${Maximize_Left_Drag_Area ? `
:root[customtitlebar]:not([sizemode="normal"], [inFullscreen]) &[type="pre-tabs"] {
display: flex;
}
` : ``}
${Fullscreen_Drag_Area ? `
:root[customtitlebar][inFullscreen] & {
display: flex;
}
` : ``}
}
#navigator-toolbox[tabs-hidden] & {
#new-tab-button {
display: none;
}
}
}
`,
sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService),
uri = Services.io.newURI("data:text/css;charset=UTF=8," + encodeURIComponent(css));
["0", "2", "dragend", "SSTabRestored", "TabAttrModified"].find(eventType => {
if(!sss.sheetRegistered(uri, eventType)) sss.loadAndRegisterSheet(uri, eventType);
if (MultiRowTab_OnOff_and_TabBar_Rows > 0) {
gBrowser.tabContainer.addEventListener(eventType, (e) => {
e.target.scrollIntoView({ behavior: "instant", block: "nearest" })
})
}
})
if (TabBar_Position == 2) {
document.body.appendChild(
document.createXULElement("tabbox")
).appendChild(
document.importNode(document.getElementById("navigator-toolbox"))
).appendChild(
document.adoptNode(document.getElementById("TabsToolbar"))
)
}
gBrowser.tabContainer._getDropIndex = function(event) {
let tabToDropAt = getTabFromEventTarget(event, false);
if (tabToDropAt?.localName == "tab-group") {
tabToDropAt = tabToDropAt.previousSibling;
if (!tabToDropAt) {
tabToDropAt = gBrowser.visibleTabs[0];
}
}
if (!tabToDropAt) {
tabToDropAt = gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1];
}
const tabPos = gBrowser.tabContainer.getIndexOfItem(tabToDropAt);
if (window.getComputedStyle(this).direction == "ltr") {
let rect = tabToDropAt.getBoundingClientRect();
if (event.clientX < rect.x + rect.width / 2)
return tabPos;
else
return tabPos + 1;
} else {
let rect = tabToDropAt.getBoundingClientRect();
if (event.clientX > rect.x + rect.width / 2)
return tabPos;
else
return tabPos + 1;
}
};
// We set this to check if the listeners were added before
let listenersActive = false;
gBrowser.tabContainer.startTabDrag = function startTabDrag(event, tab, { fromTabList = false } = {}) {
const isTab = element => gBrowser.isTab(element);
const isTabGroup = element => gBrowser.isTabGroup(element);
const isTabGroupLabel = element => gBrowser.isTabGroupLabel(element);
if (tab.multiselected) {
for (let multiselectedTab of gBrowser.selectedTabs.filter(
t => t.pinned != tab.pinned
)) {
gBrowser.removeFromMultiSelectedTabs(multiselectedTab);
}
}
let dataTransferOrderedTabs;
if (fromTabList || isTabGroupLabel(tab)) {
// Dragging a group label or an item in the all tabs menu doesn't
// change the currently selected tabs, and it's not possible to select
// multiple tabs from the list, thus handle only the dragged tab in
// this case.
dataTransferOrderedTabs = [tab];
} else {
this.selectedItem = tab;
let selectedTabs = gBrowser.selectedTabs;
let otherSelectedTabs = selectedTabs.filter(
selectedTab => selectedTab != tab
);
dataTransferOrderedTabs = [tab].concat(otherSelectedTabs);
}
let dt = event.dataTransfer;
for (let i = 0; i < dataTransferOrderedTabs.length; i++) {
let dtTab = dataTransferOrderedTabs[i];
dt.mozSetDataAt(TAB_DROP_TYPE, dtTab, i);
if (isTab(dtTab)) {
let dtBrowser = dtTab.linkedBrowser;
// We must not set text/x-moz-url or text/plain data here,
// otherwise trying to detach the tab by dropping it on the desktop
// may result in an "internet shortcut"
dt.mozSetDataAt(
"text/x-moz-text-internal",
dtBrowser.currentURI.spec,
i
);
}
}
// Set the cursor to an arrow during tab drags.
dt.mozCursor = "default";
// Set the tab as the source of the drag, which ensures we have a stable
// node to deliver the `dragend` event. See bug 1345473.
dt.addElement(tab);
// Create a canvas to which we capture the current tab.
// Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
// canvas size (in CSS pixels) to the window's backing resolution in order
// to get a full-resolution drag image for use on HiDPI displays.
let scale = window.devicePixelRatio;
let canvas = this._dndCanvas;
if (!canvas) {
this._dndCanvas = canvas = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"canvas"
);
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.mozOpaque = true;
}
canvas.width = 160 * scale;
canvas.height = 90 * scale;
let toDrag = canvas;
let dragImageOffset = -16;
let browser = isTab(tab) && tab.linkedBrowser;
if (isTabGroupLabel(tab)) {
toDrag = tab;
} else if (gMultiProcessBrowser) {
var context = canvas.getContext("2d");
context.fillStyle = "white";
context.fillRect(0, 0, canvas.width, canvas.height);
let captureListener;
let platform = AppConstants.platform;
// On Windows and Mac we can update the drag image during a drag
// using updateDragImage. On Linux, we can use a panel.
if (platform == "win" || platform == "macosx") {
captureListener = function () {
dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
};
} else {
// Create a panel to use it in setDragImage
// which will tell xul to render a panel that follows
// the pointer while a dnd session is on.
if (!this._dndPanel) {
this._dndCanvas = canvas;
this._dndPanel = document.createXULElement("panel");
this._dndPanel.className = "dragfeedback-tab";
this._dndPanel.setAttribute("type", "drag");
let wrapper = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"div"
);
wrapper.style.width = "160px";
wrapper.style.height = "90px";
wrapper.appendChild(canvas);
this._dndPanel.appendChild(wrapper);
document.documentElement.appendChild(this._dndPanel);
}
toDrag = this._dndPanel;
}
// PageThumb is async with e10s but that's fine
// since we can update the image during the dnd.
PageThumbs.captureToCanvas(browser, canvas)
.then(captureListener)
.catch(e => console.error(e));
} else {
// For the non e10s case we can just use PageThumbs
// sync, so let's use the canvas for setDragImage.
PageThumbs.captureToCanvas(browser, canvas).catch(e =>
console.error(e)
);
dragImageOffset = dragImageOffset * scale;
}
dt.setDragImage(toDrag, dragImageOffset, dragImageOffset);
// _dragData.offsetX/Y give the coordinates that the mouse should be
// positioned relative to the corner of the new window created upon
// dragend such that the mouse appears to have the same position
// relative to the corner of the dragged tab.
let clientPos = ele => {
const rect = ele.getBoundingClientRect();
return this.verticalMode ? rect.top : rect.left;
};
let tabOffset = clientPos(tab) - clientPos(this);
let movingTabs = tab.multiselected ? gBrowser.selectedTabs : [tab];
let movingTabsSet = new Set(movingTabs);
tab._dragData = {
offsetX: this.verticalMode
? event.screenX - window.screenX
: event.screenX - window.screenX - tabOffset,
offsetY: this.verticalMode
? event.screenY - window.screenY - tabOffset
: event.screenY - window.screenY,
scrollPos:
this.verticalMode && tab.pinned
? this.verticalPinnedTabsContainer.scrollPosition
: this.arrowScrollbox.scrollPosition,
screenX: event.screenX,
screenY: event.screenY,
movingTabs,
movingTabsSet,
fromTabList,
tabGroupCreationColor: gBrowser.tabGroupMenu.nextUnusedColor,
expandGroupOnDrop: false,
};
event.stopPropagation();
}
// This sets when to apply the fix (by default a new row starts after the 23th open tab, unless you changed the min-size of tabs)
gBrowser.tabContainer.addEventListener("dragstart", () => {
// Multiple tab select fix
gBrowser.visibleTabs.forEach(t => t.style.transform = "");
// Event handling
if (!listenersActive) {
gBrowser.tabContainer.getDropEffectForTabDrag = function(){};
gBrowser.tabContainer._getDropEffectForTabDrag = function(){};
gBrowser.tabContainer.on_dragover = (dragoverEvent) => performTabDragOver(dragoverEvent);
gBrowser.tabContainer._onDragOver = (dragoverEvent) => performTabDragOver(dragoverEvent);
gBrowser.tabContainer.ondrop = (dropEvent) => performTabDropEvent(dropEvent);
gBrowser.tabContainer.ondragleave = (ondragleave) => {gBrowser.tabContainer._dragTime = 0};
gBrowser.tabContainer.ondragend = (ondragend) => clearTimeout(dragovertimer);
listenersActive = true;
}
});
}
var lastKnownIndex = null;
var lastGroupStart = null;
var lastGroupEnd = null;
let dragovertimer = null;
let lasttabgroup = null;
/**
* Gets the tab from the event target.
* @param {*} event The event.
* @returns The tab if it was part of the target or its parents, otherwise null
*/
function getTabFromEventTarget(event, { ignoreTabSides = false } = {}) {
let { target } = event;
if (target.nodeType != Node.ELEMENT_NODE) {
target = target.parentElement;
}
let tab = target?.closest("tab") || target?.closest("tab-group");
const selectedTab = gBrowser.selectedTab;
if (tab && ignoreTabSides) {
let { width, height } = tab.getBoundingClientRect();
if (
event.screenX < tab.screenX + width * 0.25 ||
event.screenX > tab.screenX + width * 0.75 ||
((event.screenY < tab.screenY + height * 0.25 ||
event.screenY > tab.screenY + height * 0.75) &&
gBrowser.tabContainer.verticalMode)
) {
return selectedTab;
}
}
return tab;
}
/**
* Performs the tab drag over event.
* @param {*} event The drag over event.
*/
function performTabDragOver(event) {
event.preventDefault();
event.stopPropagation();
let ind = gBrowser.tabContainer._tabDropIndicator;
let effects = orig_getDropEffectForTabDrag(event);
let tab;
if (effects == "link") {
tab = getTabFromEventTarget(event, true);
if (tab) {
if (!gBrowser.tabContainer._dragTime)
gBrowser.tabContainer._dragTime = Date.now();
if (!tab.hasAttribute("pendingicon") && // annoying fix
Date.now() >= gBrowser.tabContainer._dragTime + Services.prefs.getIntPref("browser.tabs.dragDrop.selectTab.delayMS")) {
gBrowser.selectedTab = tab;
ind.hidden = true;
return;
}
}
}
if (!tab) {
tab = getTabFromEventTarget(event, false);
}
if (tab?.nodeName == "tab-group") {
if(lasttabgroup !== tab) {
lasttabgroup = tab
clearTimeout(dragovertimer);
dragovertimer = setTimeout((tabgroup) => {
tabgroup.collapsed = !tabgroup.collapsed
}, Services.prefs.getIntPref("browser.tabs.dragDrop.expandGroup.delayMS"), tab);
}
} else {
clearTimeout(dragovertimer);
lasttabgroup = null;
}
let newIndex = gBrowser.tabContainer._getDropIndex(event);
if (newIndex == null)
return;
// Update the last known index and group position
lastKnownIndex = newIndex;
if (tab?.nodeName == "tab-group" && !lastGroupStart) {
lastGroupStart = tab.querySelector("tab:first-of-type")._tPos;
lastGroupEnd = tab.querySelector("tab:last-of-type")._tPos;
}
let tabs = gBrowser.tabs;
let ltr = (window.getComputedStyle(gBrowser.tabContainer).direction == "ltr");
let rect = gBrowser.tabContainer.arrowScrollbox.getBoundingClientRect();
let newMarginX, newMarginY;
if (newIndex == tabs.length) {
let tabRect = tabs[newIndex - 1].getBoundingClientRect();
if (ltr)
newMarginX = tabRect.right - rect.left;
else
newMarginX = rect.right - tabRect.left;
newMarginY = tabRect.top + tabRect.height - rect.top - rect.height; // multirow fix
if (CSS.supports("offset-anchor", "left bottom")) // Compatibility fix for FF72+
newMarginY += rect.height / 2 - tabRect.height / 2;
} else if (newIndex != null || newIndex != 0) {
let tabRect = tabs[newIndex].getBoundingClientRect();
if (ltr)
newMarginX = tabRect.left - rect.left;
else
newMarginX = rect.right - tabRect.right;
newMarginY = tabRect.top + tabRect.height - rect.top - rect.height; // multirow fix
if (CSS.supports("offset-anchor", "left bottom")) // Compatibility fix for FF72+
newMarginY += rect.height / 2 - tabRect.height / 2;
}
newMarginX += ind.clientWidth / 2;
if (!ltr)
newMarginX *= -1;
ind.hidden = false;
ind.style.transform = "translate(" + Math.round(newMarginX) + "px," + Math.round(newMarginY) + "px)"; // multirow fix
ind.style.marginInlineStart = (-ind.clientWidth) + "px";
}
/**
* Performs the tab drop event.
* @param {*} event The drop event.
*/
function performTabDropEvent(event) {
clearTimeout(dragovertimer);
let newIndex;
let dt = event.dataTransfer;
let dropEffect = dt.dropEffect;
let draggedTab;
if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) {
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (!draggedTab) {
return;
}
}
if (draggedTab && dropEffect != "copy" && draggedTab.container == gBrowser.tabContainer) {
newIndex = gBrowser.tabContainer._getDropIndex(event);
let selectedTabs = gBrowser.selectedTabs.length > 1 ? gBrowser.selectedTabs : [draggedTab];
let tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex);
let tab = getTabFromEventTarget(event, false);
let tabgroup = tab?.closest("tab-group");
if (!tab) {
newIndex = gBrowser.tabs.length;
tabToMoveAt = null;
}
if (tab?.pinned && !selectedTabs[0].pinned) {
selectedTabs.forEach(t => gBrowser.pinTab(t));
if (tabToMoveAt == tab) {
selectedTabs.forEach(t => {gBrowser.moveTabBefore(t, tab)});
} else {
selectedTabs.forEach(t => {gBrowser.moveTabBefore(t, tabToMoveAt)});
}
return;
} else if(!tab?.pinned && selectedTabs[0].pinned) {
selectedTabs.forEach(t => gBrowser.unpinTab(t));
}
if (tabgroup && !tabgroup.previousSibling) {
newIndex = 0;
selectedTabs.forEach(t => {gBrowser.moveTabTo(t, { tabIndex: newIndex++,forceUngrouped:true});});
} else if (!tab ||
!tabgroup && !tabToMoveAt?.group ||
tabgroup && tabToMoveAt?.group) {
if (tab !== tabToMoveAt) {
tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex -1);
selectedTabs.forEach(t => {gBrowser.moveTabAfter(t, tabToMoveAt); tabToMoveAt = t});
} else {
selectedTabs.forEach(t => {gBrowser.moveTabBefore(t, tabToMoveAt)});
}
} else {
tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex -1);
selectedTabs.forEach(t => {gBrowser.moveTabAfter(t, tabToMoveAt); tabToMoveAt = t});
}
// Restart global vars
lastKnownIndex = null;
lastGroupStart = null;
lastGroupEnd = null;
}
}
// copy of the original and overrided _getDropEffectForTabDrag method
function orig_getDropEffectForTabDrag(event) {
let dt = event.dataTransfer;
let isMovingTabs = dt.mozItemCount > 0;
for (let i = 0; i < dt.mozItemCount; i++) {
// tabs are always added as the first type
let types = dt.mozTypesAt(0);
if (types[0] != TAB_DROP_TYPE) {
isMovingTabs = false;
break;
}
}
if (isMovingTabs) {
let sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
if (XULElement.isInstance(sourceNode) &&
sourceNode.localName == "tab" &&
sourceNode.ownerGlobal.isChromeWindow &&
sourceNode.ownerDocument.documentElement.getAttribute("windowtype") ==
"navigator:browser" &&
sourceNode.ownerGlobal.gBrowser.tabContainer == sourceNode.container) {
// Do not allow transfering a private tab to a non-private window
// and vice versa.
if (PrivateBrowsingUtils.isWindowPrivate(window) !=
PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerGlobal))
return "none";
if (window.gMultiProcessBrowser !=
sourceNode.ownerGlobal.gMultiProcessBrowser)
return "none";
if (window.gFissionBrowser != sourceNode.ownerGlobal.gFissionBrowser)
return "none";
return dt.dropEffect == "copy" ? "copy" : "move";
}
}
if (Services.droppedLinkHandler.canDropLink(event, true))
return "link";
return "none";
}
Alles anzeigen