summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/changelog.asciidoc2
-rw-r--r--doc/faq.asciidoc11
-rw-r--r--doc/help/settings.asciidoc5
-rw-r--r--qutebrowser/browser/adblock.py31
-rw-r--r--qutebrowser/config/configdata.yml5
-rw-r--r--qutebrowser/mainwindow/tabwidget.py35
-rw-r--r--tests/unit/browser/test_adblock.py19
-rw-r--r--tests/unit/mainwindow/test_tabwidget.py35
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)