From 1a7d4b476bba727ed8a8f1cac68a7c9d05a6378f Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Sat, 27 Dec 2025 01:26:42 +0200 Subject: [PATCH 1/8] implement firefox like behaviour for selecting tabs after closing current tab --- qutebrowser/browser/commands.py | 6 +- qutebrowser/config/configtypes.py | 5 ++ qutebrowser/mainwindow/tabbedbrowser.py | 54 +++++++++++++- qutebrowser/mainwindow/tabwidget.py | 7 +- tests/end2end/features/tabs_firefox.feature | 78 +++++++++++++++++++++ 5 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 tests/end2end/features/tabs_firefox.feature diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index ebce4b37a..548ac6738 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -208,6 +208,10 @@ class CommandDispatcher: raise cmdutils.CommandError( "-o is not supported with 'tabs.select_on_remove' set to " "'last-used'!") + elif conf_selection == 'firefox': + raise cmdutils.CommandError( + "-o is not supported with 'tabs.select_on_remove' set to " + "'firefox'!") else: # pragma: no cover raise ValueError("Invalid select_on_remove value " "{!r}!".format(conf_selection)) @@ -233,7 +237,7 @@ class CommandDispatcher: else: old_selection_behavior = tabbar.selectionBehaviorOnRemove() tabbar.setSelectionBehaviorOnRemove(selection_override) - self._tabbed_browser.close_tab(tab) + self._tabbed_browser.close_tab(tab, allow_firefox_behavior=False) tabbar.setSelectionBehaviorOnRemove(old_selection_behavior) @cmdutils.register(instance='command-dispatcher', scope='window') diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index a64600652..d80709de5 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1890,6 +1890,11 @@ class SelectOnRemove(MappingType): QTabBar.SelectionBehavior.SelectPreviousTab, "Select the previously selected tab.", ), + 'firefox': ( + 'firefox', + ("Select the tab that was opened before this tab (if closed immediately " + "without switching away). Falls back to 'next' otherwise."), + ), } diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index e0938ae36..3f2828871 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -15,7 +15,7 @@ from typing import ( Any, Optional) from collections.abc import Mapping, MutableMapping, MutableSequence -from qutebrowser.qt.widgets import QSizePolicy, QWidget, QApplication +from qutebrowser.qt.widgets import QSizePolicy, QWidget, QApplication, QTabBar from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QTimer, QUrl, QPoint from qutebrowser.config import config @@ -242,6 +242,8 @@ class TabbedBrowser(QWidget): self.default_window_icon = self._window().windowIcon() self.is_private = private self.tab_deque = TabDeque() + # Last opened tab tracking for tabs.select_on_remove = 'firefox' + self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None config.instance.changed.connect(self._on_config_changed) quitter.instance.shutting_down.connect(self.shutdown) @@ -444,7 +446,30 @@ class TabbedBrowser(QWidget): else: yes_action() - def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False): + def _should_select_opener(self, tab): + """Check if we should select the opener tab (behave like last-used). + + Args: + tab: The tab that is about to be closed. + + Return: + True if we should fall back to 'last-used' behavior to select the opener. + False if we should stick to the default (next). + """ + # Only apply if config is 'firefox' mode + if config.val.tabs.select_on_remove != 'firefox': + return False + + if self._opened_tab is None: + return False + + opened = self._opened_tab() + self._opened_tab = None # Clear state + + return opened is tab + + def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False, + allow_firefox_behavior=True): """Close a tab. Args: @@ -452,6 +477,7 @@ class TabbedBrowser(QWidget): add_undo: Whether the tab close can be undone. new_undo: Whether the undo entry should be a new item in the stack. transfer: Whether the tab is closing because it is moving to a new window. + allow_firefox_behavior: Whether to try selecting the 'opener' tab (if configured). """ if config.val.tabs.tabs_are_windows or transfer: last_close = 'close' @@ -463,8 +489,20 @@ class TabbedBrowser(QWidget): if last_close == 'ignore' and count == 1: return + # Handle 'firefox' selection mode + restore_behavior = None + if allow_firefox_behavior and self._should_select_opener(tab): + # Temporarily switch to 'last-used' behavior, which will select the 'opener' + # since we verified the relationship and navigation state. + tabbar = self.widget.tabBar() + restore_behavior = tabbar.selectionBehaviorOnRemove() + tabbar.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectPreviousTab) + self._remove_tab(tab, add_undo=add_undo, new_undo=new_undo) + if restore_behavior is not None: + self.widget.tabBar().setSelectionBehaviorOnRemove(restore_behavior) + if count == 1: # We just closed the last tab above. if last_close == 'close': self.close_window.emit() @@ -659,6 +697,11 @@ class TabbedBrowser(QWidget): if background is None: background = config.val.tabs.background + + # Track opened tab for tabs.select_on_remove = 'firefox' + if self.widget.count() > 0: + self._opened_tab = weakref.ref(tab) + if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the @@ -904,6 +947,13 @@ class TabbedBrowser(QWidget): .format(idx)) return + # Clear state if user switched to a tab that's not the opened tab + if self._opened_tab is not None: + opened = self._opened_tab() + if tab is not opened: + # User navigated to a third tab, forget opened tab + self._opened_tab = None + log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 8d50ac45d..8a6e686a0 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -70,7 +70,12 @@ class TabWidget(QTabWidget): tabbar = self.tab_bar() tabbar.vertical = position in [ QTabWidget.TabPosition.West, QTabWidget.TabPosition.East] - tabbar.setSelectionBehaviorOnRemove(selection_behavior) + # 'firefox' mode uses custom selection logic; fall back to 'next' for Qt + if selection_behavior == 'firefox': + tabbar.setSelectionBehaviorOnRemove( + QTabBar.SelectionBehavior.SelectRightTab) + else: + tabbar.setSelectionBehaviorOnRemove(selection_behavior) tabbar.refresh() def tab_bar(self) -> "TabBar": diff --git a/tests/end2end/features/tabs_firefox.feature b/tests/end2end/features/tabs_firefox.feature new file mode 100644 index 000000000..549c0112e --- /dev/null +++ b/tests/end2end/features/tabs_firefox.feature @@ -0,0 +1,78 @@ +Feature: Tab selection on remove (firefox behavior) + Tests for tabs.select_on_remove = firefox + + Background: + Given I clean up open tabs + And I set tabs.tabs_are_windows to false + And I set tabs.background to false + And I clear the log + + Scenario: :tab-close with tabs.select_on_remove = firefox + When I set tabs.select_on_remove to firefox + And I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I open data/numbers/4.txt in a new tab + And I run :tab-focus 2 + And I run :tab-close + Then the following tabs should be open: + """ + - data/numbers/1.txt + - data/numbers/3.txt (active) + - data/numbers/4.txt + """ + + Scenario: :tab-close with tabs.select_on_remove = firefox + When I set tabs.select_on_remove to firefox + And I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I run :tab-focus 1 + And I open data/numbers/4.txt in a new tab + And I run :tab-close + Then the following tabs should be open: + """ + - data/numbers/1.txt (active) + - data/numbers/2.txt + - data/numbers/3.txt + """ + + Scenario: Error with --opposite + When I set tabs.select_on_remove to firefox + And I run :tab-close --opposite + Then the error "-o is not supported with 'tabs.select_on_remove' set to 'firefox'!" should be shown + + Scenario: Override with --next + When I set tabs.select_on_remove to firefox + And I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I open data/numbers/4.txt in a new tab + And I run :tab-focus 2 + And I run :tab-close --next + Then the following tabs should be open: + """ + - data/numbers/1.txt + - data/numbers/3.txt (active) + - data/numbers/4.txt + """ + + Scenario: Override with --prev + When I set tabs.select_on_remove to firefox + And I open data/numbers/1.txt + And I open data/numbers/2.txt in a new tab + And I open data/numbers/3.txt in a new tab + And I open data/numbers/4.txt in a new tab + And I run :tab-focus 3 + And I run :tab-close --prev + Then the following tabs should be open: + """ + - data/numbers/1.txt + - data/numbers/2.txt (active) + - data/numbers/4.txt + """ + + Scenario: :tab-close with tabs.select_on_remove = firefox and --opposite + When I set tabs.select_on_remove to firefox + And I run :tab-close --opposite + Then the error "-o is not supported with 'tabs.select_on_remove' set to 'firefox'!" should be shown From df1893860c1e3ca424a7b51c98e893ccadbcb920 Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Sat, 27 Dec 2025 19:03:30 +0200 Subject: [PATCH 2/8] using self.widget.tab_bar() instead of self.widget.tabBar() --- qutebrowser/mainwindow/tabbedbrowser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 3f2828871..a96c909a5 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -494,14 +494,14 @@ class TabbedBrowser(QWidget): if allow_firefox_behavior and self._should_select_opener(tab): # Temporarily switch to 'last-used' behavior, which will select the 'opener' # since we verified the relationship and navigation state. - tabbar = self.widget.tabBar() + tabbar = self.widget.tab_bar() restore_behavior = tabbar.selectionBehaviorOnRemove() tabbar.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectPreviousTab) self._remove_tab(tab, add_undo=add_undo, new_undo=new_undo) if restore_behavior is not None: - self.widget.tabBar().setSelectionBehaviorOnRemove(restore_behavior) + self.widget.tab_bar().setSelectionBehaviorOnRemove(restore_behavior) if count == 1: # We just closed the last tab above. if last_close == 'close': From 68ccc3c136e163a50961323ca200dabfc266f6ac Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Mon, 29 Dec 2025 21:21:47 +0200 Subject: [PATCH 3/8] dont forget state on closing unrelated tab; forget state if more than one background children is created --- qutebrowser/mainwindow/tabbedbrowser.py | 11 ++++++-- tests/end2end/features/tabs_firefox.feature | 31 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index a96c909a5..52c96f795 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -464,9 +464,11 @@ class TabbedBrowser(QWidget): return False opened = self._opened_tab() - self._opened_tab = None # Clear state + if opened is tab: + self._opened_tab = None # Consume state + return True - return opened is tab + return False def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False, allow_firefox_behavior=True): @@ -700,7 +702,10 @@ class TabbedBrowser(QWidget): # Track opened tab for tabs.select_on_remove = 'firefox' if self.widget.count() > 0: - self._opened_tab = weakref.ref(tab) + if self._opened_tab is not None and background: + self._opened_tab = None + else: + self._opened_tab = weakref.ref(tab) if background: # Make sure the background tab has the correct initial size. diff --git a/tests/end2end/features/tabs_firefox.feature b/tests/end2end/features/tabs_firefox.feature index 549c0112e..87bfbd123 100644 --- a/tests/end2end/features/tabs_firefox.feature +++ b/tests/end2end/features/tabs_firefox.feature @@ -76,3 +76,34 @@ Feature: Tab selection on remove (firefox behavior) When I set tabs.select_on_remove to firefox And I run :tab-close --opposite Then the error "-o is not supported with 'tabs.select_on_remove' set to 'firefox'!" should be shown + + Scenario: Opening a second background tab forgets the state + When I set tabs.select_on_remove to firefox + And I open data/numbers/1.txt + And I open data/numbers/4.txt in a new tab + And I run :tab-focus 1 + And I open data/numbers/2.txt in a new background tab + And I open data/numbers/3.txt in a new background tab + And I run :tab-focus 3 + And I run :tab-close + Then the following tabs should be open: + """ + - data/numbers/1.txt + - data/numbers/2.txt + - data/numbers/4.txt (active) + """ + + Scenario: Opening a foreground tab creates a state + When I set tabs.select_on_remove to firefox + And I open data/numbers/1.txt + And I open data/numbers/4.txt in a new tab + And I run :tab-focus 1 + And I open data/numbers/2.txt in a new background tab + And I open data/numbers/3.txt in a new tab + And I run :tab-close + Then the following tabs should be open: + """ + - data/numbers/1.txt (active) + - data/numbers/4.txt + - data/numbers/2.txt + """ From 33c17fd23893f773bb759819845c74077f699b05 Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Mon, 29 Dec 2025 22:14:12 +0200 Subject: [PATCH 4/8] make changes more extensible and less intrusive --- qutebrowser/browser/commands.py | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 117 +++++++++++++++--------- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 548ac6738..36e3bda40 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -237,7 +237,7 @@ class CommandDispatcher: else: old_selection_behavior = tabbar.selectionBehaviorOnRemove() tabbar.setSelectionBehaviorOnRemove(selection_override) - self._tabbed_browser.close_tab(tab, allow_firefox_behavior=False) + self._tabbed_browser.close_tab(tab, allow_selection_strategy=False) tabbar.setSelectionBehaviorOnRemove(old_selection_behavior) @cmdutils.register(instance='command-dispatcher', scope='window') diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 52c96f795..60c55aec4 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -135,6 +135,62 @@ class TabDeletedError(Exception): """Exception raised when _tab_index is called for a deleted tab.""" +class SelectionStrategy: + + """Base class for tab selection strategies (on remove).""" + + def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab, + related: bool, background: bool) -> None: + """Called when a new tab is opened.""" + + def on_current_changed(self, tabbed_browser: "TabbedBrowser", + tab: browsertab.AbstractTab) -> None: + """Called when the current tab changes.""" + + def should_select_parent(self, tabbed_browser: "TabbedBrowser", + tab: browsertab.AbstractTab) -> bool: + """Return True if we should select the parent/opener instead of default behavior.""" + return False + + +class FirefoxSelectionStrategy(SelectionStrategy): + + """Strategy implementing Firefox-like "return to parent" behavior.""" + + def __init__(self) -> None: + self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None + + def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab, + related: bool, background: bool) -> None: + # Track opened tab + if tabbed_browser.widget.count() > 0: + if self._opened_tab is not None and background: + self._opened_tab = None + else: + self._opened_tab = weakref.ref(tab) + + def on_current_changed(self, tabbed_browser: "TabbedBrowser", + tab: browsertab.AbstractTab) -> None: + # Clear state if user switched to a tab that's not the opened tab + if self._opened_tab is not None: + opened = self._opened_tab() + if tab is not opened: + # User navigated to a third tab, forget opened tab + self._opened_tab = None + + def should_select_parent(self, tabbed_browser: "TabbedBrowser", + tab: browsertab.AbstractTab) -> bool: + if self._opened_tab is None: + return False + + opened = self._opened_tab() + if opened is tab: + self._opened_tab = None # Consume state + return True + + return False + + class TabbedBrowser(QWidget): """A TabWidget with QWebViews inside. @@ -242,8 +298,8 @@ class TabbedBrowser(QWidget): self.default_window_icon = self._window().windowIcon() self.is_private = private self.tab_deque = TabDeque() - # Last opened tab tracking for tabs.select_on_remove = 'firefox' - self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None + self._selection_strategy: SelectionStrategy = SelectionStrategy() + self._update_selection_strategy() config.instance.changed.connect(self._on_config_changed) quitter.instance.shutting_down.connect(self.shutdown) @@ -254,6 +310,16 @@ class TabbedBrowser(QWidget): # We can't resize a collections.deque so just recreate it >:( self.undo_stack = collections.deque(self.undo_stack, maxlen=newsize) + def _update_selection_strategy(self): + """Update the selection strategy based on config.""" + strategy = config.val.tabs.select_on_remove + if strategy == 'firefox': + if not isinstance(self._selection_strategy, FirefoxSelectionStrategy): + self._selection_strategy = FirefoxSelectionStrategy() + else: + if isinstance(self._selection_strategy, FirefoxSelectionStrategy): + self._selection_strategy = SelectionStrategy() + def __repr__(self): return utils.get_repr(self, count=self.widget.count()) @@ -269,6 +335,8 @@ class TabbedBrowser(QWidget): self.widget.update_tab_titles() elif option == "tabs.focus_stack_size": self.tab_deque.update_size() + elif option == "tabs.select_on_remove": + self._update_selection_strategy() def _tab_index(self, tab): """Get the index of a given tab. @@ -446,32 +514,8 @@ class TabbedBrowser(QWidget): else: yes_action() - def _should_select_opener(self, tab): - """Check if we should select the opener tab (behave like last-used). - - Args: - tab: The tab that is about to be closed. - - Return: - True if we should fall back to 'last-used' behavior to select the opener. - False if we should stick to the default (next). - """ - # Only apply if config is 'firefox' mode - if config.val.tabs.select_on_remove != 'firefox': - return False - - if self._opened_tab is None: - return False - - opened = self._opened_tab() - if opened is tab: - self._opened_tab = None # Consume state - return True - - return False - def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False, - allow_firefox_behavior=True): + allow_selection_strategy=True): """Close a tab. Args: @@ -479,7 +523,7 @@ class TabbedBrowser(QWidget): add_undo: Whether the tab close can be undone. new_undo: Whether the undo entry should be a new item in the stack. transfer: Whether the tab is closing because it is moving to a new window. - allow_firefox_behavior: Whether to try selecting the 'opener' tab (if configured). + allow_selection_strategy: Whether to try selecting the 'opener' tab (if configured). """ if config.val.tabs.tabs_are_windows or transfer: last_close = 'close' @@ -491,11 +535,9 @@ class TabbedBrowser(QWidget): if last_close == 'ignore' and count == 1: return - # Handle 'firefox' selection mode restore_behavior = None - if allow_firefox_behavior and self._should_select_opener(tab): + if allow_selection_strategy and self._selection_strategy.should_select_parent(self, tab): # Temporarily switch to 'last-used' behavior, which will select the 'opener' - # since we verified the relationship and navigation state. tabbar = self.widget.tab_bar() restore_behavior = tabbar.selectionBehaviorOnRemove() tabbar.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectPreviousTab) @@ -700,12 +742,7 @@ class TabbedBrowser(QWidget): if background is None: background = config.val.tabs.background - # Track opened tab for tabs.select_on_remove = 'firefox' - if self.widget.count() > 0: - if self._opened_tab is not None and background: - self._opened_tab = None - else: - self._opened_tab = weakref.ref(tab) + self._selection_strategy.on_tab_opened(self, tab, related, background) if background: # Make sure the background tab has the correct initial size. @@ -953,11 +990,7 @@ class TabbedBrowser(QWidget): return # Clear state if user switched to a tab that's not the opened tab - if self._opened_tab is not None: - opened = self._opened_tab() - if tab is not opened: - # User navigated to a third tab, forget opened tab - self._opened_tab = None + self._selection_strategy.on_current_changed(self, tab) log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() From 1119fa26129ac60b6e37628f071e367f8d58e3c4 Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Mon, 29 Dec 2025 23:31:10 +0200 Subject: [PATCH 5/8] fix linter warning --- qutebrowser/mainwindow/tabbedbrowser.py | 29 ++++++++++++++----------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 60c55aec4..4b43147e5 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -139,16 +139,16 @@ class SelectionStrategy: """Base class for tab selection strategies (on remove).""" - def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab, - related: bool, background: bool) -> None: + def on_tab_opened(self, _tabbed_browser: "TabbedBrowser", _tab: browsertab.AbstractTab, + _related: bool, _background: bool) -> None: """Called when a new tab is opened.""" - def on_current_changed(self, tabbed_browser: "TabbedBrowser", - tab: browsertab.AbstractTab) -> None: + def on_current_changed(self, _tabbed_browser: "TabbedBrowser", + _tab: browsertab.AbstractTab) -> None: """Called when the current tab changes.""" - def should_select_parent(self, tabbed_browser: "TabbedBrowser", - tab: browsertab.AbstractTab) -> bool: + def should_select_parent(self, _tabbed_browser: "TabbedBrowser", + _tab: browsertab.AbstractTab) -> bool: """Return True if we should select the parent/opener instead of default behavior.""" return False @@ -312,13 +312,16 @@ class TabbedBrowser(QWidget): def _update_selection_strategy(self): """Update the selection strategy based on config.""" - strategy = config.val.tabs.select_on_remove - if strategy == 'firefox': - if not isinstance(self._selection_strategy, FirefoxSelectionStrategy): - self._selection_strategy = FirefoxSelectionStrategy() - else: - if isinstance(self._selection_strategy, FirefoxSelectionStrategy): - self._selection_strategy = SelectionStrategy() + strategy_map = { + "default": SelectionStrategy, + "firefox": FirefoxSelectionStrategy, + } + + strategy_key = config.val.tabs.select_on_remove or "default" + strategy_cls = strategy_map.get(strategy_key, SelectionStrategy) + + if type(self._selection_strategy) is not strategy_cls: + self._selection_strategy = strategy_cls() def __repr__(self): return utils.get_repr(self, count=self.widget.count()) From 8f781ada7f7cdba5caeb189d9afd99fcc6a4dcd9 Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Tue, 30 Dec 2025 11:46:50 +0200 Subject: [PATCH 6/8] remove unused parameters; disable pylint for type check --- qutebrowser/mainwindow/tabbedbrowser.py | 26 ++++++++++--------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 4b43147e5..dabad788e 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -139,16 +139,13 @@ class SelectionStrategy: """Base class for tab selection strategies (on remove).""" - def on_tab_opened(self, _tabbed_browser: "TabbedBrowser", _tab: browsertab.AbstractTab, - _related: bool, _background: bool) -> None: + def on_tab_opened(self, _tabbed_browser: "TabbedBrowser", _tab: browsertab.AbstractTab, _background: bool) -> None: """Called when a new tab is opened.""" - def on_current_changed(self, _tabbed_browser: "TabbedBrowser", - _tab: browsertab.AbstractTab) -> None: + def on_current_changed(self, _tab: browsertab.AbstractTab) -> None: """Called when the current tab changes.""" - def should_select_parent(self, _tabbed_browser: "TabbedBrowser", - _tab: browsertab.AbstractTab) -> bool: + def should_select_parent(self, _tab: browsertab.AbstractTab) -> bool: """Return True if we should select the parent/opener instead of default behavior.""" return False @@ -160,8 +157,7 @@ class FirefoxSelectionStrategy(SelectionStrategy): def __init__(self) -> None: self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None - def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab, - related: bool, background: bool) -> None: + def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab, background: bool) -> None: # Track opened tab if tabbed_browser.widget.count() > 0: if self._opened_tab is not None and background: @@ -169,8 +165,7 @@ class FirefoxSelectionStrategy(SelectionStrategy): else: self._opened_tab = weakref.ref(tab) - def on_current_changed(self, tabbed_browser: "TabbedBrowser", - tab: browsertab.AbstractTab) -> None: + def on_current_changed(self, tab: browsertab.AbstractTab) -> None: # Clear state if user switched to a tab that's not the opened tab if self._opened_tab is not None: opened = self._opened_tab() @@ -178,8 +173,7 @@ class FirefoxSelectionStrategy(SelectionStrategy): # User navigated to a third tab, forget opened tab self._opened_tab = None - def should_select_parent(self, tabbed_browser: "TabbedBrowser", - tab: browsertab.AbstractTab) -> bool: + def should_select_parent(self, tab: browsertab.AbstractTab) -> bool: if self._opened_tab is None: return False @@ -320,7 +314,7 @@ class TabbedBrowser(QWidget): strategy_key = config.val.tabs.select_on_remove or "default" strategy_cls = strategy_map.get(strategy_key, SelectionStrategy) - if type(self._selection_strategy) is not strategy_cls: + if type(self._selection_strategy) is not strategy_cls: # pylint: disable=unidiomatic-typecheck self._selection_strategy = strategy_cls() def __repr__(self): @@ -539,7 +533,7 @@ class TabbedBrowser(QWidget): return restore_behavior = None - if allow_selection_strategy and self._selection_strategy.should_select_parent(self, tab): + if allow_selection_strategy and self._selection_strategy.should_select_parent(tab): # Temporarily switch to 'last-used' behavior, which will select the 'opener' tabbar = self.widget.tab_bar() restore_behavior = tabbar.selectionBehaviorOnRemove() @@ -745,7 +739,7 @@ class TabbedBrowser(QWidget): if background is None: background = config.val.tabs.background - self._selection_strategy.on_tab_opened(self, tab, related, background) + self._selection_strategy.on_tab_opened(self, tab, background) if background: # Make sure the background tab has the correct initial size. @@ -993,7 +987,7 @@ class TabbedBrowser(QWidget): return # Clear state if user switched to a tab that's not the opened tab - self._selection_strategy.on_current_changed(self, tab) + self._selection_strategy.on_current_changed(tab) log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() From 3456460da6f61ec632886c67bf2ad3518b2f9b89 Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Tue, 30 Dec 2025 11:54:28 +0200 Subject: [PATCH 7/8] fix lint error two spaces before comment --- qutebrowser/mainwindow/tabbedbrowser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index dabad788e..c2fc156b2 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -314,7 +314,7 @@ class TabbedBrowser(QWidget): strategy_key = config.val.tabs.select_on_remove or "default" strategy_cls = strategy_map.get(strategy_key, SelectionStrategy) - if type(self._selection_strategy) is not strategy_cls: # pylint: disable=unidiomatic-typecheck + if type(self._selection_strategy) is not strategy_cls: # pylint: disable=unidiomatic-typecheck self._selection_strategy = strategy_cls() def __repr__(self): From 78ea30215fb580af5df3ba6bec7636eb51d5d1f2 Mon Sep 17 00:00:00 2001 From: Stilian Iliev Date: Tue, 30 Dec 2025 14:37:51 +0200 Subject: [PATCH 8/8] standardize tab selection terminology to "opener" --- qutebrowser/mainwindow/tabbedbrowser.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index c2fc156b2..591a7ae8e 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -145,20 +145,20 @@ class SelectionStrategy: def on_current_changed(self, _tab: browsertab.AbstractTab) -> None: """Called when the current tab changes.""" - def should_select_parent(self, _tab: browsertab.AbstractTab) -> bool: - """Return True if we should select the parent/opener instead of default behavior.""" + def should_select_opener(self, _tab: browsertab.AbstractTab) -> bool: + """Check if we should return to the opener tab.""" return False class FirefoxSelectionStrategy(SelectionStrategy): - """Strategy implementing Firefox-like "return to parent" behavior.""" + """Strategy for Firefox-like "return to opener" behavior.""" def __init__(self) -> None: self._opened_tab: Optional[weakref.ReferenceType[browsertab.AbstractTab]] = None def on_tab_opened(self, tabbed_browser: "TabbedBrowser", tab: browsertab.AbstractTab, background: bool) -> None: - # Track opened tab + # Track relationship if tabbed_browser.widget.count() > 0: if self._opened_tab is not None and background: self._opened_tab = None @@ -166,14 +166,13 @@ class FirefoxSelectionStrategy(SelectionStrategy): self._opened_tab = weakref.ref(tab) def on_current_changed(self, tab: browsertab.AbstractTab) -> None: - # Clear state if user switched to a tab that's not the opened tab + # Clear state if user switched away if self._opened_tab is not None: opened = self._opened_tab() if tab is not opened: - # User navigated to a third tab, forget opened tab self._opened_tab = None - def should_select_parent(self, tab: browsertab.AbstractTab) -> bool: + def should_select_opener(self, tab: browsertab.AbstractTab) -> bool: if self._opened_tab is None: return False @@ -533,8 +532,8 @@ class TabbedBrowser(QWidget): return restore_behavior = None - if allow_selection_strategy and self._selection_strategy.should_select_parent(tab): - # Temporarily switch to 'last-used' behavior, which will select the 'opener' + if allow_selection_strategy and self._selection_strategy.should_select_opener(tab): + # Temporarily switch to 'last-used' behavior to select the opener tabbar = self.widget.tab_bar() restore_behavior = tabbar.selectionBehaviorOnRemove() tabbar.setSelectionBehaviorOnRemove(QTabBar.SelectionBehavior.SelectPreviousTab)