From d98e5f86c8bf3fa456ce51f626ffced072b63ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Fri, 29 Jun 2018 18:06:12 -0400 Subject: [PATCH 001/114] userscripts/readme: list userscripts in markdown --- misc/userscripts/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 misc/userscripts/README.md diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md new file mode 100644 index 000000000..216ded6a9 --- /dev/null +++ b/misc/userscripts/README.md @@ -0,0 +1,32 @@ +# Userscripts + +- [cast](./cast): Cast content on your Chromecast using [castnow][]. Only + [youtube-dl][] downloadable content. +- [dmenu_qutebrowser](./dmenu_qutebrowser): Pipes history, quickmarks, and URL into dmenu. +- [format_json](./format_json): Pretty prints current page's JSON code in other + tab. +- [getbib](./getbib): Scraping the current web page for DOIs and downloading + corresponding bibtex information. +- [open_download](./open_download): Opens a rofi menu with + all files from the download directory and opens the selected file. +- [openfeeds](./openfeeds): Opens all links to feeds defined in the head of a site. +- [password_fill](./password_fill): Find a username/password entry and fill it + with credentials given by the configured backend (currently only pass) for the + current website. +- [qute-keepass](./qute-keepass): Insertion of usernames and passwords from keepass + databases using pykeepass. +- [qute-pass](./qute-pass): Insert login information using pass and a + dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...). +- [qutedmenu](./qutedmenu): Handle open -s && open -t with bemenu. +- [readability](./readability): Executes python-readability on current page and + opens the summary as new tab. +- [ripbang](./ripbang): Adds DuckDuckGo bang as searchengine. +- [rss](./rss): Keeps track of URLs in RSS feeds and opens new ones. +- [taskadd](./taskadd): Adds a task to taskwarrior. +- [tor_identity](./tor_identity): Change your tor identity. +- [view_in_mpv](./view_in_mpv): Views the current web page in mpv using + sensible mpv-flags. + +[castnow]: https://github.com/xat/castnow +[youtube-dl]: https://rg3.github.io/youtube-dl/ + From 224cf294099d9a0b07e3db3ad912b313cbbbdfad Mon Sep 17 00:00:00 2001 From: SubbulakshmiRS Date: Fri, 3 Aug 2018 20:46:26 +0530 Subject: [PATCH 002/114] ./misc/Makefile: Use of variables Variables like $(mandir),$(prefix),$(datadir) and $(datarootdir) are used in the Makefile . Closes https://github.com/qutebrowser/qutebrowser/issues/3538 --- misc/Makefile | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/misc/Makefile b/misc/Makefile index 210480e01..504bcdbc8 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -1,7 +1,10 @@ PYTHON = python3 -PREFIX = /usr/local +PREFIX ?= /usr/local DESTDIR = ICONSIZES = 16 24 32 48 64 128 256 512 +DATAROOTDIR = $(PREFIX)/share +DATADIR ?= $(DATAROOTDIR) +MANDIR = $(DATAROOTDIR)/man SETUPTOOLSOPTIONS = ifdef DESTDIR @@ -16,18 +19,18 @@ doc/qutebrowser.1.html: install: doc/qutebrowser.1.html $(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS) install -Dm644 misc/qutebrowser.appdata.xml \ - "$(DESTDIR)$(PREFIX)/share/metainfo/qutebrowser.appdata.xml" + "$(DESTDIR)$(DATADIR)/metainfo/qutebrowser.appdata.xml" install -Dm644 doc/qutebrowser.1 \ - "$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1" + "$(DESTDIR)$(MANDIR)/man1/qutebrowser.1" install -Dm644 misc/qutebrowser.desktop \ - "$(DESTDIR)$(PREFIX)/share/applications/qutebrowser.desktop" + "$(DESTDIR)$(DATADIR)/applications/qutebrowser.desktop" $(foreach i,$(ICONSIZES),install -Dm644 "icons/qutebrowser-$(i)x$(i).png" \ - "$(DESTDIR)$(PREFIX)/share/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";) + "$(DESTDIR)$(DATADIR)/icons/hicolor/$(i)x$(i)/apps/qutebrowser.png";) install -Dm644 icons/qutebrowser.svg \ - "$(DESTDIR)$(PREFIX)/share/icons/hicolor/scalable/apps/qutebrowser.svg" - install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/userscripts/" \ + "$(DESTDIR)$(DATADIR)/icons/hicolor/scalable/apps/qutebrowser.svg" + install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/userscripts/" \ $(wildcard misc/userscripts/*) - install -Dm755 -t "$(DESTDIR)$(PREFIX)/share/qutebrowser/scripts/" \ + install -Dm755 -t "$(DESTDIR)$(DATADIR)/qutebrowser/scripts/" \ $(filter-out scripts/__init__.py scripts/__pycache__ scripts/dev \ scripts/testbrowser scripts/asciidoc2html.py scripts/setupcommon.py \ scripts/link_pyqt.py,$(wildcard scripts/*)) From 69ad1d33a65f161d5ab221c43d4f4ead6bbc7d86 Mon Sep 17 00:00:00 2001 From: SubbulakshmiRS Date: Sat, 4 Aug 2018 09:23:38 +0530 Subject: [PATCH 003/114] misc/Makefile: Use of Variables Variables such as $(prefix),$(datarootdir),$(mandir), $(datadir) have been used . Closes https://github.com/qutebrowser/qutebrowser/issues/3538 --- misc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/Makefile b/misc/Makefile index 504bcdbc8..79b712c21 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -4,7 +4,7 @@ DESTDIR = ICONSIZES = 16 24 32 48 64 128 256 512 DATAROOTDIR = $(PREFIX)/share DATADIR ?= $(DATAROOTDIR) -MANDIR = $(DATAROOTDIR)/man +MANDIR ?= $(DATAROOTDIR)/man SETUPTOOLSOPTIONS = ifdef DESTDIR From 29d8034284aed6e09fe835dc4507191ce8fdaeb9 Mon Sep 17 00:00:00 2001 From: R S Subbulakshmi <32260751+SubbulakshmiRS@users.noreply.github.com> Date: Wed, 15 Aug 2018 23:01:39 +0530 Subject: [PATCH 004/114] Update Makefile --- misc/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/misc/Makefile b/misc/Makefile index 79b712c21..4625b288e 100644 --- a/misc/Makefile +++ b/misc/Makefile @@ -1,6 +1,5 @@ PYTHON = python3 PREFIX ?= /usr/local -DESTDIR = ICONSIZES = 16 24 32 48 64 128 256 512 DATAROOTDIR = $(PREFIX)/share DATADIR ?= $(DATAROOTDIR) From b4789e454abd2faf14850ede56b7144e79f34289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20D=C3=A9saulniers?= Date: Fri, 29 Jun 2018 18:06:33 -0400 Subject: [PATCH 005/114] userscripts/readme: other userscripts (own repos) Other userscripts found on their own repository listed under the section "Other". --- misc/userscripts/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md index 216ded6a9..5680267b7 100644 --- a/misc/userscripts/README.md +++ b/misc/userscripts/README.md @@ -1,5 +1,7 @@ # Userscripts +The following userscripts are included in the current directory. + - [cast](./cast): Cast content on your Chromecast using [castnow][]. Only [youtube-dl][] downloadable content. - [dmenu_qutebrowser](./dmenu_qutebrowser): Pipes history, quickmarks, and URL into dmenu. @@ -30,3 +32,30 @@ [castnow]: https://github.com/xat/castnow [youtube-dl]: https://rg3.github.io/youtube-dl/ +## Others + +The following userscripts can be found on their own repositories. + +- [qurlshare](https://github.com/sim590/qurlshare): *secure* sharing of an URL between qutebrowser + instances using a distributed hash table. +- [qutebrowser-userscripts](https://github.com/cryzed/qutebrowser-userscripts): + a small pack of userscripts. +- [qutebrowser-zotero](https://github.com/parchd-1/qutebrowser-zotero): connects + qutebrowser to [Zotero][] standalone. +- [qute.match](https://github.com/bziur/qute.match): execute script based on + visisted url. +- [qutepocket](https://github.com/kepi/qutepocket): Add URL to your [Pocket][] + bookmark manager. +- [qb-scripts](https://github.com/peterjschroeder/qb-scripts): a small pack of + userscripts. +- [instapaper.zsh](https://github.com/dmcgrady/instapaper.zsh): Add URL to + your [Instapaper][] bookmark manager. +- [qtb.us](https://github.com/Chinggis6/qtb.us): small pack of userscripts. +- [pinboard.zsh](https://github.com/dmix/pinboard.zsh): Add URL to your + [Pinboard][] bookmark manager. + +[Zotero]: https://www.zotero.org/ +[Pocket]: https://getpocket.com/ +[Instapaper]: https://www.instapaper.com/ +[Pinboard]: https://pinboard.in/ + From 067d76616b09c6f84583096026455e867ab1b86f Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sun, 2 Sep 2018 13:02:30 -0700 Subject: [PATCH 006/114] Implement config cache system --- qutebrowser/config/configcache.py | 45 +++++++++++++++++++++++++++ tests/unit/config/test_configcache.py | 35 +++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 qutebrowser/config/configcache.py create mode 100644 tests/unit/config/test_configcache.py diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py new file mode 100644 index 000000000..ebb112503 --- /dev/null +++ b/qutebrowser/config/configcache.py @@ -0,0 +1,45 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 Jay Kamat +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""A 'high-performance' cache for the config system. + +Useful for areas which call out to the config system very frequently, DO NOT +modify the value returned, and DO NOT require per-url settings. + +If any of these requirements are broken, you will get incorrect behavior. +""" + +from qutebrowser.config import config + + +class ConfigCache(): + + def __init__(self) -> None: + self.cache = {} + config.instance.changed.connect(self.config_changed) + + def config_changed(self, attr: str) -> None: + if attr in self.cache: + self.cache[attr] = config.instance.get(attr) + + def __getattr__(self, attr: str): + if attr not in self.cache: + assert not config.instance.get_opt(attr).supports_pattern + self.cache[attr] = config.instance.get(attr) + return self.cache[attr] diff --git a/tests/unit/config/test_configcache.py b/tests/unit/config/test_configcache.py new file mode 100644 index 000000000..1236cf8a7 --- /dev/null +++ b/tests/unit/config/test_configcache.py @@ -0,0 +1,35 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 Jay Kamat : +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Tests for qutebrowser.misc.autoupdate.""" + +import pytest + +from qutebrowser.config import configcache, config + + +class TestConfigCache: + + @pytest.fixture + def ccache(self, config_stub): + return configcache.ConfigCache() + + def test_configcache_except_pattern(self, ccache): + with pytest.raises(AssertionError): + ccache['content.javascript.enabled'] From 0335fc31c185047ce4e641f0078fd2ac8ccdcb74 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sun, 2 Sep 2018 13:02:39 -0700 Subject: [PATCH 007/114] Use config cache to cache static hotspots --- qutebrowser/config/config.py | 1 + qutebrowser/config/configcache.py | 11 +++++--- qutebrowser/config/configinit.py | 2 ++ qutebrowser/mainwindow/tabbedbrowser.py | 6 ++--- qutebrowser/mainwindow/tabwidget.py | 34 ++++++++++++------------- 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index d173eb1e0..fa89124ed 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -34,6 +34,7 @@ from qutebrowser.keyinput import keyutils val = None instance = None key_instance = None +configcache = None # Keeping track of all change filters to validate them later. change_filters = [] diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index ebb112503..7187b822a 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -20,9 +20,11 @@ """A 'high-performance' cache for the config system. Useful for areas which call out to the config system very frequently, DO NOT -modify the value returned, and DO NOT require per-url settings. +modify the value returned, DO NOT require per-url settings, do not change +frequently, and do not require partially 'expanded' config paths. -If any of these requirements are broken, you will get incorrect behavior. +If any of these requirements are broken, you will get incorrect or slow +behavior. """ from qutebrowser.config import config @@ -38,7 +40,10 @@ class ConfigCache(): if attr in self.cache: self.cache[attr] = config.instance.get(attr) - def __getattr__(self, attr: str): + def __setitem__(self, attr): + raise Exception("ConfigCache cannot be used to set values.") + + def __getitem__(self, attr: str): if attr not in self.cache: assert not config.instance.get_opt(attr).supports_pattern self.cache[attr] = config.instance.get(attr) diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 99a3ff91c..55ac4add7 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -28,6 +28,7 @@ from qutebrowser.config import (config, configdata, configfiles, configtypes, configexc, configcommands) from qutebrowser.utils import (objreg, usertypes, log, standarddir, message, qtutils) +from qutebrowser.config import configcache from qutebrowser.misc import msgbox, objects @@ -44,6 +45,7 @@ def early_init(args): config.instance = config.Config(yaml_config=yaml_config) config.val = config.ConfigContainer(config.instance) config.key_instance = config.KeyConfig(config.instance) + config.configcache = configcache.ConfigCache() yaml_config.setParent(config.instance) for cf in config.change_filters: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 9c4473874..4238009b2 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -719,9 +719,9 @@ class TabbedBrowser(QWidget): except TabDeletedError: # We can get signals for tabs we already deleted... return - start = config.val.colors.tabs.indicator.start - stop = config.val.colors.tabs.indicator.stop - system = config.val.colors.tabs.indicator.system + start = config.configcache['colors.tabs.indicator.start'] + stop = config.configcache['colors.tabs.indicator.stop'] + system = config.configcache['colors.tabs.indicator.system'] color = utils.interpolate_color(start, stop, perc, system) self.widget.set_tab_indicator_color(idx, color) self.widget.update_tab_title(idx) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 02934a532..895f999ee 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -139,9 +139,9 @@ class TabWidget(QTabWidget): """ tab = self.widget(idx) if tab.data.pinned: - fmt = config.val.tabs.title.format_pinned + fmt = config.configcache['tabs.title.format_pinned'] else: - fmt = config.val.tabs.title.format + fmt = config.configcache['tabs.title.format'] if (field is not None and (fmt is None or ('{' + field + '}') not in fmt)): @@ -604,7 +604,7 @@ class TabBar(QTabBar): minimum_size = self.minimumTabSizeHint(index) height = minimum_size.height() if self.vertical: - confwidth = str(config.val.tabs.width) + confwidth = str(config.configcache['tabs.width']) if confwidth.endswith('%'): main_window = objreg.get('main-window', scope='window', window=self._win_id) @@ -614,7 +614,7 @@ class TabBar(QTabBar): width = int(confwidth) size = QSize(max(minimum_size.width(), width), height) else: - if config.val.tabs.pinned.shrink: + if config.configcache['tabs.pinned.shrink']: pinned = self._tab_pinned(index) pinned_count, pinned_width = self._pinned_statistics() else: @@ -652,15 +652,15 @@ class TabBar(QTabBar): tab = QStyleOptionTab() self.initStyleOption(tab, idx) - # pylint: disable=bad-config-option - setting = config.val.colors.tabs - # pylint: enable=bad-config-option + setting = 'colors.tabs' if idx == selected: - setting = setting.selected - setting = setting.odd if (idx + 1) % 2 else setting.even + setting += '.selected' + setting += '.odd' if (idx + 1) % 2 else '.even' - tab.palette.setColor(QPalette.Window, setting.bg) - tab.palette.setColor(QPalette.WindowText, setting.fg) + tab.palette.setColor(QPalette.Window, + config.configcache[setting + '.bg']) + tab.palette.setColor(QPalette.WindowText, + config.configcache[setting + '.fg']) indicator_color = self.tab_indicator_color(idx) tab.palette.setColor(QPalette.Base, indicator_color) @@ -805,7 +805,7 @@ class TabBarStyle(QCommonStyle): elif element == QStyle.CE_TabBarTabLabel: if not opt.icon.isNull() and layouts.icon.isValid(): self._draw_icon(layouts, opt, p) - alignment = (config.val.tabs.title.alignment | + alignment = (config.configcache['tabs.title.alignment'] | Qt.AlignVCenter | Qt.TextHideMnemonic) self._style.drawItemText(p, layouts.text, alignment, opt.palette, opt.state & QStyle.State_Enabled, @@ -878,8 +878,8 @@ class TabBarStyle(QCommonStyle): Return: A Layout object with two QRects. """ - padding = config.val.tabs.padding - indicator_padding = config.val.tabs.indicator.padding + padding = config.configcache['tabs.padding'] + indicator_padding = config.configcache['tabs.indicator.padding'] text_rect = QRect(opt.rect) if not text_rect.isValid(): @@ -890,7 +890,7 @@ class TabBarStyle(QCommonStyle): text_rect.adjust(padding.left, padding.top, -padding.right, -padding.bottom) - indicator_width = config.val.tabs.indicator.width + indicator_width = config.configcache['tabs.indicator.width'] if indicator_width == 0: indicator_rect = QRect() else: @@ -933,9 +933,9 @@ class TabBarStyle(QCommonStyle): icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) # reserve space for favicon when tab bar is vertical (issue #1968) - position = config.val.tabs.position + position = config.configcache['tabs.position'] if (position in [QTabWidget.East, QTabWidget.West] and - config.val.tabs.favicons.show != 'never'): + config.configcache['tabs.favicons.show'] != 'never'): tab_icon_size = icon_size else: actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) From cc09f6c962c33661d764fd7a1234830516c94872 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sun, 2 Sep 2018 14:37:22 -0700 Subject: [PATCH 008/114] Fix doc issues in configcache --- qutebrowser/config/configcache.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index 7187b822a..c9e23ff66 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -17,20 +17,22 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""A 'high-performance' cache for the config system. -Useful for areas which call out to the config system very frequently, DO NOT -modify the value returned, DO NOT require per-url settings, do not change -frequently, and do not require partially 'expanded' config paths. - -If any of these requirements are broken, you will get incorrect or slow -behavior. -""" +"""Implementation of a basic config cache.""" from qutebrowser.config import config class ConfigCache(): + """A 'high-performance' cache for the config system. + + Useful for areas which call out to the config system very frequently, DO + NOT modify the value returned, DO NOT require per-url settings, do not + change frequently, and do not require partially 'expanded' config paths. + + If any of these requirements are broken, you will get incorrect or slow + behavior. + """ def __init__(self) -> None: self.cache = {} @@ -40,7 +42,7 @@ class ConfigCache(): if attr in self.cache: self.cache[attr] = config.instance.get(attr) - def __setitem__(self, attr): + def __setitem__(self, attr, _value): raise Exception("ConfigCache cannot be used to set values.") def __getitem__(self, attr: str): From d4cf5045ab92d2b859e64b763bf8584e2ff47b9c Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Sun, 2 Sep 2018 14:57:55 -0700 Subject: [PATCH 009/114] Fix tests for configcache --- qutebrowser/config/configcache.py | 3 --- scripts/dev/check_coverage.py | 2 ++ tests/helpers/fixtures.py | 5 ++++- tests/unit/config/test_configcache.py | 19 ++++++++++++++++--- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index c9e23ff66..471a42213 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -42,9 +42,6 @@ class ConfigCache(): if attr in self.cache: self.cache[attr] = config.instance.get(attr) - def __setitem__(self, attr, _value): - raise Exception("ConfigCache cannot be used to set values.") - def __getitem__(self, attr: str): if attr not in self.cache: assert not config.instance.get_opt(attr).supports_pattern diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 32c5afc49..8d6f3ccae 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -147,6 +147,8 @@ PERFECT_FILES = [ 'config/configcommands.py'), ('tests/unit/config/test_configutils.py', 'config/configutils.py'), + ('tests/unit/config/test_configcache.py', + 'config/configcache.py'), ('tests/unit/utils/test_qtutils.py', 'utils/qtutils.py'), diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index ec562c3b4..beb43f82b 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -42,7 +42,7 @@ from PyQt5.QtNetwork import QNetworkCookieJar import helpers.stubs as stubsmod import helpers.utils from qutebrowser.config import (config, configdata, configtypes, configexc, - configfiles) + configfiles, configcache) from qutebrowser.utils import objreg, standarddir, utils, usertypes from qutebrowser.browser import greasemonkey from qutebrowser.browser.webkit import cookies @@ -253,6 +253,9 @@ def config_stub(stubs, monkeypatch, configdata_init, yaml_config_stub): container = config.ConfigContainer(conf) monkeypatch.setattr(config, 'val', container) + cache = configcache.ConfigCache() + monkeypatch.setattr(config, 'configcache', cache) + try: configtypes.Font.monospace_fonts = container.fonts.monospace except configexc.NoOptionError: diff --git a/tests/unit/config/test_configcache.py b/tests/unit/config/test_configcache.py index 1236cf8a7..982e0c3f1 100644 --- a/tests/unit/config/test_configcache.py +++ b/tests/unit/config/test_configcache.py @@ -21,15 +21,28 @@ import pytest -from qutebrowser.config import configcache, config +from qutebrowser.config import config class TestConfigCache: @pytest.fixture def ccache(self, config_stub): - return configcache.ConfigCache() + return config.configcache def test_configcache_except_pattern(self, ccache): with pytest.raises(AssertionError): - ccache['content.javascript.enabled'] + assert ccache['content.javascript.enabled'] + + def test_configcache_error_set(self, ccache): + with pytest.raises(TypeError): + ccache['content.javascript.enabled'] = True + + def test_configcache_get(self, ccache): + assert not ccache['auto_save.session'] + assert not ccache['auto_save.session'] + + def test_configcache_get_after_set(self, ccache): + assert not ccache['auto_save.session'] + config.val.auto_save.session = True + assert ccache['auto_save.session'] From e2b14caa5cac19706f7ed05248a49acebbe12945 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 5 Sep 2018 14:48:31 +0200 Subject: [PATCH 010/114] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 49125de2e..792e0f64b 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -43,6 +43,8 @@ Changed already did). - The `completion.web_history_max_items` setting got renamed to `completion.web_history.max_items`. +- The Makefile shipped with qutebrowser now supports overriding variables + DATADIR and MANDIR. v1.4.2 ------ From 67f1d8abdd322720fca91239985e0c4653d306a9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 5 Sep 2018 20:39:41 +0200 Subject: [PATCH 011/114] Revert "Add exam comments to contributing docs" This reverts commit ae32b79d54fa70dff5f948d49371bc1648ba2fe8. --- .github/CONTRIBUTING.asciidoc | 5 ----- doc/contributing.asciidoc | 5 ----- 2 files changed, 10 deletions(-) diff --git a/.github/CONTRIBUTING.asciidoc b/.github/CONTRIBUTING.asciidoc index 4421f071a..6449c6323 100644 --- a/.github/CONTRIBUTING.asciidoc +++ b/.github/CONTRIBUTING.asciidoc @@ -1,8 +1,3 @@ -IMPORTANT: I'm currently (July 2018) more busy than usual until September, -because of exams coming up. Review of non-trivial pull requests will thus be -delayed until then. If you're reading this note after mid-September, please -open an issue. - - Before you start to work on something, please leave a comment on the relevant issue (or open one). This makes sure there is no duplicate work done. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 031d63a22..69aac9a0d 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -5,11 +5,6 @@ The Compiler :data-uri: :toc: -IMPORTANT: I'm currently (July 2018) more busy than usual until September, -because of exams coming up. Review of non-trivial pull requests will thus be -delayed until then. If you're reading this note after mid-September, please -open an issue. - I `<3` footnote:[Of course, that says `<3` in HTML.] contributors! This document contains guidelines for contributing to qutebrowser, as well as From 8e82adc3068779b5965e6ec1d01ab8cb8a9a4633 Mon Sep 17 00:00:00 2001 From: Jay Kamat Date: Wed, 5 Sep 2018 18:35:50 -0700 Subject: [PATCH 012/114] Refactor configcache to cache Also fix and improve configcache tests --- qutebrowser/config/config.py | 2 +- qutebrowser/config/configcache.py | 19 +++++++------- qutebrowser/config/configinit.py | 2 +- qutebrowser/mainwindow/tabbedbrowser.py | 6 ++--- qutebrowser/mainwindow/tabwidget.py | 24 ++++++++--------- tests/helpers/fixtures.py | 2 +- tests/unit/config/test_configcache.py | 35 ++++++++++++------------- 7 files changed, 45 insertions(+), 45 deletions(-) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index fa89124ed..98f2c67b3 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -34,7 +34,7 @@ from qutebrowser.keyinput import keyutils val = None instance = None key_instance = None -configcache = None +cache = None # Keeping track of all change filters to validate them later. change_filters = [] diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index 471a42213..dfead6664 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -23,7 +23,8 @@ from qutebrowser.config import config -class ConfigCache(): +class ConfigCache: + """A 'high-performance' cache for the config system. Useful for areas which call out to the config system very frequently, DO @@ -35,15 +36,15 @@ class ConfigCache(): """ def __init__(self) -> None: - self.cache = {} - config.instance.changed.connect(self.config_changed) + self._cache = {} + config.instance.changed.connect(self._on_config_changed) - def config_changed(self, attr: str) -> None: - if attr in self.cache: - self.cache[attr] = config.instance.get(attr) + def _on_config_changed(self, attr: str) -> None: + if attr in self._cache: + self._cache[attr] = config.instance.get(attr) def __getitem__(self, attr: str): - if attr not in self.cache: + if attr not in self._cache: assert not config.instance.get_opt(attr).supports_pattern - self.cache[attr] = config.instance.get(attr) - return self.cache[attr] + self._cache[attr] = config.instance.get(attr) + return self._cache[attr] diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 55ac4add7..b8c50500f 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -45,7 +45,7 @@ def early_init(args): config.instance = config.Config(yaml_config=yaml_config) config.val = config.ConfigContainer(config.instance) config.key_instance = config.KeyConfig(config.instance) - config.configcache = configcache.ConfigCache() + config.cache = configcache.ConfigCache() yaml_config.setParent(config.instance) for cf in config.change_filters: diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 4238009b2..d6164fd49 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -719,9 +719,9 @@ class TabbedBrowser(QWidget): except TabDeletedError: # We can get signals for tabs we already deleted... return - start = config.configcache['colors.tabs.indicator.start'] - stop = config.configcache['colors.tabs.indicator.stop'] - system = config.configcache['colors.tabs.indicator.system'] + start = config.cache['colors.tabs.indicator.start'] + stop = config.cache['colors.tabs.indicator.stop'] + system = config.cache['colors.tabs.indicator.system'] color = utils.interpolate_color(start, stop, perc, system) self.widget.set_tab_indicator_color(idx, color) self.widget.update_tab_title(idx) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 895f999ee..94e88ea71 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -139,9 +139,9 @@ class TabWidget(QTabWidget): """ tab = self.widget(idx) if tab.data.pinned: - fmt = config.configcache['tabs.title.format_pinned'] + fmt = config.cache['tabs.title.format_pinned'] else: - fmt = config.configcache['tabs.title.format'] + fmt = config.cache['tabs.title.format'] if (field is not None and (fmt is None or ('{' + field + '}') not in fmt)): @@ -604,7 +604,7 @@ class TabBar(QTabBar): minimum_size = self.minimumTabSizeHint(index) height = minimum_size.height() if self.vertical: - confwidth = str(config.configcache['tabs.width']) + confwidth = str(config.cache['tabs.width']) if confwidth.endswith('%'): main_window = objreg.get('main-window', scope='window', window=self._win_id) @@ -614,7 +614,7 @@ class TabBar(QTabBar): width = int(confwidth) size = QSize(max(minimum_size.width(), width), height) else: - if config.configcache['tabs.pinned.shrink']: + if config.cache['tabs.pinned.shrink']: pinned = self._tab_pinned(index) pinned_count, pinned_width = self._pinned_statistics() else: @@ -658,9 +658,9 @@ class TabBar(QTabBar): setting += '.odd' if (idx + 1) % 2 else '.even' tab.palette.setColor(QPalette.Window, - config.configcache[setting + '.bg']) + config.cache[setting + '.bg']) tab.palette.setColor(QPalette.WindowText, - config.configcache[setting + '.fg']) + config.cache[setting + '.fg']) indicator_color = self.tab_indicator_color(idx) tab.palette.setColor(QPalette.Base, indicator_color) @@ -805,7 +805,7 @@ class TabBarStyle(QCommonStyle): elif element == QStyle.CE_TabBarTabLabel: if not opt.icon.isNull() and layouts.icon.isValid(): self._draw_icon(layouts, opt, p) - alignment = (config.configcache['tabs.title.alignment'] | + alignment = (config.cache['tabs.title.alignment'] | Qt.AlignVCenter | Qt.TextHideMnemonic) self._style.drawItemText(p, layouts.text, alignment, opt.palette, opt.state & QStyle.State_Enabled, @@ -878,8 +878,8 @@ class TabBarStyle(QCommonStyle): Return: A Layout object with two QRects. """ - padding = config.configcache['tabs.padding'] - indicator_padding = config.configcache['tabs.indicator.padding'] + padding = config.cache['tabs.padding'] + indicator_padding = config.cache['tabs.indicator.padding'] text_rect = QRect(opt.rect) if not text_rect.isValid(): @@ -890,7 +890,7 @@ class TabBarStyle(QCommonStyle): text_rect.adjust(padding.left, padding.top, -padding.right, -padding.bottom) - indicator_width = config.configcache['tabs.indicator.width'] + indicator_width = config.cache['tabs.indicator.width'] if indicator_width == 0: indicator_rect = QRect() else: @@ -933,9 +933,9 @@ class TabBarStyle(QCommonStyle): icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) # reserve space for favicon when tab bar is vertical (issue #1968) - position = config.configcache['tabs.position'] + position = config.cache['tabs.position'] if (position in [QTabWidget.East, QTabWidget.West] and - config.configcache['tabs.favicons.show'] != 'never'): + config.cache['tabs.favicons.show'] != 'never'): tab_icon_size = icon_size else: actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index beb43f82b..5c16f894b 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -254,7 +254,7 @@ def config_stub(stubs, monkeypatch, configdata_init, yaml_config_stub): monkeypatch.setattr(config, 'val', container) cache = configcache.ConfigCache() - monkeypatch.setattr(config, 'configcache', cache) + monkeypatch.setattr(config, 'cache', cache) try: configtypes.Font.monospace_fonts = container.fonts.monospace diff --git a/tests/unit/config/test_configcache.py b/tests/unit/config/test_configcache.py index 982e0c3f1..54fd98d03 100644 --- a/tests/unit/config/test_configcache.py +++ b/tests/unit/config/test_configcache.py @@ -17,32 +17,31 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""Tests for qutebrowser.misc.autoupdate.""" +"""Tests for qutebrowser.config.configcache.""" import pytest from qutebrowser.config import config -class TestConfigCache: +def test_configcache_except_pattern(config_stub): + with pytest.raises(AssertionError): + assert config.cache['content.javascript.enabled'] - @pytest.fixture - def ccache(self, config_stub): - return config.configcache - def test_configcache_except_pattern(self, ccache): - with pytest.raises(AssertionError): - assert ccache['content.javascript.enabled'] +def test_configcache_error_set(config_stub): + with pytest.raises(TypeError): + config.cache['content.javascript.enabled'] = True - def test_configcache_error_set(self, ccache): - with pytest.raises(TypeError): - ccache['content.javascript.enabled'] = True - def test_configcache_get(self, ccache): - assert not ccache['auto_save.session'] - assert not ccache['auto_save.session'] +def test_configcache_get(config_stub): + assert len(config.cache._cache) == 0 + assert not config.cache['auto_save.session'] + assert len(config.cache._cache) == 1 + assert not config.cache['auto_save.session'] - def test_configcache_get_after_set(self, ccache): - assert not ccache['auto_save.session'] - config.val.auto_save.session = True - assert ccache['auto_save.session'] + +def test_configcache_get_after_set(config_stub): + assert not config.cache['auto_save.session'] + config_stub.val.auto_save.session = True + assert config.cache['auto_save.session'] From cd8fd8ab3c17adc52ecfaf6f2e7f00554bd9befe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 16:17:23 +0200 Subject: [PATCH 013/114] Use config cache for completion.web_history.exclude On my machine, this speeds up regenerating from ~6min to ~25s. --- qutebrowser/browser/history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 10d3ec35f..06a787fb3 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -151,8 +151,8 @@ class WebHistory(sql.SqlTable): def _is_excluded(self, url): """Check if the given URL is excluded from the completion.""" - return any(pattern.matches(url) - for pattern in config.val.completion.web_history.exclude) + patterns = config.cache['completion.web_history.exclude'] + return any(pattern.matches(url) for pattern in patterns) def _rebuild_completion(self): data = {'url': [], 'title': [], 'last_atime': []} From 9a9aefeabfb23a8010765b92ed08592341bbe32f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 16:18:26 +0200 Subject: [PATCH 014/114] Update changelog --- doc/changelog.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 792e0f64b..1fbd3e9f3 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -45,6 +45,7 @@ Changed `completion.web_history.max_items`. - The Makefile shipped with qutebrowser now supports overriding variables DATADIR and MANDIR. +- Various performance improvements when many tabs are opened. v1.4.2 ------ From c8be2d4f7e83300a961e638a5a3474e171f7268e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 16:43:10 +0200 Subject: [PATCH 015/114] Fix pylint issues with config cache --- tests/unit/config/test_configcache.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/config/test_configcache.py b/tests/unit/config/test_configcache.py index 54fd98d03..91e2f22fa 100644 --- a/tests/unit/config/test_configcache.py +++ b/tests/unit/config/test_configcache.py @@ -17,6 +17,10 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +# False-positives +# FIXME: Report this to pylint? +# pylint: disable=unsubscriptable-object + """Tests for qutebrowser.config.configcache.""" import pytest @@ -30,6 +34,7 @@ def test_configcache_except_pattern(config_stub): def test_configcache_error_set(config_stub): + # pylint: disable=unsupported-assignment-operation with pytest.raises(TypeError): config.cache['content.javascript.enabled'] = True From 2109b2276e161ccd599f864e0ab6fe8e8e67865a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 16:52:14 +0200 Subject: [PATCH 016/114] Initial progress dialog for history rebuild --- qutebrowser/browser/history.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 06a787fb3..cf33f4e60 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -24,6 +24,7 @@ import time import contextlib from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal +from PyQt5.QtWidgets import QProgressDialog, QApplication from qutebrowser.config import config from qutebrowser.commands import cmdutils, cmdexc @@ -160,7 +161,23 @@ class WebHistory(sql.SqlTable): q = sql.Query('SELECT url, title, max(atime) AS atime FROM History ' 'WHERE NOT redirect and url NOT LIKE "qute://back%" ' 'GROUP BY url ORDER BY atime asc') - for entry in q.run(): + entries = list(q.run()) + + if len(entries) > 1000: + progress = QProgressDialog() + progress.setLabelText("Rebuilding completion...") + progress.show() + progress.setMaximum(len(entries)) + progress.setCancelButton(None) + QApplication.processEvents() + else: + progress = None + + for i, entry in enumerate(entries): + if progress is not None: + progress.setValue(i) + QApplication.processEvents() + url = QUrl(entry.url) if self._is_excluded(url): continue @@ -168,6 +185,9 @@ class WebHistory(sql.SqlTable): data['title'].append(entry.title) data['last_atime'].append(entry.atime) + if progress is not None: + progress.hide() + self.completion.insert_batch(data, replace=True) sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run() From e4b7786bcc04e7cd280a3da65cab8c1c7e152888 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 17:04:26 +0200 Subject: [PATCH 017/114] Factor out GUI stuff to a HistoryProgress object --- qutebrowser/browser/history.py | 65 ++++++++++++++++++++++-------- tests/unit/browser/test_history.py | 30 ++++++++++---- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index cf33f4e60..96d37db83 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -37,6 +37,38 @@ from qutebrowser.misc import objects, sql _USER_VERSION = 2 +class HistoryProgress: + + """Progress dialog for history imports/conversions. + + This makes WebHistory simpler as it can call methods of this class even + when we don't want to show a progress dialog (for very small imports). This + means tick() and finish() can be called even when start() wasn't. + """ + + def __init__(self): + self._progress = None + self._value = 0 + + def start(self, text, maximum): + self._progress = QProgressDialog() + self._progress.setLabelText(text) + self._progress.setMaximum(maximum) + self._progress.setCancelButton(None) + self._progress.show() + QApplication.processEvents() + + def tick(self): + self._value += 1 + if self._progress is not None: + self._progress.setValue(self._value) + QApplication.processEvents() + + def finish(self): + if self._progress is not None: + self._progress.hide() + + class CompletionMetaInfo(sql.SqlTable): """Table containing meta-information for the completion.""" @@ -87,20 +119,28 @@ class CompletionHistory(sql.SqlTable): class WebHistory(sql.SqlTable): - """The global history of visited pages.""" + """The global history of visited pages. + + Attributes: + completion: A CompletionHistory instance. + metainfo: A CompletionMetaInfo instance. + _progress: A HistoryProgress instance. + """ # All web history cleared history_cleared = pyqtSignal() # one url cleared url_cleared = pyqtSignal(QUrl) - def __init__(self, parent=None): + def __init__(self, progress, parent=None): super().__init__("History", ['url', 'title', 'atime', 'redirect'], constraints={'url': 'NOT NULL', 'title': 'NOT NULL', 'atime': 'NOT NULL', 'redirect': 'NOT NULL'}, parent=parent) + self._progress = progress + self.completion = CompletionHistory(parent=self) self.metainfo = CompletionMetaInfo(parent=self) @@ -164,19 +204,10 @@ class WebHistory(sql.SqlTable): entries = list(q.run()) if len(entries) > 1000: - progress = QProgressDialog() - progress.setLabelText("Rebuilding completion...") - progress.show() - progress.setMaximum(len(entries)) - progress.setCancelButton(None) - QApplication.processEvents() - else: - progress = None + self._progress.start("Rebuilding completion...", len(entries)) - for i, entry in enumerate(entries): - if progress is not None: - progress.setValue(i) - QApplication.processEvents() + for entry in entries: + self._progress.tick() url = QUrl(entry.url) if self._is_excluded(url): @@ -185,8 +216,7 @@ class WebHistory(sql.SqlTable): data['title'].append(entry.title) data['last_atime'].append(entry.atime) - if progress is not None: - progress.hide() + self._progress.finish() self.completion.insert_batch(data, replace=True) sql.Query('pragma user_version = {}'.format(_USER_VERSION)).run() @@ -438,7 +468,8 @@ def init(parent=None): Args: parent: The parent to use for WebHistory. """ - history = WebHistory(parent=parent) + progress = HistoryProgress() + history = WebHistory(progress=progress, parent=parent) objreg.register('web-history', history) if objects.backend == usertypes.Backend.QtWebKit: # pragma: no cover diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 2f4827b0a..4e4ef2e10 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -37,9 +37,23 @@ def prerequisites(config_stub, fake_save_manager, init_sql, fake_args): config_stub.data = {'general': {'private-browsing': False}} +class FakeHistoryProgress: + + """Fake for a WebHistoryProgress object.""" + + def start(self, _text, _maximum): + pass + + def tick(self): + pass + + def finish(self): + pass + + @pytest.fixture() def hist(tmpdir): - return history.WebHistory() + return history.WebHistory(progress=FakeHistoryProgress()) class TestSpecialMethods: @@ -434,7 +448,7 @@ class TestRebuild: 'redirect': False, 'atime': 5}) hist.completion.delete_all() - hist2 = history.WebHistory() + hist2 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist2.completion) == [ ('example.com/1', 'example1', 2), ('example.com/2 3', 'example2', 5), @@ -446,7 +460,7 @@ class TestRebuild: hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) hist.completion.delete('url', 'example.com/2') - hist2 = history.WebHistory() + hist2 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist2.completion) == [('example.com/1', '', 1)] def test_user_version(self, hist, monkeypatch): @@ -455,12 +469,12 @@ class TestRebuild: hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) hist.completion.delete('url', 'example.com/2') - hist2 = history.WebHistory() + hist2 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist2.completion) == [('example.com/1', '', 1)] monkeypatch.setattr(history, '_USER_VERSION', history._USER_VERSION + 1) - hist3 = history.WebHistory() + hist3 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist3.completion) == [ ('example.com/1', '', 1), ('example.com/2', '', 2), @@ -472,11 +486,11 @@ class TestRebuild: hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) hist.completion.delete('url', 'example.com/2') - hist2 = history.WebHistory() + hist2 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist2.completion) == [('example.com/1', '', 1)] hist2.metainfo['force_rebuild'] = True - hist3 = history.WebHistory() + hist3 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist3.completion) == [ ('example.com/1', '', 1), ('example.com/2', '', 2), @@ -494,7 +508,7 @@ class TestRebuild: hist.add_url(QUrl('http://example.com'), redirect=False, atime=1) hist.add_url(QUrl('http://example.org'), redirect=False, atime=2) - hist2 = history.WebHistory() + hist2 = history.WebHistory(progress=FakeHistoryProgress()) assert list(hist2.completion) == [('http://example.com', '', 1)] def test_unrelated_config_change(self, config_stub, hist): From ec774379bdfe2de47804f8bd529014b253c0b1ec Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 17:13:46 +0200 Subject: [PATCH 018/114] Add tests for history progress --- qutebrowser/browser/history.py | 7 ++++++- tests/unit/browser/test_history.py | 26 +++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 96d37db83..6b172a6e8 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -125,6 +125,9 @@ class WebHistory(sql.SqlTable): completion: A CompletionHistory instance. metainfo: A CompletionMetaInfo instance. _progress: A HistoryProgress instance. + + Class attributes: + _PROGRESS_THRESHOLD: When to start showing progress dialogs. """ # All web history cleared @@ -132,6 +135,8 @@ class WebHistory(sql.SqlTable): # one url cleared url_cleared = pyqtSignal(QUrl) + _PROGRESS_THRESHOLD = 1000 + def __init__(self, progress, parent=None): super().__init__("History", ['url', 'title', 'atime', 'redirect'], constraints={'url': 'NOT NULL', @@ -203,7 +208,7 @@ class WebHistory(sql.SqlTable): 'GROUP BY url ORDER BY atime asc') entries = list(q.run()) - if len(entries) > 1000: + if len(entries) > self._PROGRESS_THRESHOLD: self._progress.start("Rebuilding completion...", len(entries)) for entry in entries: diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 4e4ef2e10..0dc97f316 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -41,14 +41,19 @@ class FakeHistoryProgress: """Fake for a WebHistoryProgress object.""" + def __init__(self): + self._started = False + self._finished = False + self._value = 0 + def start(self, _text, _maximum): - pass + self._started = True def tick(self): - pass + self._value += 1 def finish(self): - pass + self._finished = True @pytest.fixture() @@ -515,6 +520,21 @@ class TestRebuild: config_stub.val.history_gap_interval = 1234 assert not hist.metainfo['force_rebuild'] + @pytest.mark.parametrize('patch_threshold', [True, False]) + def test_progress(self, hist, config_stub, monkeypatch, patch_threshold): + hist.add_url(QUrl('example.com/1'), redirect=False, atime=1) + hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) + hist.metainfo['force_rebuild'] = True + + if patch_threshold: + monkeypatch.setattr(history.WebHistory, '_PROGRESS_THRESHOLD', 1) + + progress = FakeHistoryProgress() + history.WebHistory(progress=progress) + assert progress._value == 2 + assert progress._finished + assert progress._started == patch_threshold + class TestCompletionMetaInfo: From d4f16f88b6e7ec53c37fd62294ac6a4a971d4ddf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 17:17:31 +0200 Subject: [PATCH 019/114] Remove support for importing pre-v1.0.0 history --- qutebrowser/app.py | 2 - qutebrowser/browser/history.py | 102 ----------------------------- tests/unit/browser/test_history.py | 93 -------------------------- 3 files changed, 197 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 8d3a11656..594764c37 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -185,8 +185,6 @@ def init(args, crash_handler): QDesktopServices.setUrlHandler('https', open_desktopservices_url) QDesktopServices.setUrlHandler('qute', open_desktopservices_url) - objreg.get('web-history').import_txt() - log.init.debug("Init done!") crash_handler.raise_crashdlg() diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 6b172a6e8..e13923309 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -338,108 +338,6 @@ class WebHistory(sql.SqlTable): 'last_atime': atime }, replace=True) - def _parse_entry(self, line): - """Parse a history line like '12345 http://example.com title'.""" - if not line or line.startswith('#'): - return None - data = line.split(maxsplit=2) - if len(data) == 2: - atime, url = data - title = "" - elif len(data) == 3: - atime, url, title = data - else: - raise ValueError("2 or 3 fields expected") - - # http://xn--pple-43d.com/ with - # https://bugreports.qt.io/browse/QTBUG-60364 - if url in ['http://.com/', 'https://.com/', - 'http://www..com/', 'https://www..com/']: - return None - - url = QUrl(url) - if not url.isValid(): - raise ValueError("Invalid URL: {}".format(url.errorString())) - - # https://github.com/qutebrowser/qutebrowser/issues/2646 - if url.scheme() == 'data': - return None - - # https://github.com/qutebrowser/qutebrowser/issues/670 - atime = atime.lstrip('\0') - - if '-' in atime: - atime, flags = atime.split('-') - else: - flags = '' - - if not set(flags).issubset('r'): - raise ValueError("Invalid flags {!r}".format(flags)) - - redirect = 'r' in flags - return (url, title, int(atime), redirect) - - def import_txt(self): - """Import a history text file into sqlite if it exists. - - In older versions of qutebrowser, history was stored in a text format. - This converts that file into the new sqlite format and moves it to a - backup location. - """ - path = os.path.join(standarddir.data(), 'history') - if not os.path.isfile(path): - return - - def action(): - """Actually run the import.""" - with debug.log_time(log.init, 'Import old history file to sqlite'): - try: - self._read(path) - except ValueError as ex: - message.error('Failed to import history: {}'.format(ex)) - else: - self._write_backup(path) - - # delay to give message time to appear before locking down for import - message.info('Converting {} to sqlite...'.format(path)) - QTimer.singleShot(100, action) - - def _read(self, path): - """Import a text file into the sql database.""" - with open(path, 'r', encoding='utf-8') as f: - data = {'url': [], 'title': [], 'atime': [], 'redirect': []} - completion_data = {'url': [], 'title': [], 'last_atime': []} - for (i, line) in enumerate(f): - try: - parsed = self._parse_entry(line.strip()) - if parsed is None: - continue - url, title, atime, redirect = parsed - data['url'].append(self._format_url(url)) - data['title'].append(title) - data['atime'].append(atime) - data['redirect'].append(redirect) - if not redirect: - completion_data['url'].append( - self._format_completion_url(url)) - completion_data['title'].append(title) - completion_data['last_atime'].append(atime) - except ValueError as ex: - raise ValueError('Failed to parse line #{} of {}: "{}"' - .format(i, path, ex)) - self.insert_batch(data) - self.completion.insert_batch(completion_data, replace=True) - - def _write_backup(self, path): - bak = path + '.bak' - message.info('History import complete. Appending {} to {}' - .format(path, bak)) - with open(path, 'r', encoding='utf-8') as infile: - with open(bak, 'a', encoding='utf-8') as outfile: - for line in infile: - outfile.write('\n' + line) - os.remove(path) - def _format_url(self, url): return url.toString(QUrl.RemovePassword | QUrl.FullyEncoded) diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 0dc97f316..40dce3ec4 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -323,99 +323,6 @@ class TestInit: assert default_interface is None -class TestImport: - - def test_import_txt(self, hist, data_tmpdir, monkeypatch, stubs): - monkeypatch.setattr(history, 'QTimer', stubs.InstaTimer) - histfile = data_tmpdir / 'history' - # empty line is deliberate, to test skipping empty lines - histfile.write('''12345 http://example.com/ title - 12346 http://qutebrowser.org/ - 67890 http://example.com/path - - 68891-r http://example.com/path/other ''') - - hist.import_txt() - - assert list(hist) == [ - ('http://example.com/', 'title', 12345, False), - ('http://qutebrowser.org/', '', 12346, False), - ('http://example.com/path', '', 67890, False), - ('http://example.com/path/other', '', 68891, True) - ] - - assert not histfile.exists() - assert (data_tmpdir / 'history.bak').exists() - - def test_existing_backup(self, hist, data_tmpdir, monkeypatch, stubs): - monkeypatch.setattr(history, 'QTimer', stubs.InstaTimer) - histfile = data_tmpdir / 'history' - bakfile = data_tmpdir / 'history.bak' - histfile.write('12345 http://example.com/ title') - bakfile.write('12346 http://qutebrowser.org/') - - hist.import_txt() - - assert list(hist) == [('http://example.com/', 'title', 12345, False)] - - assert not histfile.exists() - assert bakfile.read().split('\n') == ['12346 http://qutebrowser.org/', - '12345 http://example.com/ title'] - - @pytest.mark.parametrize('line', [ - '', - '#12345 http://example.com/commented', - - # https://bugreports.qt.io/browse/QTBUG-60364 - '12345 http://.com/', - '12345 https://.com/', - '12345 http://www..com/', - '12345 https://www..com/', - - # issue #2646 - ('12345 data:text/html;' - 'charset=UTF-8,%3C%21DOCTYPE%20html%20PUBLIC%20%22-'), - ]) - def test_skip(self, hist, data_tmpdir, monkeypatch, stubs, line): - """import_txt should skip certain lines silently.""" - monkeypatch.setattr(history, 'QTimer', stubs.InstaTimer) - histfile = data_tmpdir / 'history' - histfile.write(line) - - hist.import_txt() - - assert not histfile.exists() - assert not len(hist) - - @pytest.mark.parametrize('line', [ - 'xyz http://example.com/bad-timestamp', - '12345', - 'http://example.com/no-timestamp', - '68891-r-r http://example.com/double-flag', - '68891-x http://example.com/bad-flag', - '68891 http://.com', - ]) - def test_invalid(self, hist, data_tmpdir, monkeypatch, stubs, caplog, - line): - """import_txt should fail on certain lines.""" - monkeypatch.setattr(history, 'QTimer', stubs.InstaTimer) - histfile = data_tmpdir / 'history' - histfile.write(line) - - with caplog.at_level(logging.ERROR): - hist.import_txt() - - assert any(rec.msg.startswith("Failed to import history:") - for rec in caplog.records) - - assert histfile.exists() - - def test_nonexistent(self, hist, data_tmpdir, monkeypatch, stubs): - """import_txt should do nothing if the history file doesn't exist.""" - monkeypatch.setattr(history, 'QTimer', stubs.InstaTimer) - hist.import_txt() - - class TestDump: def test_debug_dump_history(self, hist, tmpdir): From 9a6c8fe1b98095500c03b4476f761541889becfe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 17:18:15 +0200 Subject: [PATCH 020/114] Update changelog --- doc/changelog.asciidoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 1fbd3e9f3..b708de742 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -46,6 +46,12 @@ Changed - The Makefile shipped with qutebrowser now supports overriding variables DATADIR and MANDIR. - Various performance improvements when many tabs are opened. +- Regenerating completion history now shows a progress dialog. + +Removed +~~~~~~~ + +- Support for importing pre-v1.0.0 history files has been removed. v1.4.2 ------ From 935d93d10e757a347d7f5e94d369ea6773b456c5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 17:23:39 +0200 Subject: [PATCH 021/114] Add tests for HistoryProgress --- tests/unit/browser/test_history.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 40dce3ec4..ce02cff9b 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -468,3 +468,32 @@ class TestCompletionMetaInfo: assert not metainfo['force_rebuild'] metainfo['force_rebuild'] = True assert metainfo['force_rebuild'] + + +class TestHistoryProgress: + + @pytest.fixture + def progress(self): + return history.HistoryProgress() + + def test_no_start(self, progress): + """Test calling tick/finish without start.""" + progress.tick() + progress.finish() + assert progress._progress is None + assert progress._value == 1 + + def test_gui(self, qtbot, progress): + progress.start("Hello World", 42) + dialog = progress._progress + qtbot.add_widget(dialog) + progress.tick() + + assert dialog.isVisible() + assert dialog.labelText() == "Hello World" + assert dialog.minimum() == 0 + assert dialog.maximum() == 42 + assert dialog.value() == 1 + + progress.finish() + assert not dialog.isVisible() From ab6c8dde9a30b5d27233a06eb21979e64c4adf8c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 17:24:59 +0200 Subject: [PATCH 022/114] Decrease minimum QProgressDialog duration We already have a threshold before we use a QProgressDialog at all, so let's show it quite quickly and not after 4 seconds. --- qutebrowser/browser/history.py | 1 + tests/unit/browser/test_history.py | 1 + 2 files changed, 2 insertions(+) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index e13923309..615f65923 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -52,6 +52,7 @@ class HistoryProgress: def start(self, text, maximum): self._progress = QProgressDialog() + self._progress.setMinimumDuration(500) self._progress.setLabelText(text) self._progress.setMaximum(maximum) self._progress.setCancelButton(None) diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index ce02cff9b..a44391260 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -494,6 +494,7 @@ class TestHistoryProgress: assert dialog.minimum() == 0 assert dialog.maximum() == 42 assert dialog.value() == 1 + assert dialog.minimumDuration() == 500 progress.finish() assert not dialog.isVisible() From 4ca8fc0cb760dc7fd01774ad7aa3b9c9ca213b0b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 20:04:11 +0200 Subject: [PATCH 023/114] Use a shared web_history fixture --- tests/helpers/fixtures.py | 13 +- tests/helpers/stubs.py | 19 ++ tests/unit/browser/test_history.py | 310 ++++++++++++-------------- tests/unit/browser/test_qutescheme.py | 22 +- tests/unit/completion/test_models.py | 14 +- 5 files changed, 186 insertions(+), 192 deletions(-) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index 5c16f894b..5ce081e3b 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -44,7 +44,7 @@ import helpers.utils from qutebrowser.config import (config, configdata, configtypes, configexc, configfiles, configcache) from qutebrowser.utils import objreg, standarddir, utils, usertypes -from qutebrowser.browser import greasemonkey +from qutebrowser.browser import greasemonkey, history from qutebrowser.browser.webkit import cookies from qutebrowser.misc import savemanager, sql, objects from qutebrowser.keyinput import modeman @@ -572,3 +572,14 @@ def download_stub(win_registry, tmpdir, stubs): objreg.register('qtnetwork-download-manager', stub) yield stub objreg.delete('qtnetwork-download-manager') + + +@pytest.fixture +def web_history(fake_save_manager, tmpdir, init_sql, config_stub, stubs): + """Create a web history and register it into objreg.""" + config_stub.val.completion.timestamp_format = '%Y-%m-%d' + config_stub.val.completion.web_history.max_items = -1 + web_history = history.WebHistory(stubs.FakeHistoryProgress()) + objreg.register('web-history', web_history) + yield web_history + objreg.delete('web-history') diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 84e5b0125..f10522a02 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -632,3 +632,22 @@ class FakeDownloadManager: shutil.copyfileobj(fake_url_file, download_item.fileobj) self.downloads.append(download_item) return download_item + + +class FakeHistoryProgress: + + """Fake for a WebHistoryProgress object.""" + + def __init__(self): + self._started = False + self._finished = False + self._value = 0 + + def start(self, _text, _maximum): + self._started = True + + def tick(self): + self._value += 1 + + def finish(self): + self._finished = True diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index a44391260..5b84eac4c 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -37,129 +37,106 @@ def prerequisites(config_stub, fake_save_manager, init_sql, fake_args): config_stub.data = {'general': {'private-browsing': False}} -class FakeHistoryProgress: - - """Fake for a WebHistoryProgress object.""" - - def __init__(self): - self._started = False - self._finished = False - self._value = 0 - - def start(self, _text, _maximum): - self._started = True - - def tick(self): - self._value += 1 - - def finish(self): - self._finished = True - - -@pytest.fixture() -def hist(tmpdir): - return history.WebHistory(progress=FakeHistoryProgress()) - - class TestSpecialMethods: - def test_iter(self, hist): + def test_iter(self, web_history): urlstr = 'http://www.example.com/' url = QUrl(urlstr) - hist.add_url(url, atime=12345) + web_history.add_url(url, atime=12345) - assert list(hist) == [(urlstr, '', 12345, False)] + assert list(web_history) == [(urlstr, '', 12345, False)] - def test_len(self, hist): - assert len(hist) == 0 + def test_len(self, web_history): + assert len(web_history) == 0 url = QUrl('http://www.example.com/') - hist.add_url(url) + web_history.add_url(url) - assert len(hist) == 1 + assert len(web_history) == 1 - def test_contains(self, hist): - hist.add_url(QUrl('http://www.example.com/'), title='Title', - atime=12345) - assert 'http://www.example.com/' in hist - assert 'www.example.com' not in hist - assert 'Title' not in hist - assert 12345 not in hist + def test_contains(self, web_history): + web_history.add_url(QUrl('http://www.example.com/'), + title='Title', atime=12345) + assert 'http://www.example.com/' in web_history + assert 'www.example.com' not in web_history + assert 'Title' not in web_history + assert 12345 not in web_history class TestGetting: - def test_get_recent(self, hist): - hist.add_url(QUrl('http://www.qutebrowser.org/'), atime=67890) - hist.add_url(QUrl('http://example.com/'), atime=12345) - assert list(hist.get_recent()) == [ + def test_get_recent(self, web_history): + web_history.add_url(QUrl('http://www.qutebrowser.org/'), atime=67890) + web_history.add_url(QUrl('http://example.com/'), atime=12345) + assert list(web_history.get_recent()) == [ ('http://www.qutebrowser.org/', '', 67890, False), ('http://example.com/', '', 12345, False), ] - def test_entries_between(self, hist): - hist.add_url(QUrl('http://www.example.com/1'), atime=12345) - hist.add_url(QUrl('http://www.example.com/2'), atime=12346) - hist.add_url(QUrl('http://www.example.com/3'), atime=12347) - hist.add_url(QUrl('http://www.example.com/4'), atime=12348) - hist.add_url(QUrl('http://www.example.com/5'), atime=12348) - hist.add_url(QUrl('http://www.example.com/6'), atime=12349) - hist.add_url(QUrl('http://www.example.com/7'), atime=12350) + def test_entries_between(self, web_history): + web_history.add_url(QUrl('http://www.example.com/1'), atime=12345) + web_history.add_url(QUrl('http://www.example.com/2'), atime=12346) + web_history.add_url(QUrl('http://www.example.com/3'), atime=12347) + web_history.add_url(QUrl('http://www.example.com/4'), atime=12348) + web_history.add_url(QUrl('http://www.example.com/5'), atime=12348) + web_history.add_url(QUrl('http://www.example.com/6'), atime=12349) + web_history.add_url(QUrl('http://www.example.com/7'), atime=12350) - times = [x.atime for x in hist.entries_between(12346, 12349)] + times = [x.atime for x in web_history.entries_between(12346, 12349)] assert times == [12349, 12348, 12348, 12347] - def test_entries_before(self, hist): - hist.add_url(QUrl('http://www.example.com/1'), atime=12346) - hist.add_url(QUrl('http://www.example.com/2'), atime=12346) - hist.add_url(QUrl('http://www.example.com/3'), atime=12347) - hist.add_url(QUrl('http://www.example.com/4'), atime=12348) - hist.add_url(QUrl('http://www.example.com/5'), atime=12348) - hist.add_url(QUrl('http://www.example.com/6'), atime=12348) - hist.add_url(QUrl('http://www.example.com/7'), atime=12349) - hist.add_url(QUrl('http://www.example.com/8'), atime=12349) + def test_entries_before(self, web_history): + web_history.add_url(QUrl('http://www.example.com/1'), atime=12346) + web_history.add_url(QUrl('http://www.example.com/2'), atime=12346) + web_history.add_url(QUrl('http://www.example.com/3'), atime=12347) + web_history.add_url(QUrl('http://www.example.com/4'), atime=12348) + web_history.add_url(QUrl('http://www.example.com/5'), atime=12348) + web_history.add_url(QUrl('http://www.example.com/6'), atime=12348) + web_history.add_url(QUrl('http://www.example.com/7'), atime=12349) + web_history.add_url(QUrl('http://www.example.com/8'), atime=12349) times = [x.atime for x in - hist.entries_before(12348, limit=3, offset=2)] + web_history.entries_before(12348, limit=3, offset=2)] assert times == [12348, 12347, 12346] class TestDelete: - def test_clear(self, qtbot, tmpdir, hist, mocker): - hist.add_url(QUrl('http://example.com/')) - hist.add_url(QUrl('http://www.qutebrowser.org/')) + def test_clear(self, qtbot, tmpdir, web_history, mocker): + web_history.add_url(QUrl('http://example.com/')) + web_history.add_url(QUrl('http://www.qutebrowser.org/')) m = mocker.patch('qutebrowser.browser.history.message.confirm_async', new=mocker.Mock, spec=[]) - hist.clear() + web_history.clear() assert m.called - def test_clear_force(self, qtbot, tmpdir, hist): - hist.add_url(QUrl('http://example.com/')) - hist.add_url(QUrl('http://www.qutebrowser.org/')) - hist.clear(force=True) - assert not len(hist) - assert not len(hist.completion) + def test_clear_force(self, qtbot, tmpdir, web_history): + web_history.add_url(QUrl('http://example.com/')) + web_history.add_url(QUrl('http://www.qutebrowser.org/')) + web_history.clear(force=True) + assert not len(web_history) + assert not len(web_history.completion) @pytest.mark.parametrize('raw, escaped', [ ('http://example.com/1', 'http://example.com/1'), ('http://example.com/1 2', 'http://example.com/1%202'), ]) - def test_delete_url(self, hist, raw, escaped): - hist.add_url(QUrl('http://example.com/'), atime=0) - hist.add_url(QUrl(escaped), atime=0) - hist.add_url(QUrl('http://example.com/2'), atime=0) + def test_delete_url(self, web_history, raw, escaped): + web_history.add_url(QUrl('http://example.com/'), atime=0) + web_history.add_url(QUrl(escaped), atime=0) + web_history.add_url(QUrl('http://example.com/2'), atime=0) - before = set(hist) - completion_before = set(hist.completion) + before = set(web_history) + completion_before = set(web_history.completion) - hist.delete_url(QUrl(raw)) + web_history.delete_url(QUrl(raw)) - diff = before.difference(set(hist)) + diff = before.difference(set(web_history)) assert diff == {(escaped, '', 0, False)} - completion_diff = completion_before.difference(set(hist.completion)) + completion_diff = completion_before.difference( + set(web_history.completion)) assert completion_diff == {(raw, '', 0)} @@ -183,30 +160,32 @@ class TestAdd: 'https://user@example.com', 'https://user@example.com'), ] ) - def test_add_url(self, qtbot, hist, + def test_add_url(self, qtbot, web_history, url, atime, title, redirect, history_url, completion_url): - hist.add_url(QUrl(url), atime=atime, title=title, redirect=redirect) - assert list(hist) == [(history_url, title, atime, redirect)] + web_history.add_url(QUrl(url), atime=atime, title=title, + redirect=redirect) + assert list(web_history) == [(history_url, title, atime, redirect)] if completion_url is None: - assert not len(hist.completion) + assert not len(web_history.completion) else: - assert list(hist.completion) == [(completion_url, title, atime)] + expected = [(completion_url, title, atime)] + assert list(web_history.completion) == expected - def test_no_sql_history(self, hist, fake_args): + def test_no_sql_web_history(self, web_history, fake_args): fake_args.debug_flags = 'no-sql-history' - hist.add_url(QUrl('https://www.example.com/'), atime=12346, - title='Hello World', redirect=False) - assert not list(hist) + web_history.add_url(QUrl('https://www.example.com/'), atime=12346, + title='Hello World', redirect=False) + assert not list(web_history) - def test_invalid(self, qtbot, hist, caplog): + def test_invalid(self, qtbot, web_history, caplog): with caplog.at_level(logging.WARNING): - hist.add_url(QUrl()) - assert not list(hist) - assert not list(hist.completion) + web_history.add_url(QUrl()) + assert not list(web_history) + assert not list(web_history.completion) @pytest.mark.parametrize('environmental', [True, False]) @pytest.mark.parametrize('completion', [True, False]) - def test_error(self, monkeypatch, hist, message_mock, caplog, + def test_error(self, monkeypatch, web_history, message_mock, caplog, environmental, completion): def raise_error(url, replace=False): if environmental: @@ -215,18 +194,18 @@ class TestAdd: raise sql.SqlBugError("Error message") if completion: - monkeypatch.setattr(hist.completion, 'insert', raise_error) + monkeypatch.setattr(web_history.completion, 'insert', raise_error) else: - monkeypatch.setattr(hist, 'insert', raise_error) + monkeypatch.setattr(web_history, 'insert', raise_error) if environmental: with caplog.at_level(logging.ERROR): - hist.add_url(QUrl('https://www.example.org/')) + web_history.add_url(QUrl('https://www.example.org/')) msg = message_mock.getmsg(usertypes.MessageLevel.error) assert msg.text == "Failed to write history: Error message" else: with pytest.raises(sql.SqlBugError): - hist.add_url(QUrl('https://www.example.org/')) + web_history.add_url(QUrl('https://www.example.org/')) @pytest.mark.parametrize('level, url, req_url, expected', [ (logging.DEBUG, 'a.com', 'a.com', [('a.com', 'title', 12345, False)]), @@ -237,32 +216,33 @@ class TestAdd: (logging.WARNING, 'data:foo', '', []), (logging.WARNING, 'a.com', 'data:foo', []), ]) - def test_from_tab(self, hist, caplog, mock_time, + def test_from_tab(self, web_history, caplog, mock_time, level, url, req_url, expected): with caplog.at_level(level): - hist.add_from_tab(QUrl(url), QUrl(req_url), 'title') - assert set(hist) == set(expected) + web_history.add_from_tab(QUrl(url), QUrl(req_url), 'title') + assert set(web_history) == set(expected) - def test_exclude(self, hist, config_stub): + def test_exclude(self, web_history, config_stub): """Excluded URLs should be in the history but not completion.""" config_stub.val.completion.web_history.exclude = ['*.example.org'] url = QUrl('http://www.example.org/') - hist.add_from_tab(url, url, 'title') - assert list(hist) - assert not list(hist.completion) + web_history.add_from_tab(url, url, 'title') + assert list(web_history) + assert not list(web_history.completion) class TestHistoryInterface: @pytest.fixture - def hist_interface(self, hist): + def hist_interface(self, web_history): # pylint: disable=invalid-name QtWebKit = pytest.importorskip('PyQt5.QtWebKit') from qutebrowser.browser.webkit import webkithistory QWebHistoryInterface = QtWebKit.QWebHistoryInterface # pylint: enable=invalid-name - hist.add_url(url=QUrl('http://www.example.com/'), title='example') - interface = webkithistory.WebHistoryInterface(hist) + web_history.add_url(url=QUrl('http://www.example.com/'), + title='example') + interface = webkithistory.WebHistoryInterface(web_history) QWebHistoryInterface.setDefaultInterface(interface) yield QWebHistoryInterface.setDefaultInterface(None) @@ -280,9 +260,9 @@ class TestInit: def cleanup_init(self): # prevent test_init from leaking state yield - hist = objreg.get('web-history', None) - if hist is not None: - hist.setParent(None) + web_history = objreg.get('web-history', None) + if web_history is not None: + web_history.setParent(None) objreg.delete('web-history') try: from PyQt5.QtWebKit import QWebHistoryInterface @@ -325,118 +305,124 @@ class TestInit: class TestDump: - def test_debug_dump_history(self, hist, tmpdir): - hist.add_url(QUrl('http://example.com/1'), title="Title1", atime=12345) - hist.add_url(QUrl('http://example.com/2'), title="Title2", atime=12346) - hist.add_url(QUrl('http://example.com/3'), title="Title3", atime=12347) - hist.add_url(QUrl('http://example.com/4'), title="Title4", atime=12348, - redirect=True) + def test_debug_dump_history(self, web_history, tmpdir): + web_history.add_url(QUrl('http://example.com/1'), + title="Title1", atime=12345) + web_history.add_url(QUrl('http://example.com/2'), + title="Title2", atime=12346) + web_history.add_url(QUrl('http://example.com/3'), + title="Title3", atime=12347) + web_history.add_url(QUrl('http://example.com/4'), + title="Title4", atime=12348, redirect=True) histfile = tmpdir / 'history' - hist.debug_dump_history(str(histfile)) + web_history.debug_dump_history(str(histfile)) expected = ['12345 http://example.com/1 Title1', '12346 http://example.com/2 Title2', '12347 http://example.com/3 Title3', '12348-r http://example.com/4 Title4'] assert histfile.read() == '\n'.join(expected) - def test_nonexistent(self, hist, tmpdir): + def test_nonexistent(self, web_history, tmpdir): histfile = tmpdir / 'nonexistent' / 'history' with pytest.raises(cmdexc.CommandError): - hist.debug_dump_history(str(histfile)) + web_history.debug_dump_history(str(histfile)) class TestRebuild: - def test_delete(self, hist): - hist.insert({'url': 'example.com/1', 'title': 'example1', - 'redirect': False, 'atime': 1}) - hist.insert({'url': 'example.com/1', 'title': 'example1', - 'redirect': False, 'atime': 2}) - hist.insert({'url': 'example.com/2%203', 'title': 'example2', - 'redirect': False, 'atime': 3}) - hist.insert({'url': 'example.com/3', 'title': 'example3', - 'redirect': True, 'atime': 4}) - hist.insert({'url': 'example.com/2 3', 'title': 'example2', - 'redirect': False, 'atime': 5}) - hist.completion.delete_all() + def test_delete(self, web_history, stubs): + web_history.insert({'url': 'example.com/1', 'title': 'example1', + 'redirect': False, 'atime': 1}) + web_history.insert({'url': 'example.com/1', 'title': 'example1', + 'redirect': False, 'atime': 2}) + web_history.insert({'url': 'example.com/2%203', 'title': 'example2', + 'redirect': False, 'atime': 3}) + web_history.insert({'url': 'example.com/3', 'title': 'example3', + 'redirect': True, 'atime': 4}) + web_history.insert({'url': 'example.com/2 3', 'title': 'example2', + 'redirect': False, 'atime': 5}) + web_history.completion.delete_all() - hist2 = history.WebHistory(progress=FakeHistoryProgress()) + hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist2.completion) == [ ('example.com/1', 'example1', 2), ('example.com/2 3', 'example2', 5), ] - def test_no_rebuild(self, hist): + def test_no_rebuild(self, web_history, stubs): """Ensure that completion is not regenerated unless empty.""" - hist.add_url(QUrl('example.com/1'), redirect=False, atime=1) - hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) - hist.completion.delete('url', 'example.com/2') + web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1) + web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2) + web_history.completion.delete('url', 'example.com/2') - hist2 = history.WebHistory(progress=FakeHistoryProgress()) + hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist2.completion) == [('example.com/1', '', 1)] - def test_user_version(self, hist, monkeypatch): + def test_user_version(self, web_history, stubs, monkeypatch): """Ensure that completion is regenerated if user_version changes.""" - hist.add_url(QUrl('example.com/1'), redirect=False, atime=1) - hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) - hist.completion.delete('url', 'example.com/2') + web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1) + web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2) + web_history.completion.delete('url', 'example.com/2') - hist2 = history.WebHistory(progress=FakeHistoryProgress()) + hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist2.completion) == [('example.com/1', '', 1)] monkeypatch.setattr(history, '_USER_VERSION', history._USER_VERSION + 1) - hist3 = history.WebHistory(progress=FakeHistoryProgress()) + hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist3.completion) == [ ('example.com/1', '', 1), ('example.com/2', '', 2), ] - def test_force_rebuild(self, hist): + def test_force_rebuild(self, web_history, stubs): """Ensure that completion is regenerated if we force a rebuild.""" - hist.add_url(QUrl('example.com/1'), redirect=False, atime=1) - hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) - hist.completion.delete('url', 'example.com/2') + web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1) + web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2) + web_history.completion.delete('url', 'example.com/2') - hist2 = history.WebHistory(progress=FakeHistoryProgress()) + hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist2.completion) == [('example.com/1', '', 1)] hist2.metainfo['force_rebuild'] = True - hist3 = history.WebHistory(progress=FakeHistoryProgress()) + hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist3.completion) == [ ('example.com/1', '', 1), ('example.com/2', '', 2), ] assert not hist3.metainfo['force_rebuild'] - def test_exclude(self, config_stub, hist): + def test_exclude(self, config_stub, web_history, stubs): """Ensure that patterns in completion.web_history.exclude are ignored. This setting should only be used for the completion. """ config_stub.val.completion.web_history.exclude = ['*.example.org'] - assert hist.metainfo['force_rebuild'] + assert web_history.metainfo['force_rebuild'] - hist.add_url(QUrl('http://example.com'), redirect=False, atime=1) - hist.add_url(QUrl('http://example.org'), redirect=False, atime=2) + web_history.add_url(QUrl('http://example.com'), + redirect=False, atime=1) + web_history.add_url(QUrl('http://example.org'), + redirect=False, atime=2) - hist2 = history.WebHistory(progress=FakeHistoryProgress()) + hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress()) assert list(hist2.completion) == [('http://example.com', '', 1)] - def test_unrelated_config_change(self, config_stub, hist): + def test_unrelated_config_change(self, config_stub, web_history): config_stub.val.history_gap_interval = 1234 - assert not hist.metainfo['force_rebuild'] + assert not web_history.metainfo['force_rebuild'] @pytest.mark.parametrize('patch_threshold', [True, False]) - def test_progress(self, hist, config_stub, monkeypatch, patch_threshold): - hist.add_url(QUrl('example.com/1'), redirect=False, atime=1) - hist.add_url(QUrl('example.com/2'), redirect=False, atime=2) - hist.metainfo['force_rebuild'] = True + def test_progress(self, web_history, config_stub, monkeypatch, stubs, + patch_threshold): + web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1) + web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2) + web_history.metainfo['force_rebuild'] = True if patch_threshold: monkeypatch.setattr(history.WebHistory, '_PROGRESS_THRESHOLD', 1) - progress = FakeHistoryProgress() + progress = stubs.FakeHistoryProgress() history.WebHistory(progress=progress) assert progress._value == 2 assert progress._finished diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 46113561b..fbe9287ac 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -24,8 +24,7 @@ import time from PyQt5.QtCore import QUrl import pytest -from qutebrowser.browser import history, qutescheme -from qutebrowser.utils import objreg +from qutebrowser.browser import qutescheme class TestJavascriptHandler: @@ -96,21 +95,12 @@ class TestHistoryHandler: return items - @pytest.fixture - def fake_web_history(self, fake_save_manager, tmpdir, init_sql, - config_stub): - """Create a fake web-history and register it into objreg.""" - web_history = history.WebHistory() - objreg.register('web-history', web_history) - yield web_history - objreg.delete('web-history') - @pytest.fixture(autouse=True) - def fake_history(self, fake_web_history, fake_args, entries): + def fake_history(self, web_history, fake_args, entries): """Create fake history.""" fake_args.debug_flags = [] for item in entries: - fake_web_history.add_url(**item) + web_history.add_url(**item) @pytest.mark.parametrize("start_time_offset, expected_item_count", [ (0, 4), @@ -134,7 +124,7 @@ class TestHistoryHandler: assert item['time'] <= start_time assert item['time'] > end_time - def test_exclude(self, fake_web_history, now, config_stub): + def test_exclude(self, web_history, now, config_stub): """Make sure the completion.web_history.exclude setting is not used.""" config_stub.val.completion.web_history.exclude = ['www.x.com'] @@ -143,7 +133,7 @@ class TestHistoryHandler: items = json.loads(data) assert items - def test_qute_history_benchmark(self, fake_web_history, benchmark, now): + def test_qute_history_benchmark(self, web_history, benchmark, now): r = range(100000) entries = { 'atime': [int(now - t) for t in r], @@ -152,7 +142,7 @@ class TestHistoryHandler: 'redirect': [False for _ in r], } - fake_web_history.insert_batch(entries) + web_history.insert_batch(entries) url = QUrl("qute://history/data?start_time={}".format(now)) _mimetype, data = benchmark(qutescheme.qute_history, url) assert len(json.loads(data)) > 1 diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 5181cab6f..81cdfc19e 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -30,8 +30,7 @@ from PyQt5.QtCore import QUrl from qutebrowser.completion import completer from qutebrowser.completion.models import miscmodels, urlmodel, configmodel from qutebrowser.config import configdata, configtypes -from qutebrowser.utils import objreg, usertypes -from qutebrowser.browser import history +from qutebrowser.utils import usertypes from qutebrowser.commands import cmdutils @@ -168,17 +167,6 @@ def bookmarks(bookmark_manager_stub): return bookmark_manager_stub -@pytest.fixture -def web_history(init_sql, stubs, config_stub): - """Fixture which provides a web-history object.""" - config_stub.val.completion.timestamp_format = '%Y-%m-%d' - config_stub.val.completion.web_history.max_items = -1 - stub = history.WebHistory() - objreg.register('web-history', stub) - yield stub - objreg.delete('web-history') - - @pytest.fixture def web_history_populated(web_history): """Pre-populate the web-history database.""" From 5ca911bcdbfddeee0343b03edf8bce89c57d112d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 6 Sep 2018 20:09:57 +0200 Subject: [PATCH 024/114] Fix lint --- qutebrowser/browser/history.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 615f65923..f98ec51a5 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -23,13 +23,12 @@ import os import time import contextlib -from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal +from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal from PyQt5.QtWidgets import QProgressDialog, QApplication from qutebrowser.config import config from qutebrowser.commands import cmdutils, cmdexc -from qutebrowser.utils import (utils, objreg, log, usertypes, message, - debug, standarddir, qtutils) +from qutebrowser.utils import utils, objreg, log, usertypes, message, qtutils from qutebrowser.misc import objects, sql @@ -51,6 +50,7 @@ class HistoryProgress: self._value = 0 def start(self, text, maximum): + """Start showing a progress dialog.""" self._progress = QProgressDialog() self._progress.setMinimumDuration(500) self._progress.setLabelText(text) @@ -60,12 +60,14 @@ class HistoryProgress: QApplication.processEvents() def tick(self): + """Increase the displayed progress value.""" self._value += 1 if self._progress is not None: self._progress.setValue(self._value) QApplication.processEvents() def finish(self): + """Finish showing the progress dialog.""" if self._progress is not None: self._progress.hide() From 15c547b3f5cbec2567dfc91866b38c9afd29a6a0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 7 Sep 2018 12:24:11 +0200 Subject: [PATCH 025/114] Move QuteSchemeHandler._check_initiator to its own method --- .../browser/webengine/webenginequtescheme.py | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 3e39943a6..b94fc3844 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -39,6 +39,33 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler): profile.installUrlSchemeHandler(b'chrome-error', self) profile.installUrlSchemeHandler(b'chrome-extension', self) + def _check_initiator(self, job): + """Check whether the initiator of the job should be allowed. + + Only the browser itself or qute:// pages should access any of those + URLs. The request interceptor further locks down qute://settings/set. + + Args: + job: QWebEngineUrlRequestJob + + Return: + True if the initiator is allowed, False if it was blocked. + """ + try: + initiator = job.initiator() + except AttributeError: + # Added in Qt 5.11 + return True + + if initiator.isValid() and initiator.scheme() != 'qute': + log.misc.warning("Blocking malicious request from {} to {}" + .format(initiator.toDisplayString(), + url.toDisplayString())) + job.fail(QWebEngineUrlRequestJob.RequestDenied) + return False + + return True + def requestStarted(self, job): """Handle a request for a qute: scheme. @@ -55,21 +82,8 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler): job.fail(QWebEngineUrlRequestJob.UrlInvalid) return - # Only the browser itself or qute:// pages should access any of those - # URLs. - # The request interceptor further locks down qute://settings/set. - try: - initiator = job.initiator() - except AttributeError: - # Added in Qt 5.11 - pass - else: - if initiator.isValid() and initiator.scheme() != 'qute': - log.misc.warning("Blocking malicious request from {} to {}" - .format(initiator.toDisplayString(), - url.toDisplayString())) - job.fail(QWebEngineUrlRequestJob.RequestDenied) - return + if not self._check_initiator(job): + return if job.requestMethod() != b'GET': job.fail(QWebEngineUrlRequestJob.RequestDenied) From 0b27779c9d8c39d62c93720399b50240d679088e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 7 Sep 2018 12:25:07 +0200 Subject: [PATCH 026/114] Allow null initiator for qute:// URLs on Qt 5.11 Before Qt 5.11.2, for unique origins, we always got QUrl() and thus passed it through. With Qt 5.11.2, only missing origins (browser-initiated requests) get an empty initiator, while unique origins get QUrl("null"): https://codereview.qt-project.org/#/c/234849/ https://bugreports.qt.io/browse/QTBUG-69372 In theory, those should be locked down (as an unique origin is e.g. a sandboxed iframe) and never have access to any other content. However, thanks to a Qt bug, XHR on qute:// pages has QUrl("null") as origin as long as the URL scheme is not registered. We can only do the registering once Qt 5.12 is out. Since unique origins were effectively already allowed on Qt 5.11.0/.1, we pass them through here as well until Qt 5.12. See #4198 --- doc/changelog.asciidoc | 1 + qutebrowser/browser/webengine/webenginequtescheme.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b708de742..1fc848a1e 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -47,6 +47,7 @@ Changed DATADIR and MANDIR. - Various performance improvements when many tabs are opened. - Regenerating completion history now shows a progress dialog. +- Make qute:// pages work properly on Qt 5.11.2 Removed ~~~~~~~ diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index b94fc3844..202482084 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -19,7 +19,7 @@ """QtWebEngine specific qute://* handlers and glue code.""" -from PyQt5.QtCore import QBuffer, QIODevice +from PyQt5.QtCore import QBuffer, QIODevice, QUrl from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler, QWebEngineUrlRequestJob) @@ -57,6 +57,10 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler): # Added in Qt 5.11 return True + if initiator == QUrl('null') and not qtutils.version_check('5.12'): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70421 + return True + if initiator.isValid() and initiator.scheme() != 'qute': log.misc.warning("Blocking malicious request from {} to {}" .format(initiator.toDisplayString(), From 93eb05598ee7af4152e18aeabbed8649f58d608b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 7 Sep 2018 16:12:40 +0200 Subject: [PATCH 027/114] Fix request logging --- qutebrowser/browser/webengine/webenginequtescheme.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginequtescheme.py b/qutebrowser/browser/webengine/webenginequtescheme.py index 202482084..1db5d5e3d 100644 --- a/qutebrowser/browser/webengine/webenginequtescheme.py +++ b/qutebrowser/browser/webengine/webenginequtescheme.py @@ -62,9 +62,9 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler): return True if initiator.isValid() and initiator.scheme() != 'qute': - log.misc.warning("Blocking malicious request from {} to {}" - .format(initiator.toDisplayString(), - url.toDisplayString())) + log.misc.warning("Blocking malicious request from {} to {}".format( + initiator.toDisplayString(), + job.requestUrl().toDisplayString())) job.fail(QWebEngineUrlRequestJob.RequestDenied) return False From b611ff52cf4916062560ba0da0e9183bad519a6b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 9 Sep 2018 18:31:41 +0200 Subject: [PATCH 028/114] Support URL patterns for content.autoplay --- doc/changelog.asciidoc | 1 + doc/help/settings.asciidoc | 4 +++- qutebrowser/config/configdata.yml | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 1fc848a1e..7b98ea71d 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -48,6 +48,7 @@ Changed - Various performance improvements when many tabs are opened. - Regenerating completion history now shows a progress dialog. - Make qute:// pages work properly on Qt 5.11.2 +- The `content.autoplay` setting now supports URL patterns on Qt >= 5.11. Removed ~~~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 5e302adfa..d49c61ac5 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1488,7 +1488,9 @@ Default: [[content.autoplay]] === content.autoplay Automatically start playing `