diff --git a/addon/chrome/content/minit/minit.js b/addon/chrome/content/minit/minit.js index ebabd3a1..4844fa59 100644 --- a/addon/chrome/content/minit/minit.js +++ b/addon/chrome/content/minit/minit.js @@ -901,6 +901,7 @@ Tabmix.navToolbox = { gNavToolbox.addEventListener("beforecustomization", this); gNavToolbox.addEventListener("aftercustomization", this); + /** @type {CustomizableUIListener["onWidgetAfterDOMChange"]} */ const onWidgetAfterDOMChange = (aNode, aNextNode, aContainer, aWasRemoval) => { if (this.customizeStarted) return; @@ -927,15 +928,6 @@ Tabmix.navToolbox = { gNavToolbox.removeEventListener("beforecustomization", this); gNavToolbox.removeEventListener("aftercustomization", this); - // remove tabmix-tabs-closebutton when its position is immediately after - // tabmix-scrollbox and save its position in preference for future use. - let boxPosition = Tabmix.getPlacement("tabmix-scrollbox"); - let buttonPosition = Tabmix.getPlacement("tabmix-tabs-closebutton"); - if (buttonPosition == boxPosition + 1) { - Tabmix.prefs.setIntPref("tabs-closeButton-position", buttonPosition); - CustomizableUI.removeWidgetFromArea("tabmix-tabs-closebutton"); - } - CustomizableUI.removeWidgetFromArea("tabmix-scrollbox"); CustomizableUI.removeListener(this.listener); @@ -1340,13 +1332,13 @@ Tabmix.navToolbox = { * we restore tabmix-scrollbox position first since its position is fixed, * to be on the safe side we check tabmix-scrollbox position again after we * restore tabmix-tabs-closebutton and new-tab-button position. + * see ScriptsLoader._addCloseButton and VerticalTabs how we handle tabmix-tabs-closebutton */ - this.setScrollButtons(); - try { - this.setCloseButtonPosition(); - } catch { } - gTMPprefObserver.changeNewTabButtonSide(Tabmix.prefs.getIntPref("newTabButton.position")); - this.setScrollButtons(false, true); + if (!Tabmix.tabsUtils.isVerticalTabBar) { + this.setScrollButtons(); + gTMPprefObserver.changeNewTabButtonSide(Tabmix.prefs.getIntPref("newTabButton.position")); + this.setScrollButtons(false, true); + } // reset tabsNewtabButton and afterTabsButtonsWidth if (typeof privateTab == "object") @@ -1354,6 +1346,9 @@ Tabmix.navToolbox = { }, setScrollButtons(reset, onlyPosition) { + if (Tabmix.tabsUtils.isVerticalTabBar) { + return; + } let box = document.getElementById("tabmix-scrollbox"); if (box?.parentNode !== gBrowser.tabContainer.parentNode) { // nothing to do here when our button box is not a sibling of gBrowser.tabContainer @@ -1372,32 +1367,6 @@ Tabmix.navToolbox = { } }, - _closeButtonInitialized: false, - setCloseButtonPosition() { - if (this._closeButtonInitialized) - return; - - // if tabmix-tabs-closebutton was positioned immediately after - // tabmix-scrollbox we removed the button on exit, to avoid bug 1034394. - let pref = "tabs-closeButton-position"; - if (Tabmix.prefs.prefHasUserValue(pref)) { - let position = Tabmix.prefs.getIntPref(pref); - Tabmix.prefs.clearUserPref(pref); - CustomizableUI.moveWidgetWithinArea("tabmix-tabs-closebutton", position); - } else if (!document.getElementById("tabs-closebutton")) { - // try to restore button position from tabs-closebutton position - // if item with tabs-closebutton id exist, some other extension add it - // will throw if called too early (before placements have been fetched) - let currentset = CustomizableUI.getWidgetIdsInArea("TabsToolbar"); - let position = currentset.indexOf("tabs-closebutton"); - if (position > -1) { - CustomizableUI.removeWidgetFromArea("tabs-closebutton"); - CustomizableUI.moveWidgetWithinArea("tabmix-tabs-closebutton", position); - } - } - this._closeButtonInitialized = true; - } - }; Tabmix.getPlacement = function(id) { diff --git a/addon/chrome/content/overlay/browser.css b/addon/chrome/content/overlay/browser.css index 18c44a3a..00238c4b 100644 --- a/addon/chrome/content/overlay/browser.css +++ b/addon/chrome/content/overlay/browser.css @@ -167,8 +167,8 @@ visibility: visible; } -#TabsToolbar:not([customizing])[tabmix-show-newtabbutton*="aftertabs"] #new-tab-button, -#TabsToolbar:not([tabmix-show-newtabbutton]) #new-tab-button { +#TabsToolbar:not([customizing])[tabmix-show-newtabbutton*="aftertabs"] #new-tab-button, +#TabsToolbar:not([tabmix-show-newtabbutton]) #new-tab-button { visibility: collapse; } diff --git a/addon/chrome/content/preferences/appearance.js b/addon/chrome/content/preferences/appearance.js index 666c20a4..42322084 100644 --- a/addon/chrome/content/preferences/appearance.js +++ b/addon/chrome/content/preferences/appearance.js @@ -41,7 +41,6 @@ var gAppearancePane = { // rtl update position if (RTL_UI) { let right = $("newTabButton.position.right"); - // let left = $("newTabButton.position.left"); let left = $("newTabButton.position.left"); [right.label, left.label] = [left.label, right.label]; @@ -184,8 +183,7 @@ var gAppearancePane = { // Display > Tab bar function updateDisabledState(buttonID, itemID, aEnable) { - let button = aWindow.document.getElementById(buttonID); - let enablePosition = button && aWindow.document.getElementById("TabsToolbar").contains(button); + const enablePosition = Boolean(aWindow.CustomizableUI.getPlacementOfWidget(buttonID)); gPrefWindow.setDisabled(itemID, !enablePosition || null); gPrefWindow.setDisabled("obs_" + itemID, !aEnable || !enablePosition || null); } diff --git a/addon/chrome/content/tab/tab.js b/addon/chrome/content/tab/tab.js index c7184970..cf671c50 100644 --- a/addon/chrome/content/tab/tab.js +++ b/addon/chrome/content/tab/tab.js @@ -51,6 +51,15 @@ var TabmixTabbar = { return button && document.getElementById("TabsToolbar").contains(button); }, + isButtonOnToolBar(button) { + return ( + button && + (document.getElementById("TabsToolbar").contains(button) || + document.getElementById("nav-bar").contains(button) || + document.getElementById("widget-overflow-list").contains(button)) + ); + }, + // get privateTab-toolbar-openNewPrivateTab, when the button is on the tabbar newPrivateTabButton() { let button = document.getElementById("privateTab-toolbar-openNewPrivateTab"); @@ -149,12 +158,12 @@ var TabmixTabbar = { if (tabBar.mCloseButtons == 5) tabBar._updateCloseButtons(true); - // show on tabbar + // show on tabbar or nav-bar let tabstripClosebutton = document.getElementById("tabmix-tabs-closebutton"); - if (this.isButtonOnTabsToolBar(tabstripClosebutton)) + if (this.isButtonOnToolBar(tabstripClosebutton)) tabstripClosebutton.collapsed = Tabmix.prefs.getBoolPref("hideTabBarButton"); let allTabsButton = document.getElementById("alltabs-button"); - if (this.isButtonOnTabsToolBar(allTabsButton)) { + if (this.isButtonOnToolBar(allTabsButton)) { allTabsButton.collapsed = !Services.prefs.getBoolPref("browser.tabs.tabmanager.enabled"); Tabmix.setItem("tabbrowser-tabs", "showalltabsbutton", !allTabsButton.collapsed || null); } @@ -176,10 +185,13 @@ var TabmixTabbar = { }, setShowNewTabButtonAttr() { - let newTabButton = document.getElementById("new-tab-button"); let showNewTabButton = Tabmix.prefs.getBoolPref("newTabButton") && - this.isButtonOnTabsToolBar(newTabButton); + Boolean(CustomizableUI.getPlacementOfWidget("new-tab-button")); let position = Tabmix.prefs.getIntPref("newTabButton.position"); + if (position === 2 && gBrowser.tabContainer.verticalMode && Tabmix.tabsUtils.isVerticalTabs) { + position = 1; + Tabmix.prefs.setIntPref("newTabButton.position", 1); + } gTMPprefObserver.setShowNewTabButtonAttr(showNewTabButton, position); }, @@ -1804,13 +1816,6 @@ window.gTMPprefObserver = { const protonPrefVal = Services.prefs.getBoolPref("browser.proton.tabs.enabled", false); let newRule; if (!Tabmix.isVersion(880)) { - // the button is not ready when we call dynamicProtonRules early - // onContentLoaded>addDynamicRules>dynamicProtonRules - setTimeout(() => { - // tabmix-tabs-closebutton toolbarbutton - document.getElementById("tabmix-tabs-closebutton").setAttribute("tabmix-fill-opacity", true); - }, 100); - newRule = `#tabmix-tabs-closebutton[tabmix-fill-opacity] > .toolbarbutton-icon { padding: ${protonPrefVal ? 7.4 : 4}px 4px !important; }`; @@ -2289,9 +2294,6 @@ window.gTMPprefObserver = { }, changeNewTabButtonSide(aPosition) { - if (gBrowser.tabContainer.verticalMode && Tabmix.tabsUtils.isVerticalTabs) { - aPosition = 1; - } let $ = id => document.getElementById(id); let newTabButton = $("new-tab-button"); if (TabmixTabbar.isButtonOnTabsToolBar(newTabButton)) { @@ -2323,6 +2325,10 @@ window.gTMPprefObserver = { else doChange(); } + } else if (gBrowser.tabContainer.verticalMode && Tabmix.tabsUtils.isVerticalTabBar) { + let showNewTabButton = Tabmix.prefs.getBoolPref("newTabButton"); + this.setShowNewTabButtonAttr(showNewTabButton, 1); + Tabmix.sideNewTabButton = newTabButton; } else { this.setShowNewTabButtonAttr(false); Tabmix.sideNewTabButton = null; @@ -2346,10 +2352,7 @@ window.gTMPprefObserver = { let attrValue; if (!aShow) { attrValue = null; - } else if ( - aPosition == 1 || - gBrowser.tabContainer.verticalMode && Tabmix.tabsUtils.isVerticalTabs - ) { + } else if (aPosition == 1) { attrValue = "right-side"; } else if (aPosition === 0) { attrValue = "left-side"; @@ -2358,7 +2361,11 @@ window.gTMPprefObserver = { } // we use this value in disAllowNewtabbutton and overflow setters Tabmix.tabsUtils._show_newtabbutton = attrValue; - Tabmix.setItem("TabsToolbar", "tabmix-show-newtabbutton", attrValue); + if (gBrowser.tabContainer.verticalMode && Tabmix.tabsUtils.isVerticalTabs) { + Tabmix.setItem("new-tab-button", "collapsed", attrValue ? null : true); + } else { + Tabmix.setItem("TabsToolbar", "tabmix-show-newtabbutton", attrValue); + } }, tabBarPositionChanged(aPosition) { diff --git a/addon/chrome/content/tabmix.js b/addon/chrome/content/tabmix.js index a08704fe..297596a1 100644 --- a/addon/chrome/content/tabmix.js +++ b/addon/chrome/content/tabmix.js @@ -402,10 +402,11 @@ var TMP_eventListener = { } if (!CustomizableUI.getPlacementOfWidget("tabmix-scrollbox")) { + const tabsWidget = CustomizableUI.getPlacementOfWidget("tabbrowser-tabs"); CustomizableUI.addWidgetToArea( "tabmix-scrollbox", CustomizableUI.AREA_TABSTRIP, - CustomizableUI.getPlacementOfWidget("tabbrowser-tabs").position + 1 + tabsWidget?.area === CustomizableUI.AREA_TABSTRIP ? tabsWidget.position + 1 : 0 ); } @@ -564,25 +565,6 @@ var TMP_eventListener = { Tabmix.setNewFunction(tabBar, "_updateCloseButtons", Tabmix._updateCloseButtons); delete Tabmix._updateCloseButtons; - // update tooltip for tabmix-tabs-closebutton - const closeButton = document.getElementById("tabmix-tabs-closebutton"); - if (Tabmix.isVersion(1090)) { - const [l10Id, attrName] = Tabmix.isVersion(1310) ? ["tabbrowser-close-tabs-button", "tooltiptext"] : ["tabbrowser-close-tabs-tooltip", "label"]; - document.l10n.setAttributes(closeButton, l10Id, {tabCount: 1}); - document.l10n.translateElements([closeButton]).then(() => { - closeButton.removeAttribute("data-l10n-id"); - closeButton.setAttribute("tooltiptext", closeButton.getAttribute(attrName)); - }); - } else { - closeButton.setAttribute("tooltiptext", - window.PluralForm.get( - 1, - // eslint-disable-next-line no-undef - gTabBrowserBundle.GetStringFromName("tabs.closeTabs.tooltip") - ) - ); - } - Tabmix.allTabs.init(); MozXULElement.insertFTLIfNeeded("browser/tabContextMenu.ftl"); diff --git a/addon/chrome/content/tabmix.xhtml b/addon/chrome/content/tabmix.xhtml index c731128a..5473fc90 100644 --- a/addon/chrome/content/tabmix.xhtml +++ b/addon/chrome/content/tabmix.xhtml @@ -282,12 +282,6 @@ tooltip="tabmix-rows-tooltip" cui-areatype="toolbar" removable="false"/> - - diff --git a/addon/chrome/skin/general.css b/addon/chrome/skin/general.css index 87ccd857..5d47cfe8 100644 --- a/addon/chrome/skin/general.css +++ b/addon/chrome/skin/general.css @@ -22,10 +22,14 @@ width: unset; } -#tabmix-tabs-closebutton:hover { +#tabmix-tabs-closebutton:not([overflowedItem]):hover { background-color: transparent; } +#widget-overflow-list #tabmix-tabs-closebutton.close-icon > .toolbarbutton-text { + display: block; +} + /* :::: for light weight themes :::: */ #main-window[tabmix_lwt]:-moz-lwtheme { background-repeat: repeat-y !important; diff --git a/addon/modules/bootstrap/ScriptsLoader.jsm b/addon/modules/bootstrap/ScriptsLoader.jsm index 6c75c71b..1776a109 100644 --- a/addon/modules/bootstrap/ScriptsLoader.jsm +++ b/addon/modules/bootstrap/ScriptsLoader.jsm @@ -12,12 +12,17 @@ const {TabmixChromeUtils} = ChromeUtils.import("chrome://tabmix-resource/content const lazy = {}; TabmixChromeUtils.defineLazyModuleGetters(lazy, { + CustomizableUI: "resource:///modules/CustomizableUI.jsm", Overlays: "chrome://tabmix-resource/content/bootstrap/Overlays.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", SessionStore: "resource:///modules/sessionstore/SessionStore.jsm", }); -const isVersion119 = Services.vc.compare(Services.appinfo.version, "119.0a1"); +function isVersion(versionNo) { + return Services.vc.compare(Services.appinfo.version, versionNo / 10 + ".0a1") >= 0; +} + +const isVersion119 = isVersion(1190); /** * stylesheets and scripts for navigator:browser @@ -59,6 +64,7 @@ const ScriptsLoader = { } initialized.add(window); + this._addCloseButton(); this._loadCSS(window); this._loadScripts(window, promiseOverlayLoaded); this._addListeners(window); @@ -67,6 +73,25 @@ const ScriptsLoader = { } }, + _closeButtonAdded: false, + _addCloseButton() { + // since Firefox version 132, we need to allow tabmix-tabs-closebutton to move to nav-bar + // when vertical tabs are enabled + // we create it as widget and add it to the proper area TabsToolbar or nav-bar + // we add it after alltabs-button by default but keep its position if user + // move it in the toolbar + if (!this._closeButtonAdded) { + const allTabsButtonPlacement = lazy.CustomizableUI.getPlacementOfWidget("alltabs-button"); + const closeButtonPlacement = lazy.CustomizableUI.getPlacementOfWidget("tabmix-tabs-closebutton"); + if (!closeButtonPlacement || closeButtonPlacement.area !== allTabsButtonPlacement?.area) { + const {area, position} = allTabsButtonPlacement ?? {area: lazy.CustomizableUI.AREA_TABSTRIP}; + const finalPosition = typeof position === "number" ? position + 1 : undefined; + lazy.CustomizableUI.addWidgetToArea("tabmix-tabs-closebutton", area, finalPosition); + } + } + this._closeButtonAdded = true; + }, + _loadCSS(window) { const winUtils = window.windowUtils; for (const url of CSS_URLS) { diff --git a/addon/modules/bootstrap/TabmixWidgets.jsm b/addon/modules/bootstrap/TabmixWidgets.jsm index 66987531..e4b57967 100644 --- a/addon/modules/bootstrap/TabmixWidgets.jsm +++ b/addon/modules/bootstrap/TabmixWidgets.jsm @@ -98,16 +98,59 @@ const widgets = { `; }, }, + tabsCloseButton: { + id: "tabmix-tabs-closebutton", + localizeFiles: [], + get updateMarkup() { + const l10nId = lazy.TabmixSvc.version(1310) ? + "tabbrowser-close-tabs-button" : + lazy.TabmixSvc.version(1090) ? + "tabbrowser-close-tabs-tooltip" : + "close-tab"; + const markup = this.markup.replace('command="cmd_close"', `$& data-l10n-id="${l10nId}"`); + if (!lazy.TabmixSvc.version(880)) { + return markup.replace('command="cmd_close"', '$& tabmix-fill-opacity="true"'); + } + return markup; + }, + get markup() { + return ` + `; + }, + onBuild(document, node) { + if (!lazy.TabmixSvc.version(880)) { + document.ownerGlobal.MozXULElement.insertFTLIfNeeded("browser/tabContextMenu.ftl"); + } + document.l10n?.translateElements([node]).then(() => { + node.removeAttribute("data-l10n-id"); + node.removeAttribute("data-l10n-args"); + if (node.hasAttribute("label")) { + node.setAttribute("tooltiptext", node.getAttribute("label")); + } else if (node.hasAttribute("tooltiptext")) { + node.setAttribute("label", node.getAttribute("tooltiptext")); + } + }); + }, + }, }; function on_build(widget) { - const {markup, updateMarkup, localizeFiles} = widget; + const {markup, updateMarkup, localizeFiles, onBuild} = widget; return function(document) { const MozXULElement = document.ownerGlobal.MozXULElement; const node = MozXULElement.parseXULToFragment(updateMarkup ?? markup, localizeFiles); const parent = document.createXULElement("box"); parent.appendChild(node); - return parent.childNodes[0]; + const child = parent.childNodes[0]; + if (onBuild) { + onBuild(document, child); + } + return child; }; } diff --git a/types/general.d.ts b/types/general.d.ts index 72627a37..37c6a743 100644 --- a/types/general.d.ts +++ b/types/general.d.ts @@ -602,6 +602,22 @@ interface HistoryMenu { populateUndoWindowSubmenu: () => void; } +interface CustomizableUIListener { + onWidgetAfterDOMChange(aNode: Node, aNextNode: Node, aContainer: ParentNode, aWasRemoval: boolean): void; +} + +interface CustomizableUI { + AREA_TABSTRIP: string; + addListener: (aListener: CustomizableUIListener) => void; + addShortcut(aShortcutNode: Node, aTargetNode?: Node): void; + addWidgetToArea: (aWidgetId: string, aArea: string, aPosition: number, aInitialAdd?: boolean) => void; + getWidgetIdsInArea: (aArea: string) => string[]; + getPlacementOfWidget: (aWidgetId: string, aOnlyRegistered?: boolean, aDeadAreas?: boolean) => {area: string; position: number} | null; + moveWidgetWithinArea: (aWidgetId: string, aPosition: number) => void; + removeListener: (aListener: CustomizableUIListener) => void; + removeWidgetFromArea: (aWidgetId: string) => void; +} + interface E10SUtils { SERIALIZED_SYSTEMPRINCIPAL: string; DEFAULT_REMOTE_TYPE: string; @@ -693,6 +709,7 @@ declare function OpenBrowserWindow(params?: {private?: boolean; features?: strin declare function urlSecurityCheck(aURL: string, aPrincipal: nsIPrincipal, aFlags?: nsIScriptSecurityManager): void; declare function undoCloseWindow(index: number): void; +declare var CustomizableUI: CustomizableUI; declare var E10SUtils: E10SUtils; declare var FullScreen: FullScreen; declare var gBrowser: MockedGeckoTypes.TabBrowser; @@ -743,7 +760,6 @@ declare var BrowserUtils: any; declare var closeMenus: any; declare var Components: any; declare var ctrlTab: any; -declare var CustomizableUI: any; /** @deprecated removed from firefox on version 87 */ declare var getHtmlBrowser: any; declare var gMultiProcessBrowser: any; diff --git a/types/preferences.d.ts b/types/preferences.d.ts index 776ba7f9..dd355bc8 100644 --- a/types/preferences.d.ts +++ b/types/preferences.d.ts @@ -40,6 +40,7 @@ interface XULCommandDispatcher { interface Window { _sminstalled: boolean; $(selectors: K | string): K extends keyof GetByMap ? GetByMap[K] : HTMLElement | null; + CustomizableUI: typeof CustomizableUI; } interface EventTarget {