1. Nachrichten
  2. Forum
    1. Unerledigte Themen
    2. Forenregeln
  3. Spenden
  • Anmelden
  • Registrieren
  • Suche
Alles
  • Alles
  • Artikel
  • Seiten
  • Forum
  • Erweiterte Suche
  1. camp-firefox.de
  2. FuchsFan

Beiträge von FuchsFan

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 28. März 2026 um 11:19

    geldhuegel

    Das wollte hier auch nicht funktionieren, nun habe ich auch mal versucht, und das ist das Ergebnis. Wenn es jetzt bei dir auch laufen sollte, dann kannst du die Farben anpassen, dafür habe ich extra die Klarnamen eingebaut, so leichter zu finden. Das Ganze in die userChrome.css.

    CSS
    /* ===== Popup Container Searchbar-New ===== */
        #urlbar-results.urlbarView-results{
          border: 2px solid magenta !important;
          border-radius: 5px !important;
    	  padding: 5px !important;
    	  margin: 0 6px 6px 6px !important;
          box-shadow: none !important;
          overflow: hidden !important;
    	}
    
        /* ===== im Popup die Button (Item) ===== */
        #urlbar-results.urlbarView-results div.urlbarView-row {
          padding: 0 !important;
          min-height: 24px !important;
    	  max-height: 24px !important;
    	  background-color: lightyellow !important;
    	  margin: 1px 0 0 0 !important;
    	  border: 1px solid grey !important;
    	  border-radius: 0 !important;
    	  color: blue !important;
          display: flex !important;
          align-items: center !important;
        }
    	
    	#urlbar-results.urlbarView-results div.urlbarView-row:hover {
    	  background-color: AliceBlue !important;
          color: brown !important;	  
    	}  
    Alles anzeigen
  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 27. März 2026 um 08:45
    Zitat von geldhuegel

    Offtopic: Die neue Anzeige der Suchergebnisse ist grausig.

    Es ging nur um die Funktionsweise des Skriptes, für das Popup-Menü muss natürlich mit css-Code eine Anpassung vorgenommen werden.

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 17:03

    Bei dem Script aus #2 habe ich jetzt (wo ich weiß es funktioniert) Anpassungen vorgenommen. Den Button mit in das Script für für Änderungen eingebaut. So sieht es i.M. aus:

    ...das Script dazu:

    Code
    // ==UserScript==
    // @name         ShowSearchBarHistoryByClick_FF149.uc.js
    // @description  Zeigt den Suchverlauf per Button in der Suchleiste (FF149+)
    // @charset      UTF-8
    // @include      main
    // @version      1.1.0
    // ==/UserScript==
    
    (function () {
      "use strict";
    
      const BTN_ID = "searchbar-history-dropmarker";
      const createButton = true;
    
      // Prefs
      const noLimitResult = true;
      const timeSeries = true;
    
      applySearchPrefs();
    
      if (window.gBrowserInit?.delayedStartupFinished) {
        init();
      } else {
        const obs = {
          observe(subject, topic) {
            if (topic === "browser-delayed-startup-finished" && subject === window) {
              Services.obs.removeObserver(obs, topic);
              init();
            }
          },
        };
        Services.obs.addObserver(obs, "browser-delayed-startup-finished");
      }
    
      window.addEventListener(
        "aftercustomization",
        () => window.setTimeout(installButton, 0),
        false
      );
    
      function init() {
        injectArrowCSS(); // <- neu
        installButton();
      }
    
      function getSearchBar() {
        return document.getElementById("searchbar-new") || document.getElementById("searchbar");
      }
    
      function getSearchInput(bar) {
        if (!bar) return null;
        return bar?._textbox || bar?.inputField || bar;
      }
    
      function injectArrowCSS() {
        const STYLE_ID = "searchbar-history-arrow-css";
        if (document.getElementById(STYLE_ID)) return;
    
        const style = document.createElement("style");
        style.id = STYLE_ID;
    
        // Pfeil nach unten
        style.textContent = `
          #${BTN_ID}::before {
            content: "";
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
    
            width: 0;
            height: 0;
    		
            border-left: 4px solid transparent;
            border-right: 4px solid transparent;
            border-top: 7px solid #222;
    
            pointer-events: none;
            z-index: 2;
          }
    
          #${BTN_ID}:hover::before {
            border-top-color: #000;
          }
        `;
    
        document.documentElement.appendChild(style);
      }
    
      function styleButton(btn) {
        btn.style.MozAppearance = "none";
        btn.style.appearance = "none";
    
        btn.style.width = "16px";
        btn.style.height = "16px";
        btn.style.minWidth = "16px";
        btn.style.minHeight = "16px";
    
        btn.style.padding = "0";
        btn.style.margin = "6px 4px 0px 0px";
        btn.style.boxSizing = "border-box";
    
        btn.style.backgroundColor = "white";
        btn.style.border = "1px solid #555";
        btn.style.borderRadius = "50px";
    
        // Wichtig für ::before
        btn.style.position = "relative";
        btn.style.overflow = "visible";
    
        btn.style.display = "flex";
        btn.style.alignItems = "center";
        btn.style.justifyContent = "center";
    
        btn.style.pointerEvents = "auto";
        btn.style.zIndex = "100";
        btn.style.transform = "translateZ(0)";
      }
    
      function installButton() {
        if (!createButton) return;
    
        const bar = getSearchBar();
        if (!bar) {
          window.setTimeout(installButton, 300);
          return;
        }
    
        let btn = document.getElementById(BTN_ID);
        if (!btn) {
          btn = document.createXULElement("toolbarbutton");
    
          btn.setAttribute("id", BTN_ID);
          btn.setAttribute(
            "class",
            "toolbarbutton-1 chromeclass-toolbar-additional urlbar-page-action"
          );
    
          // <- entfernt: image/Icon, weil wir den Pfeil per CSS zeichnen
          btn.setAttribute("tooltiptext", "Suchverlauf anzeigen");
          btn.setAttribute("removable", "true");
          btn.setAttribute("cui-areatype", "toolbar");
    
          styleButton(btn);
    
          btn.addEventListener(
            "mousedown",
            (ev) => {
              ev.preventDefault();
              ev.stopPropagation();
            },
            true
          );
    
          btn.addEventListener(
            "click",
            (ev) => {
              ev.preventDefault();
              ev.stopPropagation();
              showHistory();
            },
            false
          );
    
          btn.style.marginInlineStart = "4px";
          btn.style.order = "9999";
        }
    
        const legacyGoContainer = bar.querySelector?.(".search-go-container");
        if (legacyGoContainer) {
          if (btn.parentNode !== legacyGoContainer) legacyGoContainer.appendChild(btn);
        } else {
          const goButton = bar.querySelector?.(".urlbar-go-button");
          if (goButton && goButton.parentNode) {
            const parent = goButton.parentNode;
            if (btn.parentNode !== parent) {
              parent.insertBefore(btn, goButton.nextSibling);
            } else if (btn.previousSibling !== goButton) {
              parent.insertBefore(btn, goButton.nextSibling);
            }
          } else if (btn.parentNode !== bar) {
            bar.appendChild(btn);
          }
        }
    
        if (noLimitResult) {
          const input = getSearchInput(bar);
          if (input?.popup) input.popup.setAttribute("nomaxresults", "true");
        }
      }
    
      function showHistory() {
        const bar = getSearchBar();
        if (!bar) return;
    
        const input = getSearchInput(bar);
        if (!input) return;
    
        if (typeof input.showHistoryPopup === "function") {
          try {
            const old = input.value || "";
            input.value = "";
            input.showHistoryPopup();
            input.value = old;
          } catch (ex) {
            console.error("showHistoryPopup failed:", ex);
          }
          return;
        }
    
        try {
          const oldValue = input.value || "";
          input.value = "";
    
          if (typeof input.focus === "function") input.focus();
    
          const down = new KeyboardEvent("keydown", {
            key: "ArrowDown",
            code: "ArrowDown",
            keyCode: 40,
            which: 40,
            bubbles: true,
            cancelable: true,
          });
    
          input.dispatchEvent(down);
          input.value = oldValue;
        } catch (ex) {
          console.error("ShowSearchBarHistoryByClick (fallback) error:", ex);
        }
      }
    
      function applySearchPrefs() {
        if (timeSeries) {
          setPref("browser.formfill.bucketSize", "int", -1);
          setPref("browser.formfill.maxTimeGroupings", "int", -1);
          setPref("browser.formfill.timeGroupingSize", "int", -1);
        } else {
          clearPref("browser.formfill.bucketSize");
          clearPref("browser.formfill.maxTimeGroupings");
          clearPref("browser.formfill.timeGroupingSize");
        }
    
        if (noLimitResult) {
          setPref("browser.urlbar.recentsearches.maxResults", "int", 9999);
        } else {
          clearPref("browser.urlbar.recentsearches.maxResults");
        }
      }
    
      function setPref(aPrefString, aPrefType, aValue) {
        try {
          switch (aPrefType) {
            case "int":
              return Services.prefs.setIntPref(aPrefString, parseInt(aValue, 10));
            case "str":
              return Services.prefs.setStringPref(aPrefString, String(aValue));
            case "bool":
            default:
              return Services.prefs.setBoolPref(aPrefString, !!aValue);
          }
        } catch (e) {}
        return null;
      }
    
      function clearPref(aPrefString) {
        try {
          Services.prefs.clearUserPref(aPrefString);
        } catch (e) {}
      }
    })();
    Alles anzeigen
  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 14:56

    Übrigens habe ich jetzt festgestellt, das Script aus #2 funktioniert auch, aber es erstellt in der Searchbar den Button, der dort im Bild angezeigt wird.

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 14:41
    Zitat von 2002Andreas

    Ihr immer mit euren Basteleien am Firefox :saint:

    Echt schlimm manchmal.;):)

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 14:32

    Das war (ist) das Problem.

    CSS
    /*** Popup-Menü verhindert bei Suche über Adressfeld ***/
    
    .urlbarView {
        display: none !important;
    }
  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 14:08
    Zitat von 2002Andreas

    wer oder was hatte es denn auf false gestellt

    Du kannst Fragen stellen;). Ich nicht, denn es ist ja in allen meinen Profilen auf false. Wenn du aber so fragst, dann muss ja eine Einstellung im Fox das bewirkt haben. Keine Ahnung!:/

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 13:55
    Zitat von Sören Hentzschel

    Die Meldung besagt, dass browser.formfill.enable nicht auf true steht.

    Sören Hentzschel

    :) Auf true gestellt, und jetzt funktioniert es hier auch. Danke!

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 13:51

    Hat das auch damit zu tun? Standard ist wohl false.

    browser.search.suggest.enabled → true

    browser.urlbar.suggest.searches → true.

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 13:44

    Ja, und hier die Batterie an Fehlern:

    Error: Form history is disabled, only remove operations are allowed
    update resource://gre/modules/FormHistory.sys.mjs:1075
    addToFormHistory moz-src:///browser/components/urlbar/UrlbarUtils.sys.mjs:1243
    pickResult chrome://browser/content/urlbar/UrlbarInput.mjs:1704
    pickElement chrome://browser/content/urlbar/UrlbarInput.mjs:1386
    handleNavigation chrome://browser/content/urlbar/UrlbarInput.mjs:1126
    handleCommand chrome://browser/content/urlbar/UrlbarInput.mjs:1063
    _on_click chrome://browser/content/urlbar/UrlbarInput.mjs:4931
    handleEvent chrome://browser/content/urlbar/UrlbarInput.mjs:1024

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 13:28
    Zitat von 2002Andreas

    Zwecks Test hier so eingestellt:

    Nichts zu machen, ob Stable, Beta oder Nightly, funktioniert hier nicht.:/

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 12:55
    Zitat von 2002Andreas

    Sieht hier so damit aus:

    Hier geht es nicht damit, komisch, hat das auch was mit Einstellungen zu tun, dass es überhaupt erst Wirkung zeigt?

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 26. März 2026 um 08:46
    Zitat von Endor

    Es fügt oben in der Suchliste einen Pfeil nach unten ein und bei klick drauf
    wird die Suchchronik angezeigt.

    Endor

    Der Pfeil wird angezeigt, das ist nicht das Problem. Das Problem besteht neuerdings darin (wenn ich es richtig verstanden habe), dass aus Sicherheitsgründen nicht mehr einfach so auf die Chronik zugegriffen werden kann. Deshalb scheitert auch das Script aus #1, wobei mein vorgestelltes Script eine eigene Suchchronik anlegt, und deshalb funktioniert.

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 25. März 2026 um 16:21

    Ja, geldhuegel , alles versucht, aber vergebens. Nun habe ich aber ein eigenes Script für die Funktion gebastelt. Es erstellt in der Navbar einen verschiebbaren Button, bei Klick darauf öffnet sich ein Sub-Menü. Darin kannst du über Einstellungen angeben, wieviel Einträge gespeichert werden sollen, und außerdem die History löschen. Sind Einträge vorhanden, dann kannst du mit einem Klick darauf diese in einem Tab wieder aufrufen. Achte darauf, es ist der Pfad zu einem Icon für den Button angegeben, den du dann bei dir anpassen musst (nur wenn du Lust darauf hast;)). So, dann teste bitte!

    JavaScript
    // ==UserScript==
    // @name                SearchHistoryButton.uc.js
    // @include             main
    // @charset             UTF-8
    // @note                Button in der Navbar + Popup mit eigener Suchhistory
    // @version             2.2
    // ==/UserScript==
    
    (function () {
      if (location != 'chrome://browser/content/browser.xhtml') return;
      'use strict';
    
      const lazy = {};
      ChromeUtils.defineESModuleGetters(lazy, {
        SearchService: "moz-src:///toolkit/components/search/SearchService.sys.mjs",
        SearchUIUtils: "moz-src:///browser/components/search/SearchUIUtils.sys.mjs",
      });
    
      if (window.SHB) return;
    
      window.SHB = {
        buttonCreated: false,
        hooked: false,
        _hookInterval: null,
        _hookStart: 0,
        _urlbarHooked: false,
        _searchbarHooked: false,
        _lastRecord: { q: '', t: 0 },
    
        icon: 'file:///C:/FoxIcons2/002.png',
        maxItems: 30,
        maxItemsPrefName: 'extensions.shb.maxItems',
        prefName: 'extensions.shb.searchHistory',
    
        init: function () {
          this.maxItems = this.loadMaxItems();
    
          const waitForToolbar = () => {
            const navbar = document.getElementById('nav-bar');
            if (navbar && !this.buttonCreated) {
              this.createToolbarButton();
            } else if (!this.buttonCreated) {
              setTimeout(waitForToolbar, 100);
            }
          };
    
          waitForToolbar();
          this.hookSearchUI();
        },
    
        // =========================
        // 🔘 BUTTON
        // =========================
        createToolbarButton: function () {
          if (this.buttonCreated) return;
    
          try {
            CustomizableUI.createWidget({
              id: 'shb-button',
              type: 'custom',
              defaultArea: CustomizableUI.AREA_NAVBAR,
              removable: true,
              label: 'Suchverlauf',
    
              onBuild: (doc) => {
                const btn = doc.createXULElement('toolbarbutton');
                btn.id = 'shb-button';
                btn.className = 'toolbarbutton-1 chromeclass-toolbar-additional';
                btn.setAttribute('type', 'menu');
                btn.setAttribute('label', 'Suchverlauf');
                btn.setAttribute('tooltiptext', 'Suchverlauf anzeigen');
                btn.setAttribute('image', this.icon);
                btn.style.listStyleImage = `url("${this.icon}")`;
    
                this.initButtonPopup(btn);
                this.buttonCreated = true;
    
                return btn;
              }
            });
          } catch (e) {
            console.log('CustomizableUI Fehler:', e);
          }
        },
    
        initButtonPopup: function (btn) {
          const mp = btn.appendChild(document.createXULElement('menupopup'));
          mp.id = 'shb-popup';
          mp.addEventListener('popupshowing', (event) => this.onPopup(event));
        },
    
        // =========================
        // 📜 POPUP
        // =========================
        onPopup: function (event) {
          const mp = event.target;
          if (mp.id !== 'shb-popup') return;
    
          while (mp.firstChild) mp.firstChild.remove();
    
          this.addSettingsMenu(mp);
    
          const items = this.loadHistory();
    
          if (!items.length) {
            const empty = document.createXULElement('menuitem');
            empty.setAttribute('label', 'Keine Suchanfragen gespeichert.');
            empty.setAttribute('disabled', 'true');
            mp.appendChild(empty);
            return;
          }
    
          for (const query of items) {
            const item = document.createXULElement('menuitem');
            item.setAttribute('label', query);
            item.setAttribute('tooltiptext', 'Suche in neuem Tab öffnen');
    
            let opened = false;
            const openItem = (ev) => {
              if (opened) return;
              opened = true;
    
              try {
                ev?.preventDefault?.();
                ev?.stopPropagation?.();
                ev?.stopImmediatePropagation?.();
              } catch (e) {}
    
              try {
                mp.hidePopup();
              } catch (e) {}
    
              setTimeout(() => this.openQueryInTab(query), 0);
            };
    
            item.addEventListener('command', openItem);
            item.addEventListener('click', (ev) => {
              if (ev.button === 0) openItem(ev);
            });
    
            mp.appendChild(item);
          }
    
          mp.appendChild(document.createXULElement('menuseparator'));
    
          const clearItem = document.createXULElement('menuitem');
          clearItem.setAttribute('label', 'Suchverlauf löschen');
          clearItem.addEventListener('command', () => {
            this.saveHistory([]);
          });
          mp.appendChild(clearItem);
        },
    
        addSettingsMenu: function (mp) {
          const menu = document.createXULElement('menu');
          menu.setAttribute('label', 'Einstellungen');
    
          const popup = document.createXULElement('menupopup');
    
          const title = document.createXULElement('menuitem');
          title.setAttribute('label', 'Max. gespeicherte Suchanfragen');
          title.setAttribute('disabled', 'true');
          popup.appendChild(title);
    
          popup.appendChild(document.createXULElement('menuseparator'));
    
          const values = [10, 20, 30, 50, 100];
    
          for (const n of values) {
            const item = document.createXULElement('menuitem');
            item.setAttribute('label', `${n} Einträge`);
    
            if (this.maxItems === n) {
              item.setAttribute('type', 'checkbox');
              item.setAttribute('checked', 'true');
            }
    
            const setValue = (ev) => {
              try {
                ev?.preventDefault?.();
                ev?.stopPropagation?.();
                ev?.stopImmediatePropagation?.();
              } catch (e) {}
    
              this.saveMaxItems(n);
    
              try {
                mp.hidePopup();
              } catch (e) {}
            };
    
            item.addEventListener('command', setValue);
            item.addEventListener('click', (ev) => {
              if (ev.button === 0) setValue(ev);
            });
    
            popup.appendChild(item);
          }
    
          menu.appendChild(popup);
          mp.appendChild(menu);
    
          mp.appendChild(document.createXULElement('menuseparator'));
        },
    
        // =========================
        // 🔥 HOOKS
        // =========================
        hookSearchUI: function () {
          if (this._hookInterval) return;
    
          this._hookStart = Date.now();
    
          const record = (val, source = 'unknown') => {
            val = String(val || '').trim();
            if (!val) return;
    
            const now = Date.now();
            const key = val.toLowerCase();
    
            if (this._lastRecord.q === key && (now - this._lastRecord.t) < 1500) {
              return;
            }
    
            this._lastRecord = { q: key, t: now };
    
            console.log('[SHB] Record from', source + ':', val);
            this.recordQuery(val);
          };
    
          const patchMethod = (obj, methodName, label, valueGetter) => {
            if (!obj || typeof obj[methodName] !== 'function') return false;
            if (obj.__shbPatched?.[methodName]) return false;
    
            const original = obj[methodName].bind(obj);
            obj.__shbPatched = obj.__shbPatched || {};
            obj.__shbPatched[methodName] = true;
    
            obj[methodName] = (...args) => {
              try {
                const val = typeof valueGetter === 'function'
                  ? valueGetter(args)
                  : (obj.value || obj.textbox?.value || obj.inputField?.value || '');
    
                record(val, label);
              } catch (e) {
                console.error('[SHB] Record fehlgeschlagen in', label + ':', e);
              }
    
              return original(...args);
            };
    
            console.log('[SHB] gepatcht:', label, methodName);
            return true;
          };
    
          const hookField = (node, label) => {
            if (!node || node.__shbHooked) return false;
    
            node.addEventListener('command', () => record(node.value, label + ':command'));
            node.addEventListener('change', () => record(node.value, label + ':change'));
            node.addEventListener('blur', () => record(node.value, label + ':blur'));
    
            node.addEventListener('keydown', (e) => {
              if (e.key === 'Enter') {
                setTimeout(() => record(node.value, label + ':Enter'), 0);
              }
            }, true);
    
            node.__shbHooked = true;
            console.log('[SHB] Feld-Hook aktiv:', label);
            return true;
          };
    
          const getSearchbarCandidates = () => {
            const seen = new Set();
            const list = [];
    
            for (const id of ['searchbar-new', 'searchbar']) {
              const node = document.getElementById(id);
              if (node && !seen.has(node)) {
                seen.add(node);
                list.push(node);
              }
            }
    
            return list;
          };
    
          const tryHook = () => {
            let attachedAny = false;
    
            if (window.gURLBar) {
              const urlField = window.gURLBar.inputField || window.gURLBar.textbox || window.gURLBar;
              if (urlField) {
                attachedAny = hookField(urlField, 'gURLBar.input') || attachedAny;
              }
    
              attachedAny = patchMethod(
                window.gURLBar,
                'handleCommand',
                'gURLBar',
                () => window.gURLBar.value
              ) || attachedAny;
    
              if (urlField) {
                this._urlbarHooked = true;
              }
            }
    
            for (const sb of getSearchbarCandidates()) {
              let sbAttached = false;
    
              const field = sb.inputField || sb.textbox || sb.querySelector?.('input');
              if (field) {
                sbAttached = hookField(field, sb.id + ':field') || sbAttached;
              }
    
              sbAttached = patchMethod(
                sb,
                'doSearch',
                sb.id + ':doSearch',
                () => sb.value || sb.textbox?.value || sb.inputField?.value || field?.value || ''
              ) || sbAttached;
    
              sbAttached = patchMethod(
                sb,
                'handleSearchCommand',
                sb.id + ':handleSearchCommand',
                () => sb.value || sb.textbox?.value || sb.inputField?.value || field?.value || ''
              ) || sbAttached;
    
              sbAttached = patchMethod(
                sb,
                'handleCommand',
                sb.id + ':handleCommand',
                () => sb.value || sb.textbox?.value || sb.inputField?.value || field?.value || ''
              ) || sbAttached;
    
              sb.addEventListener?.('command', () => {
                record(
                  sb.value || sb.textbox?.value || sb.inputField?.value || field?.value || '',
                  sb.id + ':command'
                );
              }, true);
    
              sb.addEventListener?.('search', () => {
                record(
                  sb.value || sb.textbox?.value || sb.inputField?.value || field?.value || '',
                  sb.id + ':search'
                );
              }, true);
    
              if (sbAttached) {
                this._searchbarHooked = true;
              }
    
              attachedAny = sbAttached || attachedAny;
            }
    
            const timedOut = (Date.now() - this._hookStart) > 45000;
            if ((this._urlbarHooked && this._searchbarHooked) || timedOut) {
              this.hooked = true;
              console.log('[SHB] Hooks aktiv');
    
              clearInterval(this._hookInterval);
              this._hookInterval = null;
            }
    
            return attachedAny;
          };
    
          this._hookInterval = setInterval(tryHook, 300);
          tryHook();
        },
    
        // =========================
        // 🌐 SEARCH IN TAB
        // =========================
        openQueryInTab: async function (query) {
          query = String(query || '').trim();
          if (!query) return;
    
          console.log('[SHB] openQueryInTab:', query);
    
          try {
            const engine = await lazy.SearchService.getDefault();
            const submission = engine?.getSubmission(query);
            const url = submission?.uri?.spec;
    
            if (!url) {
              console.warn('[SHB] Keine Such-URL erzeugt werden konnte.');
              return;
            }
    
            const triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
            const policyContainer = window.gBrowser?.selectedBrowser?.browsingContext?.policyContainer || null;
    
            if (lazy.SearchUIUtils && typeof lazy.SearchUIUtils.loadSearch === 'function') {
              await lazy.SearchUIUtils.loadSearch({
                window,
                engine,
                searchText: query,
                where: 'tab',
                triggeringPrincipal,
                policyContainer,
                usePrivateWindow: false,
                inBackground: false,
                searchUrlType: null,
                sapSource: 'contextmenu',
              });
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            if (typeof openWebLinkIn === 'function') {
              openWebLinkIn(url, 'tab', {
                inBackground: false,
                relatedToCurrent: true,
                triggeringPrincipal,
                policyContainer,
                postData: submission.postData || null
              });
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            if (typeof openTrustedLinkIn === 'function') {
              openTrustedLinkIn(url, 'tab', {
                inBackground: false,
                relatedToCurrent: true,
                triggeringPrincipal,
                policyContainer,
                postData: submission.postData || null
              });
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            if (typeof openLinkIn === 'function') {
              openLinkIn(url, 'tab', {
                inBackground: false,
                relatedToCurrent: true,
                triggeringPrincipal,
                policyContainer,
                postData: submission.postData || null
              });
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            if (window.gBrowser && typeof gBrowser.addTrustedTab === 'function') {
              gBrowser.selectedTab = gBrowser.addTrustedTab(url);
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            if (window.gBrowser && typeof gBrowser.loadOneTab === 'function') {
              gBrowser.loadOneTab(url, { inBackground: false });
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            if (window.gBrowser && typeof gBrowser.addTab === 'function') {
              gBrowser.selectedTab = gBrowser.addTab(url);
              console.log('[SHB] Tab geöffnet für:', query);
              return;
            }
    
            console.warn('[SHB] Kein passender Öffnungsweg verfügbar.');
          } catch (e) {
            console.error('[SHB] Tab-Öffnen fehlgeschlagen:', e);
          }
        },
    
        // =========================
        // ⚙️ SETTINGS
        // =========================
        loadMaxItems: function () {
          try {
            const n = Services.prefs.getIntPref(this.maxItemsPrefName, 30);
            return Math.max(5, Math.min(200, n));
          } catch (e) {
            return 30;
          }
        },
    
        saveMaxItems: function (n) {
          try {
            n = parseInt(n, 10);
            if (!Number.isFinite(n)) return;
    
            n = Math.max(5, Math.min(200, n));
    
            Services.prefs.setIntPref(this.maxItemsPrefName, n);
            this.maxItems = n;
    
            const trimmed = this.loadHistory().slice(0, this.maxItems);
            this.saveHistory(trimmed);
    
            console.log('[SHB] MaxItems gesetzt auf:', n);
          } catch (e) {
            console.error('[SHB] MaxItems speichern fehlgeschlagen:', e);
          }
        },
    
        // =========================
        // 💾 STORAGE
        // =========================
        loadHistory: function () {
          try {
            const raw = Services.prefs.getStringPref(this.prefName, '[]');
            const arr = JSON.parse(raw);
            return Array.isArray(arr) ? arr : [];
          } catch (e) {
            return [];
          }
        },
    
        saveHistory: function (arr) {
          try {
            const clean = Array.isArray(arr)
              ? arr.map(x => String(x).trim()).filter(x => x.length > 0)
              : [];
    
            if (!clean.length) {
              Services.prefs.clearUserPref(this.prefName);
              return;
            }
    
            Services.prefs.setStringPref(this.prefName, JSON.stringify(clean));
          } catch (e) {
            console.error('[SHB] Speichern fehlgeschlagen:', e);
          }
        },
    
        recordQuery: function (query) {
          const q = String(query || '').trim();
          if (!q) return;
    
          const qLower = q.toLowerCase();
          let history = this.loadHistory();
    
          history = history.filter(item =>
            String(item).trim().toLowerCase() !== qLower
          );
    
          history.unshift(q);
          history = history.slice(0, this.maxItems);
    
          this.saveHistory(history);
          console.log('[SHB] Gespeichert:', q);
        }
      };
    
      window.SHB.init();
    })();
    Alles anzeigen
  • FavIconInUrlBar.uc.js Skript funktioniert in Fx 149 nicht mehr

    • FuchsFan
    • 25. März 2026 um 10:38
    Zitat von grisu2099

    Teste bitte:

    Ich sage danke, funktioniert wieder. :thumbup:

  • Script: ShowSearchBarHistroryByClick.uc.js

    • FuchsFan
    • 25. März 2026 um 10:26

    Ist das jetzt so gemeint?

    Wenn ja, dann teste das folgende Script, ob es funktioniert kann ich jetzt nicht beantworten, weil ich nicht genau weiß was es nun wirklich anzeigen soll (so wie es heißt die letzten Suchergebnisse). Probiere mal, ansonsten melde dich wieder.

    JavaScript
    // ==UserScript==
    // @name         ShowSearchBarHistoryByClick.uc.js
    // @description  Zeigt den Suchverlauf per Button in der Suchleiste
    // @charset      UTF-8
    // @include      main
    // @version      0.8.5 Firefox 148+
    // ==/UserScript==
    
    (function () {
      "use strict";
    
      const BTN_ID = "searchbar-history-dropmarker";
      const createButton = true;
      const noLimitResult = true;
      const timeSeries = true;
    
      applySearchPrefs();
    
      if (window.gBrowserInit?.delayedStartupFinished) {
        init();
      } else {
        const obs = {
          observe(subject, topic) {
            if (topic === "browser-delayed-startup-finished" && subject === window) {
              Services.obs.removeObserver(obs, topic);
              init();
            }
          },
        };
        Services.obs.addObserver(obs, "browser-delayed-startup-finished");
      }
    
      window.addEventListener("aftercustomization", () => {
        window.setTimeout(installButton, 0);
      }, false);
    
      function init() {
        installButton();
      }
    
      function getSearchBar() {
        return document.getElementById("searchbar-new") || document.getElementById("searchbar");
      }
    
      function getSearchInput(bar) {
        return bar?._textbox || bar?.inputField || bar;
      }
    
      function installButton() {
        if (!createButton) {
          return;
        }
    
        const bar = getSearchBar();
        if (!bar) {
          window.setTimeout(installButton, 300);
          return;
        }
    
        let btn = document.getElementById(BTN_ID);
        if (!btn) {
          btn = document.createXULElement("toolbarbutton");
          btn.setAttribute("id", BTN_ID);
          btn.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional urlbar-page-action");
          btn.setAttribute("image", "chrome://global/skin/icons/arrow-dropdown-16.svg");
          btn.setAttribute("tooltiptext", "Suchverlauf anzeigen");
          btn.setAttribute("removable", "true");
          btn.setAttribute("cui-areatype", "toolbar");
          btn.style.marginInlineStart = "4px";
          btn.style.order = "9999";
          btn.addEventListener("click", showHistory, false);
        }
    
        // Legacy Searchbar
        const legacyGoContainer = bar.querySelector?.(".search-go-container");
        if (legacyGoContainer) {
          if (btn.parentNode !== legacyGoContainer) {
            legacyGoContainer.appendChild(btn);
          }
          return;
        }
    
        // Firefox 148+ / searchbar-new:
        // Button in denselben inneren Container wie der Go-Button, direkt danach.
        const goButton = bar.querySelector?.(".urlbar-go-button");
        if (goButton && goButton.parentNode) {
          const parent = goButton.parentNode;
          if (btn.parentNode !== parent) {
            parent.insertBefore(btn, goButton.nextSibling);
          } else if (btn.previousSibling !== goButton) {
            parent.insertBefore(btn, goButton.nextSibling);
          }
        } else if (btn.parentNode !== bar) {
          bar.appendChild(btn);
        }
    
        if (noLimitResult) {
          const input = getSearchInput(bar);
          if (input?.popup) {
            input.popup.setAttribute("nomaxresults", "true");
          }
        }
      }
    
      function showHistory(e) {
        if (e.button !== 0) {
          return;
        }
    
        const bar = getSearchBar();
        if (!bar) {
          return;
        }
    
        const input = getSearchInput(bar);
        if (!input) {
          return;
        }
    
        const oldValue = input.value || "";
        input.value = "";
    
        try {
          if (typeof input.showHistoryPopup === "function") {
            input.showHistoryPopup();
          } else if (typeof bar.startQuery === "function") {
            bar.startQuery({
              searchString: "",
              event: new Event("command", { bubbles: true, cancelable: true }),
            });
          } else if (bar.view && typeof bar.view.autoOpen === "function") {
            bar.view.autoOpen({ event: { type: "command" } });
          }
        } catch (ex) {
          console.error("ShowSearchBarHistoryByClick error:", ex);
        } finally {
          input.value = oldValue;
        }
      }
    
      function applySearchPrefs() {
        if (timeSeries) {
          setPref("browser.formfill.bucketSize", "int", -1);
          setPref("browser.formfill.maxTimeGroupings", "int", -1);
          setPref("browser.formfill.timeGroupingSize", "int", -1);
        } else {
          clearPref("browser.formfill.bucketSize");
          clearPref("browser.formfill.maxTimeGroupings");
          clearPref("browser.formfill.timeGroupingSize");
        }
    
        if (noLimitResult) {
          setPref("browser.urlbar.recentsearches.maxResults", "int", 9999);
        } else {
          clearPref("browser.urlbar.recentsearches.maxResults");
        }
      }
    
      function setPref(aPrefString, aPrefType, aValue) {
        try {
          switch (aPrefType) {
            case "int":
              return Services.prefs.setIntPref(aPrefString, parseInt(aValue, 10));
            case "str":
              return Services.prefs.setStringPref(aPrefString, String(aValue));
            case "bool":
            default:
              return Services.prefs.setBoolPref(aPrefString, !!aValue);
          }
        } catch (e) {}
        return null;
      }
    
      function clearPref(aPrefString) {
        try {
          Services.prefs.clearUserPref(aPrefString);
        } catch (e) {}
      }
    })();
    Alles anzeigen
  • Mehrzeilige Tableiste für aktuelle Firefox-Versionen

    • FuchsFan
    • 25. März 2026 um 08:54
    Zitat von lenny2

    Beim Verschieben eines Tabs kann die Marker entweder am Ende des vorherigen oder am Anfang des nächsten Tabs erscheinen. Ist das so beabsichtigt?

    Nein, die Marker erscheinen mit den Settings immer innerhalb eines Tabs, entweder links oder rechts. Ein anderes Verhalten kann ich hier nicht feststellen.

    Zitat von lenny2

    Dank Ihrer Skripte ist die Mehrzeilenfunktionalität nun deutlich verbessert. Vielen Dank!

    Freut mich wenn es gefällt, aber ich betone nochmals, nur mit KI ist das möglich, ich kenne mich nicht mit JavaScript aus.:/

    Hier dann nochmals das überarbeitete Script, mit dem Settings-Modul zur einfachen Auswahl der Abstände.

    CSS
    // ==UserScript==
    // @name           TabMoveOnlyMultiRow.uc.js
    // @namespace      local
    // @description    Multirow-Tabs + Styling + nur Tab-Verschieben per Drag & Drop
    // @include        main
    // @compatibility  Firefox138+
    // @version        2026-03-24
    // ==/UserScript==
    "use strict";
    
    TabMoveOnly();
    
    function TabMoveOnly() {
      if (!window.gBrowser || !window.gBrowser.tabContainer) return;
    
      const TAB_DROP_TYPE = window.TAB_DROP_TYPE || "application/x-moz-tabbrowser-tab";
    
      // =========================================================
      // FEINEINSTELLUNGEN FÜR DIE MARKER
      // =========================================================
      // Links: Marker horizontal verschieben in px
      const LEFT_MARKER_SHIFT = 0;
    
      // Rechts: Marker horizontal verschieben in px
      const RIGHT_MARKER_SHIFT = 0;
    
      // Marker vertikal leicht nach oben/unten korrigieren in px
      const MARKER_TOP = -1;
    
      const css = `
    
    /* =========================================================
       GRUNDEINSTELLUNGEN
       ========================================================= */
    
    :root{
      --multirow-n-rows: 3;
      --multirow-tab-min-width: 250px;
      --multirow-tab-max-width: 350px;
      --multirow-tab-height: 27px;
      --multirow-tab-top-margin: 1px;
      --multirow-tab-bottom-margin: 0px;
      --multirow-row-gap: 2px;
    
      --multirow-tab-total-height: calc(
        var(--multirow-tab-height) +
        var(--multirow-tab-top-margin) +
        var(--multirow-tab-bottom-margin)
      );
    
      --proton-tab-radius: 0px;
      --proton-tab-block-margin: 0px;
      --inline-tab-padding: 5px;
    
      --scrollbutton-display-model: none;
    }
    
    /* =========================================================
       MULTIROW-TABS
       ========================================================= */
    
    #tabbrowser-tabs{
      min-height: unset !important;
      padding-inline-start: 0 !important;
      padding-inline-end: 0 !important;
      margin-top: 1px !important;
      margin-inline-end: 0 !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"]{
      min-height: calc(var(--multirow-tab-total-height)) !important;
      justify-content: flex-start !important;
      align-content: flex-start !important;
      overflow: hidden !important;
    }
    
    #scrollbutton-up~spacer,
    #scrollbutton-up,
    #scrollbutton-down{
      display: none !important;
    }
    
    scrollbox[part][orient="horizontal"],
    scrollbox[part][orient="horizontal"] > slot{
      display: flex !important;
      flex-wrap: wrap !important;
      align-content: flex-start !important;
      justify-content: flex-start !important;
    
      overflow-y: auto !important;
      overflow-x: hidden !important;
    
      max-height: calc(
        (var(--multirow-tab-total-height) * var(--multirow-n-rows)) +
        ((var(--multirow-n-rows) - 1) * var(--multirow-row-gap))
      ) !important;
    
      scrollbar-width: thin !important;
      scrollbar-color: auto !important;
      scrollbar-gutter: auto !important;
    
      padding-inline-end: 0 !important;
      margin-inline-end: 0 !important;
      border-inline-end: 0 !important;
    
      contain: none !important;
      -moz-window-dragging: no-drag !important;
    }
    
    box.scrollbox-clip[orient="horizontal"]{
      padding-inline-end: 0 !important;
      margin-inline-end: 0 !important;
      border-inline-end: 0 !important;
      overflow: visible !important;
      -moz-window-dragging: no-drag !important;
    }
    
    #tabbrowser-arrowscrollbox scrollbar,
    #tabbrowser-arrowscrollbox scrollbar *{
      -moz-window-dragging: no-drag !important;
    }
    
    #tabbrowser-arrowscrollbox{
      overflow-y: auto !important;
      overflow-x: hidden !important;
      display: block !important;
    
      scrollbar-width: thin !important;
      scrollbar-color: auto !important;
      scrollbar-gutter: auto !important;
    
      padding-inline-end: 0 !important;
      margin-inline-end: 0 !important;
      border-inline-end: 0 !important;
    
      -moz-window-dragging: no-drag !important;
    }
    
    #tabbrowser-arrowscrollbox-periphery{
      margin: 0 !important;
      padding: 0 !important;
      inset-inline-end: 0 !important;
      position: relative !important;
      -moz-window-dragging: no-drag !important;
    }
    
    .tabbrowser-tab{
      height: var(--multirow-tab-height) !important;
      min-height: var(--multirow-tab-height) !important;
      max-height: var(--multirow-tab-height) !important;
      margin-top: var(--multirow-tab-top-margin) !important;
      margin-bottom: var(--multirow-tab-bottom-margin) !important;
      scroll-snap-align: start;
      box-shadow: none !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"] .tabbrowser-tab[movingtab]{
      padding-bottom: 15px !important;
      margin-bottom: -15px !important;
    }
    
    #tabbrowser-tabs .tabbrowser-tab[pinned]{
      position: static !important;
      margin-inline-start: 0 !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"] .tabbrowser-tab[fadein]:not([pinned]){
      flex: 1 1 var(--multirow-tab-min-width) !important;
      min-width: var(--multirow-tab-min-width) !important;
      max-width: var(--multirow-tab-max-width) !important;
      width: auto !important;
    }
    
    .tabbrowser-tab > stack{
      width: 100%;
      height: 100%;
    }
    
    #alltabs-button,
    :root:not([customizing]) #TabsToolbar #new-tab-button,
    #tabbrowser-arrowscrollbox > spacer,
    .tabbrowser-tab::after{
      display: none !important;
    }
    
    .tab-background{
      margin-block: unset !important;
    }
    
    box.scrollbox-clip[orient="horizontal"]{
      display: inline !important;
    }
    
    .tab-label-container{
      height: unset !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"] .tab-background,
    #tabbrowser-tabs[orient="horizontal"]{
      min-height: unset !important;
    }
    
    tab-split-view-wrapper{
      min-width: calc(var(--multirow-tab-min-width) * 2 + 6px) !important;
    }
    
    tab-group[collapsed] .tabbrowser-tab[fadein]:not([pinned]){
      min-width: 0 !important;
    }
    
    /* =========================================================
       TAB-STYLE
       ========================================================= */
    
    #TabsToolbar .tabbrowser-tab .tab-background{
      border-radius: unset !important;
      margin-block: unset !important;
    }
    
    hbox.titlebar-spacer,
    tabs tab:not([fadein]){
      display: none !important;
    }
    
    #tabbrowser-arrowscrollbox tab.tabbrowser-tab stack.tab-stack vbox.tab-background[selected]{
      min-height: var(--multirow-tab-height) !important;
      max-height: var(--multirow-tab-height) !important;
      background: linear-gradient(45deg, gold, #FFFAF0, beige, #FFFAF0, orange) !important;
      margin-left: -2px !important;
      margin-right: -2px !important;
      border: 1px solid green !important;
      outline: none !important;
    }
    
    #main-window .tabbrowser-tab[selected]:hover{
      opacity: .5 !important;
    }
    
    .tabbrowser-tab:not([selected]){
      background: linear-gradient(0.35turn, silver, white, silver) !important;
      border: 1px solid grey !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]):hover{
      background: linear-gradient(8deg, lightblue, white, lightblue) !important;
      border: 1px solid blue !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]) img.tab-icon-image{
      margin-top: 0px !important;
      margin-left: -1px !important;
    }
    
    .tab-close-button{
      fill: grey !important;
      transform: scale(1.5, 1.5) !important;
      display: block !important;
      opacity: 0 !important;
      margin-right: -7px !important;
    }
    
    .tabbrowser-tab:hover .tab-close-button{
      opacity: 1 !important;
      color: red !important;
      background: none !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]) .tab-close-button{
      margin-top: -2px !important;
    }
    
    .tabbrowser-tab > .tab-stack > .tab-content > .tab-close-button{
      flex-shrink: 0 !important;
      width: 24px !important;
      padding-inline-start: 7px !important;
      width: unset !important;
    }
    
    .tab-label-container{
      height: unset !important;
      display: grid !important;
      justify-items: safe center !important;
      justify-content: safe center !important;
    }
    
    .tab-label{
      justify-content: center !important;
      overflow: hidden !important;
    }
    
    .tabbrowser-tab[visuallyselected] .tab-label{
      color: blue !important;
      font-weight: bold !important;
      font-size: 1.25em !important;
      font-family: "Verdana, Helvetica", serif !important;
      font-style: italic !important;
      padding-bottom: 0 !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]) .tab-label{
      color: #4a525f !important;
      font-weight: 600 !important;
      font-size: 1.1em !important;
      padding-bottom: 0px !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]):hover .tab-label{
      color: brown !important;
      font-weight: 700 !important;
      font-size: 1.1em !important;
      opacity: 1 !important;
    }
    
    .tabbrowser-tab[busy] .tab-label{
      color: blue !important;
    }
    
    #nav-bar{
      appearance: none !important;
      background-image: none !important;
      background-color: #C2D3ED !important;
      border-top: 1px solid grey !important;
      border-bottom: 1px solid grey !important;
      box-shadow: none !important;
      margin-top: 3px !important;
    }
    
    #PersonalToolbar{
      margin-top: 3px !important;
    }
    
    #TabsToolbar{
      margin-top: 2px !important;
      margin-bottom: -1px !important;
      padding-top: 0 !important;
    }
    
    #main-menubar{
      margin-top: 3px !important;
    }
    
    #TabsToolbar :is(.tabs-newtab-button,#tabs-newtab-button){
      min-width: 0px !important;
      width: 22px !important;
      height: 22px !important;
      margin-top: 3px !important;
      margin-left: 4px !important;
    }
    
    #TabsToolbar :is(.tabs-newtab-button,#tabs-newtab-button) .toolbarbutton-icon{
      min-width: 0px !important;
      min-height: 0px !important;
      width: 14px !important;
      height: 14px !important;
      margin: 0px !important;
      margin-bottom: 0px !important;
      padding: 0px !important;
      background: unset !important;
      box-shadow: unset !important;
    }
    
    #TabsToolbar #tabs-newtab-button:hover{
      background-color: #FFF !important;
      padding: 0px !important;
    }
    
    #TabsToolbar #tabs-newtab-button > image{
      min-height: 24px !important;
      max-height: 24px !important;
      min-width: 24px !important;
      max-width: 24px !important;
      margin-top: 1px !important;
      margin-left: 0px !important;
      padding: 2px !important;
      fill: grey !important;
    }
    
    #TabsToolbar #tabs-newtab-button:hover > image{
      fill: red !important;
    }
    
    /* =========================================================
       TABLEISTEN-POSITION
       ========================================================= */
    
    #navigator-toolbox{
      display: flex !important;
      flex-direction: column !important;
    }
    
    #toolbar-menubar{
      order: 1 !important;
    }
    
    #nav-bar{
      order: 2 !important;
    }
    
    #PersonalToolbar{
      order: 3 !important;
    }
    
    #TabsToolbar{
      order: 4 !important;
    }
    
    /* =========================================================
       DND-MARKER
       ========================================================= */
    
    .tabbrowser-tab[dnd-marker]{
      position: relative !important;
    }
    
    .tab-dnd-marker{
      position: absolute !important;
      pointer-events: none !important;
      z-index: 1000 !important;
      content: "" !important;
      width: 14px !important;
      height: 18px !important;
      border-radius: 50% !important;
      background:
        radial-gradient(circle at center,
        #00ffff 0 35%,
        #1dacd6 36% 60%,
        rgba(255,255,255,0) 61%) !important;
      box-shadow: 0 0 3px rgba(255, 90, 82, 0.35) !important;
    }
    `;
    
      const sss = Cc["@mozilla.org/content/style-sheet-service;1"]
        .getService(Ci.nsIStyleSheetService);
    
      const uri = Services.io.newURI(
        "data:text/css;charset=UTF-8," + encodeURIComponent(css)
      );
    
      if (!sss.sheetRegistered(uri, sss.USER_SHEET)) {
        sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
      }
    
      const tabContainer = gBrowser.tabContainer;
      let listenersActive = false;
      let lastKnownIndex = null;
      let lastGroupStart = null;
      let lastGroupEnd = null;
    
      function clearDndMarkers() {
        for (const tab of gBrowser.tabs) {
          tab.removeAttribute("dnd-marker");
          if (tab._dndMarkerEl) {
            tab._dndMarkerEl.remove();
            tab._dndMarkerEl = null;
          }
        }
      }
    
      function setDndMarker(tab, side) {
        clearDndMarkers();
        if (!tab || !side) return;
    
        tab.setAttribute("dnd-marker", side);
    
        const marker = tab.ownerDocument.createXULElement("box");
        marker.className = "tab-dnd-marker";
    
        marker.style.top = `${MARKER_TOP}px`;
    
        if (side === "right") {
          marker.style.right = `${-2 + RIGHT_MARKER_SHIFT}px`;
        } else {
          marker.style.left = `${-2 + LEFT_MARKER_SHIFT}px`;
        }
    
        tab.appendChild(marker);
        tab._dndMarkerEl = marker;
      }
    
      function getTabFromEventTarget(event, ignoreTabSides = false) {
        let { target } = event;
        if (!target) return null;
    
        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) {
          const { 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;
      }
    
      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];
        }
    
        if (!tabToDropAt) {
          return null;
        }
    
        const tabPos = gBrowser.tabContainer.getIndexOfItem(tabToDropAt);
        const rect = tabToDropAt.getBoundingClientRect();
    
        if (window.getComputedStyle(this).direction == "ltr") {
          if (event.clientX < rect.x + rect.width / 2) {
            return tabPos;
          }
          return tabPos + 1;
        } else {
          if (event.clientX > rect.x + rect.width / 2) {
            return tabPos;
          }
          return tabPos + 1;
        }
      };
    
      function orig_getDropEffectForTabDrag(event) {
        const dt = event.dataTransfer;
    
        let isMovingTabs = dt.mozItemCount > 0;
        for (let i = 0; i < dt.mozItemCount; i++) {
          const types = dt.mozTypesAt(0);
          if (types[0] != TAB_DROP_TYPE) {
            isMovingTabs = false;
            break;
          }
        }
    
        if (isMovingTabs) {
          const 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
          ) {
            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";
      }
    
      function getMarkerInfo(event) {
        let tabToDropAt = getTabFromEventTarget(event, false);
    
        if (tabToDropAt?.localName == "tab-group") {
          tabToDropAt = tabToDropAt.previousSibling;
          if (!tabToDropAt) {
            tabToDropAt = gBrowser.visibleTabs[0];
          }
        }
    
        if (!tabToDropAt) {
          const lastTab = gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1];
          return { tab: lastTab, side: "right" };
        }
    
        const rect = tabToDropAt.getBoundingClientRect();
        const ltr = window.getComputedStyle(gBrowser.tabContainer).direction == "ltr";
    
        const side = ltr
          ? (event.clientX < rect.x + rect.width / 2 ? "left" : "right")
          : (event.clientX > rect.x + rect.width / 2 ? "left" : "right");
    
        return { tab: tabToDropAt, side };
      }
    
      function performTabDragOver(event) {
        const effects = orig_getDropEffectForTabDrag(event);
    
        if (effects == "none" || effects == "link") {
          clearDndMarkers();
          return;
        }
    
        event.preventDefault();
        event.stopPropagation();
    
        const tab = getTabFromEventTarget(event, true);
    
        if (tab && tab.nodeName == "tab-group" && !lastGroupStart) {
          const first = tab.querySelector("tab:first-of-type");
          const last = tab.querySelector("tab:last-of-type");
          if (first && last) {
            lastGroupStart = first._tPos;
            lastGroupEnd = last._tPos;
          }
        }
    
        const newIndex = gBrowser.tabContainer._getDropIndex(event);
        if (newIndex == null) {
          clearDndMarkers();
          return;
        }
    
        lastKnownIndex = newIndex;
    
        const markerInfo = getMarkerInfo(event);
        setDndMarker(markerInfo.tab, markerInfo.side);
      }
    
      function performTabDropEvent(event) {
        clearDndMarkers();
    
        const dt = event.dataTransfer;
        const 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) {
          let newIndex = gBrowser.tabContainer._getDropIndex(event);
          if (newIndex == null) return;
    
          const selectedTabs = gBrowser.selectedTabs.length > 1 ? gBrowser.selectedTabs : [draggedTab];
          let tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex);
          const tab = getTabFromEventTarget(event, false);
          const tabgroup = tab?.closest("tab-group");
    
          if (!tab) {
            newIndex = gBrowser.tabs.length;
            tabToMoveAt = null;
          }
    
          if (tabgroup && !tab.previousSibling) {
            newIndex = 0;
            selectedTabs.forEach(t => {
              gBrowser.moveTabTo(t, { tabIndex: newIndex++, forceUngrouped: true });
            });
          } else if (
            !tab ||
            (!tabgroup && !tabToMoveAt?.group) ||
            (tabgroup && tabToMoveAt?.group)
          ) {
            selectedTabs.forEach(t => {
              gBrowser.moveTabBefore(t, tabToMoveAt);
            });
          } else {
            tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex - 1);
            selectedTabs.forEach(t => {
              gBrowser.moveTabAfter(t, tabToMoveAt);
            });
          }
    
          lastKnownIndex = null;
          lastGroupStart = null;
          lastGroupEnd = null;
        }
      }
    
      tabContainer.addEventListener("dragstart", () => {
        if (!listenersActive) {
          tabContainer.on_dragover = e => performTabDragOver(e);
          tabContainer._onDragOver = e => performTabDragOver(e);
          tabContainer.ondrop = e => performTabDropEvent(e);
    
          tabContainer.addEventListener("dragend", clearDndMarkers, true);
          tabContainer.addEventListener("drop", clearDndMarkers, true);
    
          listenersActive = true;
        }
      });
    }
    Alles anzeigen
  • Mehrzeilige Tableiste für aktuelle Firefox-Versionen

    • FuchsFan
    • 24. März 2026 um 18:06

    Ergänzend hierzu #1118 habe ich den CSS-Code für MultiRowTabs mit dem Script für Drag&Drop vereinigt.

    JavaScript
    // ==UserScript==
    // @name           TabMoveOnlyMultiRow.uc.js
    // @namespace      local
    // @description    Multirow-Tabs + Styling + nur Tab-Verschieben per Drag & Drop
    // @include        main
    // @compatibility  Firefox138+
    // @version        2026-03-24
    // ==/UserScript==
    "use strict";
    
    TabMoveOnly();
    
    function TabMoveOnly() {
      if (!window.gBrowser || !window.gBrowser.tabContainer) return;
    
      const TAB_DROP_TYPE = window.TAB_DROP_TYPE || "application/x-moz-tabbrowser-tab";
      const RIGHT_MARKER_SHIFT = 15; // Marker 15px weiter nach links
    
      const css = `
    
    /* =========================================================
       GRUNDEINSTELLUNGEN
       ========================================================= */
    
    :root{
      --multirow-n-rows: 3;
      --multirow-tab-min-width: 250px;
      --multirow-tab-max-width: 350px;
      --multirow-tab-height: 27px;
      --multirow-tab-top-margin: 1px;
      --multirow-tab-bottom-margin: 0px;
      --multirow-row-gap: 2px;
    
      --multirow-tab-total-height: calc(
        var(--multirow-tab-height) +
        var(--multirow-tab-top-margin) +
        var(--multirow-tab-bottom-margin)
      );
    
      --proton-tab-radius: 0px;
      --proton-tab-block-margin: 0px;
      --inline-tab-padding: 5px;
    
      --scrollbutton-display-model: none;
    }
    
    /* =========================================================
       MULTIROW-TABS
       ========================================================= */
    
    #tabbrowser-tabs{
      min-height: unset !important;
      padding-inline-start: 0 !important;
      padding-inline-end: 0 !important;
      margin-top: 1px !important;
      margin-inline-end: 0 !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"]{
      min-height: calc(var(--multirow-tab-total-height)) !important;
      justify-content: flex-start !important;
      align-content: flex-start !important;
      overflow: hidden !important;
    }
    
    #scrollbutton-up~spacer,
    #scrollbutton-up,
    #scrollbutton-down{
      display: none !important;
    }
    
    scrollbox[part][orient="horizontal"],
    scrollbox[part][orient="horizontal"] > slot{
      display: flex !important;
      flex-wrap: wrap !important;
      align-content: flex-start !important;
      justify-content: flex-start !important;
    
      overflow-y: auto !important;
      overflow-x: hidden !important;
    
      max-height: calc(
        (var(--multirow-tab-total-height) * var(--multirow-n-rows)) +
        ((var(--multirow-n-rows) - 1) * var(--multirow-row-gap))
      ) !important;
    
      scrollbar-width: thin !important;
      scrollbar-color: auto !important;
      scrollbar-gutter: auto !important;
    
      padding-inline-end: 0 !important;
      margin-inline-end: 0 !important;
      border-inline-end: 0 !important;
    
      contain: none !important;
      -moz-window-dragging: no-drag !important;
    }
    
    box.scrollbox-clip[orient="horizontal"]{
      padding-inline-end: 0 !important;
      margin-inline-end: 0 !important;
      border-inline-end: 0 !important;
      overflow: visible !important;
      -moz-window-dragging: no-drag !important;
    }
    
    #tabbrowser-arrowscrollbox scrollbar,
    #tabbrowser-arrowscrollbox scrollbar *{
      -moz-window-dragging: no-drag !important;
    }
    
    #tabbrowser-arrowscrollbox{
      overflow-y: auto !important;
      overflow-x: hidden !important;
      display: block !important;
    
      scrollbar-width: thin !important;
      scrollbar-color: auto !important;
      scrollbar-gutter: auto !important;
    
      padding-inline-end: 0 !important;
      margin-inline-end: 0 !important;
      border-inline-end: 0 !important;
    
      -moz-window-dragging: no-drag !important;
    }
    
    #tabbrowser-arrowscrollbox-periphery{
      margin: 0 !important;
      padding: 0 !important;
      inset-inline-end: 0 !important;
      position: relative !important;
      -moz-window-dragging: no-drag !important;
    }
    
    .tabbrowser-tab{
      height: var(--multirow-tab-height) !important;
      min-height: var(--multirow-tab-height) !important;
      max-height: var(--multirow-tab-height) !important;
      margin-top: var(--multirow-tab-top-margin) !important;
      margin-bottom: var(--multirow-tab-bottom-margin) !important;
      scroll-snap-align: start;
      box-shadow: none !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"] .tabbrowser-tab[movingtab]{
      padding-bottom: 15px !important;
      margin-bottom: -15px !important;
    }
    
    #tabbrowser-tabs .tabbrowser-tab[pinned]{
      position: static !important;
      margin-inline-start: 0 !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"] .tabbrowser-tab[fadein]:not([pinned]){
      flex: 1 1 var(--multirow-tab-min-width) !important;
      min-width: var(--multirow-tab-min-width) !important;
      max-width: var(--multirow-tab-max-width) !important;
      width: auto !important;
    }
    
    .tabbrowser-tab > stack{
      width: 100%;
      height: 100%;
    }
    
    #alltabs-button,
    :root:not([customizing]) #TabsToolbar #new-tab-button,
    #tabbrowser-arrowscrollbox > spacer,
    .tabbrowser-tab::after{
      display: none !important;
    }
    
    .tab-background{
      margin-block: unset !important;
    }
    
    box.scrollbox-clip[orient="horizontal"]{
      display: inline !important;
    }
    
    .tab-label-container{
      height: unset !important;
    }
    
    #tabbrowser-tabs[orient="horizontal"] .tab-background,
    #tabbrowser-tabs[orient="horizontal"]{
      min-height: unset !important;
    }
    
    tab-split-view-wrapper{
      min-width: calc(var(--multirow-tab-min-width) * 2 + 6px) !important;
    }
    
    tab-group[collapsed] .tabbrowser-tab[fadein]:not([pinned]){
      min-width: 0 !important;
    }
    
    /* =========================================================
       TAB-STYLE
       ========================================================= */
    
    #TabsToolbar .tabbrowser-tab .tab-background{
      border-radius: unset !important;
      margin-block: unset !important;
    }
    
    hbox.titlebar-spacer,
    tabs tab:not([fadein]){
      display: none !important;
    }
    
    #tabbrowser-arrowscrollbox tab.tabbrowser-tab stack.tab-stack vbox.tab-background[selected]{
      min-height: var(--multirow-tab-height) !important;
      max-height: var(--multirow-tab-height) !important;
      background: linear-gradient(45deg, gold, #FFFAF0, beige, #FFFAF0, orange) !important;
      margin-left: -2px !important;
      margin-right: -2px !important;
      border: 1px solid green !important;
      outline: none !important;
    }
    
    #main-window .tabbrowser-tab[selected]:hover{
      opacity: .5 !important;
    }
    
    .tabbrowser-tab:not([selected]){
      background: linear-gradient(0.35turn, silver, white, silver) !important;
      border: 1px solid grey !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]):hover{
      background: linear-gradient(8deg, lightblue, white, lightblue) !important;
      border: 1px solid blue !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]) img.tab-icon-image{
      margin-top: 0px !important;
      margin-left: -1px !important;
    }
    
    .tab-close-button{
      fill: grey !important;
      transform: scale(1.5, 1.5) !important;
      display: block !important;
      opacity: 0 !important;
      margin-right: -7px !important;
    }
    
    .tabbrowser-tab:hover .tab-close-button{
      opacity: 1 !important;
      color: red !important;
      background: none !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]) .tab-close-button{
      margin-top: -2px !important;
    }
    
    .tabbrowser-tab > .tab-stack > .tab-content > .tab-close-button{
      flex-shrink: 0 !important;
      width: 24px !important;
      padding-inline-start: 7px !important;
      width: unset !important;
    }
    
    .tab-label-container{
      height: unset !important;
      display: grid !important;
      justify-items: safe center !important;
      justify-content: safe center !important;
    }
    
    .tab-label{
      justify-content: center !important;
      overflow: hidden !important;
    }
    
    .tabbrowser-tab[visuallyselected] .tab-label{
      color: blue !important;
      font-weight: bold !important;
      font-size: 1.25em !important;
      font-family: "Verdana, Helvetica", serif !important;
      font-style: italic !important;
      padding-bottom: 0 !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]) .tab-label{
      color: #4a525f !important;
      font-weight: 600 !important;
      font-size: 1.1em !important;
      padding-bottom: 0px !important;
    }
    
    .tabbrowser-tab:not([visuallyselected]):hover .tab-label{
      color: brown !important;
      font-weight: 700 !important;
      font-size: 1.1em !important;
      opacity: 1 !important;
    }
    
    .tabbrowser-tab[busy] .tab-label{
      color: blue !important;
    }
    
    #nav-bar{
      appearance: none !important;
      background-image: none !important;
      background-color: #C2D3ED !important;
      border-top: 1px solid grey !important;
      border-bottom: 1px solid grey !important;
      box-shadow: none !important;
      margin-top: 3px !important;
    }
    
    #PersonalToolbar{
      margin-top: 3px !important;
    }
    
    #TabsToolbar{
      margin-top: 2px !important;
      margin-bottom: -1px !important;
      padding-top: 0 !important;
    }
    
    #main-menubar{
      margin-top: 3px !important;
    }
    
    #TabsToolbar :is(.tabs-newtab-button,#tabs-newtab-button){
      min-width: 0px !important;
      width: 22px !important;
      height: 22px !important;
      margin-top: 3px !important;
      margin-left: 4px !important;
    }
    
    #TabsToolbar :is(.tabs-newtab-button,#tabs-newtab-button) .toolbarbutton-icon{
      min-width: 0px !important;
      min-height: 0px !important;
      width: 14px !important;
      height: 14px !important;
      margin: 0px !important;
      margin-bottom: 0px !important;
      padding: 0px !important;
      background: unset !important;
      box-shadow: unset !important;
    }
    
    #TabsToolbar #tabs-newtab-button:hover{
      background-color: #FFF !important;
      padding: 0px !important;
    }
    
    #TabsToolbar #tabs-newtab-button > image{
      min-height: 24px !important;
      max-height: 24px !important;
      min-width: 24px !important;
      max-width: 24px !important;
      margin-top: 1px !important;
      margin-left: 0px !important;
      padding: 2px !important;
      fill: grey !important;
    }
    
    #TabsToolbar #tabs-newtab-button:hover > image{
      fill: red !important;
    }
    
    /* =========================================================
       TABLEISTEN-POSITION
       ========================================================= */
    
    #navigator-toolbox{
      display: flex !important;
      flex-direction: column !important;
    }
    
    #toolbar-menubar{
      order: 1 !important;
    }
    
    #nav-bar{
      order: 2 !important;
    }
    
    #PersonalToolbar{
      order: 3 !important;
    }
    
    #TabsToolbar{
      order: 4 !important;
    }
    
    /* =========================================================
       DND-MARKER
       ========================================================= */
    
    .tabbrowser-tab[dnd-marker]{
      position: relative !important;
    }
    
    .tab-dnd-marker{
      position: absolute !important;
      top: 6px !important;
      pointer-events: none !important;
      z-index: 1000 !important;
      content: "" !important;
      width: 14px !important;
      height: 18px !important;
      border-radius: 50% !important;
      background:
        radial-gradient(circle at center,
        #00ffff 0 35%,
        #1dacd6 36% 60%,
        rgba(255,255,255,0) 61%) !important;
      box-shadow: 0 0 3px rgba(255, 90, 82, 0.35) !important;
    }
    
    .tab-dnd-marker.left{
      left: 1px !important;
      top: -1px !important;
    }
    
    .tab-dnd-marker.right{
      right: 1px !important;
      top: -1px !important;
    }
    `;
    
      const sss = Cc["@mozilla.org/content/style-sheet-service;1"]
        .getService(Ci.nsIStyleSheetService);
    
      const uri = Services.io.newURI(
        "data:text/css;charset=UTF-8," + encodeURIComponent(css)
      );
    
      if (!sss.sheetRegistered(uri, sss.USER_SHEET)) {
        sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
      }
    
      const tabContainer = gBrowser.tabContainer;
      let listenersActive = false;
      let lastKnownIndex = null;
      let lastGroupStart = null;
      let lastGroupEnd = null;
    
      function clearDndMarkers() {
        for (const tab of gBrowser.tabs) {
          tab.removeAttribute("dnd-marker");
          if (tab._dndMarkerEl) {
            tab._dndMarkerEl.remove();
            tab._dndMarkerEl = null;
          }
        }
      }
    
      function setDndMarker(tab, side) {
        clearDndMarkers();
        if (!tab || !side) return;
    
        tab.setAttribute("dnd-marker", side);
    
        const marker = tab.ownerDocument.createXULElement("box");
        marker.className = `tab-dnd-marker ${side}`;
    
        if (side === "right") {
          marker.style.right = `${-2 + RIGHT_MARKER_SHIFT}px`;
        } else {
          marker.style.left = "-2px";
        }
    
        tab.appendChild(marker);
        tab._dndMarkerEl = marker;
      }
    
      function getTabFromEventTarget(event, ignoreTabSides = false) {
        let { target } = event;
        if (!target) return null;
    
        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) {
          const { 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;
      }
    
      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];
        }
    
        if (!tabToDropAt) {
          return null;
        }
    
        const tabPos = gBrowser.tabContainer.getIndexOfItem(tabToDropAt);
        const rect = tabToDropAt.getBoundingClientRect();
    
        if (window.getComputedStyle(this).direction == "ltr") {
          if (event.clientX < rect.x + rect.width / 2) {
            return tabPos;
          }
          return tabPos + 1;
        } else {
          if (event.clientX > rect.x + rect.width / 2) {
            return tabPos;
          }
          return tabPos + 1;
        }
      };
    
      function orig_getDropEffectForTabDrag(event) {
        const dt = event.dataTransfer;
    
        let isMovingTabs = dt.mozItemCount > 0;
        for (let i = 0; i < dt.mozItemCount; i++) {
          const types = dt.mozTypesAt(0);
          if (types[0] != TAB_DROP_TYPE) {
            isMovingTabs = false;
            break;
          }
        }
    
        if (isMovingTabs) {
          const 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
          ) {
            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";
      }
    
      function getMarkerInfo(event) {
        let tabToDropAt = getTabFromEventTarget(event, false);
    
        if (tabToDropAt?.localName == "tab-group") {
          tabToDropAt = tabToDropAt.previousSibling;
          if (!tabToDropAt) {
            tabToDropAt = gBrowser.visibleTabs[0];
          }
        }
    
        if (!tabToDropAt) {
          const lastTab = gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1];
          return { tab: lastTab, side: "right" };
        }
    
        const rect = tabToDropAt.getBoundingClientRect();
        const ltr = window.getComputedStyle(gBrowser.tabContainer).direction == "ltr";
    
        const side = ltr
          ? (event.clientX < rect.x + rect.width / 2 ? "left" : "right")
          : (event.clientX > rect.x + rect.width / 2 ? "left" : "right");
    
        return { tab: tabToDropAt, side };
      }
    
      function performTabDragOver(event) {
        const effects = orig_getDropEffectForTabDrag(event);
    
        if (effects == "none" || effects == "link") {
          clearDndMarkers();
          return;
        }
    
        event.preventDefault();
        event.stopPropagation();
    
        const tab = getTabFromEventTarget(event, true);
    
        if (tab && tab.nodeName == "tab-group" && !lastGroupStart) {
          const first = tab.querySelector("tab:first-of-type");
          const last = tab.querySelector("tab:last-of-type");
          if (first && last) {
            lastGroupStart = first._tPos;
            lastGroupEnd = last._tPos;
          }
        }
    
        const newIndex = gBrowser.tabContainer._getDropIndex(event);
        if (newIndex == null) {
          clearDndMarkers();
          return;
        }
    
        lastKnownIndex = newIndex;
    
        const markerInfo = getMarkerInfo(event);
        setDndMarker(markerInfo.tab, markerInfo.side);
      }
    
      function performTabDropEvent(event) {
        clearDndMarkers();
    
        const dt = event.dataTransfer;
        const 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) {
          let newIndex = gBrowser.tabContainer._getDropIndex(event);
          if (newIndex == null) return;
    
          const selectedTabs = gBrowser.selectedTabs.length > 1 ? gBrowser.selectedTabs : [draggedTab];
          let tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex);
          const tab = getTabFromEventTarget(event, false);
          const tabgroup = tab?.closest("tab-group");
    
          if (!tab) {
            newIndex = gBrowser.tabs.length;
            tabToMoveAt = null;
          }
    
          if (tabgroup && !tab.previousSibling) {
            newIndex = 0;
            selectedTabs.forEach(t => {
              gBrowser.moveTabTo(t, { tabIndex: newIndex++, forceUngrouped: true });
            });
          } else if (
            !tab ||
            (!tabgroup && !tabToMoveAt?.group) ||
            (tabgroup && tabToMoveAt?.group)
          ) {
            selectedTabs.forEach(t => {
              gBrowser.moveTabBefore(t, tabToMoveAt);
            });
          } else {
            tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex - 1);
            selectedTabs.forEach(t => {
              gBrowser.moveTabAfter(t, tabToMoveAt);
            });
          }
    
          lastKnownIndex = null;
          lastGroupStart = null;
          lastGroupEnd = null;
        }
      }
    
      tabContainer.addEventListener("dragstart", () => {
        if (!listenersActive) {
          tabContainer.on_dragover = e => performTabDragOver(e);
          tabContainer._onDragOver = e => performTabDragOver(e);
          tabContainer.ondrop = e => performTabDropEvent(e);
    
          tabContainer.addEventListener("dragend", clearDndMarkers, true);
          tabContainer.addEventListener("drop", clearDndMarkers, true);
    
          listenersActive = true;
        }
      });
    }
    Alles anzeigen
  • Mehrzeilige Tableiste für aktuelle Firefox-Versionen

    • FuchsFan
    • 24. März 2026 um 15:04

    Das Script für Drag&Drop aus diesem Beitrag #1116 erhält hiermit ein Update. Es funktionierten die Abstände der Marker nicht richtig, was hiermit korrigiert wird. Im oberen Teil ist jetzt außerdem ein Settings-Modul eingebaut, von dem aus die Abstände der Marker eingestellt werden können.

    ....das Script für Drag&Drop (Update):

    CSS
    // ==UserScript==
    // @name           TabMoveOnly.uc.js
    // @namespace      local
    // @description    Nur das Verschieben von Tabs per Drag & Drop
    // @include        main
    // @compatibility  Firefox138+
    // @version        2026-03-24
    // ==/UserScript==
    "use strict";
    
    TabMoveOnly();
    
    function TabMoveOnly() {
      if (!window.gBrowser) return;
    
      const TAB_DROP_TYPE = window.TAB_DROP_TYPE || "application/x-moz-tabbrowser-tab";
    
      // =========================================================
      // FEINEINSTELLUNGEN FÜR DIE MARKER
      // =========================================================
      // Links: Marker horizontal verschieben in px
      const LEFT_MARKER_SHIFT = 0;
    
      // Rechts: Marker horizontal verschieben in px
      const RIGHT_MARKER_SHIFT = 0;
    
      // Marker vertikal leicht nach oben/unten korrigieren in px
      const MARKER_TOP = -1;
    
      // =========================================================
      // MARKER-CSS
      // =========================================================
      const css = `
    .tabbrowser-tab[dnd-marker]{
      position: relative !important;
    }
    
    .tab-dnd-marker{
      position: absolute !important;
      pointer-events: none !important;
      z-index: 1000 !important;
      content: "" !important;
      width: 14px !important;
      height: 18px !important;
      border-radius: 50% !important;
      background:
        radial-gradient(circle at center,
        #00ffff 0 35%,
        #1dacd6 36% 60%,
        rgba(255,255,255,0) 61%) !important;
      box-shadow: 0 0 3px rgba(255, 90, 82, 0.35) !important;
    }
    `;
    
      // =========================================================
      // CSS EINBINDEN
      // =========================================================
      const sss = Cc["@mozilla.org/content/style-sheet-service;1"]
        .getService(Ci.nsIStyleSheetService);
    
      const uri = Services.io.newURI(
        "data:text/css;charset=UTF-8," + encodeURIComponent(css)
      );
    
      if (!sss.sheetRegistered(uri, sss.USER_SHEET)) {
        sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
      }
    
      const tabContainer = gBrowser.tabContainer;
    
      // Listener nur einmal aktivieren
      let listenersActive = false;
    
      // Reserviert für Gruppen-/Drop-Zustand
      let lastGroupStart = null;
      let lastGroupEnd = null;
    
      // =========================================================
      // MARKER-VERWALTUNG
      // =========================================================
    
      function clearDndMarkers() {
        for (const tab of gBrowser.tabs) {
          tab.removeAttribute("dnd-marker");
          tab.style.removeProperty("--tab-dnd-marker-top");
    
          if (tab._dndMarkerEl) {
            tab._dndMarkerEl.remove();
            tab._dndMarkerEl = null;
          }
        }
      }
    
      function setDndMarker(tab, side) {
        clearDndMarkers();
        if (!tab || !side) return;
    
        // Nur zur Erkennung / CSS-Selektoren
        tab.setAttribute("dnd-marker", side);
    
        // Vertikale Feinkorrektur als Variable direkt am Tab
        tab.style.setProperty("--tab-dnd-marker-top", `${MARKER_TOP}px`);
    
        // Echtes Marker-Element, damit dein CSS sicher greift
        const marker = tab.ownerDocument.createXULElement("box");
        marker.className = `tab-dnd-marker ${side}`;
    
        // Horizontale Feineinstellung pro Seite
        if (side === "left") {
          marker.style.setProperty("left", `${1 + LEFT_MARKER_SHIFT}px`, "important");
        } else if (side === "right") {
          marker.style.setProperty("right", `${1 + RIGHT_MARKER_SHIFT}px`, "important");
        }
    
        tab.appendChild(marker);
        tab._dndMarkerEl = marker;
      }
    
      // =========================================================
      // HILFSFUNKTIONEN FÜR TAB / DROP
      // =========================================================
    
      function getTabFromEventTarget(event, ignoreTabSides = false) {
        let { target } = event;
        if (!target) return null;
    
        if (target.nodeType !== Node.ELEMENT_NODE) {
          target = target.parentElement;
        }
    
        let tab = target?.closest("tab") || target?.closest("tab-group");
        const selectedTab = gBrowser.selectedTab;
    
        // Falls der Cursor nur im Randbereich ist, nimm den aktuell ausgewählten Tab
        if (tab && ignoreTabSides) {
          const { 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;
      }
    
      // Firefox-interne Drop-Index-Berechnung für Multirow / normales Tab-Layout
      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];
        }
    
        if (!tabToDropAt) return null;
    
        const tabPos = gBrowser.tabContainer.getIndexOfItem(tabToDropAt);
        const rect = tabToDropAt.getBoundingClientRect();
        const ltr = window.getComputedStyle(this).direction == "ltr";
    
        if (ltr) {
          return event.clientX < rect.x + rect.width / 2 ? tabPos : tabPos + 1;
        }
        return event.clientX > rect.x + rect.width / 2 ? tabPos : tabPos + 1;
      };
    
      // Prüft, ob der Drag wirklich ein Tab-Drag ist
      function orig_getDropEffectForTabDrag(event) {
        const dt = event.dataTransfer;
    
        let isMovingTabs = dt.mozItemCount > 0;
        for (let i = 0; i < dt.mozItemCount; i++) {
          const types = dt.mozTypesAt(0);
          if (types[0] != TAB_DROP_TYPE) {
            isMovingTabs = false;
            break;
          }
        }
    
        if (isMovingTabs) {
          const 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
          ) {
            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";
      }
    
      function getMarkerInfo(event) {
        let tabToDropAt = getTabFromEventTarget(event, false);
    
        if (tabToDropAt?.localName == "tab-group") {
          tabToDropAt = tabToDropAt.previousSibling;
          if (!tabToDropAt) {
            tabToDropAt = gBrowser.visibleTabs[0];
          }
        }
    
        if (!tabToDropAt) {
          const lastTab = gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1];
          return { tab: lastTab, side: "right" };
        }
    
        const rect = tabToDropAt.getBoundingClientRect();
        const ltr = window.getComputedStyle(gBrowser.tabContainer).direction == "ltr";
    
        const side = ltr
          ? (event.clientX < rect.x + rect.width / 2 ? "left" : "right")
          : (event.clientX > rect.x + rect.width / 2 ? "left" : "right");
    
        return { tab: tabToDropAt, side };
      }
    
      // =========================================================
      // DRAG OVER
      // =========================================================
      function performTabDragOver(event) {
        const effects = orig_getDropEffectForTabDrag(event);
    
        // Nur echte Tab-Drags zulassen
        if (effects == "none" || effects == "link") {
          clearDndMarkers();
          return;
        }
    
        event.preventDefault();
        event.stopPropagation();
    
        const tab = getTabFromEventTarget(event, true);
    
        // Kleine Starthilfe für Gruppen-Merker
        if (tab && tab.nodeName == "tab-group" && !lastGroupStart) {
          const first = tab.querySelector("tab:first-of-type");
          const last = tab.querySelector("tab:last-of-type");
          if (first && last) {
            lastGroupStart = first._tPos;
            lastGroupEnd = last._tPos;
          }
        }
    
        const newIndex = gBrowser.tabContainer._getDropIndex(event);
        if (newIndex == null) {
          clearDndMarkers();
          return;
        }
    
        const markerInfo = getMarkerInfo(event);
        setDndMarker(markerInfo.tab, markerInfo.side);
      }
    
      // =========================================================
      // DROP
      // =========================================================
      function performTabDropEvent(event) {
        clearDndMarkers();
    
        const dt = event.dataTransfer;
        const 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) {
          let newIndex = gBrowser.tabContainer._getDropIndex(event);
          if (newIndex == null) return;
    
          const selectedTabs = gBrowser.selectedTabs.length > 1 ? gBrowser.selectedTabs : [draggedTab];
          let tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex);
          const tab = getTabFromEventTarget(event, false);
          const tabgroup = tab?.closest("tab-group");
    
          if (!tab) {
            newIndex = gBrowser.tabs.length;
            tabToMoveAt = null;
          }
    
          if (tabgroup && !tab.previousSibling) {
            newIndex = 0;
            selectedTabs.forEach(t => {
              gBrowser.moveTabTo(t, { tabIndex: newIndex++, forceUngrouped: true });
            });
          } else if (
            !tab ||
            (!tabgroup && !tabToMoveAt?.group) ||
            (tabgroup && tabToMoveAt?.group)
          ) {
            selectedTabs.forEach(t => {
              gBrowser.moveTabBefore(t, tabToMoveAt);
            });
          } else {
            tabToMoveAt = gBrowser.tabContainer.getItemAtIndex(newIndex - 1);
            selectedTabs.forEach(t => {
              gBrowser.moveTabAfter(t, tabToMoveAt);
            });
          }
    
          lastGroupStart = null;
          lastGroupEnd = null;
        }
      }
    
      // =========================================================
      // EINMALIGE INITIALISIERUNG
      // =========================================================
      tabContainer.addEventListener("dragstart", () => {
        if (listenersActive) return;
    
        tabContainer.on_dragover = e => performTabDragOver(e);
        tabContainer._onDragOver = e => performTabDragOver(e);
        tabContainer.ondrop = e => performTabDropEvent(e);
    
        tabContainer.addEventListener("dragend", clearDndMarkers, true);
        tabContainer.addEventListener("drop", clearDndMarkers, true);
    
        listenersActive = true;
      });
    }
    Alles anzeigen
  • Mehrzeilige Tableiste für aktuelle Firefox-Versionen

    • FuchsFan
    • 23. März 2026 um 10:16
    Zitat von yaqwa

    hat funktioniert - vielen Dank

    Freut mich!:thumbup:

Unterstütze uns!

Jährlich (2026)

71,6 %

71,6% (554,79 von 775 EUR)

Jetzt spenden
  1. Kontakt
  2. Datenschutz
  3. Impressum
Community-Software: WoltLab Suite™
Mastodon