This commit is contained in:
Sirisak Lueangsaksri 2026-01-07 16:35:56 -08:00 committed by GitHub
commit 02e61e5008
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 205 additions and 10 deletions

View File

@ -127,6 +127,7 @@ class TabData:
open_target: usertypes.ClickTarget = usertypes.ClickTarget.normal
override_target: Optional[usertypes.ClickTarget] = None
pinned: bool = False
custom_title: Optional[str] = None
fullscreen: bool = False
netrc_used: bool = False
input_mode: usertypes.KeyMode = usertypes.KeyMode.normal
@ -1132,12 +1133,13 @@ class AbstractTab(QWidget):
qtutils.ensure_valid(url)
url_string = url.toDisplayString()
log.webview.debug("Going to start loading: {}".format(url_string))
self.title_changed.emit(url_string)
if not self.title() and not self.data.custom_title:
self.title_changed.emit(url_string)
@pyqtSlot(QUrl)
def _on_url_changed(self, url: QUrl) -> None:
"""Update title when URL has changed and no title is available."""
if url.isValid() and not self.title():
if url.isValid() and not self.title() and not self.data.custom_title:
self.title_changed.emit(url.toDisplayString())
self.url_changed.emit(url)
@ -1189,7 +1191,7 @@ class AbstractTab(QWidget):
self.load_finished.emit(ok)
if not self.title():
if not self.title() and not self.data.custom_title:
self.title_changed.emit(self.url().toDisplayString())
self.zoom.reapply()
@ -1285,9 +1287,25 @@ class AbstractTab(QWidget):
"""
raise NotImplementedError
def title(self) -> str:
def raw_title(self) -> str:
raise NotImplementedError
def title(self) -> str:
return self.data.custom_title or self.raw_title()
def set_title(self, title: str) -> None:
"""Set a custom tab's title, or reset it if empty is passed.
Args:
title: A custom tab's title
"""
if title:
self.data.custom_title = title
self.title_changed.emit(title)
else:
self.data.custom_title = None
self.title_changed.emit(self.raw_title())
def icon(self) -> QIcon:
raise NotImplementedError

View File

@ -258,6 +258,24 @@ class CommandDispatcher:
self._tabbed_browser.tab_close_prompt_if_pinned(tab, force, close)
@cmdutils.register(instance='command-dispatcher', scope='window',
name='tab-title')
@cmdutils.argument('count', value=cmdutils.Value.count)
@cmdutils.argument('title')
def tab_title(self, count=None, title=None):
"""Set/Unset the current/[count]th tab's title.
Set the text to empty to unset the tab's title.
Args:
count: The tab index to set or unset tab's title, or None
title: The tab title to renamed to, or empty to reset it
"""
tab = self._cntwidget(count)
if tab is None:
return
tab.set_title(title or '')
@cmdutils.register(instance='command-dispatcher', scope='window',
name='tab-pin')
@cmdutils.argument('count', value=cmdutils.Value.count)
@ -426,6 +444,7 @@ class CommandDispatcher:
newtab.history.private_api.deserialize(history)
newtab.zoom.set_factor(curtab.zoom.factor())
newtab.set_title(curtab.data.custom_title)
newtab.set_pinned(curtab.data.pinned)
return newtab

View File

@ -1406,7 +1406,7 @@ class WebEngineTab(browsertab.AbstractTab):
def stop(self):
self._widget.stop()
def title(self):
def raw_title(self):
return self._widget.title()
def renderer_process_pid(self) -> int:
@ -1724,6 +1724,12 @@ class WebEngineTab(browsertab.AbstractTab):
else:
selection.selectNone()
def _on_title_changed(self, title):
"""Handle title updates."""
if self.data.custom_title:
return
self.title_changed.emit(title)
def _connect_signals(self):
view = self._widget
page = view.page()
@ -1742,7 +1748,7 @@ class WebEngineTab(browsertab.AbstractTab):
page.printRequested.connect(self._on_print_requested)
page.selectClientCertificate.connect(self._on_select_client_certificate)
view.titleChanged.connect(self.title_changed)
view.titleChanged.connect(self._on_title_changed)
view.urlChanged.connect(self._on_url_changed)
view.renderProcessTerminated.connect(
self._on_render_process_terminated)

View File

@ -927,7 +927,7 @@ class WebKitTab(browsertab.AbstractTab):
def stop(self):
self._widget.stop()
def title(self):
def raw_title(self):
return self._widget.title()
def renderer_process_pid(self) -> Optional[int]:
@ -1017,6 +1017,12 @@ class WebKitTab(browsertab.AbstractTab):
def _on_ssl_errors(self, reply):
self._insecure_hosts.add(reply.url().host())
def _on_title_changed(self, title):
"""Handle title updates."""
if self.data.custom_title:
return
self.title_changed.emit(title)
def _connect_signals(self):
view = self._widget
page = view.page()
@ -1031,7 +1037,7 @@ class WebKitTab(browsertab.AbstractTab):
self._on_load_started)
view.scroll_pos_changed.connect(self.scroller.perc_changed)
view.titleChanged.connect( # type: ignore[attr-defined]
self.title_changed)
self._on_title_changed)
view.urlChanged.connect( # type: ignore[attr-defined]
self._on_url_changed)
view.shutting_down.connect(self.shutting_down)

View File

@ -3707,6 +3707,7 @@ bindings.default:
K: tab-prev
<Ctrl-PgUp>: tab-prev
gC: tab-clone
tt: cmd-set-text -s :tab-title
r: reload
<F5>: reload
R: reload -f

View File

@ -36,6 +36,7 @@ class _UndoEntry:
history: bytes
index: int
pinned: bool
title: Optional[str]
created_at: datetime.datetime = dataclasses.field(
default_factory=datetime.datetime.now)
@ -516,6 +517,7 @@ class TabbedBrowser(QWidget):
entry = _UndoEntry(url=tab.url(),
history=history_data,
index=idx,
title=tab.data.custom_title,
pinned=tab.data.pinned)
if new_undo or not self.undo_stack:
self.undo_stack.append([entry])
@ -563,6 +565,7 @@ class TabbedBrowser(QWidget):
newtab = self.tabopen(background=False, idx=entry.index)
newtab.history.private_api.deserialize(entry.history)
newtab.set_title(entry.title or '')
newtab.set_pinned(entry.pinned)
newtab.setFocus()

View File

@ -220,6 +220,7 @@ class SessionManager(QObject):
data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()}
data['pinned'] = tab.data.pinned
data['custom_title'] = tab.data.custom_title
return data
@ -412,6 +413,9 @@ class SessionManager(QObject):
if 'pinned' in histentry:
new_tab.data.pinned = histentry['pinned']
if 'custom_title' in histentry:
new_tab.data.custom_title = histentry['custom_title']
if (config.val.session.lazy_restore and
histentry.get('active', False) and
not histentry['url'].startswith('qute://back')):
@ -449,7 +453,10 @@ class SessionManager(QObject):
last_visited=last_visited)
entries.append(entry)
if active:
new_tab.title_changed.emit(histentry['title'])
if new_tab.data.custom_title:
new_tab.title_changed.emit(new_tab.data.custom_title)
else:
new_tab.title_changed.emit(histentry['title'])
try:
new_tab.history.private_api.load_items(entries)
@ -470,6 +477,8 @@ class SessionManager(QObject):
tab_to_focus = i
if new_tab.data.pinned:
new_tab.set_pinned(True)
if new_tab.data.custom_title:
new_tab.set_title(new_tab.data.custom_title)
if tab_to_focus is not None:
tabbed_browser.widget.setCurrentIndex(tab_to_focus)

View File

@ -1925,6 +1925,136 @@ Feature: Tab management
"""
# :tab-title
Scenario: Set tab title
When I open data/title.html
And I open data/title.html in a new tab
And I run :tab-title renamed
Then the session should look like:
"""
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
title: Test title
custom_title:
- active: true
history:
- url: http://localhost:*/data/title.html
title: Test title
custom_title: renamed
"""
Scenario: Set specific tab title using count
When I open data/title.html
And I open data/title.html in a new tab
And I run :tab-title renamed with count 1
Then the session should look like:
"""
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
title: Test title
custom_title: renamed
- active: true
history:
- url: http://localhost:*/data/title.html
title: Test title
custom_title:
"""
Scenario: Set tab title with space
When I open data/title.html
And I open data/title.html in a new tab
And I run :tab-title 'a new title'
Then the session should look like:
"""
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
title: Test title
custom_title:
- active: true
history:
- url: http://localhost:*/data/title.html
title: Test title
custom_title: a new title
"""
Scenario: Custom tab title when navigated
When I open data/numbers/1.txt
And I run :tab-title 'a new title'
And I open data/title.html
Then the session should look like:
"""
windows:
- tabs:
- active: true
history:
- url: about:blank
- url: http://localhost:*/data/numbers/1.txt
custom_title: a new title
- url: http://localhost:*/data/title.html
title: Test title
custom_title: a new title
"""
Scenario: Cloning a tab with a custom tab title
When I open data/title.html
And I open data/title.html in a new tab
And I run :tab-title 'a new title'
And I run :tab-clone
And I wait until data/title.html is loaded
Then the session should look like:
"""
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
title: Test title
custom_title:
- history:
- url: http://localhost:*/data/title.html
title: Test title
custom_title: a new title
- active: true
history:
- url: http://localhost:*/data/title.html
title: Test title
custom_title: a new title
"""
Scenario: Undo a tab with a custom tab title
When I open data/title.html
And I open data/title.html in a new tab
And I run :tab-title 'a new title'
And I run :tab-close --force
And I run :undo
And I wait until data/title.html is loaded
Then the session should look like:
"""
windows:
- tabs:
- history:
- url: about:blank
- url: http://localhost:*/data/title.html
title: Test title
custom_title:
- active: true
history:
- url: http://localhost:*/data/title.html
title: Test title
custom_title: a new title
"""
Scenario: Focused webview after clicking link in bg
When I open data/hints/link_input.html
And I run :click-element id qute-input-existing

View File

@ -1413,12 +1413,15 @@ def test_undo_completion(tabbed_browser_stubs, info):
"""Test :undo completion."""
entry1 = tabbedbrowser._UndoEntry(url=QUrl('https://example.org/'),
history=None, index=None, pinned=None,
title=None,
created_at=datetime(2020, 1, 1))
entry2 = tabbedbrowser._UndoEntry(url=QUrl('https://example.com/'),
history=None, index=None, pinned=None,
title=None,
created_at=datetime(2020, 1, 2))
entry3 = tabbedbrowser._UndoEntry(url=QUrl('https://example.net/'),
history=None, index=None, pinned=None,
title=None,
created_at=datetime(2020, 1, 2))
# Most recently closed is at the end
@ -1453,7 +1456,7 @@ def undo_completion_retains_sort_order(tabbed_browser_stubs, info):
tabbedbrowser._UndoEntry(
url=QUrl(f'https://example.org/{idx}'),
history=None, index=None, pinned=None,
created_at=created_dt,
title=None, created_at=created_dt,
)
for idx in range(1, 11)
]