// ==UserScript== // @name Persistent Tab Renamer with Position Tracking // @namespace https://github.com/YourUsername/ // @version 2.1 // @author YourName // @license MIT // @compatibility Firefox 100+ // @include main // @run-at document-start // ==/UserScript== (function() { // ===== CONFIG ===== // const PERSISTENT_MODE_DEFAULT = true; // Default persistence state const CHECK_INTERVAL = 100; // Enforcement loop interval (ms) const BLOCK_TITLE_CHANGES = true; // Block document.title changes for renamed tabs // ===== STATE ===== // let persistentMode = PERSISTENT_MODE_DEFAULT; const renamedTabs = new Map(); // { tabId: customName } let hoveredTab = null; // Track currently hovered tab // ===== CORE FUNCTIONALITY ===== // function enforceTabNames() { const tabs = gBrowser.tabs || document.querySelectorAll('tab'); tabs.forEach((tab) => { const tabId = tab.getAttribute('linkedpanel') || tab.id; if (!tabId) return; const customName = renamedTabs.get(tabId); if (customName && persistentMode) { const label = tab.querySelector('.tab-text, .tab-label'); if (label && label.textContent !== customName) { label.textContent = customName; } } }); } function startTabRename(tab) { if (!tab) return; const tabId = tab.getAttribute('linkedpanel') || tab.id; const label = tab.querySelector('.tab-text, .tab-label'); if (!label) return; const originalText = label.textContent; const input = document.createElement('input'); Object.assign(input.style, { width: `${document.documentElement.clientWidth - 4}px`, height: `${label.clientHeight}px`, backgroundColor: 'var(--toolbar-field-background-color)', color: 'var(--toolbar-field-color)', border: '1px solid var(--toolbar-field-border-color)', borderRadius: '5px', padding: '0 2px', margin: '0', font: 'inherit' }); input.value = originalText; label.replaceWith(input); input.focus(); input.select(); const save = () => { const newText = input.value.trim(); if (newText && newText !== originalText) { renamedTabs.set(tabId, newText); label.textContent = newText; } input.replaceWith(label); }; input.addEventListener('keydown', (e) => { if (e.key === 'Enter') save(); else if (e.key === 'Escape') input.replaceWith(label); }); input.addEventListener('blur', save); } function handleTabLabelClick(event) { if (event.button !== 0 || event.detail !== 2) return; const label = event.target.closest('.tab-text, .tab-label'); if (!label) return; event.preventDefault(); event.stopPropagation(); const tab = label.closest('tab'); startTabRename(tab); } // ===== F2 RENAMING SUPPORT ===== // function handleTabHover(event) { const tab = event.target.closest('tab'); hoveredTab = tab || null; } function handleKeyDown(event) { if (event.key === 'F2' && hoveredTab) { event.preventDefault(); event.stopPropagation(); startTabRename(hoveredTab); } } // ===== TITLE CHANGE BLOCKING ===== // function blockTitleChanges() { if (!BLOCK_TITLE_CHANGES) return; const { content } = Components.utils.Sandbox(gBrowser, { sandboxPrototype: gBrowser, wantXrays: false }); Object.defineProperty(content.document.wrappedJSObject, 'title', { set: function(value) { const browser = gBrowser.getBrowserForDocument(this); if (browser) { const tabId = browser.outerWindowID; if (renamedTabs.has(tabId)) { return renamedTabs.get(tabId); // Block title change for renamed tabs } } // Allow title change for non-renamed tabs this._title = value; return value; }, get: function() { return this._title || ''; }, configurable: true, enumerable: true }); } // ===== INIT ===== // setInterval(enforceTabNames, CHECK_INTERVAL); const tabContainer = document.getElementById('tabbrowser-tabs'); // Event listeners tabContainer.addEventListener('click', handleTabLabelClick, true); tabContainer.addEventListener('mouseover', handleTabHover); tabContainer.addEventListener('mouseout', () => { hoveredTab = null; }); window.addEventListener('keydown', handleKeyDown, true); // Initialize title blocking if (BLOCK_TITLE_CHANGES) { if (gBrowser) { blockTitleChanges(); } else { window.addEventListener('load', blockTitleChanges, { once: true }); } } // Cleanup window.addEventListener('unload', () => { tabContainer.removeEventListener('click', handleTabLabelClick, true); tabContainer.removeEventListener('mouseover', handleTabHover); tabContainer.removeEventListener('mouseout', () => { hoveredTab = null; }); window.removeEventListener('keydown', handleKeyDown, true); }, { once: true }); })();