From e746325d9e4f1dc1d56155eb487436bcaa4f19e3 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 27 Sep 2018 20:22:16 -0700 Subject: Turn off visibility of tabs while updating titles Improves performance dramatically when all titles change at once, such as when the first tab is removed. --- qutebrowser/mainwindow/tabwidget.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 751a294c3..928f1079a 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -216,8 +216,17 @@ class TabWidget(QTabWidget): def update_tab_titles(self): """Update all texts.""" + # Every single call to setTabText calls the size hinting functions for + # every single tab, which are slow. Since we know we are updating all + # the tab's titles, we can delay this processing by making the tab + # non-visible. To avoid flickering, disable repaint updates whlie we + # work. + self.setUpdatesEnabled(False) + self.setVisible(False) for idx in range(self.count()): self.update_tab_title(idx) + self.setVisible(True) + self.setUpdatesEnabled(True) def tabInserted(self, idx): """Update titles when a tab was inserted.""" -- cgit v1.2.3-54-g00ecf From 3081d017ce692b48bb9ea42ca9fa40eb2e13ed86 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Thu, 27 Sep 2018 22:19:11 -0700 Subject: Move tabbedbrowser test to tabwidget --- tests/unit/mainwindow/test_tabwidget.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index d16d31e01..2418a6d80 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -40,14 +40,6 @@ class TestTabWidget: usertypes.Backend.QtWebKit) return w - @pytest.fixture - def browser(self, qtbot, monkeypatch, config_stub): - w = tabbedbrowser.TabbedBrowser(win_id=0, private=False) - qtbot.addWidget(w) - monkeypatch.setattr(tabwidget.objects, 'backend', - usertypes.Backend.QtWebKit) - return w - def test_small_icon_doesnt_crash(self, widget, qtbot, fake_web_tab): """Test that setting a small icon doesn't produce a crash. @@ -108,7 +100,7 @@ class TestTabWidget: for i in range(num_tabs): if i in pinned_num and shrink_pinned and not vertical: - assert (first_size.width() < + assert (first_size.width() > widget.tabBar().tabSizeHint(i).width()) assert (first_size_min.width() < widget.tabBar().minimumTabSizeHint(i).width()) @@ -129,17 +121,22 @@ class TestTabWidget: benchmark(widget.update_tab_titles) @pytest.mark.parametrize("num_tabs", [4, 10]) - def test_add_remove_tab_benchmark(self, benchmark, browser, - qtbot, fake_web_tab, num_tabs): + @pytest.mark.parametrize("rev", [True, False]) + def test_add_remove_tab_benchmark(self, benchmark, widget, + qtbot, fake_web_tab, num_tabs, rev): """Benchmark for addTab and removeTab.""" def _run_bench(): + with qtbot.wait_exposed(widget): + widget.show() for i in range(num_tabs): - browser.widget.addTab(fake_web_tab(), 'foobar' + str(i)) - - with qtbot.waitExposed(browser): - browser.show() - - browser.shutdown() + idx = i if rev else 0 + widget.insertTab(idx, fake_web_tab(), 'foobar' + str(i)) + + to_del = range(num_tabs) + if rev: + to_del = reversed(to_del) + for i in to_del: + widget.removeTab(i) benchmark(_run_bench) -- cgit v1.2.3-54-g00ecf From a5f9115b2f13305a31bc7e0e6236211d9696498e Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sat, 29 Sep 2018 12:25:49 -0700 Subject: Only apply visibility toggle if we have >10 tabs --- qutebrowser/mainwindow/tabwidget.py | 39 +++++++++++++++++++++++---------- tests/unit/mainwindow/test_tabwidget.py | 6 ++--- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 928f1079a..69f0b1b92 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -21,6 +21,7 @@ import functools import enum +import contextlib import attr from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, @@ -214,19 +215,35 @@ class TabWidget(QTabWidget): fields['scroll_pos'] = scroll_pos return fields + @contextlib.contextmanager + def _toggle_visibility(self, force_toggle=False): + """Toggle visibility while running. + + Every single call to setTabText calls the size hinting functions for + every single tab, which are slow. Since we know we are updating all + the tab's titles, we can delay this processing by making the tab + non-visible. To avoid flickering, disable repaint updates whlie we + work. + + Args: + force_toggle: Whether to always force the toggle, or only do it + if we have enough tabs for it to matter + """ + if self.count() > 10: + force_toggle = True + if force_toggle: + self.setUpdatesEnabled(False) + self.setVisible(False) + yield + if force_toggle: + self.setVisible(True) + self.setUpdatesEnabled(True) + def update_tab_titles(self): """Update all texts.""" - # Every single call to setTabText calls the size hinting functions for - # every single tab, which are slow. Since we know we are updating all - # the tab's titles, we can delay this processing by making the tab - # non-visible. To avoid flickering, disable repaint updates whlie we - # work. - self.setUpdatesEnabled(False) - self.setVisible(False) - for idx in range(self.count()): - self.update_tab_title(idx) - self.setVisible(True) - self.setUpdatesEnabled(True) + with self._toggle_visibility(): + for idx in range(self.count()): + self.update_tab_title(idx) def tabInserted(self, idx): """Update titles when a tab was inserted.""" diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 2418a6d80..bc5ee9deb 100644 --- a/tests/unit/mainwindow/test_tabwidget.py +++ b/tests/unit/mainwindow/test_tabwidget.py @@ -24,7 +24,7 @@ import functools import pytest from PyQt5.QtGui import QIcon, QPixmap -from qutebrowser.mainwindow import tabwidget, tabbedbrowser +from qutebrowser.mainwindow import tabwidget from qutebrowser.utils import usertypes @@ -108,7 +108,7 @@ class TestTabWidget: assert first_size == widget.tabBar().tabSizeHint(i) assert first_size_min == widget.tabBar().minimumTabSizeHint(i) - @pytest.mark.parametrize("num_tabs", [4, 10]) + @pytest.mark.parametrize("num_tabs", [4, 10, 50, 100]) def test_update_tab_titles_benchmark(self, benchmark, widget, qtbot, fake_web_tab, num_tabs): """Benchmark for update_tab_titles.""" @@ -120,7 +120,7 @@ class TestTabWidget: benchmark(widget.update_tab_titles) - @pytest.mark.parametrize("num_tabs", [4, 10]) + @pytest.mark.parametrize("num_tabs", [4, 100]) @pytest.mark.parametrize("rev", [True, False]) def test_add_remove_tab_benchmark(self, benchmark, widget, qtbot, fake_web_tab, num_tabs, rev): -- cgit v1.2.3-54-g00ecf