diff options
-rw-r--r-- | doc/changelog.asciidoc | 2 | ||||
-rw-r--r-- | doc/faq.asciidoc | 11 | ||||
-rw-r--r-- | doc/help/settings.asciidoc | 5 | ||||
-rw-r--r-- | qutebrowser/browser/adblock.py | 31 | ||||
-rw-r--r-- | qutebrowser/config/configdata.yml | 5 | ||||
-rw-r--r-- | qutebrowser/mainwindow/tabwidget.py | 35 | ||||
-rw-r--r-- | tests/unit/browser/test_adblock.py | 19 | ||||
-rw-r--r-- | tests/unit/mainwindow/test_tabwidget.py | 35 |
8 files changed, 107 insertions, 36 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 1e554452b..037da5ee5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -83,6 +83,8 @@ Changed boolean: `always`, `never`, and `when-searching` (which only displays it while a search is active). - '@@' now repeats the last run macro. +- The `content.host_blocking.lists` setting now accepts a `file://` URL to a + directory, and reads all files in that directory. Fixed ~~~~~ diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 145f9fcb5..c4176a438 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -147,13 +147,14 @@ It also works nicely with rapid hints: ---- How do I use qutebrowser with mutt?:: - Due to a Qt limitation, local files without `.html` extensions are - "downloaded" instead of displayed, see - https://github.com/qutebrowser/qutebrowser/issues/566[#566]. You can work - around this by using this in your `mailcap`: + For security reasons, local files without `.html` extensions aren't + rendered as HTML, see + https://bugs.chromium.org/p/chromium/issues/detail?id=777737[this Chromium issue] + for details. You can do this in your `mailcap` file to get a proper + extension: + ---- - text/html; mv %s %s.html && qutebrowser %s.html >/dev/null 2>/dev/null; needsterminal; + text/html; qutebrowser %s; nametemplate=%s.html ---- What is the difference between bookmarks and quickmarks?:: diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 962e9ebae..60257de62 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1729,6 +1729,11 @@ The file can be in one of the following formats: - A zip-file of any of the above, with either only one file, or a file named `hosts` (with any extension). +It's also possible to add a local file or directory via a `file://` URL. In +case of a directory, all files in the directory are read as adblock lists. + +The file `~/.config/qutebrowser/blocked-hosts` is always read if it exists. + Type: <<types,List of Url>> diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index e38b44d5f..f2a56d3c1 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -173,15 +173,12 @@ class HostBlocker: for url in config.val.content.host_blocking.lists: if url.scheme() == 'file': filename = url.toLocalFile() - try: - fileobj = open(filename, 'rb') - except OSError as e: - message.error("adblock: Error while reading {}: {}".format( - filename, e.strerror)) - continue - download = _FakeDownload(fileobj) - self._in_progress.append(download) - self._on_download_finished(download) + if os.path.isdir(filename): + for entry in os.scandir(filename): + if entry.is_file(): + self._import_local(entry.path) + else: + self._import_local(filename) else: fobj = io.BytesIO() fobj.name = 'adblock: ' + url.host() @@ -192,6 +189,22 @@ class HostBlocker: download.finished.connect( functools.partial(self._on_download_finished, download)) + def _import_local(self, filename): + """Adds the contents of a file to the blocklist. + + Args: + filename: path to a local file to import. + """ + try: + fileobj = open(filename, 'rb') + except OSError as e: + message.error("adblock: Error while reading {}: {}".format( + filename, e.strerror)) + return + download = _FakeDownload(fileobj) + self._in_progress.append(download) + self._on_download_finished(download) + def _parse_line(self, line): """Parse a line from a host file. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index ae9351899..34b2d4923 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -547,6 +547,11 @@ content.host_blocking.lists: - A zip-file of any of the above, with either only one file, or a file named `hosts` (with any extension). + It's also possible to add a local file or directory via a `file://` URL. In + case of a directory, all files in the directory are read as adblock lists. + + The file `~/.config/qutebrowser/blocked-hosts` is always read if it exists. + content.host_blocking.whitelist: default: - piwik.org diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index d2b0f744e..9fc38fcf1 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,10 +215,32 @@ class TabWidget(QTabWidget): fields['scroll_pos'] = scroll_pos return fields + @contextlib.contextmanager + def _toggle_visibility(self): + """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. + """ + toggle = self.count() > 10 + if toggle: + self.setUpdatesEnabled(False) + self.setVisible(False) + + yield + + if toggle: + self.setVisible(True) + self.setUpdatesEnabled(True) + def update_tab_titles(self): """Update all texts.""" - for idx in range(self.count()): - self.update_tab_title(idx) + 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.""" @@ -576,7 +599,13 @@ class TabBar(QTabBar): if not 0 <= index < self.count(): raise IndexError("Tab index ({}) out of range ({})!".format( index, self.count())) - return self.parent().widget(index).data.pinned + + widget = self.parent().widget(index) + if widget is None: + # This could happen when Qt calls tabSizeHint while initializing + # tabs. + return False + return widget.data.pinned def tabSizeHint(self, index: int): """Override tabSizeHint to customize qb's tab size. diff --git a/tests/unit/browser/test_adblock.py b/tests/unit/browser/test_adblock.py index 470bce5cd..8bcbf3eb6 100644 --- a/tests/unit/browser/test_adblock.py +++ b/tests/unit/browser/test_adblock.py @@ -434,3 +434,22 @@ def test_config_change(config_stub, basedir, download_stub, host_blocker.read_hosts() for str_url in URLS_TO_CHECK: assert not host_blocker.is_blocked(QUrl(str_url)) + + +def test_add_directory(config_stub, basedir, download_stub, + data_tmpdir, tmpdir): + """Ensure adblocker can import all files in a directory.""" + blocklist_hosts2 = [] + for i in BLOCKLIST_HOSTS[1:]: + blocklist_hosts2.append('1' + i) + + create_blocklist(tmpdir, blocked_hosts=BLOCKLIST_HOSTS, + name='blocked-hosts', line_format='one_per_line') + create_blocklist(tmpdir, blocked_hosts=blocklist_hosts2, + name='blocked-hosts2', line_format='one_per_line') + + config_stub.val.content.host_blocking.lists = [tmpdir.strpath] + config_stub.val.content.host_blocking.enabled = True + host_blocker = adblock.HostBlocker() + host_blocker.adblock_update() + assert len(host_blocker._blocked_hosts) == len(blocklist_hosts2) * 2 diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py index 3b780a3fc..24acb6d87 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 @@ -41,14 +41,6 @@ class TestTabWidget: w.show() 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. @@ -117,7 +109,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.""" @@ -142,18 +134,23 @@ class TestTabWidget: config_stub.val.tabs.max_width = max_size assert widget.tabBar().tabRect(0).width() == max_size - @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("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): """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) |