From faf692657c79cc50a1f5be78cb170416b7ebd6bd Mon Sep 17 00:00:00 2001 From: Eolien55 Date: Sun, 26 Feb 2023 15:47:13 +0100 Subject: [PATCH 001/403] misc/userscripts/ripbang: Use lite.duckduckgo The previous way of obtaining the Bang's URL was using the response's body, using the url atrribute of a meta tag. The way this was handled was a bit problematic: some URLs were of the format /l/?uddg=&, while others (such as google) is simply . This could have been fixed by using an if-else, but I preferred another approach. Lite DuckDuckGo sends a 303 response when using Bangs, instead of a 200 response which makes the redirect. The location header of the 303 response is the search engine's URL, so it's easy to implement this function, working for both afformentioned URL templates. --- misc/userscripts/ripbang | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/misc/userscripts/ripbang b/misc/userscripts/ripbang index 2f867c838..21317fe02 100755 --- a/misc/userscripts/ripbang +++ b/misc/userscripts/ripbang @@ -9,18 +9,16 @@ # :spawn --userscript ripbang amazon maps # -import os, re, requests, sys -from urllib.parse import urlparse, parse_qs +import os, requests, sys for argument in sys.argv[1:]: bang = '!' + argument - r = requests.get('https://duckduckgo.com/', + r = requests.get('https://html.duckduckgo.com/html/', + allow_redirects=False, params={'q': bang + ' SEARCHTEXT'}, headers={'user-agent': 'qutebrowser ripbang'}) - searchengine = re.search("url=([^']+)", r.text).group(1) - searchengine = urlparse(searchengine).query - searchengine = parse_qs(searchengine)['uddg'][0] + searchengine = r.headers['location'] searchengine = searchengine.replace('SEARCHTEXT', '{}') if os.getenv('QUTE_FIFO'): From 3dc80fa1928bac215640e4a52fec57c7c20cd850 Mon Sep 17 00:00:00 2001 From: Michael M Date: Fri, 1 Sep 2023 20:15:45 -0500 Subject: [PATCH 002/403] Move URL to yankable string conversion to urlutils See issue #7693 --- qutebrowser/browser/commands.py | 25 ++----------------------- qutebrowser/browser/hints.py | 6 +----- qutebrowser/utils/urlutils.py | 24 +++++++++++++++++++++++- tests/unit/utils/test_urlutils.py | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 83a846b85..fdca10628 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -698,28 +698,6 @@ class CommandDispatcher: "Numeric argument is too large for internal int " "representation.") - def _yank_url(self, what): - """Helper method for yank() to get the URL to copy.""" - assert what in ['url', 'pretty-url'], what - - if what == 'pretty-url': - flags = urlutils.FormatOption.DECODE_RESERVED - else: - flags = urlutils.FormatOption.ENCODED - flags |= urlutils.FormatOption.REMOVE_PASSWORD - - url = QUrl(self._current_url()) - url_query = QUrlQuery() - url_query_str = url.query() - if '&' not in url_query_str and ';' in url_query_str: - url_query.setQueryDelimiters('=', ';') - url_query.setQuery(url_query_str) - for key in dict(url_query.queryItems()): - if key in config.val.url.yank_ignored_parameters: - url_query.removeQueryItem(key) - url.setQuery(url_query) - return url.toString(flags) - @cmdutils.register(instance='command-dispatcher', scope='window') @cmdutils.argument('what', choices=['selection', 'url', 'pretty-url', 'title', 'domain', 'inline']) @@ -754,7 +732,8 @@ class CommandDispatcher: self._current_url().host(), ':' + str(port) if port > -1 else '') elif what in ['url', 'pretty-url']: - s = self._yank_url(what) + url = self._current_url() + s = urlutils.get_url_yank_text(url, what == 'pretty-url') what = 'URL' # For printing elif what == 'selection': def _selection_callback(s): diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index e32567e4d..abdcab905 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -237,11 +237,7 @@ class HintActions: sel = (context.target == Target.yank_primary and utils.supports_selection()) - flags = urlutils.FormatOption.ENCODED | urlutils.FormatOption.REMOVE_PASSWORD - if url.scheme() == 'mailto': - flags |= urlutils.FormatOption.REMOVE_SCHEME - urlstr = url.toString(flags) - + urlstr = urlutils.get_url_yank_text(url, False) new_content = urlstr # only second and consecutive yanks are to append to the clipboard diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 7b613c0a2..a73b46681 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -14,7 +14,7 @@ import mimetypes from typing import Optional, Tuple, Union, Iterable, cast from qutebrowser.qt import machinery -from qutebrowser.qt.core import QUrl +from qutebrowser.qt.core import QUrl, QUrlQuery from qutebrowser.qt.network import QHostInfo, QHostAddress, QNetworkProxy from qutebrowser.api import cmdutils @@ -682,3 +682,25 @@ def widened_hostnames(hostname: str) -> Iterable[str]: while hostname: yield hostname hostname = hostname.partition(".")[-1] + + +def get_url_yank_text(url: QUrl, is_pretty: bool) -> str: + """Get the text that should be yanked for the given URL.""" + flags = FormatOption.REMOVE_PASSWORD + if url.scheme() == 'mailto': + flags |= FormatOption.REMOVE_SCHEME + if is_pretty: + flags |= FormatOption.DECODE_RESERVED + else: + flags |= FormatOption.ENCODED + + url_query = QUrlQuery() + url_query_str = url.query() + if '&' not in url_query_str and ';' in url_query_str: + url_query.setQueryDelimiters('=', ';') + url_query.setQuery(url_query_str) + for key in dict(url_query.queryItems()): + if key in config.val.url.yank_ignored_parameters: + url_query.removeQueryItem(key) + url.setQuery(url_query) + return url.toString(flags) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index d2eab5928..5dc2267a3 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -787,6 +787,20 @@ class TestParseJavascriptUrl: assert parsed == source +@pytest.mark.parametrize('url, is_pretty, expected', [ + (QUrl('https://example.com'), False, 'https://example.com'), + (QUrl('https://example.com/page'), False, 'https://example.com/page'), + (QUrl('ftp://example.com'), False, 'ftp://example.com'), + (QUrl('ftp://user:password@example.com'), False, 'ftp://user@example.com'), + (QUrl('https://example.com?ref=test'), False, 'https://example.com'), + (QUrl('mailto:email@example.com'), False, 'email@example.com'), + (QUrl('https://example.com/?pipe=%7C'), False, 'https://example.com/?pipe=%7C'), + (QUrl('https://example.com/?pipe=%7C'), True, 'https://example.com/?pipe=|'), +]) +def test_get_url_yank_text(url, is_pretty, expected): + assert urlutils.get_url_yank_text(url, is_pretty) == expected + + class TestWiden: @pytest.mark.parametrize('hostname, expected', [ From 5801d4b5321510863dbe589686ea6c71b773a436 Mon Sep 17 00:00:00 2001 From: Michael M Date: Wed, 13 Sep 2023 22:24:06 -0500 Subject: [PATCH 003/403] expand usage of urlutils.get_url_yank_text --- doc/changelog.asciidoc | 4 ++++ qutebrowser/commands/__init__.py | 2 ++ qutebrowser/commands/runners.py | 3 ++- qutebrowser/config/configdata.yml | 6 +++--- qutebrowser/mainwindow/prompt.py | 9 +++++---- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 8a5bb6ce3..f3fcfb41b 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -27,6 +27,10 @@ Fixed - Setting `url.auto_search` to `dns` works correctly now with Qt 6. - Counts passed via keypresses now have a digit limit (4300) to avoid exceptions due to cats sleeping on numpads. (#7834) +- Ignored URL query parameters (via `url.yank_ignored_parameters`) are now + respected when yanking any URL (for example, through hints with `hint links + yank`). The `{yank_url}` subsitution has also been added as a version of + `{url}` that respects ignored URL query parameters. (#7879) [[v3.0.0]] v3.0.0 (2023-08-18) diff --git a/qutebrowser/commands/__init__.py b/qutebrowser/commands/__init__.py index 7c6249371..ae1201010 100644 --- a/qutebrowser/commands/__init__.py +++ b/qutebrowser/commands/__init__.py @@ -14,6 +14,8 @@ For command arguments, there are also some variables you can use: - `{url:host}`, `{url:domain}`, `{url:auth}`, `{url:scheme}`, `{url:username}`, `{url:password}`, `{url:port}`, `{url:path}` and `{url:query}` expand to the respective parts of the current URL +- `{yank_url}` expands to the URL of the current page but strips all the query + parameters in the `url.yank_ignored_parameters` setting. - `{title}` expands to the current page's title - `{clipboard}` expands to the clipboard contents - `{primary}` expands to the primary selection contents diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 1ad563c5d..4c8e1e0ef 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -13,7 +13,7 @@ from qutebrowser.qt.core import pyqtSlot, QUrl, QObject from qutebrowser.api import cmdutils from qutebrowser.commands import cmdexc, parser -from qutebrowser.utils import message, objreg, qtutils, usertypes, utils +from qutebrowser.utils import message, objreg, qtutils, usertypes, utils, urlutils from qutebrowser.keyinput import macros, modeman if TYPE_CHECKING: @@ -57,6 +57,7 @@ def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]: _url(tb).port()) if _url(tb).port() != -1 else "", 'url:path': lambda tb: _url(tb).path(), 'url:query': lambda tb: _url(tb).query(), + 'yank_url': lambda tb: urlutils.get_url_yank_text(_url(tb), False), 'title': lambda tb: tb.widget.page_title(tb.widget.currentIndex()), 'clipboard': lambda _: utils.get_clipboard(), 'primary': lambda _: utils.get_clipboard(selection=True), diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index e8eba9de3..0dbd6436b 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -2554,7 +2554,7 @@ url.yank_ignored_parameters: - utm_term - utm_content - utm_name - desc: URL parameters to strip with `:yank url`. + desc: URL parameters to strip when yanking a URL. ## window @@ -3716,8 +3716,8 @@ bindings.default: yD: yank domain -s yp: yank pretty-url yP: yank pretty-url -s - ym: yank inline [{title}]({url}) - yM: yank inline [{title}]({url}) -s + ym: yank inline [{title}]({yank_url}) + yM: yank inline [{title}]({yank_url}) -s pp: open -- {clipboard} pP: open -- {primary} Pp: open -t -- {clipboard} diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 80edf4412..53f3eddc2 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -12,7 +12,7 @@ import dataclasses from typing import Deque, MutableSequence, Optional, cast from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, - QItemSelectionModel, QObject, QEventLoop) + QItemSelectionModel, QObject, QEventLoop, QUrl) from qutebrowser.qt.widgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QTreeView, QSizePolicy, QSpacerItem) @@ -23,7 +23,7 @@ from qutebrowser.config import config, configtypes, configexc, stylesheet from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message from qutebrowser.keyinput import modeman from qutebrowser.api import cmdutils -from qutebrowser.utils import urlmatch +from qutebrowser.utils import urlmatch, urlutils prompt_queue = cast('PromptQueue', None) @@ -443,8 +443,9 @@ class PromptContainer(QWidget): else: sel = False target = 'clipboard' - utils.set_clipboard(question.url, sel) - message.info("Yanked to {}: {}".format(target, question.url)) + url_str = urlutils.get_url_yank_text(QUrl(question.url), False) + utils.set_clipboard(url_str, sel) + message.info("Yanked to {}: {}".format(target, url_str)) @cmdutils.register( instance='prompt-container', scope='window', From 38e5513f4f37487067744d955b88a6c06eb99369 Mon Sep 17 00:00:00 2001 From: Michael M Date: Wed, 13 Sep 2023 23:10:02 -0500 Subject: [PATCH 004/403] add more test --- tests/end2end/features/yankpaste.feature | 2 +- tests/end2end/misc/test_runners_e2e.py | 1 + tests/unit/utils/test_urlutils.py | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature index f551d732f..ebd7f9683 100644 --- a/tests/end2end/features/yankpaste.feature +++ b/tests/end2end/features/yankpaste.feature @@ -41,7 +41,7 @@ Feature: Yanking and pasting. Scenario: Yanking inline to clipboard When I open data/title.html - And I run :yank inline '[[{url}][qutebrowser Date: Fri, 15 Sep 2023 11:09:41 -0500 Subject: [PATCH 005/403] Update test_urlutils.py Co-authored-by: port19 --- tests/unit/utils/test_urlutils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index a3f140fba..e11c367d2 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -795,7 +795,6 @@ class TestParseJavascriptUrl: (QUrl('https://example.com?ref=test'), False, 'https://example.com'), (QUrl('https://example.com?ref=test&example=yes'), False, 'https://example.com?example=yes'), - (QUrl('https://example.com?ref=test'), False, 'https://example.com'), (QUrl('https://example.com?ref'), False, 'https://example.com'), (QUrl('https://example.com?example'), False, 'https://example.com?example'), (QUrl('mailto:email@example.com'), False, 'email@example.com'), From 1b815695c8d5597d10389d3b8055835dce0244fa Mon Sep 17 00:00:00 2001 From: Michael M Date: Mon, 18 Sep 2023 09:01:39 -0500 Subject: [PATCH 006/403] change {yank_url} to {url:yank} --- doc/changelog.asciidoc | 2 +- qutebrowser/commands/__init__.py | 2 +- qutebrowser/commands/runners.py | 2 +- qutebrowser/config/configdata.yml | 4 ++-- tests/end2end/features/yankpaste.feature | 2 +- tests/end2end/misc/test_runners_e2e.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9b20ffb65..a07dde5d5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -29,7 +29,7 @@ Fixed exceptions due to cats sleeping on numpads. (#7834) - Ignored URL query parameters (via `url.yank_ignored_parameters`) are now respected when yanking any URL (for example, through hints with `hint links - yank`). The `{yank_url}` subsitution has also been added as a version of + yank`). The `{url:yank}` subsitution has also been added as a version of `{url}` that respects ignored URL query parameters. (#7879) - Navigating via hints to a remote URL from a file:// one works again. (#7847) - The timers related to the tab audible indicator and the auto follow timeout diff --git a/qutebrowser/commands/__init__.py b/qutebrowser/commands/__init__.py index ae1201010..fe2730e4d 100644 --- a/qutebrowser/commands/__init__.py +++ b/qutebrowser/commands/__init__.py @@ -14,7 +14,7 @@ For command arguments, there are also some variables you can use: - `{url:host}`, `{url:domain}`, `{url:auth}`, `{url:scheme}`, `{url:username}`, `{url:password}`, `{url:port}`, `{url:path}` and `{url:query}` expand to the respective parts of the current URL -- `{yank_url}` expands to the URL of the current page but strips all the query +- `{url:yank}` expands to the URL of the current page but strips all the query parameters in the `url.yank_ignored_parameters` setting. - `{title}` expands to the current page's title - `{clipboard}` expands to the clipboard contents diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 4c8e1e0ef..7293d2576 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -57,7 +57,7 @@ def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]: _url(tb).port()) if _url(tb).port() != -1 else "", 'url:path': lambda tb: _url(tb).path(), 'url:query': lambda tb: _url(tb).query(), - 'yank_url': lambda tb: urlutils.get_url_yank_text(_url(tb), False), + 'url:yank': lambda tb: urlutils.get_url_yank_text(_url(tb), False), 'title': lambda tb: tb.widget.page_title(tb.widget.currentIndex()), 'clipboard': lambda _: utils.get_clipboard(), 'primary': lambda _: utils.get_clipboard(selection=True), diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 0dbd6436b..d98ed4def 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -3716,8 +3716,8 @@ bindings.default: yD: yank domain -s yp: yank pretty-url yP: yank pretty-url -s - ym: yank inline [{title}]({yank_url}) - yM: yank inline [{title}]({yank_url}) -s + ym: yank inline [{title}]({url:yank}) + yM: yank inline [{title}]({url:yank}) -s pp: open -- {clipboard} pP: open -- {primary} Pp: open -t -- {clipboard} diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature index ebd7f9683..5dc43cfda 100644 --- a/tests/end2end/features/yankpaste.feature +++ b/tests/end2end/features/yankpaste.feature @@ -41,7 +41,7 @@ Feature: Yanking and pasting. Scenario: Yanking inline to clipboard When I open data/title.html - And I run :yank inline '[[{yank_url}][qutebrowser Date: Tue, 19 Sep 2023 19:26:57 -0500 Subject: [PATCH 007/403] Update doc/changelog.asciidoc Co-authored-by: Florian Bruhin --- doc/changelog.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index a07dde5d5..283b95f59 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -29,7 +29,7 @@ Fixed exceptions due to cats sleeping on numpads. (#7834) - Ignored URL query parameters (via `url.yank_ignored_parameters`) are now respected when yanking any URL (for example, through hints with `hint links - yank`). The `{url:yank}` subsitution has also been added as a version of + yank`). The `{url:yank}` substitution has also been added as a version of `{url}` that respects ignored URL query parameters. (#7879) - Navigating via hints to a remote URL from a file:// one works again. (#7847) - The timers related to the tab audible indicator and the auto follow timeout From 7c8d5572f118da927d8f9c6ba99604f20e2533ca Mon Sep 17 00:00:00 2001 From: Michael M Date: Tue, 19 Sep 2023 19:35:10 -0500 Subject: [PATCH 008/403] use keyword arguments for get_url_yank_text --- qutebrowser/browser/commands.py | 3 ++- qutebrowser/browser/hints.py | 2 +- qutebrowser/commands/runners.py | 3 ++- qutebrowser/mainwindow/prompt.py | 3 ++- qutebrowser/utils/urlutils.py | 2 +- tests/unit/utils/test_urlutils.py | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index fdca10628..089425e9d 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -733,7 +733,8 @@ class CommandDispatcher: ':' + str(port) if port > -1 else '') elif what in ['url', 'pretty-url']: url = self._current_url() - s = urlutils.get_url_yank_text(url, what == 'pretty-url') + pretty = what == 'pretty-url' + s = urlutils.get_url_yank_text(url, is_pretty=pretty) what = 'URL' # For printing elif what == 'selection': def _selection_callback(s): diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index abdcab905..fe9901c36 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -237,7 +237,7 @@ class HintActions: sel = (context.target == Target.yank_primary and utils.supports_selection()) - urlstr = urlutils.get_url_yank_text(url, False) + urlstr = urlutils.get_url_yank_text(url, is_pretty=False) new_content = urlstr # only second and consecutive yanks are to append to the clipboard diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 7293d2576..1d6c39afa 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -57,7 +57,8 @@ def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]: _url(tb).port()) if _url(tb).port() != -1 else "", 'url:path': lambda tb: _url(tb).path(), 'url:query': lambda tb: _url(tb).query(), - 'url:yank': lambda tb: urlutils.get_url_yank_text(_url(tb), False), + 'url:yank': lambda tb: urlutils.get_url_yank_text(_url(tb), + is_pretty=False), 'title': lambda tb: tb.widget.page_title(tb.widget.currentIndex()), 'clipboard': lambda _: utils.get_clipboard(), 'primary': lambda _: utils.get_clipboard(selection=True), diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 53f3eddc2..baf266801 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -443,7 +443,8 @@ class PromptContainer(QWidget): else: sel = False target = 'clipboard' - url_str = urlutils.get_url_yank_text(QUrl(question.url), False) + url_str = urlutils.get_url_yank_text(QUrl(question.url), + is_pretty=False) utils.set_clipboard(url_str, sel) message.info("Yanked to {}: {}".format(target, url_str)) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index a73b46681..ab141bf64 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -684,7 +684,7 @@ def widened_hostnames(hostname: str) -> Iterable[str]: hostname = hostname.partition(".")[-1] -def get_url_yank_text(url: QUrl, is_pretty: bool) -> str: +def get_url_yank_text(url: QUrl, *, is_pretty: bool) -> str: """Get the text that should be yanked for the given URL.""" flags = FormatOption.REMOVE_PASSWORD if url.scheme() == 'mailto': diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index e11c367d2..85b0499c4 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -804,7 +804,7 @@ class TestParseJavascriptUrl: (QUrl('https://example.com/?pipe=%7C'), True, 'https://example.com/?pipe=|'), ]) def test_get_url_yank_text(url, is_pretty, expected): - assert urlutils.get_url_yank_text(url, is_pretty) == expected + assert urlutils.get_url_yank_text(url, is_pretty=is_pretty) == expected class TestWiden: From 37db251be70ab19de0987af9d04529b3f862bbe8 Mon Sep 17 00:00:00 2001 From: Michael M Date: Tue, 19 Sep 2023 19:38:41 -0500 Subject: [PATCH 009/403] change is_pretty to pretty --- qutebrowser/browser/commands.py | 2 +- qutebrowser/browser/hints.py | 2 +- qutebrowser/commands/runners.py | 2 +- qutebrowser/mainwindow/prompt.py | 3 +-- qutebrowser/utils/urlutils.py | 4 ++-- tests/unit/utils/test_urlutils.py | 6 +++--- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 089425e9d..8c23ddcf8 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -734,7 +734,7 @@ class CommandDispatcher: elif what in ['url', 'pretty-url']: url = self._current_url() pretty = what == 'pretty-url' - s = urlutils.get_url_yank_text(url, is_pretty=pretty) + s = urlutils.get_url_yank_text(url, pretty=pretty) what = 'URL' # For printing elif what == 'selection': def _selection_callback(s): diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index fe9901c36..dc5c55b05 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -237,7 +237,7 @@ class HintActions: sel = (context.target == Target.yank_primary and utils.supports_selection()) - urlstr = urlutils.get_url_yank_text(url, is_pretty=False) + urlstr = urlutils.get_url_yank_text(url, pretty=False) new_content = urlstr # only second and consecutive yanks are to append to the clipboard diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 1d6c39afa..c22ff3ac1 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -58,7 +58,7 @@ def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]: 'url:path': lambda tb: _url(tb).path(), 'url:query': lambda tb: _url(tb).query(), 'url:yank': lambda tb: urlutils.get_url_yank_text(_url(tb), - is_pretty=False), + pretty=False), 'title': lambda tb: tb.widget.page_title(tb.widget.currentIndex()), 'clipboard': lambda _: utils.get_clipboard(), 'primary': lambda _: utils.get_clipboard(selection=True), diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index baf266801..0bb62d7b5 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -443,8 +443,7 @@ class PromptContainer(QWidget): else: sel = False target = 'clipboard' - url_str = urlutils.get_url_yank_text(QUrl(question.url), - is_pretty=False) + url_str = urlutils.get_url_yank_text(QUrl(question.url), pretty=False) utils.set_clipboard(url_str, sel) message.info("Yanked to {}: {}".format(target, url_str)) diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index ab141bf64..6f0e910cf 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -684,12 +684,12 @@ def widened_hostnames(hostname: str) -> Iterable[str]: hostname = hostname.partition(".")[-1] -def get_url_yank_text(url: QUrl, *, is_pretty: bool) -> str: +def get_url_yank_text(url: QUrl, *, pretty: bool) -> str: """Get the text that should be yanked for the given URL.""" flags = FormatOption.REMOVE_PASSWORD if url.scheme() == 'mailto': flags |= FormatOption.REMOVE_SCHEME - if is_pretty: + if pretty: flags |= FormatOption.DECODE_RESERVED else: flags |= FormatOption.ENCODED diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 85b0499c4..2e617d58c 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -787,7 +787,7 @@ class TestParseJavascriptUrl: assert parsed == source -@pytest.mark.parametrize('url, is_pretty, expected', [ +@pytest.mark.parametrize('url, pretty, expected', [ (QUrl('https://example.com'), False, 'https://example.com'), (QUrl('https://example.com/page'), False, 'https://example.com/page'), (QUrl('ftp://example.com'), False, 'ftp://example.com'), @@ -803,8 +803,8 @@ class TestParseJavascriptUrl: (QUrl('https://example.com/?pipe=%7C'), False, 'https://example.com/?pipe=%7C'), (QUrl('https://example.com/?pipe=%7C'), True, 'https://example.com/?pipe=|'), ]) -def test_get_url_yank_text(url, is_pretty, expected): - assert urlutils.get_url_yank_text(url, is_pretty=is_pretty) == expected +def test_get_url_yank_text(url, pretty, expected): + assert urlutils.get_url_yank_text(url, pretty=pretty) == expected class TestWiden: From bc7d956f77e36771074257ce08d2f22689a1353f Mon Sep 17 00:00:00 2001 From: Tobias Naumann Date: Mon, 18 Mar 2024 16:30:13 +0100 Subject: [PATCH 010/403] Add Unicode normalization and IDNA encoding to qute-pass userscript for the 'pass' mode (gopass support missing) --- misc/userscripts/qute-pass | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 70a497b63..405e340b5 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -40,11 +40,13 @@ import argparse import enum import fnmatch import functools +import idna import os import re import shlex import subprocess import sys +import unicodedata from urllib.parse import urlparse import tldextract @@ -116,6 +118,23 @@ def qute_command(command): fifo.write(command + '\n') fifo.flush() +# Encode candidate string parts as Internationalized Domain Name, doing +# Unicode normalization before. This allows to properly match (non-ASCII) +# pass entries with the corresponding domain names. +def idna_encode(name): + # Do Unicode normalization first, we use form NFKC because: + # 1. Use the compatibility normalization because these sequences have "the same meaning in some contexts" + # 2. idna.encode() below requires the Unicode strings to be in normalization form C + # See https://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms + unicode_normalized = unicodedata.normalize("NFKC", name) + # Empty strings can not be encoded, they appear for example as empty + # parts in split_path. If something like this happens, we just fall back + # to the unicode representation (which may already be ASCII then). + try: + idna_encoded = idna.encode(unicode_normalized) + except idna.IDNAError: + idna_encoded = unicode_normalized + return idna_encoded def find_pass_candidates(domain, unfiltered=False): candidates = [] @@ -140,9 +159,13 @@ def find_pass_candidates(domain, unfiltered=False): split_path = pass_path.split(os.path.sep) for secret in secrets: secret_base = os.path.splitext(secret)[0] - if not unfiltered and domain not in (split_path + [secret_base]): + idna_domain = idna_encode(domain) + idna_split_path = [idna_encode(part) for part in split_path] + idna_secret_base = idna_encode(secret_base) + if not unfiltered and idna_domain not in (idna_split_path + [idna_secret_base]): continue + # Append the unencoded Unicode path/name since this is how pass uses them candidates.append(os.path.join(pass_path, secret_base)) return candidates From a209c86c55a47170f2ad23c5756a4a4e607419dd Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Apr 2024 13:40:52 +1200 Subject: [PATCH 011/403] Delay QEvent driven mouse clicks by 10ms On CI with Qt 6.7 we are seeing several tests failing in a flaky, but frequent, manner. These tests all seem to be doing :click-element and sending a "fake" click, as in one driven by QEvents, to a button on the test page after opening the page in an existing tab. In the logs we see that the element was identified with JS correctly but we don't see any evidence of the actual click after that. I've been testing locally with `python3 -m pytest tests/end2end/features/test_prompts_bdd.py -k test_javascript_confirm`. Delaying the click event by as little as 5ms seems to make the tests consistently pass. I'm setting it to an arbitrary 10ms here for no good reason. It's unfortunate that we have to change production code to make tests pass, it's unlikely that a 10ms window will affect real usage, and the problem is only immediately after a navigation. If we can't find a better way to resolve this and want to get rid of the 10ms delay when no needed we could maybe look at when the last navigation time for the tab was to decide whether to delay or not. I also tried changing all the "open in" lines in the failing tests to be "open in new tab" and that seemed to make the flaky tests pass consistently too. But relying on people writing tests in a specific way wouldn't really be fixing the problem. Additionally, running just one test at a time (with `taskset -c 1 ...`) made all the tests pass consistently. So it seems like the root cause could have something to do with that, somehow. Or maybe just running the pytest process and the browser process on the same CPU causes enough of a delay to not trigger it. I don't know what the root cause of this change is, or if we can use a more event based way of knowing when the page (or its focus proxy) is ready to receive events. I've tried digging through events and debugging webengine and haven't got anywhere, hopefully this delay is painless enough that it'll do. To summarize, the page, or focus proxy, doesn't process events (all events, just mouse events?) immediately after navigating. I initially tried delaying all events in `tab.send_event()`, but that caused some caret tests to fail (the `selectionfollow_with_link_tabbing` e2e ones), not sure why. So I walked back to just delaying click events. I've seen some intermittent flakyness in `test_misc_bdd.py::test_clicking_on_focused_element` and `tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_keychain_to_the_website` which may indicate that `:fake-key` needs the same treatment. We'll see how things stand once it's been stable on the main branch for a while instead of being muddled up in this investigation branch. I thought the issue might be that the RenderWidgetHostViewQt was being swapped out on the new page load and the old one being sent the event. So I added a bunch of debug logging to try to get a better view of that. None of this debug logging is showing up when the tests fail so it seems that isn't the case. The tests failing in the last four bleeding edge qt6 tests before these changes were (test, count): ('tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_special_key_to_the_website', 2), ('tests/end2end/features/test_prompts_bdd.py::test_using_contentjavascriptalert', 2), ('tests/end2end/features/test_editor_bdd.py::test_select_two_files_with_multiple_files_command', 2), ('tests/end2end/features/test_misc_bdd.py::test_clicking_first_element_matching_a_selector', 2), ('tests/end2end/features/test_misc_bdd.py::test_clicking_on_focused_element', 2), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_prompt_with_default', 2), ('tests/end2end/features/test_prompts_bdd.py::test_using_contentjavascriptprompt', 2), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_confirm__aborted', 2), ('tests/end2end/features/test_editor_bdd.py::test_file_selector_deleting_temporary_file', 1), ('tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_keychain_to_the_website', 1), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_confirm__no', 1), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_confirm_with_default_value', 1), ('tests/end2end/test_insert_mode.py::test_insert_mode[100-input.html-qute-input-keypress-awesomequtebrowser]', 1), ('tests/end2end/features/test_misc_bdd.py::test_clicking_an_element_by_position', 1), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_confirm__yes', 1), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_confirm_with_invalid_value', 1), ('tests/end2end/test_insert_mode.py::test_insert_mode[125-input.html-qute-input-keypress-awesomequtebrowser]', 1), ('tests/end2end/features/test_editor_bdd.py::test_select_two_files_with_single_file_command', 1), ('tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_key_to_the_website', 1), ('tests/end2end/features/test_misc_bdd.py::test_clicking_an_element_by_id_with_dot', 1), ('tests/end2end/features/test_prompts_bdd.py::test_javascript_alert_with_value', 1) For debugging in GBD under test I ran the test process under GDB and passed a script to GDB to print out a message, and then continue, when it hit a breakpoint. Unfortunately, the tests always passed when run under GDB. For that I changed `_executable_args()` in quteprocess to return `"gdb", "-q -x handleMouseEvent-breakpoint -args".split() + [executable] + args` where `handleMouseEvent-breakpoint` is: set breakpoint pending on set debuginfod enabled off break RenderWidgetHostViewQtDelegateClient::handleMouseEvent commands call (void) fprintf(stderr, "Entering handleMouseEvent\n") continue end run The tests were consistently passing, but erroring on the invalid gdb log lines. --- qutebrowser/browser/webelem.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 2356ad086..721ab83df 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -355,10 +355,14 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a QMouseEvent(QEvent.Type.MouseButtonRelease, pos, button, Qt.MouseButton.NoButton, modifiers), ] - for evt in events: - self._tab.send_event(evt) + def _send_events_after_delay() -> None: + """Delay clicks to workaround timing issue in e2e tests on 6.7.""" + for evt in events: + self._tab.send_event(evt) - QTimer.singleShot(0, self._move_text_cursor) + QTimer.singleShot(0, self._move_text_cursor) + + QTimer.singleShot(10, _send_events_after_delay) def _click_editable(self, click_target: usertypes.ClickTarget) -> None: """Fake a click on an editable input field.""" From 44a63d9b3a45fd713df525608b25dbe90846766e Mon Sep 17 00:00:00 2001 From: toofar Date: Thu, 25 Apr 2024 20:33:43 +1200 Subject: [PATCH 012/403] Wait for evidence of click in e2e before proceeding I've added a 10ms delay when sending a fake click via QEvents to help with a timing issue in the prompt e2e tests in Qt6.7. Apparently that throws off the tight timing of this one test! I guess the `:scroll bottom` comes before the iframe has been clicked. I'm a little worried that if we are depending on timing stuff like this we should be looking at a more thorough solution. Maybe the "and follow" suffix can trigger it to wait for "Clicked *editable element!"? Also I can try to reduce the delay in the click, but 10ms is pretty short already. --- tests/end2end/features/hints.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index ddf42132f..b2a549fb5 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -279,6 +279,7 @@ Feature: Using hints When I open data/hints/iframe_scroll.html And I wait for "* simple loaded" in the log And I hint with args "all normal" and follow a + And I wait for "Clicked non-editable element!" in the log And I run :scroll bottom And I hint with args "links normal" and follow a Then "navigation request: url http://localhost:*/data/hello2.txt (current http://localhost:*/data/hints/iframe_scroll.html), type link_clicked, *" should be logged From 24d01ad25729458f0584a35c6b4d9a36f0b5e580 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 26 Apr 2024 20:25:12 +1200 Subject: [PATCH 013/403] Exit mode before edit tests. Exit prompt mode on the last test in downloads.feature because it's preventing the next tests in editor.feature from running. Screenshots of the first failing test in editor.feature shows a prompt open and jsprompt open in the background. This shouldn't happen as there is a module level fixture in conftest.py that is supposed to close and re-open the browser process between each feature file. That bears more examination but for now this change looks pretty painless. --- tests/end2end/features/conftest.py | 6 ++++++ tests/end2end/features/downloads.feature | 1 + 2 files changed, 7 insertions(+) diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 082b999b1..b7f112182 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -759,3 +759,9 @@ def set_up_fileselector(quteproc, py_proc, tmpdir, kind, files, output_type): fileselect_cmd = json.dumps([cmd, *args]) quteproc.set_setting('fileselect.handler', 'external') quteproc.set_setting(f'fileselect.{kind}.command', fileselect_cmd) + + +@bdd.then(bdd.parsers.parse("I run {command}")) +def run_command_then(quteproc, command): + """Run a qutebrowser command.""" + quteproc.send_cmd(command) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index c2f359f14..a1bbee870 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -712,3 +712,4 @@ Feature: Downloading things from a website. And I wait for "Asking question *" in the log And I run :prompt-fileselect-external Then the error "Can only launch external fileselect for FilenamePrompt, not LineEditPrompt" should be shown + And I run :mode-leave From 9f050c7460c42f317ceaa20b320e97d371a2c0a0 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Apr 2024 14:47:59 +1200 Subject: [PATCH 014/403] Delay fake-key events by 10ms Similar to a209c86c55a4 "Delay QEvent driven mouse clicks by 10ms" it seems that sending fake-key QEvents immediately after a navigation causes the events to not be processed. This is causing e2e tests in keyinput.feature to fail in a flaky, but frequent, manner. This can also be resolved in some other ways like putting a wait in the e2e tests like so: When I open data/keyinput/log.html And I wait 0.01s And I run :fake-key But relying on maintainers to remember to do that seems error prone. If this 10ms delay turns out to be something to get rid of we could try keep this delay to be used in less cases: 1. add some magic to e2e tests where it compares the previous and current line and if it sees an open and a click-element or fake-key line next to each other it delays for a bit 2. in the application code in tab.send_event() store the time of last navigation and queue events for sending till after that The affected tests in this case where: tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_key_to_the_website tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_special_key_to_the_website tests/end2end/features/test_keyinput_bdd.py::test_fakekey_sending_keychain_to_the_website --- qutebrowser/browser/commands.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 83a846b85..4ce677caa 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -10,7 +10,7 @@ import functools from typing import cast, Callable, Dict, Union, Optional from qutebrowser.qt.widgets import QApplication, QTabBar -from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery +from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery, QTimer from qutebrowser.commands import userscripts, runners from qutebrowser.api import cmdutils @@ -1790,20 +1790,26 @@ class CommandDispatcher: except keyutils.KeyParseError as e: raise cmdutils.CommandError(str(e)) + events = [] for keyinfo in sequence: - press_event = keyinfo.to_event(QEvent.Type.KeyPress) - release_event = keyinfo.to_event(QEvent.Type.KeyRelease) + events.append(keyinfo.to_event(QEvent.Type.KeyPress)) + events.append(keyinfo.to_event(QEvent.Type.KeyRelease)) - if global_: - window = QApplication.focusWindow() - if window is None: - raise cmdutils.CommandError("No focused window!") - QApplication.postEvent(window, press_event) - QApplication.postEvent(window, release_event) - else: - tab = self._current_widget() - tab.send_event(press_event) - tab.send_event(release_event) + if global_: + window = QApplication.focusWindow() + if window is None: + raise cmdutils.CommandError("No focused window!") + for event in events: + QApplication.postEvent(window, event) + else: + tab = self._current_widget() + + def _send_fake_key_after_delay(): + """Delay events to workaround timing issue in e2e tests on 6.7.""" + for event in events: + tab.send_event(event) + + QTimer.singleShot(10, _send_fake_key_after_delay) @cmdutils.register(instance='command-dispatcher', scope='window', debug=True, backend=usertypes.Backend.QtWebKit) From d47d247941c4b1fe3adac0dae8be301b36aec85b Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Apr 2024 15:19:34 +1200 Subject: [PATCH 015/403] Wait for evidence of Tab key press in tests before proceeding In the previous commit 9f050c7460c4 "Delay fake-key events by 10ms" I delayed events for fake-key by 10ms to deal with a timing issue with sending events right after a navigation not being processed. Just like last time that's caused a few tests with tight timing constraints to break because they proceed to the next command before the fake-key events have taken effect. Maybe it would have been better to just put the waits in the e2e tests in the first case instead of in the production code? Well, we'll see. Maybe I'll never have to deal with this again. --- tests/end2end/features/caret.feature | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature index d6e65440c..44df60c03 100644 --- a/tests/end2end/features/caret.feature +++ b/tests/end2end/features/caret.feature @@ -74,6 +74,7 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key + And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow Then data/hello.txt should be loaded @@ -83,6 +84,7 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key + And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow Then data/hello.txt should be loaded @@ -92,6 +94,7 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key + And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow --tab Then data/hello.txt should be loaded @@ -101,5 +104,6 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key + And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow --tab Then data/hello.txt should be loaded From 817091c61d12bf41c45008ae714db1c317c3d24e Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Apr 2024 17:52:43 +1200 Subject: [PATCH 016/403] Revert "delay fake-key" and add waits in e2e tests instead Previously (a209c86c55a4) I've added a delay in browser code before sending events to the page to account with a race condition where events weren't processed after navigating. Then I had to add extra checks to tests that had tight timing requirements. That was for click-element, this commit reverts an attempt at the same strategy for fake-key and instead adds wait statements in the tests that where hitting the original race condition (sending events "too soon" after a navigation). The reason for the different approach here is that after adding the delay in fake-key I had to add an extra "wait for log line" message to a few tests with tight timing requirements to watch for a Tab key press. That worked great for webengine but it made some tests start failing on webkit. We don't seem to get that log message on webkit. I've got no-idea why and frankly think I've spent way too much time on just a handful of tests already. It's unfortunate we have to add manually delays in the e2e tests. It makes me think if anyone else adds a test in the future with this combination of steps (open page, run fake-key) they'll run into the same issue and it'll be hard to spot. Oh well, we'll deal with that when it comes up. The tests that where failing on webkit are the ones in caret.feature touched in this commit. Reverts included in this commit: Revert "Delay fake-key events by 10ms" This reverts commit 9f050c7460c42f317ceaa20b320e97d371a2c0a0. Revert "Wait for evidence of Tab key press in tests before proceeding" This reverts commit d47d247941c4b1fe3adac0dae8be301b36aec85b. --- qutebrowser/browser/commands.py | 32 ++++++++++--------------- tests/end2end/features/caret.feature | 4 ---- tests/end2end/features/keyinput.feature | 3 +++ 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 4ce677caa..83a846b85 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -10,7 +10,7 @@ import functools from typing import cast, Callable, Dict, Union, Optional from qutebrowser.qt.widgets import QApplication, QTabBar -from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery, QTimer +from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery from qutebrowser.commands import userscripts, runners from qutebrowser.api import cmdutils @@ -1790,26 +1790,20 @@ class CommandDispatcher: except keyutils.KeyParseError as e: raise cmdutils.CommandError(str(e)) - events = [] for keyinfo in sequence: - events.append(keyinfo.to_event(QEvent.Type.KeyPress)) - events.append(keyinfo.to_event(QEvent.Type.KeyRelease)) + press_event = keyinfo.to_event(QEvent.Type.KeyPress) + release_event = keyinfo.to_event(QEvent.Type.KeyRelease) - if global_: - window = QApplication.focusWindow() - if window is None: - raise cmdutils.CommandError("No focused window!") - for event in events: - QApplication.postEvent(window, event) - else: - tab = self._current_widget() - - def _send_fake_key_after_delay(): - """Delay events to workaround timing issue in e2e tests on 6.7.""" - for event in events: - tab.send_event(event) - - QTimer.singleShot(10, _send_fake_key_after_delay) + if global_: + window = QApplication.focusWindow() + if window is None: + raise cmdutils.CommandError("No focused window!") + QApplication.postEvent(window, press_event) + QApplication.postEvent(window, release_event) + else: + tab = self._current_widget() + tab.send_event(press_event) + tab.send_event(release_event) @cmdutils.register(instance='command-dispatcher', scope='window', debug=True, backend=usertypes.Backend.QtWebKit) diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature index 44df60c03..d6e65440c 100644 --- a/tests/end2end/features/caret.feature +++ b/tests/end2end/features/caret.feature @@ -74,7 +74,6 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key - And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow Then data/hello.txt should be loaded @@ -84,7 +83,6 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key - And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow Then data/hello.txt should be loaded @@ -94,7 +92,6 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key - And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow --tab Then data/hello.txt should be loaded @@ -104,6 +101,5 @@ Feature: Caret mode And I run :mode-leave And I run :jseval document.activeElement.blur(); And I run :fake-key - And I wait for "* Got key: (dry_run True)" in the log And I run :selection-follow --tab Then data/hello.txt should be loaded diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index 3ab5d2434..f7f354def 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -32,6 +32,7 @@ Feature: Keyboard input Scenario: :fake-key sending key to the website When I open data/keyinput/log.html + And I wait 0.01s And I run :fake-key x Then the javascript message "key press: 88" should be logged And the javascript message "key release: 88" should be logged @@ -48,12 +49,14 @@ Feature: Keyboard input Scenario: :fake-key sending special key to the website When I open data/keyinput/log.html + And I wait 0.01s And I run :fake-key Then the javascript message "key press: 27" should be logged And the javascript message "key release: 27" should be logged Scenario: :fake-key sending keychain to the website When I open data/keyinput/log.html + And I wait 0.01s And I run :fake-key xy" " Then the javascript message "key press: 88" should be logged And the javascript message "key release: 88" should be logged From daa066249770259044a67478fb7db99dfe81ce18 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 22 Apr 2024 04:20:22 +0000 Subject: [PATCH 017/403] Update dependencies --- misc/requirements/requirements-dev.txt | 6 +++--- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-mypy.txt | 2 +- misc/requirements/requirements-tests.txt | 8 ++++---- misc/requirements/requirements-tox.txt | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 59a1f8a03..5887cbf83 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -backports.tarfile==1.0.0 +backports.tarfile==1.1.0 build==1.2.1 bump2version==1.0.1 certifi==2024.2.2 @@ -15,7 +15,7 @@ importlib_metadata==7.1.0 importlib_resources==6.4.0 jaraco.classes==3.4.0 jaraco.context==5.3.0 -jaraco.functools==4.0.0 +jaraco.functools==4.0.1 jeepney==0.8.0 keyring==25.1.0 manhole==1.8.0 @@ -30,7 +30,7 @@ Pygments==2.17.2 PyJWT==2.8.0 Pympler==1.0.1 pyproject_hooks==1.0.0 -PyQt-builder==1.16.0 +PyQt-builder==1.16.1 python-dateutil==2.9.0.post0 readme_renderer==43.0 requests==2.31.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index fa541e4a8..1cb8e2690 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==23.2.0 flake8==7.0.0 -flake8-bugbear==24.2.6 +flake8-bugbear==24.4.21 flake8-builtins==2.5.0 flake8-comprehensions==3.14.0 flake8-debugger==4.1.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 9bb872d13..104f7d65e 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -8,7 +8,7 @@ lxml==5.2.1 MarkupSafe==2.1.5 mypy==1.9.0 mypy-extensions==1.0.0 -pluggy==1.4.0 +pluggy==1.5.0 Pygments==2.17.2 PyQt5-stubs==5.15.6.0 tomli==2.0.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 0d8aa9bc4..6c086afe9 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -8,7 +8,7 @@ charset-normalizer==3.3.2 cheroot==10.0.0 click==8.1.7 coverage==7.4.4 -exceptiongroup==1.2.0 +exceptiongroup==1.2.1 execnet==2.1.1 filelock==3.13.4 Flask==3.0.3 @@ -17,8 +17,8 @@ hypothesis==6.100.1 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 -itsdangerous==2.1.2 -jaraco.functools==4.0.0 +itsdangerous==2.2.0 +jaraco.functools==4.0.1 # Jinja2==3.1.3 Mako==1.3.3 manhole==1.8.0 @@ -27,7 +27,7 @@ more-itertools==10.2.0 packaging==24.0 parse==1.20.1 parse-type==0.6.2 -pluggy==1.4.0 +pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.17.2 pytest==8.1.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index afea097d0..5b0a19159 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -8,10 +8,10 @@ filelock==3.13.4 packaging==24.0 pip==24.0 platformdirs==4.2.0 -pluggy==1.4.0 +pluggy==1.5.0 pyproject-api==1.6.1 setuptools==69.5.1 tomli==2.0.1 tox==4.14.2 -virtualenv==20.25.1 +virtualenv==20.25.3 wheel==0.43.0 From 085af130140bc4c7fadd60de611f4c3e4ea5ee0f Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Sat, 27 Apr 2024 07:20:49 +0000 Subject: [PATCH 018/403] Update dependencies --- misc/requirements/requirements-dev.txt | 6 +++--- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-mypy.txt | 6 +++--- misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyqt-6.txt | 8 ++++---- misc/requirements/requirements-pyqt.txt | 8 ++++---- misc/requirements/requirements-tests.txt | 6 +++--- misc/requirements/requirements-tox.txt | 6 +++--- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 5887cbf83..ceffc05fb 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -backports.tarfile==1.1.0 +backports.tarfile==1.1.1 build==1.2.1 bump2version==1.0.1 certifi==2024.2.2 @@ -17,7 +17,7 @@ jaraco.classes==3.4.0 jaraco.context==5.3.0 jaraco.functools==4.0.1 jeepney==0.8.0 -keyring==25.1.0 +keyring==25.2.0 manhole==1.8.0 markdown-it-py==3.0.0 mdurl==0.1.2 @@ -30,7 +30,7 @@ Pygments==2.17.2 PyJWT==2.8.0 Pympler==1.0.1 pyproject_hooks==1.0.0 -PyQt-builder==1.16.1 +PyQt-builder==1.16.2 python-dateutil==2.9.0.post0 readme_renderer==43.0 requests==2.31.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 1cb8e2690..a560af530 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==23.2.0 flake8==7.0.0 -flake8-bugbear==24.4.21 +flake8-bugbear==24.4.26 flake8-builtins==2.5.0 flake8-comprehensions==3.14.0 flake8-debugger==4.1.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 104f7d65e..fcea79aca 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -6,16 +6,16 @@ importlib_resources==6.4.0 Jinja2==3.1.3 lxml==5.2.1 MarkupSafe==2.1.5 -mypy==1.9.0 +mypy==1.10.0 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.17.2 PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-colorama==0.4.15.20240311 -types-docutils==0.20.0.20240406 +types-docutils==0.21.0.20240423 types-Pygments==2.17.0.20240310 types-PyYAML==6.0.12.20240311 -types-setuptools==69.5.0.20240415 +types-setuptools==69.5.0.20240423 typing_extensions==4.11.0 zipp==3.18.1 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 6c4b1d43b..8435786a5 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -4,5 +4,5 @@ altgraph==0.17.4 importlib_metadata==7.1.0 packaging==24.0 pyinstaller==6.6.0 -pyinstaller-hooks-contrib==2024.4 +pyinstaller-hooks-contrib==2024.5 zipp==3.18.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 94aac82a0..192ba4c97 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -11,7 +11,7 @@ idna==3.7 isort==5.13.2 mccabe==0.7.0 pefile==2023.2.7 -platformdirs==4.2.0 +platformdirs==4.2.1 pycparser==2.22 PyJWT==2.8.0 pylint==3.1.0 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 02f1a325f..b33f919ee 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.6.1 -PyQt6-Qt6==6.6.3 +PyQt6==6.7.0 +PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 -PyQt6-WebEngine==6.6.0 -PyQt6-WebEngine-Qt6==6.6.3 +PyQt6-WebEngine==6.7.0 +PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 02f1a325f..b33f919ee 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.6.1 -PyQt6-Qt6==6.6.3 +PyQt6==6.7.0 +PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 -PyQt6-WebEngine==6.6.0 -PyQt6-WebEngine-Qt6==6.6.3 +PyQt6-WebEngine==6.7.0 +PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 6c086afe9..6d4cffa03 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -5,9 +5,9 @@ beautifulsoup4==4.12.3 blinker==1.7.0 certifi==2024.2.2 charset-normalizer==3.3.2 -cheroot==10.0.0 +cheroot==10.0.1 click==8.1.7 -coverage==7.4.4 +coverage==7.5.0 exceptiongroup==1.2.1 execnet==2.1.1 filelock==3.13.4 @@ -30,7 +30,7 @@ parse-type==0.6.2 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.17.2 -pytest==8.1.1 +pytest==8.1.2 pytest-bdd==7.1.2 pytest-benchmark==4.0.0 pytest-cov==5.0.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 5b0a19159..975774969 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -7,11 +7,11 @@ distlib==0.3.8 filelock==3.13.4 packaging==24.0 pip==24.0 -platformdirs==4.2.0 +platformdirs==4.2.1 pluggy==1.5.0 pyproject-api==1.6.1 setuptools==69.5.1 tomli==2.0.1 -tox==4.14.2 -virtualenv==20.25.3 +tox==4.15.0 +virtualenv==20.26.0 wheel==0.43.0 From bdbbb93cd2120d8d58108da2b5fe72d65bff19f7 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 28 Apr 2024 12:40:42 +1200 Subject: [PATCH 019/403] fix lint, add cheroot log ignores mypy: Mypy knows about the QDataStream.Status.SizeLimitExceeded attribute now, so we can remove the ignore. But mypy for pyqt5 doesn't know about it, so put the whole graceful block behind an additional conditional as well to hide it from pyqt5 mypy. cheroot: looks like the format of the error message we are already ignoring changed slightly. The `ssl/tls` bit changed to `sslv3`, at least in our setup. --- qutebrowser/utils/qtutils.py | 17 +++++++++-------- tests/end2end/fixtures/webserver.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 21f3b8478..c1f05b78d 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -193,14 +193,15 @@ def check_qdatastream(stream: QDataStream) -> None: QDataStream.Status.WriteFailed: ("The data stream cannot write to the " "underlying device."), } - try: - status_to_str[QDataStream.Status.SizeLimitExceeded] = ( # type: ignore[attr-defined] - "The data stream cannot read or write the data because its size is larger " - "than supported by the current platform." - ) - except AttributeError: - # Added in Qt 6.7 - pass + if machinery.IS_QT6: + try: + status_to_str[QDataStream.Status.SizeLimitExceeded] = ( + "The data stream cannot read or write the data because its size is larger " + "than supported by the current platform." + ) + except AttributeError: + # Added in Qt 6.7 + pass if stream.status() != QDataStream.Status.Ok: raise OSError(status_to_str[stream.status()]) diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index 924cb520b..b70401745 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -116,7 +116,7 @@ def is_ignored_webserver_message(line: str) -> bool: return testutils.pattern_match( pattern=( "Client ('127.0.0.1', *) lost — peer dropped the TLS connection suddenly, " - "during handshake: (1, '[SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] ssl/tls " + "during handshake: (1, '[SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] * " "alert certificate unknown (_ssl.c:*)')" ), value=line, From 4fdc32ffe13fee65d2e0fe1ac697197089e09bad Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 29 Apr 2024 04:20:02 +0000 Subject: [PATCH 020/403] Update dependencies --- misc/requirements/requirements-tests.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 6d4cffa03..2a86c3202 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ attrs==23.2.0 beautifulsoup4==4.12.3 -blinker==1.7.0 +blinker==1.8.1 certifi==2024.2.2 charset-normalizer==3.3.2 cheroot==10.0.1 @@ -13,7 +13,7 @@ execnet==2.1.1 filelock==3.13.4 Flask==3.0.3 hunter==3.6.1 -hypothesis==6.100.1 +hypothesis==6.100.2 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 @@ -30,7 +30,7 @@ parse-type==0.6.2 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.17.2 -pytest==8.1.2 +pytest==8.2.0 pytest-bdd==7.1.2 pytest-benchmark==4.0.0 pytest-cov==5.0.0 @@ -39,7 +39,7 @@ pytest-mock==3.14.0 pytest-qt==4.4.0 pytest-repeat==0.9.3 pytest-rerunfailures==14.0 -pytest-xdist==3.5.0 +pytest-xdist==3.6.1 pytest-xvfb==3.0.0 PyVirtualDisplay==3.0 requests==2.31.0 From 26ef6bffd261d4ac876bbb35612a5a0b7a6232e7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 15:26:04 +0200 Subject: [PATCH 021/403] tabbedbrowser: Clean up QTBUG 91715 workaround By returning early, we can move the logic up a bit and handle the normal case after. --- qutebrowser/mainwindow/tabbedbrowser.py | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 28f32c4fd..47d8dc680 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -1011,16 +1011,11 @@ class TabbedBrowser(QWidget): # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-91715 versions = version.qtwebengine_versions() - is_qtbug_91715 = ( + if ( status == browsertab.TerminationStatus.unknown and code == 1002 and - versions.webengine == utils.VersionNumber(5, 15, 3)) - - def show_error_page(html): - tab.set_html(html) - log.webview.error(msg) - - if is_qtbug_91715: + versions.webengine == utils.VersionNumber(5, 15, 3) + ): log.webview.error(msg) log.webview.error('') log.webview.error( @@ -1034,12 +1029,17 @@ class TabbedBrowser(QWidget): 'A proper fix is likely available in QtWebEngine soon (which is why ' 'the workaround is disabled by default).') log.webview.error('') - else: - url_string = tab.url(requested=True).toDisplayString() - error_page = jinja.render( - 'error.html', title="Error loading {}".format(url_string), - url=url_string, error=msg) - QTimer.singleShot(100, lambda: show_error_page(error_page)) + return + + def show_error_page(html): + tab.set_html(html) + log.webview.error(msg) + + url_string = tab.url(requested=True).toDisplayString() + error_page = jinja.render( + 'error.html', title="Error loading {}".format(url_string), + url=url_string, error=msg) + QTimer.singleShot(100, lambda: show_error_page(error_page)) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. From b0002ac71f516750e342b5218caf26ef8c7bcec1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 15:44:53 +0200 Subject: [PATCH 022/403] Preload broken qutebrowser logo resource When qutebrowser is running but its installation has been deleted/moved, it fails in somewhat mysterious but predictable ways. This is e.g. the case currently, when people upgrade their Archlinux packages and upgrade from Python 3.11 to 3.12. When doing that with qutebrowser open, on the next page load, it will: - Have a crashed renderer process, because (assumingly) the process executable is gone on disk. - Which then causes us trying to render an error page, but that fails due to broken_qutebrowser_logo.png being gone from disk. - The FileNotFoundError then causes jinja2 to import jinja2.debug at runtime, but that *also* fails because the jinja2 package is gone. We work around this by loading the PNG into RAM early, and then using the cached version instead. This amends b4a2352833bfb06c86c1afb8b088cead0ef7c6d5 which did the same with HTML/JS resources, but never for this PNG, which (looking at crash logs) seems to be a somewhat common breakage. Alternatives I've considered: - Catching the FileNotFoundError and not showing an error page at all. - Generating a PNG with an explanatory text via QPainter and returning that. However, with the renderer process crash happening in the first place for unknown reasons, it's unclear if the error page ever gets actually displayed... Let's roll with this for now, and if this causes a repeating renderer process crash, fix that separately (also see #5108). --- doc/changelog.asciidoc | 3 +++ qutebrowser/utils/resources.py | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b6385b015..e90c84a31 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -47,6 +47,9 @@ Fixed condition. - Worked around qutebrowser quitting when closing a KDE file dialog due to a Qt bug. +- Worked around a crash when trying to use qutebrowser after it's been + deleted/moved on disk (e.g. after a Python upgrade). It now shows an error + asking to run `:restart` instead. [[v3.1.1]] v3.1.1 (unreleased) diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index a40f9d2bd..a97a2e994 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -26,6 +26,7 @@ else: # pragma: no cover import qutebrowser _cache: Dict[str, str] = {} +_bin_cache: Dict[str, bytes] = {} _ResourceType = Union[Traversable, pathlib.Path] @@ -88,6 +89,10 @@ def preload() -> None: for name in _glob(resource_path, subdir, ext): _cache[name] = read_file(name) + for name in _glob(resource_path, 'img', '.png'): + # e.g. broken_qutebrowser_logo.png + _bin_cache[name] = read_file_binary(name) + def read_file(filename: str) -> str: """Get the contents of a file contained with qutebrowser. @@ -115,6 +120,9 @@ def read_file_binary(filename: str) -> bytes: Return: The file contents as a bytes object. """ + if filename in _bin_cache: + return _bin_cache[filename] + path = _path(filename) with _keyerror_workaround(): return path.read_bytes() From 0fec3c7fb2806d1fa6cb6571757a92a3b15198a8 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 17:15:22 +0200 Subject: [PATCH 023/403] Update changelog --- doc/changelog.asciidoc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e90c84a31..e3326c9ee 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -25,7 +25,7 @@ Added - When qutebrowser receives a SIGHUP it will now reload any config.py file in use (same as the `:config-source` command does). (#8108) - The Chromium security patch version is now shown in the backend string in - --version and :version. This reflects the latest Chromium version that + `--version` and `:version`. This reflects the latest Chromium version that security fixes have been backported to the base QtWebEngine version from. (#7187) @@ -47,9 +47,8 @@ Fixed condition. - Worked around qutebrowser quitting when closing a KDE file dialog due to a Qt bug. -- Worked around a crash when trying to use qutebrowser after it's been - deleted/moved on disk (e.g. after a Python upgrade). It now shows an error - asking to run `:restart` instead. +- Trying to use qutebrowser after it's been deleted/moved on disk (e.g. after a + Python upgrade) should now not crash anymore. [[v3.1.1]] v3.1.1 (unreleased) From 3d96fc2656b372474c4f302617333bf2bb3e9cde Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 19:24:25 +0200 Subject: [PATCH 024/403] Make qt.machinery.Unavailable inherit ModuleNotFoundError With pytest 8.2, pytest.importorskip(...) now only considers ModuleNotFoundError rather than all ImportErrors, and warns otherwise: https://github.com/pytest-dev/pytest/pull/12220 While we could override this via pytest.importorskip(..., exc_type=machinery.Unavailable) this is a simpler solution, and it also makes more sense semantically: We only raise Unavailable when an import is being done that would otherwise result in a ModuleNotFoundError anyways (e.g. trying to import QtWebKit on Qt 6). --- qutebrowser/qt/machinery.py | 2 +- tests/unit/test_qt_machinery.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index 9f45dd6ce..45a1f6598 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -48,7 +48,7 @@ class Error(Exception): """Base class for all exceptions in this module.""" -class Unavailable(Error, ImportError): +class Unavailable(Error, ModuleNotFoundError): """Raised when a module is unavailable with the given wrapper.""" diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py index 25fc83ffd..cf7990393 100644 --- a/tests/unit/test_qt_machinery.py +++ b/tests/unit/test_qt_machinery.py @@ -9,7 +9,7 @@ import sys import html import argparse import typing -from typing import Any, Optional, List, Dict, Union +from typing import Any, Optional, List, Dict, Union, Type import dataclasses import pytest @@ -45,14 +45,14 @@ def undo_init(monkeypatch: pytest.MonkeyPatch) -> None: @pytest.mark.parametrize( - "exception", + "exception, base", [ - machinery.Unavailable(), - machinery.NoWrapperAvailableError(machinery.SelectionInfo()), + (machinery.Unavailable(), ModuleNotFoundError), + (machinery.NoWrapperAvailableError(machinery.SelectionInfo()), ImportError), ], ) -def test_importerror_exceptions(exception: Exception): - with pytest.raises(ImportError): +def test_importerror_exceptions(exception: Exception, base: Type[Exception]): + with pytest.raises(base): raise exception From a8f4feabb7dbf907745a838b731fd1c63847bfe3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 20:34:50 +0200 Subject: [PATCH 025/403] Exit command mode in editor tests Similarly to 24d01ad25729458f0584a35c6b4d9a36f0b5e580, failing Qt 5.15 tests showed some evidence of us being stuck in command mode in the next test file (hints.feature). On the first test there ("Scenario: Using :hint-follow outside of hint mode (issue 1105)"): 17:38:51.073 ERROR message message:error:63 hint-follow: This command is only allowed in hint mode, not command. but: end2end.fixtures.testprocess.WaitForTimeout: Timed out after 15000ms waiting for {'category': 'message', 'loglevel': 40, 'message': 'hint-follow: This command is only allowed in hint mode, not normal.'}. I agree with what has been said: This should never happen, because we restart the qutebrowser process between test files. I did some of the mentioned "more examination" but also don't have an explanation. To avoid more flaky tests, let's roll with another bandaid solution. --- tests/end2end/features/editor.feature | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 9ca855d27..018d65b9f 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -188,6 +188,8 @@ Feature: Opening external editors And I run :cmd-edit Then the error "command must start with one of :/?" should be shown And "Leaving mode KeyMode.command *" should not be logged + And I run :mode-leave + And "Leaving mode KeyMode.command *" should be logged ## select single file From 40f6193cc735e069386a50c2e8efcbcdd1da5961 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 17:33:12 +0200 Subject: [PATCH 026/403] Split QtWebEngine version across multiple lines More readable now that we have more information in it. Also always show the source, now that we have the space for it, and "UA" isn't the obvious default anymore anyways. --- qutebrowser/utils/version.py | 11 +++++------ tests/unit/utils/test_version.py | 25 +++++++++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 32d5357db..30585eb0c 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -636,14 +636,13 @@ class WebEngineVersions: self.chromium_major = int(self.chromium.split('.')[0]) def __str__(self) -> str: - s = f'QtWebEngine {self.webengine}' + lines = [f'QtWebEngine {self.webengine}'] if self.chromium is not None: - s += f', based on Chromium {self.chromium}' + lines.append(f' based on Chromium {self.chromium}') if self.chromium_security is not None: - s += f', with security patches up to {self.chromium_security} (plus any distribution patches)' - if self.source != 'UA': - s += f' (from {self.source})' - return s + lines.append(f' with security patches up to {self.chromium_security} (plus any distribution patches)') + lines.append(f' (source: {self.source})') + return "\n".join(lines) @classmethod def from_ua(cls, ua: 'websettings.UserAgent') -> 'WebEngineVersions': diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index f24bf2a7a..09e885b45 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -899,21 +899,32 @@ class TestWebEngineVersions: webengine=utils.VersionNumber(5, 15, 2), chromium=None, source='UA'), - "QtWebEngine 5.15.2", + ( + "QtWebEngine 5.15.2\n" + " (source: UA)" + ), ), ( version.WebEngineVersions( webengine=utils.VersionNumber(5, 15, 2), chromium='87.0.4280.144', source='UA'), - "QtWebEngine 5.15.2, based on Chromium 87.0.4280.144", + ( + "QtWebEngine 5.15.2\n" + " based on Chromium 87.0.4280.144\n" + " (source: UA)" + ), ), ( version.WebEngineVersions( webengine=utils.VersionNumber(5, 15, 2), chromium='87.0.4280.144', source='faked'), - "QtWebEngine 5.15.2, based on Chromium 87.0.4280.144 (from faked)", + ( + "QtWebEngine 5.15.2\n" + " based on Chromium 87.0.4280.144\n" + " (source: faked)" + ), ), ( version.WebEngineVersions( @@ -922,8 +933,10 @@ class TestWebEngineVersions: chromium_security='9000.1', source='faked'), ( - "QtWebEngine 5.15.2, based on Chromium 87.0.4280.144, with security " - "patches up to 9000.1 (plus any distribution patches) (from faked)" + "QtWebEngine 5.15.2\n" + " based on Chromium 87.0.4280.144\n" + " with security patches up to 9000.1 (plus any distribution patches)\n" + " (source: faked)" ), ), ]) @@ -1319,7 +1332,7 @@ def test_version_info(params, stubs, monkeypatch, config_stub): else: monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False) patches['objects.backend'] = usertypes.Backend.QtWebEngine - substitutions['backend'] = 'QtWebEngine 1.2.3 (from faked)' + substitutions['backend'] = 'QtWebEngine 1.2.3\n (source: faked)' if params.known_distribution: patches['distribution'] = lambda: version.DistributionInfo( From 6419cf282bfd339b888954bd9c8dfd6741391867 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 17:34:29 +0200 Subject: [PATCH 027/403] Move pastebin button up for version info --- qutebrowser/html/version.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/html/version.html b/qutebrowser/html/version.html index 643929088..666414b26 100644 --- a/qutebrowser/html/version.html +++ b/qutebrowser/html/version.html @@ -19,8 +19,8 @@ html { margin-left: 10px; } {% block content %} {{ super() }}

Version info

-
{{ version }}
+
{{ version }}

Copyright info

{{ copyright }}

From a85c3e2712c0934785b58048fa74ccb697dba91f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 17:55:47 +0200 Subject: [PATCH 028/403] version: Update security patch version comments Mostly based on CHROMIUM_VERSION in QtWebEngine and chromereleases.googleblog.com. --- qutebrowser/utils/version.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 30585eb0c..1f3999ca1 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -574,8 +574,14 @@ class WebEngineVersions: # 5.15.7: Security fixes up to 94.0.4606.61 (2021-09-24) # 5.15.8: Security fixes up to 96.0.4664.110 (2021-12-13) # 5.15.9: Security fixes up to 98.0.4758.102 (2022-02-14) - # 5.15.10: Security fixes up to ??? - # 5.15.11: Security fixes up to ??? + # 5.15.10: Security fixes up to 98.0.4758.102 (?) (2022-02-14) + # 5.15.11: Security fixes up to 98.0.4758.102 (?) (2022-02-14) + # 5.15.12: Security fixes up to 98.0.4758.102 (?) (2022-02-14) + # 5.15.13: Security fixes up to 108.0.5359.124 (2022-12-13) + # 5.15.14: Security fixes up to 113.0.5672.64 (2023-05-02) + # 5.15.15: Security fixes up to ??? + # 5.15.16: Security fixes up to 119.0.6045.123 (2023-11-07) + # 5.15.17: Security fixes up to 123.0.6312.58 (2024-03-19) utils.VersionNumber(5, 15): '87.0.4280.144', # >= 5.15.3 # Qt 6.2: Chromium 90 @@ -587,7 +593,8 @@ class WebEngineVersions: # 6.2.4: Security fixes up to 98.0.4758.102 (2022-02-14) # 6.2.5: Security fixes up to ??? # 6.2.6: Security fixes up to ??? - # 6.2.7: Security fixes up to ??? + # 6.2.7: Security fixes up to 107.0.5304.110 (2022-11-08) + # 6.2.8: Security fixes up to 111.0.5563.110 (2023-03-21) utils.VersionNumber(6, 2): '90.0.4430.228', # Qt 6.3: Chromium 94 @@ -624,7 +631,8 @@ class WebEngineVersions: # Qt 6.7: Chromium 118 # 118.0.5993.220 (~2023-10-24) - # 6.6.0: Security fixes up to 122.0.6261.128 (?) (2024-03-12) + # 6.7.0: Security fixes up to 122.0.6261.128 (2024-03-12) + # 6.7.1: Security fixes up to 124.0.6367.78 (?) (2024-04-24) utils.VersionNumber(6, 7): '118.0.5993.220', } From 2edfd459a42bdaa23b2e2f61df876265e9444548 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 18:56:27 +0200 Subject: [PATCH 029/403] Infer Chromium security version when API is unavailable We already had all this information in a comment anyways. I made it machine-readable using: s/#\s+(\d*)\.(\d*)\.(\d*): Security fixes up to ([^ ]*)\s+\((.*)\)/utils.VersionNumber(\1, \2, \3): (_BASES[XX], '\4'), # \5 plus some manual post-processing. Thanks to that, we can now get the security version from that data even on QtWebEngine < 6.3, if that information is known. When we fall back to a base version (e.g. 6.7.99 -> 6.7), we make sure to not pretend that we have the .0 state of things, though. Finally, we cross-check the information against the current Qt version if we have the API, which mostly ensures the data is accurate for human readers. See #7187 and #8139. --- qutebrowser/utils/version.py | 198 +++++++++++++++++-------------- tests/unit/utils/test_version.py | 45 +++++-- 2 files changed, 148 insertions(+), 95 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 1f3999ca1..775f57bd0 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -538,7 +538,19 @@ class WebEngineVersions: chromium_security: Optional[str] = None chromium_major: Optional[int] = dataclasses.field(init=False) - _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, str]] = { + _BASES: ClassVar[Dict[int, str]] = { + 83: '83.0.4103.122', # ~2020-06-24 + 87: '87.0.4280.144', # ~2020-12-02 + 90: '90.0.4430.228', # 2021-06-22 + 94: '94.0.4606.126', # 2021-11-17 + 102: '102.0.5005.177', # ~2022-05-24 + # (.220 claimed by code, .181 claimed by CHROMIUM_VERSION) + 108: '108.0.5359.220', # ~2022-12-23 + 112: '112.0.5615.213', # ~2023-04-18 + 118: '118.0.5993.220', # ~2023-10-24 + } + + _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, Tuple[str, Optional[str]]]] = { # ====== UNSUPPORTED ===== # Qt 5.12: Chromium 69 @@ -559,81 +571,61 @@ class WebEngineVersions: # 5.15.1: Security fixes up to 85.0.4183.83 (2020-08-25) # ====== SUPPORTED ===== + # base security + ## Qt 5.15 + utils.VersionNumber(5, 15, 2): (_BASES[83], '86.0.4240.183'), # 2020-11-02 + utils.VersionNumber(5, 15): (_BASES[87], None), # >= 5.15.3 + utils.VersionNumber(5, 15, 3): (_BASES[87], '88.0.4324.150'), # 2021-02-04 + # 5.15.4 to 5.15.6: unknown security fixes + utils.VersionNumber(5, 15, 7): (_BASES[87], '94.0.4606.61'), # 2021-09-24 + utils.VersionNumber(5, 15, 8): (_BASES[87], '96.0.4664.110'), # 2021-12-13 + utils.VersionNumber(5, 15, 9): (_BASES[87], '98.0.4758.102'), # 2022-02-14 + utils.VersionNumber(5, 15, 10): (_BASES[87], '98.0.4758.102'), # (?) 2022-02-14 + utils.VersionNumber(5, 15, 11): (_BASES[87], '98.0.4758.102'), # (?) 2022-02-14 + utils.VersionNumber(5, 15, 12): (_BASES[87], '98.0.4758.102'), # (?) 2022-02-14 + utils.VersionNumber(5, 15, 13): (_BASES[87], '108.0.5359.124'), # 2022-12-13 + utils.VersionNumber(5, 15, 14): (_BASES[87], '113.0.5672.64'), # 2023-05-02 + # 5.15.15: unknown security fixes + utils.VersionNumber(5, 15, 16): (_BASES[87], '119.0.6045.123'), # 2023-11-07 + utils.VersionNumber(5, 15, 17): (_BASES[87], '123.0.6312.58'), # 2024-03-19 - # Qt 5.15.2: Chromium 83 - # 83.0.4103.122 (~2020-06-24) - # 5.15.2: Security fixes up to 86.0.4240.183 (2020-11-02) - utils.VersionNumber(5, 15, 2): '83.0.4103.122', - # Qt 5.15.3: Chromium 87 - # 87.0.4280.144 (~2020-12-02) - # 5.15.3: Security fixes up to 88.0.4324.150 (2021-02-04) - # 5.15.4: Security fixes up to ??? - # 5.15.5: Security fixes up to ??? - # 5.15.6: Security fixes up to ??? - # 5.15.7: Security fixes up to 94.0.4606.61 (2021-09-24) - # 5.15.8: Security fixes up to 96.0.4664.110 (2021-12-13) - # 5.15.9: Security fixes up to 98.0.4758.102 (2022-02-14) - # 5.15.10: Security fixes up to 98.0.4758.102 (?) (2022-02-14) - # 5.15.11: Security fixes up to 98.0.4758.102 (?) (2022-02-14) - # 5.15.12: Security fixes up to 98.0.4758.102 (?) (2022-02-14) - # 5.15.13: Security fixes up to 108.0.5359.124 (2022-12-13) - # 5.15.14: Security fixes up to 113.0.5672.64 (2023-05-02) - # 5.15.15: Security fixes up to ??? - # 5.15.16: Security fixes up to 119.0.6045.123 (2023-11-07) - # 5.15.17: Security fixes up to 123.0.6312.58 (2024-03-19) - utils.VersionNumber(5, 15): '87.0.4280.144', # >= 5.15.3 + ## Qt 6.2 + utils.VersionNumber(6, 2): (_BASES[90], '93.0.4577.63'), # 2021-08-31 + utils.VersionNumber(6, 2, 1): (_BASES[90], '94.0.4606.61'), # 2021-09-24 + utils.VersionNumber(6, 2, 2): (_BASES[90], '96.0.4664.45'), # 2021-11-15 + utils.VersionNumber(6, 2, 3): (_BASES[90], '96.0.4664.45'), # 2021-11-15 + utils.VersionNumber(6, 2, 4): (_BASES[90], '98.0.4758.102'), # 2022-02-14 + # 6.2.5 / 6.2.6: unknown security fixes + utils.VersionNumber(6, 2, 7): (_BASES[90], '107.0.5304.110'), # 2022-11-08 + utils.VersionNumber(6, 2, 8): (_BASES[90], '111.0.5563.110'), # 2023-03-21 - # Qt 6.2: Chromium 90 - # 90.0.4430.228 (2021-06-22) - # 6.2.0: Security fixes up to 93.0.4577.63 (2021-08-31) - # 6.2.1: Security fixes up to 94.0.4606.61 (2021-09-24) - # 6.2.2: Security fixes up to 96.0.4664.45 (2021-11-15) - # 6.2.3: Security fixes up to 96.0.4664.45 (2021-11-15) - # 6.2.4: Security fixes up to 98.0.4758.102 (2022-02-14) - # 6.2.5: Security fixes up to ??? - # 6.2.6: Security fixes up to ??? - # 6.2.7: Security fixes up to 107.0.5304.110 (2022-11-08) - # 6.2.8: Security fixes up to 111.0.5563.110 (2023-03-21) - utils.VersionNumber(6, 2): '90.0.4430.228', + ## Qt 6.3 + utils.VersionNumber(6, 3): (_BASES[94], '99.0.4844.84'), # 2022-03-25 + utils.VersionNumber(6, 3, 1): (_BASES[94], '101.0.4951.64'), # 2022-05-10 + utils.VersionNumber(6, 3, 2): (_BASES[94], '104.0.5112.81'), # 2022-08-01 - # Qt 6.3: Chromium 94 - # 94.0.4606.126 (2021-11-17) - # 6.3.0: Security fixes up to 99.0.4844.84 (2022-03-25) - # 6.3.1: Security fixes up to 101.0.4951.64 (2022-05-10) - # 6.3.2: Security fixes up to 104.0.5112.81 (2022-08-01) - utils.VersionNumber(6, 3): '94.0.4606.126', + ## Qt 6.4 + utils.VersionNumber(6, 4): (_BASES[102], '104.0.5112.102'), # 2022-08-16 + utils.VersionNumber(6, 4, 1): (_BASES[102], '107.0.5304.88'), # 2022-10-27 + utils.VersionNumber(6, 4, 2): (_BASES[102], '108.0.5359.94'), # 2022-12-02 + utils.VersionNumber(6, 4, 3): (_BASES[102], '110.0.5481.78'), # 2023-02-07 - # Qt 6.4: Chromium 102 - # 102.0.5005.177 (~2022-05-24) - # 6.4.0: Security fixes up to 104.0.5112.102 (2022-08-16) - # 6.4.1: Security fixes up to 107.0.5304.88 (2022-10-27) - # 6.4.2: Security fixes up to 108.0.5359.94 (2022-12-02) - # 6.4.3: Security fixes up to 110.0.5481.78 (2023-02-07) - utils.VersionNumber(6, 4): '102.0.5005.177', + ## Qt 6.5 + utils.VersionNumber(6, 5): (_BASES[108], '110.0.5481.104'), # 2023-02-16 + utils.VersionNumber(6, 5, 1): (_BASES[108], '112.0.5615.138'), # 2023-04-18 + utils.VersionNumber(6, 5, 2): (_BASES[108], '114.0.5735.133'), # 2023-06-13 + utils.VersionNumber(6, 5, 3): (_BASES[108], '117.0.5938.63'), # 2023-09-12 - # Qt 6.5: Chromium 108 - # 108.0.5359.220 (~2022-12-23) - # (.220 claimed by code, .181 claimed by CHROMIUM_VERSION) - # 6.5.0: Security fixes up to 110.0.5481.104 (2023-02-16) - # 6.5.1: Security fixes up to 112.0.5615.138 (2023-04-18) - # 6.5.2: Security fixes up to 114.0.5735.133 (2023-06-13) - # 6.5.3: Security fixes up to 117.0.5938.63 (2023-09-12) - utils.VersionNumber(6, 5): '108.0.5359.220', + ## Qt 6.6 + utils.VersionNumber(6, 6): (_BASES[112], '117.0.5938.63'), # 2023-09-12 + utils.VersionNumber(6, 6, 1): (_BASES[112], '119.0.6045.123'), # 2023-11-07 + utils.VersionNumber(6, 6, 2): (_BASES[112], '121.0.6167.160'), # 2024-02-06 + utils.VersionNumber(6, 6, 3): (_BASES[112], '122.0.6261.128'), # 2024-03-12 - # Qt 6.6: Chromium 112 - # 112.0.5615.213 (~2023-04-18) - # 6.6.0: Security fixes up to 117.0.5938.63 (2023-09-12) - # 6.6.1: Security fixes up to 119.0.6045.123 (2023-11-07) - # 6.6.2: Security fixes up to 121.0.6167.160 (2024-02-06) - # 6.6.3: Security fixes up to 122.0.6261.128 (2024-03-12) - utils.VersionNumber(6, 6): '112.0.5615.213', - - # Qt 6.7: Chromium 118 - # 118.0.5993.220 (~2023-10-24) - # 6.7.0: Security fixes up to 122.0.6261.128 (2024-03-12) - # 6.7.1: Security fixes up to 124.0.6367.78 (?) (2024-04-24) - utils.VersionNumber(6, 7): '118.0.5993.220', + ## Qt 6.7 + utils.VersionNumber(6, 7): (_BASES[118], '122.0.6261.128'), # 2024-03-12 + utils.VersionNumber(6, 7, 1): (_BASES[118], '124.0.6367.78'), # (?) 2024-04-24 } def __post_init__(self) -> None: @@ -656,14 +648,25 @@ class WebEngineVersions: def from_ua(cls, ua: 'websettings.UserAgent') -> 'WebEngineVersions': """Get the versions parsed from a user agent. - This is the most reliable and "default" way to get this information (at least - until QtWebEngine adds an API for it). However, it needs a fully initialized - QtWebEngine, and we sometimes need this information before that is available. + This is the most reliable and "default" way to get this information for + older Qt versions that don't provide an API for it. However, it needs a + fully initialized QtWebEngine, and we sometimes need this information + before that is available. """ assert ua.qt_version is not None, ua + webengine = utils.VersionNumber.parse(ua.qt_version) + chromium_inferred, chromium_security = cls._infer_chromium_version(webengine) + if ua.upstream_browser_version != chromium_inferred: # pragma: no cover + # should never happen, but let's play it safe + log.misc.debug( + f"Chromium version mismatch: {ua.upstream_browser_version} (UA) != " + f"{chromium_inferred} (inferred)") + chromium_security = None + return cls( - webengine=utils.VersionNumber.parse(ua.qt_version), + webengine=webengine, chromium=ua.upstream_browser_version, + chromium_security=chromium_security, source='UA', ) @@ -678,9 +681,19 @@ class WebEngineVersions: sometimes mix and match Qt/QtWebEngine versions, so this is a more reliable (though hackish) way to get a more accurate result. """ + webengine = utils.VersionNumber.parse(versions.webengine) + chromium_inferred, chromium_security = cls._infer_chromium_version(webengine) + if versions.chromium != chromium_inferred: # pragma: no cover + # should never happen, but let's play it safe + log.misc.debug( + f"Chromium version mismatch: {versions.chromium} (ELF) != " + f"{chromium_inferred} (inferred)") + chromium_security = None + return cls( - webengine=utils.VersionNumber.parse(versions.webengine), + webengine=webengine, chromium=versions.chromium, + chromium_security=chromium_security, source='ELF', ) @@ -688,21 +701,29 @@ class WebEngineVersions: def _infer_chromium_version( cls, pyqt_webengine_version: utils.VersionNumber, - ) -> Optional[str]: - """Infer the Chromium version based on the PyQtWebEngine version.""" - chromium_version = cls._CHROMIUM_VERSIONS.get(pyqt_webengine_version) + ) -> Tuple[Optional[str], Optional[str]]: + """Infer the Chromium version based on the PyQtWebEngine version. + + Returns: + A tuple of the Chromium version and the security patch version. + """ + chromium_version, security_version = cls._CHROMIUM_VERSIONS.get( + pyqt_webengine_version, (None, None)) if chromium_version is not None: - return chromium_version + return chromium_version, security_version # 5.15 patch versions change their QtWebEngine version, but no changes are # expected after 5.15.3 and 5.15.[01] are unsupported. - if pyqt_webengine_version == utils.VersionNumber(5, 15, 2): - minor_version = pyqt_webengine_version - else: - # e.g. 5.14.2 -> 5.14 - minor_version = pyqt_webengine_version.strip_patch() + assert pyqt_webengine_version != utils.VersionNumber(5, 15, 2) - return cls._CHROMIUM_VERSIONS.get(minor_version) + # e.g. 5.15.4 -> 5.15 + # we ignore the security version as that one will have changed from .0 + # and is thus unknown. + minor_version = pyqt_webengine_version.strip_patch() + chromium_ver, _security_ver = cls._CHROMIUM_VERSIONS.get( + minor_version, (None, None)) + + return chromium_ver, None @classmethod def from_api( @@ -737,9 +758,11 @@ class WebEngineVersions: a PyQtWebEngine-Qt{,5} package from PyPI, so we could query its exact version. """ parsed = utils.VersionNumber.parse(pyqt_webengine_qt_version) + chromium, chromium_security = cls._infer_chromium_version(parsed) return cls( webengine=parsed, - chromium=cls._infer_chromium_version(parsed), + chromium=chromium, + chromium_security=chromium_security, source=source, ) @@ -782,9 +805,12 @@ class WebEngineVersions: if frozen: parsed = utils.VersionNumber(5, 15, 2) + chromium, chromium_security = cls._infer_chromium_version(parsed) + return cls( webengine=parsed, - chromium=cls._infer_chromium_version(parsed), + chromium=chromium, + chromium_security=chromium_security, source=source, ) diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 09e885b45..5d2863100 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -974,6 +974,7 @@ class TestWebEngineVersions: expected = version.WebEngineVersions( webengine=utils.VersionNumber(5, 15, 2), chromium='83.0.4103.122', + chromium_security='86.0.4240.183', source='UA', ) assert version.WebEngineVersions.from_ua(ua) == expected @@ -983,21 +984,27 @@ class TestWebEngineVersions: expected = version.WebEngineVersions( webengine=utils.VersionNumber(5, 15, 2), chromium='83.0.4103.122', + chromium_security='86.0.4240.183', source='ELF', ) assert version.WebEngineVersions.from_elf(elf_version) == expected - @pytest.mark.parametrize('pyqt_version, chromium_version', [ - ('5.15.2', '83.0.4103.122'), - ('5.15.3', '87.0.4280.144'), - ('5.15.4', '87.0.4280.144'), - ('5.15.5', '87.0.4280.144'), - ('6.2.0', '90.0.4430.228'), - ('6.3.0', '94.0.4606.126'), + @pytest.mark.parametrize('pyqt_version, chromium_version, security_version', [ + ('5.15.2', '83.0.4103.122', '86.0.4240.183'), + ('5.15.3', '87.0.4280.144', '88.0.4324.150'), + ('5.15.4', '87.0.4280.144', None), + ('5.15.5', '87.0.4280.144', None), + ('5.15.6', '87.0.4280.144', None), + ('5.15.7', '87.0.4280.144', '94.0.4606.61'), + ('6.2.0', '90.0.4430.228', '93.0.4577.63'), + ('6.2.99', '90.0.4430.228', None), + ('6.3.0', '94.0.4606.126', '99.0.4844.84'), + ('6.99.0', None, None), ]) - def test_from_pyqt(self, freezer, pyqt_version, chromium_version): - if freezer and pyqt_version in ['5.15.3', '5.15.4', '5.15.5']: + def test_from_pyqt(self, freezer, pyqt_version, chromium_version, security_version): + if freezer and utils.VersionNumber(5, 15, 3) <= utils.VersionNumber.parse(pyqt_version) < utils.VersionNumber(6): chromium_version = '83.0.4103.122' + security_version = '86.0.4240.183' expected_pyqt_version = '5.15.2' else: expected_pyqt_version = pyqt_version @@ -1005,6 +1012,7 @@ class TestWebEngineVersions: expected = version.WebEngineVersions( webengine=utils.VersionNumber.parse(expected_pyqt_version), chromium=chromium_version, + chromium_security=security_version, source='PyQt', ) assert version.WebEngineVersions.from_pyqt(pyqt_version) == expected @@ -1062,6 +1070,25 @@ class TestWebEngineVersions: security = utils.VersionNumber.parse(qWebEngineChromiumSecurityPatchVersion()) assert security >= base + def test_chromium_security_version_dict(self, qapp): + """Check if we infer the QtWebEngine security version properly. + + Note this test mostly tests that our overview in version.py (also + intended for human readers) is accurate. The code we call here is never + going to be called in real-life situations, as the API is available. + """ + try: + from qutebrowser.qt.webenginecore import ( + qWebEngineVersion, + qWebEngineChromiumSecurityPatchVersion, + ) + except ImportError: + pytest.skip("Requires QtWebEngine 6.3+") + + inferred = version.WebEngineVersions.from_webengine( + qWebEngineVersion(), source="API") + assert inferred.chromium_security == qWebEngineChromiumSecurityPatchVersion() + class FakeQSslSocket: From dfcfc686ce761500d0949f5bfb6ca1ad42834a41 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 21:17:45 +0200 Subject: [PATCH 030/403] Support setting dark mode at runtime and with URL patterns See #3636, #5542, #7743 --- doc/help/settings.asciidoc | 3 ++- qutebrowser/browser/webengine/darkmode.py | 23 ++++++++++++++++++- .../browser/webengine/webenginesettings.py | 8 ++++++- qutebrowser/config/configdata.yml | 5 +++- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index af76527c9..f2a5062c2 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -1678,6 +1678,7 @@ Default: +pass:[0.0]+ [[colors.webpage.darkmode.enabled]] === colors.webpage.darkmode.enabled Render all web contents using a dark theme. +On QtWebEngine < 6.7, this setting requires a restart and does not support URL patterns, only the global setting is applied. Example configurations from Chromium's `chrome://flags`: - "With simple HSL/CIELAB/RGB-based inversion": Set `colors.webpage.darkmode.algorithm` accordingly, and @@ -1685,7 +1686,7 @@ Example configurations from Chromium's `chrome://flags`: - "With selective image inversion": qutebrowser default settings. -This setting requires a restart. +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. This setting is only available with the QtWebEngine backend. diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index b1b81c61e..aa2ffb338 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -126,6 +126,10 @@ from typing import (Any, Iterator, Mapping, MutableMapping, Optional, Set, Tuple from qutebrowser.config import config from qutebrowser.utils import usertypes, utils, log, version +# Note: We *cannot* initialize QtWebEngine (even implicitly) in here, but checking for +# the enum attribute seems to be okay. +from qutebrowser.qt.webenginecore import QWebEngineSettings + _BLINK_SETTINGS = 'blink-settings' @@ -138,6 +142,7 @@ class Variant(enum.Enum): qt_515_3 = enum.auto() qt_64 = enum.auto() qt_66 = enum.auto() + qt_67 = enum.auto() # Mapping from a colors.webpage.darkmode.algorithm setting value to @@ -276,6 +281,13 @@ class _Definition: new._settings = self._settings + (setting,) # pylint: disable=protected-access return new + def copy_remove_setting(self, name: str) -> '_Definition': + """Get a new _Definition object with a setting removed.""" + new = copy.copy(self) + settings = tuple(s for s in self._settings if s.option != name) + new._settings = settings # pylint: disable=protected-access + return new + def copy_replace_setting(self, option: str, chromium_key: str) -> '_Definition': """Get a new _Definition object with `old` replaced by `new`. @@ -332,6 +344,8 @@ _DEFINITIONS[Variant.qt_64] = _DEFINITIONS[Variant.qt_515_3].copy_replace_settin _DEFINITIONS[Variant.qt_66] = _DEFINITIONS[Variant.qt_64].copy_add_setting( _Setting('policy.images', 'ImageClassifierPolicy', _IMAGE_CLASSIFIERS), ) +# Qt 6.7: Enabled is now handled dynamically via QWebEngineSettings +_DEFINITIONS[Variant.qt_67] = _DEFINITIONS[Variant.qt_66].copy_remove_setting('enabled') _SettingValType = Union[str, usertypes.Unset] @@ -367,7 +381,14 @@ def _variant(versions: version.WebEngineVersions) -> Variant: except KeyError: log.init.warning(f"Ignoring invalid QUTE_DARKMODE_VARIANT={env_var}") - if versions.webengine >= utils.VersionNumber(6, 6): + if ( + # We need a PyQt 6.7 as well with the API available, otherwise we can't turn on + # dark mode later in webenginesettings.py. + versions.webengine >= utils.VersionNumber(6, 7) and + hasattr(QWebEngineSettings.WebAttribute, 'ForceDarkMode') + ): + return Variant.qt_67 + elif versions.webengine >= utils.VersionNumber(6, 6): return Variant.qt_66 elif versions.webengine >= utils.VersionNumber(6, 4): return Variant.qt_64 diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 78a4946ad..436b80d29 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -150,10 +150,16 @@ class WebEngineSettings(websettings.AbstractSettings): } try: _ATTRIBUTES['content.canvas_reading'] = Attr( - QWebEngineSettings.WebAttribute.ReadingFromCanvasEnabled) # type: ignore[attr-defined,unused-ignore] + QWebEngineSettings.WebAttribute.ReadingFromCanvasEnabled) except AttributeError: # Added in QtWebEngine 6.6 pass + try: + _ATTRIBUTES['colors.webpage.darkmode.enabled'] = Attr( + QWebEngineSettings.WebAttribute.ForceDarkMode) + except AttributeError: + # Added in QtWebEngine 6.7 + pass _FONT_SIZES = { 'fonts.web.size.minimum': diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index ca92f96c1..322f88f6c 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -3272,6 +3272,9 @@ colors.webpage.darkmode.enabled: desc: >- Render all web contents using a dark theme. + On QtWebEngine < 6.7, this setting requires a restart and does not support + URL patterns, only the global setting is applied. + Example configurations from Chromium's `chrome://flags`: - "With simple HSL/CIELAB/RGB-based inversion": Set @@ -3279,7 +3282,7 @@ colors.webpage.darkmode.enabled: set `colors.webpage.darkmode.policy.images` to `never`. - "With selective image inversion": qutebrowser default settings. - restart: true + supports_pattern: true backend: QtWebEngine colors.webpage.darkmode.algorithm: From 9320c8f2e5610750ac8f35823f3338fe47824d41 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 21:49:25 +0200 Subject: [PATCH 031/403] Fix tests/lint --- qutebrowser/browser/webengine/darkmode.py | 4 +-- .../browser/webengine/webenginesettings.py | 26 ++++++++++--------- tests/unit/browser/webengine/test_darkmode.py | 5 ++-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index aa2ffb338..52bf0f24d 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -284,8 +284,8 @@ class _Definition: def copy_remove_setting(self, name: str) -> '_Definition': """Get a new _Definition object with a setting removed.""" new = copy.copy(self) - settings = tuple(s for s in self._settings if s.option != name) - new._settings = settings # pylint: disable=protected-access + filtered_settings = tuple(s for s in self._settings if s.option != name) + new._settings = filtered_settings # pylint: disable=protected-access return new def copy_replace_setting(self, option: str, chromium_key: str) -> '_Definition': diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 436b80d29..fd0d8c8de 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -148,18 +148,20 @@ class WebEngineSettings(websettings.AbstractSettings): Attr(QWebEngineSettings.WebAttribute.AutoLoadIconsForPage, converter=lambda val: val != 'never'), } - try: - _ATTRIBUTES['content.canvas_reading'] = Attr( - QWebEngineSettings.WebAttribute.ReadingFromCanvasEnabled) - except AttributeError: - # Added in QtWebEngine 6.6 - pass - try: - _ATTRIBUTES['colors.webpage.darkmode.enabled'] = Attr( - QWebEngineSettings.WebAttribute.ForceDarkMode) - except AttributeError: - # Added in QtWebEngine 6.7 - pass + + if machinery.IS_QT6: + try: + _ATTRIBUTES['content.canvas_reading'] = Attr( + QWebEngineSettings.WebAttribute.ReadingFromCanvasEnabled) + except AttributeError: + # Added in QtWebEngine 6.6 + pass + try: + _ATTRIBUTES['colors.webpage.darkmode.enabled'] = Attr( + QWebEngineSettings.WebAttribute.ForceDarkMode) + except AttributeError: + # Added in QtWebEngine 6.7 + pass _FONT_SIZES = { 'fonts.web.size.minimum': diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py index bda05feb8..d9f9e3b24 100644 --- a/tests/unit/browser/webengine/test_darkmode.py +++ b/tests/unit/browser/webengine/test_darkmode.py @@ -257,8 +257,9 @@ def test_options(configdata_init): if not name.startswith('colors.webpage.darkmode.'): continue - assert not opt.supports_pattern, name - assert opt.restart, name + if name != 'colors.webpage.darkmode.enabled': + assert not opt.supports_pattern, name + assert opt.restart, name if opt.backends: # On older Qt versions, this is an empty list. From 9c901b21017a6c2321771644d97aa0dd4d8a62fa Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 22:47:04 +0200 Subject: [PATCH 032/403] Add more dark mode logic unit tests --- qutebrowser/browser/webengine/darkmode.py | 24 +-- tests/unit/browser/webengine/test_darkmode.py | 200 +++++++++++++++++- 2 files changed, 202 insertions(+), 22 deletions(-) diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index 52bf0f24d..8f1908547 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -113,6 +113,11 @@ Qt 6.6 - New alternative image classifier: https://chromium-review.googlesource.com/c/chromium/src/+/3987823 + +Qt 6.7 +------ + +Enabling dark mode can now be done at runtime via QWebEngineSettings. """ import os @@ -192,11 +197,6 @@ _BOOLS = { False: 'false', } -_INT_BOOLS = { - True: '1', - False: '0', -} - @dataclasses.dataclass class _Setting: @@ -265,16 +265,6 @@ class _Definition: switch = self._switch_names.get(setting.option, self._switch_names[None]) yield switch, setting.with_prefix(self.prefix) - def copy_with(self, attr: str, value: Any) -> '_Definition': - """Get a new _Definition object with a changed attribute. - - NOTE: This does *not* copy the settings list. Both objects will reference the - same (immutable) tuple. - """ - new = copy.copy(self) - setattr(new, attr, value) - return new - def copy_add_setting(self, setting: _Setting) -> '_Definition': """Get a new _Definition object with an additional setting.""" new = copy.copy(self) @@ -285,13 +275,15 @@ class _Definition: """Get a new _Definition object with a setting removed.""" new = copy.copy(self) filtered_settings = tuple(s for s in self._settings if s.option != name) + if len(filtered_settings) == len(self._settings): + raise ValueError(f"Setting {name} not found in {self}") new._settings = filtered_settings # pylint: disable=protected-access return new def copy_replace_setting(self, option: str, chromium_key: str) -> '_Definition': """Get a new _Definition object with `old` replaced by `new`. - If `old` is not in the settings list, return the old _Definition object. + If `old` is not in the settings list, raise ValueError. """ new = copy.deepcopy(self) diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py index d9f9e3b24..2f7021e95 100644 --- a/tests/unit/browser/webengine/test_darkmode.py +++ b/tests/unit/browser/webengine/test_darkmode.py @@ -7,6 +7,7 @@ import logging from typing import List, Tuple import pytest +QWebEngineSettings = pytest.importorskip("qutebrowser.qt.webenginecore").QWebEngineSettings from qutebrowser.config import configdata from qutebrowser.utils import usertypes, version, utils @@ -28,6 +29,150 @@ def gentoo_versions(): ) +class TestSetting: + + @pytest.mark.parametrize("value, mapping, expected", [ + ("val", None, ("key", "val")), + (5, None, ("key", "5")), + (True, darkmode._BOOLS, ("key", "true")), + ("excluded", {"excluded": None}, None), + ]) + def test_chromium_tuple(self, value, mapping, expected): + setting = darkmode._Setting(option="opt", chromium_key="key", mapping=mapping) + assert setting.chromium_tuple(value) == expected + + def test_with_prefix(self): + mapping = {"val": "mapped"} + setting = darkmode._Setting(option="opt", chromium_key="key", mapping=mapping) + prefixed = setting.with_prefix("prefix") + assert prefixed == darkmode._Setting( + option="opt", chromium_key="prefixkey", mapping=mapping + ) + + +class TestDefinition: + + @pytest.fixture + def setting1(self) -> darkmode._Setting: + return darkmode._Setting("opt1", "key1") + + @pytest.fixture + def setting2(self) -> darkmode._Setting: + return darkmode._Setting("opt2", "key2") + + @pytest.fixture + def setting3(self) -> darkmode._Setting: + return darkmode._Setting("opt3", "key3") + + @pytest.fixture + def definition( + self, setting1: darkmode._Setting, setting2: darkmode._Setting + ) -> darkmode._Definition: + return darkmode._Definition(setting1, setting2, mandatory=set(), prefix="") + + def _get_settings(self, definition: darkmode._Definition) -> List[darkmode._Setting]: + return [setting for _key, setting in definition.prefixed_settings()] + + @pytest.mark.parametrize("prefix", ["", "prefix"]) + def test_prefixed_settings( + self, + prefix: str, + definition: darkmode._Definition, + setting1: darkmode._Setting, + setting2: darkmode._Setting, + ): + assert definition.prefix == "" # default value + definition.prefix = prefix + prefixed = self._get_settings(definition) + assert prefixed == [setting1.with_prefix(prefix), setting2.with_prefix(prefix)] + + def test_switch_names( + self, + definition: darkmode._Definition, + setting1: darkmode._Setting, + setting2: darkmode._Setting, + setting3: darkmode._Setting, + ): + switch_names = { + setting1.option: "opt1-switch", + None: "default-switch", + } + definition = darkmode._Definition( + setting1, + setting2, + setting3, + mandatory=set(), + prefix="", + switch_names=switch_names, + ) + settings = list(definition.prefixed_settings()) + assert settings == [ + ("opt1-switch", setting1), + ("default-switch", setting2), + ("default-switch", setting3), + ] + + def test_copy_remove_setting( + self, + definition: darkmode._Definition, + setting1: darkmode._Setting, + setting2: darkmode._Setting, + ): + copy = definition.copy_remove_setting(setting2.option) + orig_settings = self._get_settings(definition) + copy_settings = self._get_settings(copy) + assert orig_settings == [setting1, setting2] + assert copy_settings == [setting1] + + def test_copy_remove_setting_not_found(self, definition: darkmode._Definition): + with pytest.raises(ValueError, match="Setting not-found not found in "): + definition.copy_remove_setting("not-found") + + def test_copy_add_setting( + self, + definition: darkmode._Definition, + setting1: darkmode._Setting, + setting2: darkmode._Setting, + setting3: darkmode._Setting, + ): + copy = definition.copy_add_setting(setting3) + orig_settings = self._get_settings(definition) + copy_settings = self._get_settings(copy) + assert orig_settings == [setting1, setting2] + assert copy_settings == [setting1, setting2, setting3] + + def test_copy_add_setting_already_exists( + self, + definition: darkmode._Definition, + setting1: darkmode._Setting, + setting2: darkmode._Setting, + ): + copy = definition.copy_add_setting(setting2) + orig_settings = self._get_settings(definition) + copy_settings = self._get_settings(copy) + assert orig_settings == [setting1, setting2] + assert copy_settings == [setting1, setting2, setting2] + + def test_copy_replace_setting( + self, + definition: darkmode._Definition, + setting1: darkmode._Setting, + setting2: darkmode._Setting, + ): + replaced = darkmode._Setting(setting2.option, setting2.chromium_key + "-replaced") + copy = definition.copy_replace_setting(setting2.option, replaced.chromium_key) + orig_settings = self._get_settings(definition) + copy_settings = self._get_settings(copy) + assert orig_settings == [setting1, setting2] + assert copy_settings == [setting1, replaced] + + def test_copy_replace_setting_not_found( + self, definition: darkmode._Definition, setting3: darkmode._Setting + ): + with pytest.raises(ValueError, match="Setting opt3 not found in "): + definition.copy_replace_setting(setting3.option, setting3.chromium_key) + + @pytest.mark.parametrize('value, webengine_version, expected', [ # Auto ("auto", "5.15.2", [("preferredColorScheme", "2")]), # QTBUG-89753 @@ -122,16 +267,49 @@ QT_64_SETTINGS = { 'dark-mode-settings': [ ('InversionAlgorithm', '1'), ('ImagePolicy', '2'), - ('ForegroundBrightnessThreshold', '100'), + ('ForegroundBrightnessThreshold', '100'), # name changed ], } -@pytest.mark.parametrize('qversion, expected', [ - ('5.15.2', QT_515_2_SETTINGS), - ('5.15.3', QT_515_3_SETTINGS), - ('6.4', QT_64_SETTINGS), -]) +QT_66_SETTINGS = { + 'blink-settings': [('forceDarkModeEnabled', 'true')], + 'dark-mode-settings': [ + ('InversionAlgorithm', '1'), + ('ImagePolicy', '2'), + ('ForegroundBrightnessThreshold', '100'), + ("ImageClassifierPolicy", "0"), # added + ], +} + +QT_67_SETTINGS = { + # blink-settings removed + 'dark-mode-settings': [ + ('InversionAlgorithm', '1'), + ('ImagePolicy', '2'), + ('ForegroundBrightnessThreshold', '100'), + ("ImageClassifierPolicy", "0"), + ], +} + + +@pytest.mark.parametrize( + "qversion, expected", + [ + ("5.15.2", QT_515_2_SETTINGS), + ("5.15.3", QT_515_3_SETTINGS), + ("6.4", QT_64_SETTINGS), + ("6.6", QT_66_SETTINGS), + pytest.param( + "6.7", + QT_67_SETTINGS, + marks=pytest.mark.skipif( + not hasattr(QWebEngineSettings.WebAttribute, "ForceDarkMode"), + reason="needs PyQt 6.7", + ), + ), + ], +) def test_qt_version_differences(config_stub, qversion, expected): settings = { 'enabled': True, @@ -213,6 +391,16 @@ def test_variant(webengine_version, expected): assert darkmode._variant(versions) == expected +def test_variant_qt67() -> None: + versions = version.WebEngineVersions.from_pyqt("6.7.0") + # We can't monkeypatch the enum, so compare against the real situation + if hasattr(QWebEngineSettings.WebAttribute, "ForceDarkMode"): + expected = darkmode.Variant.qt_67 + else: + expected = darkmode.Variant.qt_66 + assert darkmode._variant(versions) == expected + + def test_variant_gentoo_workaround(gentoo_versions): assert darkmode._variant(gentoo_versions) == darkmode.Variant.qt_515_3 From edba6f18cb9a19967881e45ae5944cc8a8a27fe3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 1 May 2024 00:30:41 +0200 Subject: [PATCH 033/403] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e3326c9ee..86ad09823 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -32,6 +32,8 @@ Added Changed ~~~~~~~ +- With QtWebEngine 6.7+, the `colors.webpage.darkmode.enabled` setting can now + be changed at runtime and supports URL patterns (#8182). - A few more completions will now match search terms in any order: `:quickmark-*`, `:bookmark-*`, `:tab-take` and `:tab-select` (for the quick and bookmark categories). (#7955) From 17ff150dfd5a6e7d7796e8e0e08da72ced4dd421 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 6 May 2024 04:20:35 +0000 Subject: [PATCH 034/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 8 ++++---- misc/requirements/requirements-mypy.txt | 6 +++--- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 6 +++--- misc/requirements/requirements-tests.txt | 14 +++++++------- misc/requirements/requirements-tox.txt | 4 ++-- requirements.txt | 4 ++-- 9 files changed, 25 insertions(+), 25 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index b0993ea58..3e44ac179 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -4,6 +4,6 @@ build==1.2.1 check-manifest==0.49 importlib_metadata==7.1.0 packaging==24.0 -pyproject_hooks==1.0.0 +pyproject_hooks==1.1.0 tomli==2.0.1 zipp==3.18.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index ceffc05fb..eea57cf9b 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -6,10 +6,10 @@ bump2version==1.0.1 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==42.0.5 +cryptography==42.0.6 docutils==0.20.1 github3.py==4.0.1 -hunter==3.6.1 +hunter==3.7.0 idna==3.7 importlib_metadata==7.1.0 importlib_resources==6.4.0 @@ -26,10 +26,10 @@ nh3==0.2.17 packaging==24.0 pkginfo==1.10.0 pycparser==2.22 -Pygments==2.17.2 +Pygments==2.18.0 PyJWT==2.8.0 Pympler==1.0.1 -pyproject_hooks==1.0.0 +pyproject_hooks==1.1.0 PyQt-builder==1.16.2 python-dateutil==2.9.0.post0 readme_renderer==43.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index fcea79aca..2240465bd 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -3,18 +3,18 @@ chardet==5.2.0 diff_cover==9.0.0 importlib_resources==6.4.0 -Jinja2==3.1.3 +Jinja2==3.1.4 lxml==5.2.1 MarkupSafe==2.1.5 mypy==1.10.0 mypy-extensions==1.0.0 pluggy==1.5.0 -Pygments==2.17.2 +Pygments==2.18.0 PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-colorama==0.4.15.20240311 types-docutils==0.21.0.20240423 -types-Pygments==2.17.0.20240310 +types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 types-setuptools==69.5.0.20240423 typing_extensions==4.11.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 192ba4c97..d8ae535b8 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -4,7 +4,7 @@ astroid==3.1.0 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==42.0.5 +cryptography==42.0.6 dill==0.3.8 github3.py==4.0.1 idna==3.7 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 141faf1cb..5eff35b5c 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -7,8 +7,8 @@ docutils==0.20.1 idna==3.7 importlib_metadata==7.1.0 packaging==24.0 -Pygments==2.17.2 -pyproject_hooks==1.0.0 +Pygments==2.18.0 +pyproject_hooks==1.1.0 pyroma==4.2 requests==2.31.0 tomli==2.0.1 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 7bb66c6a0..aa0ddaafd 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -1,17 +1,17 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py alabaster==0.7.13 -Babel==2.14.0 +Babel==2.15.0 certifi==2024.2.2 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 imagesize==1.4.1 importlib_metadata==7.1.0 -Jinja2==3.1.3 +Jinja2==3.1.4 MarkupSafe==2.1.5 packaging==24.0 -Pygments==2.17.2 +Pygments==2.18.0 pytz==2024.1 requests==2.31.0 snowballstemmer==2.2.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 2a86c3202..de14dc45a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -7,19 +7,19 @@ certifi==2024.2.2 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 -coverage==7.5.0 +coverage==7.5.1 exceptiongroup==1.2.1 execnet==2.1.1 -filelock==3.13.4 +filelock==3.14.0 Flask==3.0.3 -hunter==3.6.1 -hypothesis==6.100.2 +hunter==3.7.0 +hypothesis==6.100.4 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 itsdangerous==2.2.0 jaraco.functools==4.0.1 -# Jinja2==3.1.3 +# Jinja2==3.1.4 Mako==1.3.3 manhole==1.8.0 # MarkupSafe==2.1.5 @@ -29,7 +29,7 @@ parse==1.20.1 parse-type==0.6.2 pluggy==1.5.0 py-cpuinfo==9.0.0 -Pygments==2.17.2 +Pygments==2.18.0 pytest==8.2.0 pytest-bdd==7.1.2 pytest-benchmark==4.0.0 @@ -52,5 +52,5 @@ tomli==2.0.1 typing_extensions==4.11.0 urllib3==2.2.1 vulture==2.11 -Werkzeug==3.0.2 +Werkzeug==3.0.3 zipp==3.18.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 975774969..feaf01c22 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,7 +4,7 @@ cachetools==5.3.3 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 -filelock==3.13.4 +filelock==3.14.0 packaging==24.0 pip==24.0 platformdirs==4.2.1 @@ -13,5 +13,5 @@ pyproject-api==1.6.1 setuptools==69.5.1 tomli==2.0.1 tox==4.15.0 -virtualenv==20.26.0 +virtualenv==20.26.1 wheel==0.43.0 diff --git a/requirements.txt b/requirements.txt index 229f9e9c8..3628cddd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ adblock==0.6.0 colorama==0.4.6 importlib_resources==6.4.0 ; python_version=="3.8.*" -Jinja2==3.1.3 +Jinja2==3.1.4 MarkupSafe==2.1.5 -Pygments==2.17.2 +Pygments==2.18.0 PyYAML==6.0.1 zipp==3.18.1 # Unpinned due to recompile_requirements.py limitations From 310c865f29ddeae7adee554b2574962f453fa53f Mon Sep 17 00:00:00 2001 From: Evan Chen Date: Thu, 9 May 2024 10:58:04 -0400 Subject: [PATCH 035/403] Fix some spelling errors --- doc/changelog.asciidoc | 2 +- doc/faq.asciidoc | 2 +- doc/help/configuring.asciidoc | 2 +- misc/nsis/install.nsh | 2 +- qutebrowser/browser/qtnetworkdownloads.py | 2 +- qutebrowser/misc/elf.py | 2 +- qutebrowser/misc/pakjoy.py | 2 +- qutebrowser/utils/utils.py | 2 +- tests/end2end/features/prompts.feature | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 86ad09823..2de0394be 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -3673,7 +3673,7 @@ Fixed - Continuing a search after clearing it now works correctly. - The tabbar and completion should now be more consistently and correctly styled with various system styles. -- Applying styiles in `qt5ct` now shouldn't crash anymore. +- Applying styles in `qt5ct` now shouldn't crash anymore. - The validation for colors in stylesheets is now less strict, allowing for all valid Qt values. - `data:` URLs now aren't added to the history anymore. diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index b5edb3646..bd75d7d30 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -430,7 +430,7 @@ allowing him to work part-time on qutebrowser. If you keep your donation level for long enough, you can get some qutebrowser stickers! Why GitHub Sponsors?:: - GitHub Sponsors is a crowdfundign platform nicely integrated with + GitHub Sponsors is a crowdfunding platform nicely integrated with qutebrowser's existing GitHub page and a better offering than alternatives such as Patreon or Liberapay. + diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index d61743040..61edc00a6 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -120,7 +120,7 @@ c.completion.shrink = True ---- Note that qutebrowser does some Python magic so it's able to warn you about -mistyped config settings. As an example, if you do `c.tabs.possition = "left"`, +mistyped config settings. As an example, if you do `c.tabs.position = "left"`, you'll get an error when starting. See the link:settings{outfilesuffix}[settings help page] for all available settings. The diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh index 282a254eb..5086dcb0d 100755 --- a/misc/nsis/install.nsh +++ b/misc/nsis/install.nsh @@ -137,7 +137,7 @@ var KeepReg ; Functions Function CheckInstallation ; if there's an installed version, uninstall it first (I chose not to start the uninstaller silently, so that user sees what failed) - ; if both per-user and per-machine versions are installed, unistall the one that matches $MultiUser.InstallMode + ; if both per-user and per-machine versions are installed, uninstall the one that matches $MultiUser.InstallMode StrCpy $0 "" ${if} $HasCurrentModeInstallation = 1 StrCpy $0 "$MultiUser.InstallMode" diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 0360eed66..63122208f 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -124,7 +124,7 @@ class DownloadItem(downloads.AbstractDownloadItem): log.downloads.exception("Error while closing file object") if pos == 0: - # Emtpy remaining file + # Empty remaining file filename = self._get_open_filename() log.downloads.debug(f"Removing empty file at {filename}") try: diff --git a/qutebrowser/misc/elf.py b/qutebrowser/misc/elf.py index 35af5af28..e44d8b573 100644 --- a/qutebrowser/misc/elf.py +++ b/qutebrowser/misc/elf.py @@ -235,7 +235,7 @@ def _find_versions(data: bytes) -> Versions: # Here it gets even more crazy: Sometimes, we don't have the full UA in one piece # in the string table somehow (?!). However, Qt 6.2 added a separate # qWebEngineChromiumVersion(), with PyQt wrappers following later. And *that* - # apperently stores the full version in the string table separately from the UA. + # apparently stores the full version in the string table separately from the UA. # As we clearly didn't have enough crazy heuristics yet so far, let's hunt for it! # We first get the partial Chromium version from the UA: diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index 2bcde7ce9..99e8c8426 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -168,7 +168,7 @@ def _find_webengine_resources() -> pathlib.Path: # I'm not sure how to arrive at this path without hardcoding it # ourselves. importlib_resources("PyQt6.Qt6") can serve as a # replacement for the qtutils bit but it doesn't seem to help find the - # actuall Resources folder. + # actual Resources folder. candidates.append( qt_data_path / "lib" / "QtWebEngineCore.framework" / "Resources" ) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 11c160c9e..0bea3608d 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -636,7 +636,7 @@ def expand_windows_drive(path: str) -> str: path: The path to expand. """ # Usually, "E:" on Windows refers to the current working directory on drive - # E:\. The correct way to specifify drive E: is "E:\", but most users + # E:\. The correct way to specify drive E: is "E:\", but most users # probably don't use the "multiple working directories" feature and expect # "E:" and "E:\" to be equal. if re.fullmatch(r'[A-Z]:', path, re.IGNORECASE): diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 32bdd29e7..43199fa3b 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -413,7 +413,7 @@ Feature: Prompts } @qtwebengine_skip - Scenario: Cancellling webpage authentication with QtWebKit + Scenario: Cancelling webpage authentication with QtWebKit When I open basic-auth/user6/password6 without waiting And I wait for a prompt And I run :mode-leave From 350f94222ddcacc34b03bff48f293d4021a1e2ef Mon Sep 17 00:00:00 2001 From: Evan Chen Date: Thu, 9 May 2024 13:35:13 -0400 Subject: [PATCH 036/403] reading comprehension failure more coffee required --- doc/help/configuring.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index 61edc00a6..d61743040 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -120,7 +120,7 @@ c.completion.shrink = True ---- Note that qutebrowser does some Python magic so it's able to warn you about -mistyped config settings. As an example, if you do `c.tabs.position = "left"`, +mistyped config settings. As an example, if you do `c.tabs.possition = "left"`, you'll get an error when starting. See the link:settings{outfilesuffix}[settings help page] for all available settings. The From a7a7c434e25c166238f0375b58f47ac761b3bfb1 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 10 May 2024 18:41:50 +0200 Subject: [PATCH 037/403] Fix handling of missing QtWebEngine resources I was getting crash reports from someone about this. Not sure what's going wrong there (hence the additional information in the exception). What's clear however is that we're raising ParseError, but only handling that when actually parsing. The code calling copy_/_find_webengine_resources only handles OSError. So let's raise a FileNotFoundError instead. --- doc/changelog.asciidoc | 2 ++ qutebrowser/misc/pakjoy.py | 4 +++- tests/unit/misc/test_pakjoy.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 2de0394be..8dcb4cee3 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -51,6 +51,8 @@ Fixed bug. - Trying to use qutebrowser after it's been deleted/moved on disk (e.g. after a Python upgrade) should now not crash anymore. +- When the QtWebEngine resources dir couldn't be found, qutebrowser now doesn't + crash anymore (but QtWebEngine still might). [[v3.1.1]] v3.1.1 (unreleased) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index 99e8c8426..c0e6b4d0c 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -184,7 +184,9 @@ def _find_webengine_resources() -> pathlib.Path: if (candidate / PAK_FILENAME).exists(): return candidate - raise binparsing.ParseError("Couldn't find webengine resources dir") + candidates_str = "\n".join(f" {p}" for p in candidates) + raise FileNotFoundError( + f"Couldn't find webengine resources dir, candidates:\n{candidates_str}") def copy_webengine_resources() -> Optional[pathlib.Path]: diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index 2dcfbd5b1..59185a380 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -177,7 +177,7 @@ class TestFindWebengineResources: def test_nowhere(self, fallback_path: pathlib.Path): """Test we raise if we can't find the resources.""" with pytest.raises( - binparsing.ParseError, match="Couldn't find webengine resources dir" + FileNotFoundError, match="Couldn't find webengine resources dir, candidates:\n*" ): pakjoy._find_webengine_resources() From 9bf03939233263812ed3a2c44145d0617f348d5d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 16 May 2024 09:31:42 +0200 Subject: [PATCH 038/403] tests: Don't make QSslSocket error fail tests Bleeding environment fails tests/unit/browser/webkit/test_certificateerror.py on Qt 5, maybe due to OpenSSL 3.x --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index 2de880eae..8b9ed195d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -70,6 +70,8 @@ qt_log_ignore = # The last part of the outer message gets bumped down to a line on its own, so hopefully this # catches that. And we don't see any other weird permutations of this. ^[^ ]*qtwebengine_dictionaries'$ + # Qt 5 on Archlinux + ^QSslSocket: cannot resolve .* xfail_strict = true filterwarnings = error From 117cb15b8db5202d7734b7b821f7d6f8dc1d9b72 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 19 May 2024 14:29:31 +1200 Subject: [PATCH 039/403] Handle selection model being None when clearing selection Change `CompletionView.on_clear_completion_selection()` to call the Qt selection model getter, instead of our one. Since it can be called when the selection model has already been cleared and our one asserts that there is a selection model to return. Back in the distant past there was a change to handle the completion widget's selection model being None when the `on_clear_completion_selection()` slot was called: https://github.com/qutebrowser/qutebrowser/commit/88b522fa167e2f97b More recently a common getter for the selection model was added so we could have a single place to apply type narrowing to the returned object from the Qt getter (the type hints had been updated to be wrapped in `Optional`): https://github.com/qutebrowser/qutebrowser/commit/92dea988c01e745#diff-1559d42e246323bea35fa064d54d48c990999aaf4c732b09ccd448f994da74cf It seems this is one place where it does, and already did, handle that optional. So it didn't need to change to use the new getter. This is called from the `Command.on_mode_left` signal, not sure why the selection model is None here. Perhaps it already gets cleared by the effects of the `hide_cmd` signal that gets fired earlier, or perhaps even from the `self.hide()` on the line before. Anyway, this was working for several years and seems harmless enough. --- qutebrowser/completion/completionwidget.py | 2 +- .../unit/completion/test_completionwidget.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 0f5dc0de9..27e631662 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -414,7 +414,7 @@ class CompletionView(QTreeView): def on_clear_completion_selection(self): """Clear the selection model when an item is activated.""" self.hide() - selmod = self._selection_model() + selmod = self.selectionModel() if selmod is not None: selmod.clearSelection() selmod.clearCurrentIndex() diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 4ab9dfca0..387339f4f 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -7,7 +7,7 @@ from unittest import mock import pytest -from qutebrowser.qt.core import QRect +from qutebrowser.qt.core import QRect, QItemSelectionModel from qutebrowser.completion import completionwidget from qutebrowser.completion.models import completionmodel, listcategory @@ -285,6 +285,23 @@ def test_completion_show(show, rows, quick_complete, completionview, model, assert not completionview.isVisible() +def test_completion_selection_clear_no_model(completionview): + completionview.show() + completionview.on_clear_completion_selection() + assert completionview.isVisible() is False + + +def test_completion_selection_clear_with_model(completionview, mocker): + selmod = mock.Mock(spec=QItemSelectionModel) + mocker.patch.object(completionview, "selectionModel", return_value=selmod) + completionview.show() + completionview.on_clear_completion_selection() + + assert completionview.isVisible() is False + selmod.clearSelection.assert_called_once() + selmod.clearCurrentIndex.assert_called_once() + + def test_completion_item_del(completionview, model): """Test that completion_item_del invokes delete_cur_item in the model.""" func = mock.Mock(spec=[]) From 04b0d84bda69888220f3e099b9f76b6baeb5cfb3 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 19 May 2024 14:34:23 +1200 Subject: [PATCH 040/403] update changelog for None selection model fix --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 8dcb4cee3..e253e26a5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -53,6 +53,8 @@ Fixed Python upgrade) should now not crash anymore. - When the QtWebEngine resources dir couldn't be found, qutebrowser now doesn't crash anymore (but QtWebEngine still might). +- Fixed a rare crash in the completion widget when there was no selection model + when we went to clear that, probably when leaving a mode. (#7901) [[v3.1.1]] v3.1.1 (unreleased) From 1d8a2acf771bd505360ae5d6407d7baa5aa6d1d0 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 20 May 2024 04:21:17 +0000 Subject: [PATCH 041/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 8 ++++---- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-mypy.txt | 6 +++--- misc/requirements/requirements-pyinstaller.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 10 +++++----- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 2 +- misc/requirements/requirements-tests.txt | 10 +++++----- misc/requirements/requirements-tox.txt | 4 ++-- requirements.txt | 2 +- 11 files changed, 27 insertions(+), 27 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 3e44ac179..0b868a571 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -6,4 +6,4 @@ importlib_metadata==7.1.0 packaging==24.0 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.18.1 +zipp==3.18.2 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index eea57cf9b..09cd3822c 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -6,7 +6,7 @@ bump2version==1.0.1 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==42.0.6 +cryptography==42.0.7 docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 @@ -17,7 +17,7 @@ jaraco.classes==3.4.0 jaraco.context==5.3.0 jaraco.functools==4.0.1 jeepney==0.8.0 -keyring==25.2.0 +keyring==25.2.1 manhole==1.8.0 markdown-it-py==3.0.0 mdurl==0.1.2 @@ -41,8 +41,8 @@ SecretStorage==3.3.3 sip==6.8.3 six==1.16.0 tomli==2.0.1 -twine==5.0.0 +twine==5.1.0 typing_extensions==4.11.0 uritemplate==4.1.1 # urllib3==2.2.1 -zipp==3.18.1 +zipp==3.18.2 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index a560af530..3044ce83b 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -15,7 +15,7 @@ flake8-string-format==0.3.0 flake8-tidy-imports==4.10.0 flake8-tuple==0.4.1 mccabe==0.7.0 -pep8-naming==0.13.3 +pep8-naming==0.14.1 pycodestyle==2.11.1 pydocstyle==6.3.0 pyflakes==3.2.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 2240465bd..11b3b4894 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -4,7 +4,7 @@ chardet==5.2.0 diff_cover==9.0.0 importlib_resources==6.4.0 Jinja2==3.1.4 -lxml==5.2.1 +lxml==5.2.2 MarkupSafe==2.1.5 mypy==1.10.0 mypy-extensions==1.0.0 @@ -16,6 +16,6 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20240423 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 -types-setuptools==69.5.0.20240423 +types-setuptools==69.5.0.20240519 typing_extensions==4.11.0 -zipp==3.18.1 +zipp==3.18.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 8435786a5..62cda742f 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -4,5 +4,5 @@ altgraph==0.17.4 importlib_metadata==7.1.0 packaging==24.0 pyinstaller==6.6.0 -pyinstaller-hooks-contrib==2024.5 -zipp==3.18.1 +pyinstaller-hooks-contrib==2024.6 +zipp==3.18.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index d8ae535b8..af2d2695c 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,26 +1,26 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==3.1.0 +astroid==3.2.2 certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==42.0.6 +cryptography==42.0.7 dill==0.3.8 github3.py==4.0.1 idna==3.7 isort==5.13.2 mccabe==0.7.0 pefile==2023.2.7 -platformdirs==4.2.1 +platformdirs==4.2.2 pycparser==2.22 PyJWT==2.8.0 -pylint==3.1.0 +pylint==3.2.1 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.31.0 six==1.16.0 tomli==2.0.1 -tomlkit==0.12.4 +tomlkit==0.12.5 typing_extensions==4.11.0 uritemplate==4.1.1 # urllib3==2.2.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 5eff35b5c..8f4ca2e88 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -12,6 +12,6 @@ pyproject_hooks==1.1.0 pyroma==4.2 requests==2.31.0 tomli==2.0.1 -trove-classifiers==2024.4.10 +trove-classifiers==2024.5.17 urllib3==2.2.1 -zipp==3.18.1 +zipp==3.18.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index aa0ddaafd..5f0aa3323 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -23,4 +23,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==2.2.1 -zipp==3.18.1 +zipp==3.18.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index de14dc45a..140774d7d 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ attrs==23.2.0 beautifulsoup4==4.12.3 -blinker==1.8.1 +blinker==1.8.2 certifi==2024.2.2 charset-normalizer==3.3.2 cheroot==10.0.1 @@ -13,14 +13,14 @@ execnet==2.1.1 filelock==3.14.0 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.100.4 +hypothesis==6.102.4 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 itsdangerous==2.2.0 jaraco.functools==4.0.1 # Jinja2==3.1.4 -Mako==1.3.3 +Mako==1.3.5 manhole==1.8.0 # MarkupSafe==2.1.5 more-itertools==10.2.0 @@ -30,7 +30,7 @@ parse-type==0.6.2 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 -pytest==8.2.0 +pytest==8.2.1 pytest-bdd==7.1.2 pytest-benchmark==4.0.0 pytest-cov==5.0.0 @@ -53,4 +53,4 @@ typing_extensions==4.11.0 urllib3==2.2.1 vulture==2.11 Werkzeug==3.0.3 -zipp==3.18.1 +zipp==3.18.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index feaf01c22..49ab17091 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -7,11 +7,11 @@ distlib==0.3.8 filelock==3.14.0 packaging==24.0 pip==24.0 -platformdirs==4.2.1 +platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.6.1 setuptools==69.5.1 tomli==2.0.1 tox==4.15.0 -virtualenv==20.26.1 +virtualenv==20.26.2 wheel==0.43.0 diff --git a/requirements.txt b/requirements.txt index 3628cddd8..ff70a1c08 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 PyYAML==6.0.1 -zipp==3.18.1 +zipp==3.18.2 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From f1c18360a2f12917a8d1b1bbeeadb83600372e07 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 23 May 2024 08:12:30 +0200 Subject: [PATCH 042/403] tests: Remove warning ignores for fixed issues --- pytest.ini | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pytest.ini b/pytest.ini index 8b9ed195d..560f2c0ab 100644 --- a/pytest.ini +++ b/pytest.ini @@ -76,8 +76,4 @@ xfail_strict = true filterwarnings = error default:Test process .* failed to terminate!:UserWarning - # Python 3.12: https://github.com/jendrikseipp/vulture/issues/314 - ignore:ast\.Str is deprecated and will be removed in Python 3\.14; use ast\.Constant instead:DeprecationWarning:vulture\.core - # Python 3.12: https://github.com/ionelmc/pytest-benchmark/issues/240 - ignore:(datetime\.)?datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\. Use timezone-aware objects to represent datetimes in UTC. (datetime\.)?datetime\.now\(datetime\.UTC\)\.:DeprecationWarning:pytest_benchmark\.utils faulthandler_timeout = 90 From 2d89a6f4c01c25da06c04bae1f919fd9555dc6ca Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 23 May 2024 08:35:12 +0200 Subject: [PATCH 043/403] tox: Add py313 --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 31e06e396..42c654ee5 100644 --- a/tox.ini +++ b/tox.ini @@ -38,6 +38,7 @@ basepython = py310: {env:PYTHON:python3.10} py311: {env:PYTHON:python3.11} py312: {env:PYTHON:python3.12} + py313: {env:PYTHON:python3.13} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tests.txt From 6339eacda4be899ef02ecc3f477611d506ec2e8f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 23 May 2024 14:52:10 +0200 Subject: [PATCH 044/403] py313: Upgrade typing-extensions --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 42c654ee5..933248e43 100644 --- a/tox.ini +++ b/tox.ini @@ -51,6 +51,8 @@ deps = pyqt64: -r{toxinidir}/misc/requirements/requirements-pyqt-6.4.txt pyqt65: -r{toxinidir}/misc/requirements/requirements-pyqt-6.5.txt pyqt66: -r{toxinidir}/misc/requirements/requirements-pyqt-6.6.txt +commands_pre = + py313: pip install -U --pre typing-extensions==4.12.0rc1 # FIXME remove once released commands = !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66: {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} From 12ebc843d1474ef0574848a8b35671e85b3d2e6e Mon Sep 17 00:00:00 2001 From: Tobias Naumann Date: Fri, 24 May 2024 00:16:04 +0200 Subject: [PATCH 045/403] Move idna_encode function calls out of loops whenever possible to avoid repeated computations --- misc/userscripts/qute-pass | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass index 405e340b5..0b483c0e2 100755 --- a/misc/userscripts/qute-pass +++ b/misc/userscripts/qute-pass @@ -149,6 +149,7 @@ def find_pass_candidates(domain, unfiltered=False): if unfiltered or domain in password: candidates.append(password) else: + idna_domain = idna_encode(domain) for path, directories, file_names in os.walk(arguments.password_store, followlinks=True): secrets = fnmatch.filter(file_names, '*.gpg') if not secrets: @@ -157,10 +158,9 @@ def find_pass_candidates(domain, unfiltered=False): # Strip password store path prefix to get the relative pass path pass_path = path[len(arguments.password_store):] split_path = pass_path.split(os.path.sep) + idna_split_path = [idna_encode(part) for part in split_path] for secret in secrets: secret_base = os.path.splitext(secret)[0] - idna_domain = idna_encode(domain) - idna_split_path = [idna_encode(part) for part in split_path] idna_secret_base = idna_encode(secret_base) if not unfiltered and idna_domain not in (idna_split_path + [idna_secret_base]): continue From ecf0fd225a89bd5be7755dd0da5846c5ebc3661d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 24 May 2024 16:57:20 +0200 Subject: [PATCH 046/403] Increase YAML warning deadline with --with-pydebug This makes things significantly slower --- qutebrowser/utils/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 0bea3608d..13ccf5ca2 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -17,6 +17,7 @@ import traceback import functools import contextlib import shlex +import sysconfig import mimetypes from typing import (Any, Callable, IO, Iterator, Optional, Sequence, Tuple, List, Type, Union, @@ -666,7 +667,10 @@ def yaml_load(f: Union[str, IO[str]]) -> Any: end = datetime.datetime.now() delta = (end - start).total_seconds() - deadline = 10 if 'CI' in os.environ else 2 + if "CI" in os.environ or sysconfig.get_config_var("Py_DEBUG"): + deadline = 10 + else: + deadline = 2 if delta > deadline: # pragma: no cover log.misc.warning( "YAML load took unusually long, please report this at " From f72b862da59868dd51cb8940fb4116b640f5d561 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 24 May 2024 17:07:52 +0200 Subject: [PATCH 047/403] tests: Re-add pytest-benchmark warning ignore This was fixed but never released... --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pytest.ini b/pytest.ini index 560f2c0ab..71a6f606d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -76,4 +76,6 @@ xfail_strict = true filterwarnings = error default:Test process .* failed to terminate!:UserWarning + # Python 3.12: https://github.com/ionelmc/pytest-benchmark/issues/240 (fixed but not released) + ignore:(datetime\.)?datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\. Use timezone-aware objects to represent datetimes in UTC. (datetime\.)?datetime\.now\(datetime\.UTC\)\.:DeprecationWarning:pytest_benchmark\.utils faulthandler_timeout = 90 From 3f1842b72988d9985a71690b2b2dcbf6af595d47 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 28 Apr 2024 13:58:15 +1200 Subject: [PATCH 048/403] Update requirements and CI for PyQt6.7 6.7 is released now, some distros are already shipping it! This commit: 1. adds a new 6.7 requirements file (the plain 6 one has already been updated by the bot) 2. adds a new tox env referring to the new requirements file 3. updates the mac and windows installer jobs to run with pyqt67 with the assumption we'll be including that in our next release 4. adds two new CI environments for 6.7, one each for python 3.11 and 3.12 (3.12 only came out like 6 months ago) 5. updates a couple of references to the py37 tox env that looked like they were missed, 3.7 support was dropped in 93c7fdd 6. updates various ubuntu containers to the latest LTS instead of the previous related one - this is quite unrelated to this change but I thought I would give it a go, no need to use the old one unless we are specifically testing on it? - linters - they use tox but probably use system libraries - these all run in nested containers anyway, should be fully isolated - codeql - eh, uses a third party action, check the docs if it fails - irc - as above --- .github/workflows/ci.yml | 26 ++++++++++++------- misc/requirements/requirements-pyqt-6.7.txt | 7 +++++ .../requirements-pyqt-6.7.txt-raw | 4 +++ tox.ini | 3 ++- 4 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 misc/requirements/requirements-pyqt-6.7.txt create mode 100644 misc/requirements/requirements-pyqt-6.7.txt-raw diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 599ba3b1b..f62d2bdc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: linters: if: "!contains(github.event.head_commit.message, '[ci skip]')" timeout-minutes: 10 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -86,7 +86,7 @@ jobs: tests-docker: if: "!contains(github.event.head_commit.message, '[ci skip]')" timeout-minutes: 45 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -129,7 +129,7 @@ jobs: matrix: include: ### PyQt 5.15.2 (Python 3.8) - - testenv: py37-pyqt5152 + - testenv: py38-pyqt5152 os: ubuntu-20.04 python: "3.8" ### PyQt 5.15 (Python 3.10, with coverage) @@ -142,7 +142,7 @@ jobs: os: ubuntu-20.04 python: "3.11" ### PyQt 6.2 (Python 3.8) - - testenv: py37-pyqt62 + - testenv: py38-pyqt62 os: ubuntu-20.04 python: "3.8" ### PyQt 6.3 (Python 3.8) @@ -165,18 +165,26 @@ jobs: - testenv: py312-pyqt66 os: ubuntu-22.04 python: "3.12" + ### PyQt 6.7 (Python 3.11) + - testenv: py311-pyqt67 + os: ubuntu-22.04 + python: "3.11" + ### PyQt 6.7 (Python 3.12) + - testenv: py312-pyqt67 + os: ubuntu-22.04 + python: "3.12" ### macOS Big Sur - - testenv: py312-pyqt66 + - testenv: py312-pyqt67 os: macos-11 python: "3.12" args: "tests/unit" # Only run unit tests on macOS ### macOS Monterey - - testenv: py312-pyqt66 + - testenv: py312-pyqt67 os: macos-12 python: "3.12" args: "tests/unit" # Only run unit tests on macOS ### Windows - - testenv: py312-pyqt66 + - testenv: py312-pyqt67 os: windows-2019 python: "3.12" runs-on: "${{ matrix.os }}" @@ -226,7 +234,7 @@ jobs: permissions: security-events: write timeout-minutes: 15 - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -243,7 +251,7 @@ jobs: irc: timeout-minutes: 2 continue-on-error: true - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: [linters, tests, tests-docker, codeql] if: "always() && github.repository_owner == 'qutebrowser'" steps: diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt new file mode 100644 index 000000000..b33f919ee --- /dev/null +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -0,0 +1,7 @@ +# This file is automatically generated by scripts/dev/recompile_requirements.py + +PyQt6==6.7.0 +PyQt6-Qt6==6.7.0 +PyQt6-sip==13.6.0 +PyQt6-WebEngine==6.7.0 +PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt-6.7.txt-raw b/misc/requirements/requirements-pyqt-6.7.txt-raw new file mode 100644 index 000000000..98b1340b2 --- /dev/null +++ b/misc/requirements/requirements-pyqt-6.7.txt-raw @@ -0,0 +1,4 @@ +PyQt6 >= 6.7, < 6.8 +PyQt6-Qt6 >= 6.7, < 6.8 +PyQt6-WebEngine >= 6.7, < 6.8 +PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 diff --git a/tox.ini b/tox.ini index 31e06e396..8ce7b22b3 100644 --- a/tox.ini +++ b/tox.ini @@ -50,8 +50,9 @@ deps = pyqt64: -r{toxinidir}/misc/requirements/requirements-pyqt-6.4.txt pyqt65: -r{toxinidir}/misc/requirements/requirements-pyqt-6.5.txt pyqt66: -r{toxinidir}/misc/requirements/requirements-pyqt-6.6.txt + pyqt67: -r{toxinidir}/misc/requirements/requirements-pyqt-6.7.txt commands = - !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66: {envpython} scripts/link_pyqt.py --tox {envdir} + !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66-!pyqt67: {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} cov: {envpython} scripts/dev/check_coverage.py {posargs} From 8b44c26146f27b08e81571d7ca242631651c15e6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 23:16:20 +0200 Subject: [PATCH 049/403] Try getting PyQt 6.7 from Riverbank server See https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html and https://github.com/pypi/support/issues/3949 --- misc/requirements/requirements-pyqt-6.7.txt | 2 +- misc/requirements/requirements-pyqt-6.7.txt-raw | 5 ++++- misc/requirements/requirements-pyqt-6.txt | 2 +- misc/requirements/requirements-pyqt-6.txt-raw | 5 ++++- misc/requirements/requirements-pyqt.txt | 2 +- misc/requirements/requirements-pyqt.txt-raw | 5 ++++- scripts/mkvenv.py | 9 ++++++++- tox.ini | 14 ++++++++++++++ 8 files changed, 37 insertions(+), 7 deletions(-) diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index b33f919ee..b784bd052 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -4,4 +4,4 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.0 +# PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt-6.7.txt-raw b/misc/requirements/requirements-pyqt-6.7.txt-raw index 98b1340b2..e9fe79c07 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt-raw +++ b/misc/requirements/requirements-pyqt-6.7.txt-raw @@ -1,4 +1,7 @@ +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +#@ ignore: PyQt6-WebEngine-Qt6 + PyQt6 >= 6.7, < 6.8 PyQt6-Qt6 >= 6.7, < 6.8 PyQt6-WebEngine >= 6.7, < 6.8 -PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 +# PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index b33f919ee..b784bd052 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -4,4 +4,4 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.0 +# PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt-6.txt-raw b/misc/requirements/requirements-pyqt-6.txt-raw index 68a5db685..ad747afc2 100644 --- a/misc/requirements/requirements-pyqt-6.txt-raw +++ b/misc/requirements/requirements-pyqt-6.txt-raw @@ -1,4 +1,7 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine -PyQt6-WebEngine-Qt6 + +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +#@ ignore: PyQt6-WebEngine-Qt6 +# PyQt6-WebEngine-Qt6 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index b33f919ee..b784bd052 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -4,4 +4,4 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.0 +# PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt.txt-raw b/misc/requirements/requirements-pyqt.txt-raw index 68a5db685..ad747afc2 100644 --- a/misc/requirements/requirements-pyqt.txt-raw +++ b/misc/requirements/requirements-pyqt.txt-raw @@ -1,4 +1,7 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine -PyQt6-WebEngine-Qt6 + +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +#@ ignore: PyQt6-WebEngine-Qt6 +# PyQt6-WebEngine-Qt6 diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py index 4ab5d8c10..6c871744b 100755 --- a/scripts/mkvenv.py +++ b/scripts/mkvenv.py @@ -249,8 +249,15 @@ def install_pyqt_binary(venv_dir: pathlib.Path, version: str) -> None: utils.print_error("Non-glibc Linux is not a supported platform for PyQt " "binaries, this will most likely fail.") + # WORKAROUND for + # https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html + needs_manual_install = version in ["auto", "6", "6.7"] pip_install(venv_dir, '-r', pyqt_requirements_file(version), - '--only-binary', ','.join(PYQT_PACKAGES)) + '--only-binary', ','.join(PYQT_PACKAGES), + *(["--no-deps"] if needs_manual_install else [])) + + if needs_manual_install: + pip_install(venv_dir, "PyQt6-WebEngine-Qt6") def install_pyqt_source(venv_dir: pathlib.Path, version: str) -> None: diff --git a/tox.ini b/tox.ini index 8ce7b22b3..884ff9068 100644 --- a/tox.ini +++ b/tox.ini @@ -51,6 +51,13 @@ deps = pyqt65: -r{toxinidir}/misc/requirements/requirements-pyqt-6.5.txt pyqt66: -r{toxinidir}/misc/requirements/requirements-pyqt-6.6.txt pyqt67: -r{toxinidir}/misc/requirements/requirements-pyqt-6.7.txt +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +# --no-deps since we install from a complete requirements.txt anyways. +install_command = + pyqt67: python -I -m pip install --no-deps {opts} {packages} + !pyqt67: python -I -m pip install {opts} {packages} +commands_pre = + pyqt67: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ PyQt6-WebEngine-Qt6 commands = !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66-!pyqt67: {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} @@ -193,6 +200,13 @@ passenv = PYINSTALLER_DEBUG setenv = qt5: PYINSTALLER_QT5=true +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +# --no-deps since we install from a complete requirements.txt anyways. +install_command = + !qt5: python -I -m pip install --no-deps {opts} {packages} + qt5: python -I -m pip install {opts} {packages} +commands_pre = + !qt5: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ PyQt6-WebEngine-Qt6 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt From 0f511711418f5cadd207fe31db2f782e6b860b69 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 23:56:31 +0200 Subject: [PATCH 050/403] Revert "Try getting PyQt 6.7 from Riverbank server" This reverts commit 6c4be8ef03bdcf09e86e14de02b8bd308e6e527b. Possibly easier solution in next commit. --- misc/requirements/requirements-pyqt-6.7.txt | 2 +- misc/requirements/requirements-pyqt-6.7.txt-raw | 5 +---- misc/requirements/requirements-pyqt-6.txt | 2 +- misc/requirements/requirements-pyqt-6.txt-raw | 5 +---- misc/requirements/requirements-pyqt.txt | 2 +- misc/requirements/requirements-pyqt.txt-raw | 5 +---- scripts/mkvenv.py | 9 +-------- tox.ini | 14 -------------- 8 files changed, 7 insertions(+), 37 deletions(-) diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index b784bd052..b33f919ee 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -4,4 +4,4 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -# PyQt6-WebEngine-Qt6==6.7.0 +PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt-6.7.txt-raw b/misc/requirements/requirements-pyqt-6.7.txt-raw index e9fe79c07..98b1340b2 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt-raw +++ b/misc/requirements/requirements-pyqt-6.7.txt-raw @@ -1,7 +1,4 @@ -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -#@ ignore: PyQt6-WebEngine-Qt6 - PyQt6 >= 6.7, < 6.8 PyQt6-Qt6 >= 6.7, < 6.8 PyQt6-WebEngine >= 6.7, < 6.8 -# PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 +PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index b784bd052..b33f919ee 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -4,4 +4,4 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -# PyQt6-WebEngine-Qt6==6.7.0 +PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt-6.txt-raw b/misc/requirements/requirements-pyqt-6.txt-raw index ad747afc2..68a5db685 100644 --- a/misc/requirements/requirements-pyqt-6.txt-raw +++ b/misc/requirements/requirements-pyqt-6.txt-raw @@ -1,7 +1,4 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine - -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -#@ ignore: PyQt6-WebEngine-Qt6 -# PyQt6-WebEngine-Qt6 +PyQt6-WebEngine-Qt6 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index b784bd052..b33f919ee 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -4,4 +4,4 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -# PyQt6-WebEngine-Qt6==6.7.0 +PyQt6-WebEngine-Qt6==6.7.0 diff --git a/misc/requirements/requirements-pyqt.txt-raw b/misc/requirements/requirements-pyqt.txt-raw index ad747afc2..68a5db685 100644 --- a/misc/requirements/requirements-pyqt.txt-raw +++ b/misc/requirements/requirements-pyqt.txt-raw @@ -1,7 +1,4 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine - -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -#@ ignore: PyQt6-WebEngine-Qt6 -# PyQt6-WebEngine-Qt6 +PyQt6-WebEngine-Qt6 diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py index 6c871744b..4ab5d8c10 100755 --- a/scripts/mkvenv.py +++ b/scripts/mkvenv.py @@ -249,15 +249,8 @@ def install_pyqt_binary(venv_dir: pathlib.Path, version: str) -> None: utils.print_error("Non-glibc Linux is not a supported platform for PyQt " "binaries, this will most likely fail.") - # WORKAROUND for - # https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html - needs_manual_install = version in ["auto", "6", "6.7"] pip_install(venv_dir, '-r', pyqt_requirements_file(version), - '--only-binary', ','.join(PYQT_PACKAGES), - *(["--no-deps"] if needs_manual_install else [])) - - if needs_manual_install: - pip_install(venv_dir, "PyQt6-WebEngine-Qt6") + '--only-binary', ','.join(PYQT_PACKAGES)) def install_pyqt_source(venv_dir: pathlib.Path, version: str) -> None: diff --git a/tox.ini b/tox.ini index 884ff9068..8ce7b22b3 100644 --- a/tox.ini +++ b/tox.ini @@ -51,13 +51,6 @@ deps = pyqt65: -r{toxinidir}/misc/requirements/requirements-pyqt-6.5.txt pyqt66: -r{toxinidir}/misc/requirements/requirements-pyqt-6.6.txt pyqt67: -r{toxinidir}/misc/requirements/requirements-pyqt-6.7.txt -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -# --no-deps since we install from a complete requirements.txt anyways. -install_command = - pyqt67: python -I -m pip install --no-deps {opts} {packages} - !pyqt67: python -I -m pip install {opts} {packages} -commands_pre = - pyqt67: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ PyQt6-WebEngine-Qt6 commands = !pyqt-!pyqt515-!pyqt5152-!pyqt62-!pyqt63-!pyqt64-!pyqt65-!pyqt66-!pyqt67: {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} @@ -200,13 +193,6 @@ passenv = PYINSTALLER_DEBUG setenv = qt5: PYINSTALLER_QT5=true -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -# --no-deps since we install from a complete requirements.txt anyways. -install_command = - !qt5: python -I -m pip install --no-deps {opts} {packages} - qt5: python -I -m pip install {opts} {packages} -commands_pre = - !qt5: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ PyQt6-WebEngine-Qt6 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-pyinstaller.txt From 470ec752e16fe4f4f1407b90ad5ce66da85a7b63 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 30 Apr 2024 23:56:53 +0200 Subject: [PATCH 051/403] Add Riverbank Computing as extra index for PyQt requirements Easier fix instead of 6c4be8ef03bdcf09e86e14de02b8bd308e6e527b. Seems to get picked up just fine, and shouldn't hurt when it's not needed, as we don't use --pre. Thus, no development releases should be installed. --- misc/requirements/requirements-pyqt-6.7.txt | 1 + misc/requirements/requirements-pyqt-6.7.txt-raw | 3 +++ misc/requirements/requirements-pyqt-6.txt | 1 + misc/requirements/requirements-pyqt-6.txt-raw | 3 +++ misc/requirements/requirements-pyqt.txt | 1 + misc/requirements/requirements-pyqt.txt-raw | 3 +++ scripts/dev/misc_checks.py | 2 +- 7 files changed, 13 insertions(+), 1 deletion(-) diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index b33f919ee..3d5203985 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -5,3 +5,4 @@ PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.0 +--extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.7.txt-raw b/misc/requirements/requirements-pyqt-6.7.txt-raw index 98b1340b2..7df2d49c6 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt-raw +++ b/misc/requirements/requirements-pyqt-6.7.txt-raw @@ -2,3 +2,6 @@ PyQt6 >= 6.7, < 6.8 PyQt6-Qt6 >= 6.7, < 6.8 PyQt6-WebEngine >= 6.7, < 6.8 PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 + +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index b33f919ee..3d5203985 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -5,3 +5,4 @@ PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.0 +--extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.txt-raw b/misc/requirements/requirements-pyqt-6.txt-raw index 68a5db685..16cc342cd 100644 --- a/misc/requirements/requirements-pyqt-6.txt-raw +++ b/misc/requirements/requirements-pyqt-6.txt-raw @@ -2,3 +2,6 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine PyQt6-WebEngine-Qt6 + +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index b33f919ee..3d5203985 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -5,3 +5,4 @@ PyQt6-Qt6==6.7.0 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.0 +--extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt.txt-raw b/misc/requirements/requirements-pyqt.txt-raw index 68a5db685..16cc342cd 100644 --- a/misc/requirements/requirements-pyqt.txt-raw +++ b/misc/requirements/requirements-pyqt.txt-raw @@ -2,3 +2,6 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine PyQt6-WebEngine-Qt6 + +# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html +#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 7b6404cf6..f10dc77bf 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -75,7 +75,7 @@ def check_changelog_urls(_args: argparse.Namespace = None) -> bool: with open(outfile, 'r', encoding='utf-8') as f: for line in f: line = line.strip() - if line.startswith('#') or not line: + if line.startswith(('#', '--')) or not line: continue req, _version = recompile_requirements.parse_versioned_line(line) if req.startswith('./'): From 72d7e2327b74b4e3928227648cb97706fbf28be9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 24 May 2024 21:37:15 +0200 Subject: [PATCH 052/403] Update for new pylint/astroid releases - Add a couple new "raise utils.Unreachable" to avoid possibly-used-before-assignment issues. - Simplify an "if" for the same reason - Remove an unneeded "return" - Use "NoReturn" to prepare for pylint knowing about it in the future: https://github.com/pylint-dev/pylint/issues/9674 - Add some ignores for used-before-assignment false-positives - Ignore new undefined-variable messages for Qt wrapers - Ignore a new no-member warning for KeySequence: https://github.com/pylint-dev/astroid/issues/2448#issuecomment-2130124755 --- qutebrowser/browser/commands.py | 2 ++ qutebrowser/browser/webengine/webenginetab.py | 6 +++--- qutebrowser/commands/userscripts.py | 1 - qutebrowser/config/configdata.py | 5 +++-- qutebrowser/keyinput/keyutils.py | 2 ++ qutebrowser/qt/webenginewidgets.py | 1 + qutebrowser/qt/widgets.py | 1 + tests/end2end/test_invocations.py | 1 + tests/unit/browser/test_pdfjs.py | 4 +++- tests/unit/completion/test_models.py | 4 +++- tests/unit/config/test_configcommands.py | 2 ++ tests/unit/config/test_configfiles.py | 1 + tests/unit/config/test_configtypes.py | 2 ++ tests/unit/keyinput/test_modeparsers.py | 4 ++++ tests/unit/utils/test_qtutils.py | 2 ++ 15 files changed, 30 insertions(+), 8 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 83a846b85..06298a8ca 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -948,6 +948,8 @@ class CommandDispatcher: "No window specified and couldn't find active window!") assert isinstance(active_win, mainwindow.MainWindow), active_win win_id = active_win.win_id + else: + raise utils.Unreachable(index_parts) if win_id not in objreg.window_registry: raise cmdutils.CommandError( diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 02d912a50..19defb6d3 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1477,9 +1477,9 @@ class WebEngineTab(browsertab.AbstractTab): log.network.debug("Asking for credentials") answer = shared.authentication_required( url, authenticator, abort_on=[self.abort_questions]) - if not netrc_success and answer is None: - log.network.debug("Aborting auth") - sip.assign(authenticator, QAuthenticator()) + if answer is None: + log.network.debug("Aborting auth") + sip.assign(authenticator, QAuthenticator()) @pyqtSlot() def _on_load_started(self): diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index f4ddd2bf4..01710a63c 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -330,7 +330,6 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner): self._filepath = handle.name except OSError as e: message.error("Error while creating tempfile: {}".format(e)) - return class Error(Exception): diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 9535dd727..2377841ef 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -10,7 +10,7 @@ DATA: A dict of Option objects after init() has been called. """ from typing import (Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, - Sequence, Tuple, Union, cast) + Sequence, Tuple, Union, NoReturn, cast) import functools import dataclasses @@ -57,7 +57,7 @@ class Migrations: deleted: List[str] = dataclasses.field(default_factory=list) -def _raise_invalid_node(name: str, what: str, node: Any) -> None: +def _raise_invalid_node(name: str, what: str, node: Any) -> NoReturn: """Raise an exception for an invalid configdata YAML node. Args: @@ -94,6 +94,7 @@ def _parse_yaml_type( _raise_invalid_node(name, 'type', node) try: + # pylint: disable=possibly-used-before-assignment typ = getattr(configtypes, type_name) except AttributeError: raise AttributeError("Did not find type {} for {}".format( diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 54b6e88b1..18730c74b 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -359,6 +359,8 @@ class KeyInfo: modifier_classes = (Qt.KeyboardModifier, Qt.KeyboardModifiers) elif machinery.IS_QT6: modifier_classes = Qt.KeyboardModifier + else: + raise utils.Unreachable() assert isinstance(self.key, Qt.Key), self.key assert isinstance(self.modifiers, modifier_classes), self.modifiers diff --git a/qutebrowser/qt/webenginewidgets.py b/qutebrowser/qt/webenginewidgets.py index b8833e9c8..88758cf23 100644 --- a/qutebrowser/qt/webenginewidgets.py +++ b/qutebrowser/qt/webenginewidgets.py @@ -27,6 +27,7 @@ else: if machinery.IS_QT5: + # pylint: disable=undefined-variable # moved to WebEngineCore in Qt 6 del QWebEngineSettings del QWebEngineProfile diff --git a/qutebrowser/qt/widgets.py b/qutebrowser/qt/widgets.py index eac8cafbb..f82ec2e3b 100644 --- a/qutebrowser/qt/widgets.py +++ b/qutebrowser/qt/widgets.py @@ -26,4 +26,5 @@ else: raise machinery.UnknownWrapper() if machinery.IS_QT5: + # pylint: disable=undefined-variable del QFileSystemModel # moved to QtGui in Qt 6 diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index a55efb129..ccf4534dd 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -905,6 +905,7 @@ def test_sandboxing( f"{bpf_text} supports TSYNC": "Yes" if has_seccomp else "No", f"{yama_text} (Broker)": "Yes" if has_yama else "No", + # pylint: disable-next=used-before-assignment f"{yama_text} (Non-broker)": "Yes" if has_yama_non_broker else "No", } diff --git a/tests/unit/browser/test_pdfjs.py b/tests/unit/browser/test_pdfjs.py index cb5c26229..867e8e1de 100644 --- a/tests/unit/browser/test_pdfjs.py +++ b/tests/unit/browser/test_pdfjs.py @@ -9,7 +9,7 @@ import pytest from qutebrowser.qt.core import QUrl from qutebrowser.browser import pdfjs -from qutebrowser.utils import urlmatch +from qutebrowser.utils import urlmatch, utils pytestmark = [pytest.mark.usefixtures('data_tmpdir')] @@ -154,6 +154,8 @@ def test_read_from_system(names, expected_name, tmpdir): expected = (b'text2', str(file2)) elif expected_name is None: expected = (None, None) + else: + raise utils.Unreachable(expected_name) assert pdfjs._read_from_system(str(tmpdir), names) == expected diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index e0bce8f04..ad73d9eb5 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -29,7 +29,7 @@ from qutebrowser.completion import completer from qutebrowser.completion.models import ( configmodel, listcategory, miscmodels, urlmodel, filepathcategory) from qutebrowser.config import configdata, configtypes -from qutebrowser.utils import usertypes +from qutebrowser.utils import usertypes, utils from qutebrowser.mainwindow import tabbedbrowser @@ -417,6 +417,8 @@ def test_filesystem_completion(qtmodeltester, config_stub, info, base = '~' expected_1 = str(pathlib.Path('~') / 'file1.txt') expected_2 = str(pathlib.Path('~') / 'file2.txt') + else: + raise utils.Unreachable(method) config_stub.val.completion.open_categories = ['filesystem'] model = urlmodel.url(info=info) diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 10d9e2292..f9dca3e74 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -840,6 +840,8 @@ class TestBind: func = functools.partial(commands.bind, 0) elif command == 'unbind': func = commands.unbind + else: + raise utils.Unreachable(command) with pytest.raises(cmdutils.CommandError, match=expected): func(*args, **kwargs) diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index cde0a180b..7a47b6cb0 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -399,6 +399,7 @@ class TestYaml: yaml._save() if not insert and old_config is None: + data = {} # unused lines = [] else: data = autoconfig.read() diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index bcd257ed7..09fc4fa75 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1602,6 +1602,8 @@ class TestDict: valtype=configtypes.String(), required_keys=['one', 'two']) message = 'Required keys .*' + else: + raise utils.Unreachable(kind) if ok: expectation = testutils.nop_contextmanager() diff --git a/tests/unit/keyinput/test_modeparsers.py b/tests/unit/keyinput/test_modeparsers.py index 4ba11ed58..6a83d614b 100644 --- a/tests/unit/keyinput/test_modeparsers.py +++ b/tests/unit/keyinput/test_modeparsers.py @@ -115,10 +115,12 @@ class TestHintKeyParser: seq = keyutils.KeySequence.parse(keychain) assert len(seq) == 2 + # pylint: disable-next=no-member match = keyparser.handle(seq[0].to_event()) assert match == QKeySequence.SequenceMatch.PartialMatch assert hintmanager.keystr == prefix + # pylint: disable-next=no-member match = keyparser.handle(seq[1].to_event()) assert match == QKeySequence.SequenceMatch.ExactMatch assert hintmanager.keystr == hint @@ -132,10 +134,12 @@ class TestHintKeyParser: seq = keyutils.KeySequence.parse('ασ') assert len(seq) == 2 + # pylint: disable-next=no-member match = keyparser.handle(seq[0].to_event()) assert match == QKeySequence.SequenceMatch.PartialMatch assert hintmanager.keystr == 'a' + # pylint: disable-next=no-member match = keyparser.handle(seq[1].to_event()) assert match == QKeySequence.SequenceMatch.ExactMatch assert hintmanager.keystr == 'as' diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index c7af3162c..f1cf1e73e 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -758,8 +758,10 @@ class TestPyQIODevice: # pylint: enable=no-member,useless-suppression else: pytest.skip("Needs os.SEEK_HOLE or os.SEEK_DATA available.") + pyqiodev.open(QIODevice.OpenModeFlag.ReadOnly) with pytest.raises(io.UnsupportedOperation): + # pylint: disable=possibly-used-before-assignment pyqiodev.seek(0, whence) @pytest.mark.flaky From 7144e7211689938392c0abc469c26ddfe2a6ae7a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 4 May 2024 14:09:24 +0200 Subject: [PATCH 053/403] Use a proper increasing id for ipc logging --- qutebrowser/misc/ipc.py | 48 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 21a3352d6..72159eab7 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -139,6 +139,7 @@ class IPCServer(QObject): _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. + _socket_id: An unique, incrementing ID for every socket. _socketname: The socketname to use. _atime_timer: Timer to update the atime of the socket regularly. @@ -180,6 +181,7 @@ class IPCServer(QObject): self._server.newConnection.connect(self.handle_connection) self._socket = None + self._socket_id = 0 self._old_socket = None if utils.is_windows: # pragma: no cover @@ -232,11 +234,13 @@ class IPCServer(QObject): log.ipc.debug("In on_error with None socket!") return self._timer.stop() - log.ipc.debug("Socket 0x{:x}: error {}: {}".format( - id(self._socket), self._socket.error(), + log.ipc.debug("Socket {}: error {}: {}".format( + self._socket_id, self._socket.error(), self._socket.errorString())) if err != QLocalSocket.LocalSocketError.PeerClosedError: - raise SocketError("handling IPC connection", self._socket) + raise SocketError( + f"handling IPC connection {self._socket_id}", self._socket + ) @pyqtSlot() def handle_connection(self): @@ -245,14 +249,16 @@ class IPCServer(QObject): return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " - "still handling another one (0x{:x}).".format( - id(self._socket))) + "still handling another one ({}).".format( + self._socket_id)) return socket = qtutils.add_optional(self._server.nextPendingConnection()) if socket is None: log.ipc.debug("No new connection to handle.") return - log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) + + self._socket_id += 1 + log.ipc.debug("Client connected (socket {}).".format(self._socket_id)) self._socket = socket self._timer.start() socket.readyRead.connect(self.on_ready_read) @@ -279,8 +285,7 @@ class IPCServer(QObject): @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" - log.ipc.debug("Client disconnected from socket 0x{:x}.".format( - id(self._socket))) + log.ipc.debug("Client disconnected from socket {}.".format(self._socket_id)) self._timer.stop() if self._old_socket is not None: self._old_socket.deleteLater() @@ -292,8 +297,8 @@ class IPCServer(QObject): def _handle_invalid_data(self): """Handle invalid data we got from a QLocalSocket.""" assert self._socket is not None - log.ipc.error("Ignoring invalid IPC data from socket 0x{:x}.".format( - id(self._socket))) + log.ipc.error("Ignoring invalid IPC data from socket {}.".format( + self._socket_id)) self.got_invalid_data.emit() self._socket.errorOccurred.connect(self.on_error) self._socket.disconnectFromServer() @@ -347,7 +352,7 @@ class IPCServer(QObject): self.got_args.emit(args, target_arg, cwd) def _get_socket(self, warn=True): - """Get the current socket for on_ready_read. + """Get the current socket and ID for on_ready_read. Arguments: warn: Whether to warn if no socket was found. @@ -358,31 +363,33 @@ class IPCServer(QObject): if self._old_socket is None: if warn: log.ipc.warning("In _get_socket with None socket and old_socket!") - return None + return None, None log.ipc.debug("In _get_socket with None socket!") socket = self._old_socket + # hopefully accurate guess, but at least we have a debug log + socket_id = self._socket_id - 1 else: socket = self._socket + socket_id = self._socket_id if sip.isdeleted(socket): # pragma: no cover log.ipc.warning("Ignoring deleted IPC socket") - return None + return None, None - return socket + return socket, socket_id @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" self._timer.stop() - socket = self._get_socket() + socket, socket_id = self._get_socket() while socket is not None and socket.canReadLine(): data = bytes(socket.readLine()) self.got_raw.emit(data) - log.ipc.debug("Read from socket 0x{:x}: {!r}".format( - id(socket), data)) + log.ipc.debug("Read from socket {}: {!r}".format(socket_id, data)) self._handle_data(data) - socket = self._get_socket(warn=False) + socket, socket_id = self._get_socket(warn=False) if self._socket is not None: self._timer.start() @@ -392,7 +399,7 @@ class IPCServer(QObject): """Cancel the current connection if it was idle for too long.""" assert self._socket is not None log.ipc.error("IPC connection timed out " - "(socket 0x{:x}).".format(id(self._socket))) + "(socket {}).".format(self._socket_id)) self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None @@ -436,8 +443,7 @@ class IPCServer(QObject): # we get called again when the application is about to quit. return - log.ipc.debug("Shutting down IPC (socket 0x{:x})".format( - id(self._socket))) + log.ipc.debug("Shutting down IPC (socket {})".format(self._socket_id)) if self._socket is not None: self._socket.deleteLater() From 4fadca2ae709b28e999cb578e1e312597188319b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 May 2024 15:56:17 +0200 Subject: [PATCH 054/403] tests: Make ssl message matching fuzzier Windows seems to struggle with the dash --- tests/end2end/fixtures/webserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index b70401745..f8e28cc40 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -115,7 +115,7 @@ class ExpectedRequest: def is_ignored_webserver_message(line: str) -> bool: return testutils.pattern_match( pattern=( - "Client ('127.0.0.1', *) lost — peer dropped the TLS connection suddenly, " + "Client ('127.0.0.1', *) lost * peer dropped the TLS connection suddenly, " "during handshake: (1, '[SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] * " "alert certificate unknown (_ssl.c:*)')" ), From ef0517da69ea53029fc1320cacf4598fe9ffda35 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 May 2024 18:29:09 +0200 Subject: [PATCH 055/403] Use a separate IPCConnection class --- .mypy.ini | 4 +- qutebrowser/misc/ipc.py | 318 ++++++++++++++++++++-------------------- 2 files changed, 156 insertions(+), 166 deletions(-) diff --git a/.mypy.ini b/.mypy.ini index 81f69a09e..eef89ae20 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -240,9 +240,6 @@ disallow_untyped_defs = False [mypy-qutebrowser.misc.httpclient] disallow_untyped_defs = False -[mypy-qutebrowser.misc.ipc] -disallow_untyped_defs = False - [mypy-qutebrowser.misc.keyhintwidget] disallow_untyped_defs = False @@ -267,6 +264,7 @@ disallow_untyped_defs = False [mypy-qutebrowser.misc.split] disallow_untyped_defs = False + [mypy-qutebrowser.qutebrowser] disallow_untyped_defs = False diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 72159eab7..9342ab55b 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -10,7 +10,9 @@ import json import getpass import binascii import hashlib -from typing import Optional +import itertools +import argparse +from typing import Optional, List from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt from qutebrowser.qt.network import QLocalSocket, QLocalServer, QAbstractSocket @@ -31,7 +33,7 @@ PROTOCOL_VERSION = 1 server: Optional["IPCServer"] = None -def _get_socketname_windows(basedir): +def _get_socketname_windows(basedir: Optional[str]) -> str: """Get a socketname to use for Windows.""" try: username = getpass.getuser() @@ -52,7 +54,7 @@ def _get_socketname_windows(basedir): return '-'.join(parts) -def _get_socketname(basedir): +def _get_socketname(basedir: Optional[str]) -> str: """Get a socketname to use.""" if utils.is_windows: # pragma: no cover return _get_socketname_windows(basedir) @@ -84,7 +86,7 @@ class SocketError(Error): action: The action which was taken when the error happened. """ - def __init__(self, action, socket): + def __init__(self, action: str, socket: QLocalSocket) -> None: """Constructor. Args: @@ -96,7 +98,7 @@ class SocketError(Error): self.code: QLocalSocket.LocalSocketError = socket.error() self.message: str = socket.errorString() - def __str__(self): + def __str__(self) -> str: return "Error while {}: {} ({})".format( self.action, self.message, debug.qenum_key(QLocalSocket, self.code)) @@ -110,7 +112,7 @@ class ListenError(Error): message: The error message. """ - def __init__(self, local_server): + def __init__(self, local_server: QLocalServer) -> None: """Constructor. Args: @@ -120,7 +122,7 @@ class ListenError(Error): self.code: QAbstractSocket.SocketError = local_server.serverError() self.message: str = local_server.errorString() - def __str__(self): + def __str__(self) -> str: return "Error while listening to IPC server: {} ({})".format( self.message, debug.qenum_key(QAbstractSocket, self.code)) @@ -130,6 +132,114 @@ class AddressInUseError(ListenError): """Emitted when the server address is already in use.""" +class IPCConnection(QObject): + """A connection to an IPC socket. + + Multiple connections might be active in parallel. + + Attributes: + _socket: The QLocalSocket to use. + + Signals: + got_raw: Emitted with the connection ID and raw data from the socket. + """ + + got_raw = pyqtSignal(int, bytes) + id_gen = itertools.count() + + def __init__(self, socket: QLocalSocket, parent: Optional[QObject] = None): + super().__init__(parent) + self.conn_id = next(self.id_gen) + log.ipc.debug("Client connected (socket {}).".format(self.conn_id)) + + self._timer = usertypes.Timer(self, "ipc-timeout") + self._timer.setInterval(READ_TIMEOUT) + self._timer.timeout.connect(self.on_timeout) + self._timer.start() + + self._socket: Optional[QLocalSocket] = socket + self._socket.readyRead.connect(self.on_ready_read) + + if socket.canReadLine(): + log.ipc.debug("We can read a line immediately.") + self.on_ready_read() + + socket.errorOccurred.connect(self.on_error) + + # FIXME:v4 Ignore needed due to overloaded signal/method in Qt 5 + socket_error = socket.error() # type: ignore[operator,unused-ignore] + if socket_error not in [ + QLocalSocket.LocalSocketError.UnknownSocketError, + QLocalSocket.LocalSocketError.PeerClosedError, + ]: + log.ipc.debug("We got an error immediately.") + self.on_error(socket_error) + + socket.disconnected.connect(self.on_disconnected) + if socket.state() == QLocalSocket.LocalSocketState.UnconnectedState: + log.ipc.debug("Socket was disconnected immediately.") + self.on_disconnected() + + @pyqtSlot("QLocalSocket::LocalSocketError") + def on_error(self, err: QLocalSocket.LocalSocketError) -> None: + """Raise SocketError on fatal errors.""" + if self._socket is None: + # Sometimes this gets called from stale sockets. + log.ipc.debug("In on_error with None socket!") + return + self._timer.stop() + log.ipc.debug( + "Socket {}: error {}: {}".format( + self.conn_id, self._socket.error(), self._socket.errorString() + ) + ) + if err != QLocalSocket.LocalSocketError.PeerClosedError: + raise SocketError(f"handling IPC connection {self.conn_id}", self._socket) + + @pyqtSlot() + def on_disconnected(self) -> None: + """Clean up socket when the client disconnected.""" + assert self._socket is not None + log.ipc.debug(f"Client disconnected from socket {self.conn_id}.") + self._timer.stop() + self._socket.deleteLater() + self._socket = None + self.deleteLater() + + @pyqtSlot() + def on_ready_read(self) -> None: + """Read json data from the client.""" + self._timer.stop() + + while self._socket is not None and self._socket.canReadLine(): + data = self._socket.readLine().data() + log.ipc.debug("Read from socket {}: {!r}".format(self.conn_id, data)) + self.got_raw.emit(self.conn_id, data) + + if self._socket is not None: + self._timer.start() + + @pyqtSlot() + def on_timeout(self) -> None: + """Cancel the current connection if it was idle for too long.""" + assert self._socket is not None + log.ipc.error(f"IPC connection timed out (socket {self.conn_id}).") + self._socket.disconnectFromServer() + if self._socket is not None: # pragma: no cover + # on_disconnected sets it to None + self._socket.waitForDisconnected(CONNECT_TIMEOUT) + if self._socket is not None: # pragma: no cover + # on_disconnected sets it to None + self._socket.abort() + + @pyqtSlot(int) + def on_invalid_data(self, conn_id: int) -> None: + if conn_id != self.conn_id: + return + assert self._socket is not None + self._socket.disconnectFromServer() + + class IPCServer(QObject): """IPC server to which clients connect to. @@ -138,23 +248,23 @@ class IPCServer(QObject): ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. - _socket: The QLocalSocket we're currently connected to. - _socket_id: An unique, incrementing ID for every socket. _socketname: The socketname to use. _atime_timer: Timer to update the atime of the socket regularly. Signals: got_args: Emitted when there was an IPC connection and arguments were passed. - got_args: Emitted with the raw data an IPC connection got. + got_raw: Emitted with the raw data an IPC connection got. got_invalid_data: Emitted when there was invalid incoming data. + shutting_down: IPC is shutting down. """ got_args = pyqtSignal(list, str, str) got_raw = pyqtSignal(bytes) - got_invalid_data = pyqtSignal() + got_invalid_data = pyqtSignal(int) + shutting_down = pyqtSignal() - def __init__(self, socketname, parent=None): + def __init__(self, socketname: str, parent: QObject = None) -> None: """Start the IPC server and listen to commands. Args: @@ -165,10 +275,6 @@ class IPCServer(QObject): self.ignored = False self._socketname = socketname - self._timer = usertypes.Timer(self, 'ipc-timeout') - self._timer.setInterval(READ_TIMEOUT) - self._timer.timeout.connect(self.on_timeout) - if utils.is_windows: # pragma: no cover self._atime_timer = None else: @@ -180,10 +286,6 @@ class IPCServer(QObject): self._server: Optional[QLocalServer] = QLocalServer(self) self._server.newConnection.connect(self.handle_connection) - self._socket = None - self._socket_id = 0 - self._old_socket = None - if utils.is_windows: # pragma: no cover # As a WORKAROUND for a Qt bug, we can't use UserAccessOption on Unix. If we # do, we don't get an AddressInUseError anymore: @@ -196,14 +298,14 @@ class IPCServer(QObject): else: # pragma: no cover log.ipc.debug("Not calling setSocketOptions") - def _remove_server(self): + def _remove_server(self) -> None: """Remove an existing server.""" ok = QLocalServer.removeServer(self._socketname) if not ok: raise Error("Error while removing server {}!".format( self._socketname)) - def listen(self): + def listen(self) -> None: """Start listening on self._socketname.""" assert self._server is not None log.ipc.debug("Listening as {}".format(self._socketname)) @@ -226,90 +328,30 @@ class IPCServer(QObject): # True, so report this as an error. raise ListenError(self._server) - @pyqtSlot('QLocalSocket::LocalSocketError') - def on_error(self, err): - """Raise SocketError on fatal errors.""" - if self._socket is None: - # Sometimes this gets called from stale sockets. - log.ipc.debug("In on_error with None socket!") - return - self._timer.stop() - log.ipc.debug("Socket {}: error {}: {}".format( - self._socket_id, self._socket.error(), - self._socket.errorString())) - if err != QLocalSocket.LocalSocketError.PeerClosedError: - raise SocketError( - f"handling IPC connection {self._socket_id}", self._socket - ) - @pyqtSlot() - def handle_connection(self): + def handle_connection(self) -> None: """Handle a new connection to the server.""" if self.ignored or self._server is None: return - if self._socket is not None: - log.ipc.debug("Got new connection but ignoring it because we're " - "still handling another one ({}).".format( - self._socket_id)) - return + socket = qtutils.add_optional(self._server.nextPendingConnection()) if socket is None: log.ipc.debug("No new connection to handle.") return - self._socket_id += 1 - log.ipc.debug("Client connected (socket {}).".format(self._socket_id)) - self._socket = socket - self._timer.start() - socket.readyRead.connect(self.on_ready_read) - if socket.canReadLine(): - log.ipc.debug("We can read a line immediately.") - self.on_ready_read() + conn = IPCConnection(socket, parent=self) + conn.got_raw.connect(self.handle_data) + self.got_invalid_data.connect(conn.on_invalid_data) + self.shutting_down.connect(conn.on_disconnected) - socket.errorOccurred.connect(self.on_error) - - # FIXME:v4 Ignore needed due to overloaded signal/method in Qt 5 - socket_error = socket.error() # type: ignore[operator,unused-ignore] - if socket_error not in [ - QLocalSocket.LocalSocketError.UnknownSocketError, - QLocalSocket.LocalSocketError.PeerClosedError - ]: - log.ipc.debug("We got an error immediately.") - self.on_error(socket_error) - - socket.disconnected.connect(self.on_disconnected) - if socket.state() == QLocalSocket.LocalSocketState.UnconnectedState: - log.ipc.debug("Socket was disconnected immediately.") - self.on_disconnected() - - @pyqtSlot() - def on_disconnected(self): - """Clean up socket when the client disconnected.""" - log.ipc.debug("Client disconnected from socket {}.".format(self._socket_id)) - self._timer.stop() - if self._old_socket is not None: - self._old_socket.deleteLater() - self._old_socket = self._socket - self._socket = None - # Maybe another connection is waiting. - self.handle_connection() - - def _handle_invalid_data(self): - """Handle invalid data we got from a QLocalSocket.""" - assert self._socket is not None - log.ipc.error("Ignoring invalid IPC data from socket {}.".format( - self._socket_id)) - self.got_invalid_data.emit() - self._socket.errorOccurred.connect(self.on_error) - self._socket.disconnectFromServer() - - def _handle_data(self, data): - """Handle data (as bytes) we got from on_ready_read.""" + @pyqtSlot(int, bytes) + def handle_data(self, conn_id: int, data: bytes) -> None: + """Handle data we got from a connection.""" try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("invalid utf-8: {!r}".format(binascii.hexlify(data))) - self._handle_invalid_data() + self._handle_invalid_data(conn_id) return log.ipc.debug("Processing: {}".format(decoded)) @@ -317,26 +359,26 @@ class IPCServer(QObject): json_data = json.loads(decoded) except ValueError: log.ipc.error("invalid json: {}".format(decoded.strip())) - self._handle_invalid_data() + self._handle_invalid_data(conn_id) return for name in ['args', 'target_arg']: if name not in json_data: log.ipc.error("Missing {}: {}".format(name, decoded.strip())) - self._handle_invalid_data() + self._handle_invalid_data(conn_id) return try: protocol_version = int(json_data['protocol_version']) except (KeyError, ValueError): log.ipc.error("invalid version: {}".format(decoded.strip())) - self._handle_invalid_data() + self._handle_invalid_data(conn_id) return if protocol_version != PROTOCOL_VERSION: log.ipc.error("incompatible version: expected {}, got {}".format( PROTOCOL_VERSION, protocol_version)) - self._handle_invalid_data() + self._handle_invalid_data(conn_id) return args = json_data['args'] @@ -351,65 +393,13 @@ class IPCServer(QObject): self.got_args.emit(args, target_arg, cwd) - def _get_socket(self, warn=True): - """Get the current socket and ID for on_ready_read. - - Arguments: - warn: Whether to warn if no socket was found. - """ - if self._socket is None: # pragma: no cover - # This happens when doing a connection while another one is already - # active for some reason. - if self._old_socket is None: - if warn: - log.ipc.warning("In _get_socket with None socket and old_socket!") - return None, None - log.ipc.debug("In _get_socket with None socket!") - socket = self._old_socket - # hopefully accurate guess, but at least we have a debug log - socket_id = self._socket_id - 1 - else: - socket = self._socket - socket_id = self._socket_id - - if sip.isdeleted(socket): # pragma: no cover - log.ipc.warning("Ignoring deleted IPC socket") - return None, None - - return socket, socket_id + def _handle_invalid_data(self, conn_id: int) -> None: + """Handle invalid data we got from a QLocalSocket.""" + log.ipc.error(f"Ignoring invalid IPC data from socket {conn_id}.") + self.got_invalid_data.emit(conn_id) @pyqtSlot() - def on_ready_read(self): - """Read json data from the client.""" - self._timer.stop() - - socket, socket_id = self._get_socket() - while socket is not None and socket.canReadLine(): - data = bytes(socket.readLine()) - self.got_raw.emit(data) - log.ipc.debug("Read from socket {}: {!r}".format(socket_id, data)) - self._handle_data(data) - socket, socket_id = self._get_socket(warn=False) - - if self._socket is not None: - self._timer.start() - - @pyqtSlot() - def on_timeout(self): - """Cancel the current connection if it was idle for too long.""" - assert self._socket is not None - log.ipc.error("IPC connection timed out " - "(socket {}).".format(self._socket_id)) - self._socket.disconnectFromServer() - if self._socket is not None: # pragma: no cover - # on_socket_disconnected sets it to None - self._socket.waitForDisconnected(CONNECT_TIMEOUT) - if self._socket is not None: # pragma: no cover - # on_socket_disconnected sets it to None - self._socket.abort() - - @pyqtSlot() - def update_atime(self): + def update_atime(self) -> None: """Update the atime of the socket file all few hours. From the XDG basedir spec: @@ -435,7 +425,7 @@ class IPCServer(QObject): self.listen() @pyqtSlot() - def shutdown(self): + def shutdown(self) -> None: """Shut down the IPC server cleanly.""" if self._server is None: # We can get called twice when using :restart -- there, IPC is shut down @@ -443,13 +433,9 @@ class IPCServer(QObject): # we get called again when the application is about to quit. return - log.ipc.debug("Shutting down IPC (socket {})".format(self._socket_id)) + log.ipc.debug("Shutting down IPC") + self.shutting_down.emit() - if self._socket is not None: - self._socket.deleteLater() - self._socket = None - - self._timer.stop() if self._atime_timer is not None: # pragma: no branch self._atime_timer.stop() try: @@ -463,7 +449,13 @@ class IPCServer(QObject): self._server = None -def send_to_running_instance(socketname, command, target_arg, *, socket=None): +def send_to_running_instance( + socketname: int, + command: List[str], + target_arg: str, + *, + socket: Optional[QLocalSocket] = None, +) -> None: """Try to send a commandline to a running instance. Blocks for CONNECT_TIMEOUT ms. @@ -515,14 +507,14 @@ def send_to_running_instance(socketname, command, target_arg, *, socket=None): return False -def display_error(exc, args): +def display_error(exc: Exception, args: argparse.Namespace) -> None: """Display a message box with an IPC error.""" error.handle_fatal_exc( exc, "Error while connecting to running instance!", no_err_windows=args.no_err_windows) -def send_or_listen(args): +def send_or_listen(args: argparse.Namespace) -> None: """Send the args to a running instance or start a new IPCServer. Args: From 48b27a356cd6fede40fc2bc89055132ec653ef69 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 May 2024 18:44:30 +0200 Subject: [PATCH 056/403] timestamps --- qutebrowser/misc/ipc.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 9342ab55b..9f31d023c 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -6,6 +6,7 @@ import os import time +import functools import json import getpass import binascii @@ -154,7 +155,7 @@ class IPCConnection(QObject): self._timer = usertypes.Timer(self, "ipc-timeout") self._timer.setInterval(READ_TIMEOUT) - self._timer.timeout.connect(self.on_timeout) + self._timer.timeout.connect(functools.partial(self.on_timeout, time.time())) self._timer.start() self._socket: Optional[QLocalSocket] = socket @@ -219,11 +220,11 @@ class IPCConnection(QObject): if self._socket is not None: self._timer.start() - @pyqtSlot() - def on_timeout(self) -> None: + @pyqtSlot(float) + def on_timeout(self, t: float) -> None: """Cancel the current connection if it was idle for too long.""" assert self._socket is not None - log.ipc.error(f"IPC connection timed out (socket {self.conn_id}).") + log.ipc.error(f"IPC connection timed out (socket {self.conn_id}, t {t}, now {time.time()}).") self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_disconnected sets it to None From 5b84600fa6e85d9cf2dfa1c5f979955b5c492405 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 May 2024 20:02:23 +0200 Subject: [PATCH 057/403] logging --- qutebrowser/misc/ipc.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 9f31d023c..d232235d2 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -155,8 +155,9 @@ class IPCConnection(QObject): self._timer = usertypes.Timer(self, "ipc-timeout") self._timer.setInterval(READ_TIMEOUT) - self._timer.timeout.connect(functools.partial(self.on_timeout, time.time())) + self._timer.timeout.connect(self.on_timeout) self._timer.start() + log.ipc.debug(f"Connection {self.conn_id} -> timer {self._timer.timerId()} at {time.time()}") self._socket: Optional[QLocalSocket] = socket self._socket.readyRead.connect(self.on_ready_read) @@ -220,11 +221,12 @@ class IPCConnection(QObject): if self._socket is not None: self._timer.start() - @pyqtSlot(float) - def on_timeout(self, t: float) -> None: + @pyqtSlot() + def on_timeout(self) -> None: """Cancel the current connection if it was idle for too long.""" assert self._socket is not None - log.ipc.error(f"IPC connection timed out (socket {self.conn_id}, t {t}, now {time.time()}).") + now = time.time() + log.ipc.error(f"IPC connection timed out (socket {self.conn_id}, now {now}, timer id {self.sender().timerId()}).") self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_disconnected sets it to None From 00c56456bed6bdc513719ada538a39be77013120 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 May 2024 20:39:55 +0200 Subject: [PATCH 058/403] back to normal ipc impl --- .mypy.ini | 4 +- qutebrowser/misc/ipc.py | 321 ++++++++++++++++++++-------------------- 2 files changed, 166 insertions(+), 159 deletions(-) diff --git a/.mypy.ini b/.mypy.ini index eef89ae20..81f69a09e 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -240,6 +240,9 @@ disallow_untyped_defs = False [mypy-qutebrowser.misc.httpclient] disallow_untyped_defs = False +[mypy-qutebrowser.misc.ipc] +disallow_untyped_defs = False + [mypy-qutebrowser.misc.keyhintwidget] disallow_untyped_defs = False @@ -264,7 +267,6 @@ disallow_untyped_defs = False [mypy-qutebrowser.misc.split] disallow_untyped_defs = False - [mypy-qutebrowser.qutebrowser] disallow_untyped_defs = False diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index d232235d2..72159eab7 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -6,14 +6,11 @@ import os import time -import functools import json import getpass import binascii import hashlib -import itertools -import argparse -from typing import Optional, List +from typing import Optional from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt from qutebrowser.qt.network import QLocalSocket, QLocalServer, QAbstractSocket @@ -34,7 +31,7 @@ PROTOCOL_VERSION = 1 server: Optional["IPCServer"] = None -def _get_socketname_windows(basedir: Optional[str]) -> str: +def _get_socketname_windows(basedir): """Get a socketname to use for Windows.""" try: username = getpass.getuser() @@ -55,7 +52,7 @@ def _get_socketname_windows(basedir: Optional[str]) -> str: return '-'.join(parts) -def _get_socketname(basedir: Optional[str]) -> str: +def _get_socketname(basedir): """Get a socketname to use.""" if utils.is_windows: # pragma: no cover return _get_socketname_windows(basedir) @@ -87,7 +84,7 @@ class SocketError(Error): action: The action which was taken when the error happened. """ - def __init__(self, action: str, socket: QLocalSocket) -> None: + def __init__(self, action, socket): """Constructor. Args: @@ -99,7 +96,7 @@ class SocketError(Error): self.code: QLocalSocket.LocalSocketError = socket.error() self.message: str = socket.errorString() - def __str__(self) -> str: + def __str__(self): return "Error while {}: {} ({})".format( self.action, self.message, debug.qenum_key(QLocalSocket, self.code)) @@ -113,7 +110,7 @@ class ListenError(Error): message: The error message. """ - def __init__(self, local_server: QLocalServer) -> None: + def __init__(self, local_server): """Constructor. Args: @@ -123,7 +120,7 @@ class ListenError(Error): self.code: QAbstractSocket.SocketError = local_server.serverError() self.message: str = local_server.errorString() - def __str__(self) -> str: + def __str__(self): return "Error while listening to IPC server: {} ({})".format( self.message, debug.qenum_key(QAbstractSocket, self.code)) @@ -133,116 +130,6 @@ class AddressInUseError(ListenError): """Emitted when the server address is already in use.""" -class IPCConnection(QObject): - """A connection to an IPC socket. - - Multiple connections might be active in parallel. - - Attributes: - _socket: The QLocalSocket to use. - - Signals: - got_raw: Emitted with the connection ID and raw data from the socket. - """ - - got_raw = pyqtSignal(int, bytes) - id_gen = itertools.count() - - def __init__(self, socket: QLocalSocket, parent: Optional[QObject] = None): - super().__init__(parent) - self.conn_id = next(self.id_gen) - log.ipc.debug("Client connected (socket {}).".format(self.conn_id)) - - self._timer = usertypes.Timer(self, "ipc-timeout") - self._timer.setInterval(READ_TIMEOUT) - self._timer.timeout.connect(self.on_timeout) - self._timer.start() - log.ipc.debug(f"Connection {self.conn_id} -> timer {self._timer.timerId()} at {time.time()}") - - self._socket: Optional[QLocalSocket] = socket - self._socket.readyRead.connect(self.on_ready_read) - - if socket.canReadLine(): - log.ipc.debug("We can read a line immediately.") - self.on_ready_read() - - socket.errorOccurred.connect(self.on_error) - - # FIXME:v4 Ignore needed due to overloaded signal/method in Qt 5 - socket_error = socket.error() # type: ignore[operator,unused-ignore] - if socket_error not in [ - QLocalSocket.LocalSocketError.UnknownSocketError, - QLocalSocket.LocalSocketError.PeerClosedError, - ]: - log.ipc.debug("We got an error immediately.") - self.on_error(socket_error) - - socket.disconnected.connect(self.on_disconnected) - if socket.state() == QLocalSocket.LocalSocketState.UnconnectedState: - log.ipc.debug("Socket was disconnected immediately.") - self.on_disconnected() - - @pyqtSlot("QLocalSocket::LocalSocketError") - def on_error(self, err: QLocalSocket.LocalSocketError) -> None: - """Raise SocketError on fatal errors.""" - if self._socket is None: - # Sometimes this gets called from stale sockets. - log.ipc.debug("In on_error with None socket!") - return - self._timer.stop() - log.ipc.debug( - "Socket {}: error {}: {}".format( - self.conn_id, self._socket.error(), self._socket.errorString() - ) - ) - if err != QLocalSocket.LocalSocketError.PeerClosedError: - raise SocketError(f"handling IPC connection {self.conn_id}", self._socket) - - @pyqtSlot() - def on_disconnected(self) -> None: - """Clean up socket when the client disconnected.""" - assert self._socket is not None - log.ipc.debug(f"Client disconnected from socket {self.conn_id}.") - self._timer.stop() - self._socket.deleteLater() - self._socket = None - self.deleteLater() - - @pyqtSlot() - def on_ready_read(self) -> None: - """Read json data from the client.""" - self._timer.stop() - - while self._socket is not None and self._socket.canReadLine(): - data = self._socket.readLine().data() - log.ipc.debug("Read from socket {}: {!r}".format(self.conn_id, data)) - self.got_raw.emit(self.conn_id, data) - - if self._socket is not None: - self._timer.start() - - @pyqtSlot() - def on_timeout(self) -> None: - """Cancel the current connection if it was idle for too long.""" - assert self._socket is not None - now = time.time() - log.ipc.error(f"IPC connection timed out (socket {self.conn_id}, now {now}, timer id {self.sender().timerId()}).") - self._socket.disconnectFromServer() - if self._socket is not None: # pragma: no cover - # on_disconnected sets it to None - self._socket.waitForDisconnected(CONNECT_TIMEOUT) - if self._socket is not None: # pragma: no cover - # on_disconnected sets it to None - self._socket.abort() - - @pyqtSlot(int) - def on_invalid_data(self, conn_id: int) -> None: - if conn_id != self.conn_id: - return - assert self._socket is not None - self._socket.disconnectFromServer() - - class IPCServer(QObject): """IPC server to which clients connect to. @@ -251,23 +138,23 @@ class IPCServer(QObject): ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. + _socket: The QLocalSocket we're currently connected to. + _socket_id: An unique, incrementing ID for every socket. _socketname: The socketname to use. _atime_timer: Timer to update the atime of the socket regularly. Signals: got_args: Emitted when there was an IPC connection and arguments were passed. - got_raw: Emitted with the raw data an IPC connection got. + got_args: Emitted with the raw data an IPC connection got. got_invalid_data: Emitted when there was invalid incoming data. - shutting_down: IPC is shutting down. """ got_args = pyqtSignal(list, str, str) got_raw = pyqtSignal(bytes) - got_invalid_data = pyqtSignal(int) - shutting_down = pyqtSignal() + got_invalid_data = pyqtSignal() - def __init__(self, socketname: str, parent: QObject = None) -> None: + def __init__(self, socketname, parent=None): """Start the IPC server and listen to commands. Args: @@ -278,6 +165,10 @@ class IPCServer(QObject): self.ignored = False self._socketname = socketname + self._timer = usertypes.Timer(self, 'ipc-timeout') + self._timer.setInterval(READ_TIMEOUT) + self._timer.timeout.connect(self.on_timeout) + if utils.is_windows: # pragma: no cover self._atime_timer = None else: @@ -289,6 +180,10 @@ class IPCServer(QObject): self._server: Optional[QLocalServer] = QLocalServer(self) self._server.newConnection.connect(self.handle_connection) + self._socket = None + self._socket_id = 0 + self._old_socket = None + if utils.is_windows: # pragma: no cover # As a WORKAROUND for a Qt bug, we can't use UserAccessOption on Unix. If we # do, we don't get an AddressInUseError anymore: @@ -301,14 +196,14 @@ class IPCServer(QObject): else: # pragma: no cover log.ipc.debug("Not calling setSocketOptions") - def _remove_server(self) -> None: + def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(self._socketname) if not ok: raise Error("Error while removing server {}!".format( self._socketname)) - def listen(self) -> None: + def listen(self): """Start listening on self._socketname.""" assert self._server is not None log.ipc.debug("Listening as {}".format(self._socketname)) @@ -331,30 +226,90 @@ class IPCServer(QObject): # True, so report this as an error. raise ListenError(self._server) + @pyqtSlot('QLocalSocket::LocalSocketError') + def on_error(self, err): + """Raise SocketError on fatal errors.""" + if self._socket is None: + # Sometimes this gets called from stale sockets. + log.ipc.debug("In on_error with None socket!") + return + self._timer.stop() + log.ipc.debug("Socket {}: error {}: {}".format( + self._socket_id, self._socket.error(), + self._socket.errorString())) + if err != QLocalSocket.LocalSocketError.PeerClosedError: + raise SocketError( + f"handling IPC connection {self._socket_id}", self._socket + ) + @pyqtSlot() - def handle_connection(self) -> None: + def handle_connection(self): """Handle a new connection to the server.""" if self.ignored or self._server is None: return - + if self._socket is not None: + log.ipc.debug("Got new connection but ignoring it because we're " + "still handling another one ({}).".format( + self._socket_id)) + return socket = qtutils.add_optional(self._server.nextPendingConnection()) if socket is None: log.ipc.debug("No new connection to handle.") return - conn = IPCConnection(socket, parent=self) - conn.got_raw.connect(self.handle_data) - self.got_invalid_data.connect(conn.on_invalid_data) - self.shutting_down.connect(conn.on_disconnected) + self._socket_id += 1 + log.ipc.debug("Client connected (socket {}).".format(self._socket_id)) + self._socket = socket + self._timer.start() + socket.readyRead.connect(self.on_ready_read) + if socket.canReadLine(): + log.ipc.debug("We can read a line immediately.") + self.on_ready_read() - @pyqtSlot(int, bytes) - def handle_data(self, conn_id: int, data: bytes) -> None: - """Handle data we got from a connection.""" + socket.errorOccurred.connect(self.on_error) + + # FIXME:v4 Ignore needed due to overloaded signal/method in Qt 5 + socket_error = socket.error() # type: ignore[operator,unused-ignore] + if socket_error not in [ + QLocalSocket.LocalSocketError.UnknownSocketError, + QLocalSocket.LocalSocketError.PeerClosedError + ]: + log.ipc.debug("We got an error immediately.") + self.on_error(socket_error) + + socket.disconnected.connect(self.on_disconnected) + if socket.state() == QLocalSocket.LocalSocketState.UnconnectedState: + log.ipc.debug("Socket was disconnected immediately.") + self.on_disconnected() + + @pyqtSlot() + def on_disconnected(self): + """Clean up socket when the client disconnected.""" + log.ipc.debug("Client disconnected from socket {}.".format(self._socket_id)) + self._timer.stop() + if self._old_socket is not None: + self._old_socket.deleteLater() + self._old_socket = self._socket + self._socket = None + # Maybe another connection is waiting. + self.handle_connection() + + def _handle_invalid_data(self): + """Handle invalid data we got from a QLocalSocket.""" + assert self._socket is not None + log.ipc.error("Ignoring invalid IPC data from socket {}.".format( + self._socket_id)) + self.got_invalid_data.emit() + self._socket.errorOccurred.connect(self.on_error) + self._socket.disconnectFromServer() + + def _handle_data(self, data): + """Handle data (as bytes) we got from on_ready_read.""" try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("invalid utf-8: {!r}".format(binascii.hexlify(data))) - self._handle_invalid_data(conn_id) + self._handle_invalid_data() return log.ipc.debug("Processing: {}".format(decoded)) @@ -362,26 +317,26 @@ class IPCServer(QObject): json_data = json.loads(decoded) except ValueError: log.ipc.error("invalid json: {}".format(decoded.strip())) - self._handle_invalid_data(conn_id) + self._handle_invalid_data() return for name in ['args', 'target_arg']: if name not in json_data: log.ipc.error("Missing {}: {}".format(name, decoded.strip())) - self._handle_invalid_data(conn_id) + self._handle_invalid_data() return try: protocol_version = int(json_data['protocol_version']) except (KeyError, ValueError): log.ipc.error("invalid version: {}".format(decoded.strip())) - self._handle_invalid_data(conn_id) + self._handle_invalid_data() return if protocol_version != PROTOCOL_VERSION: log.ipc.error("incompatible version: expected {}, got {}".format( PROTOCOL_VERSION, protocol_version)) - self._handle_invalid_data(conn_id) + self._handle_invalid_data() return args = json_data['args'] @@ -396,13 +351,65 @@ class IPCServer(QObject): self.got_args.emit(args, target_arg, cwd) - def _handle_invalid_data(self, conn_id: int) -> None: - """Handle invalid data we got from a QLocalSocket.""" - log.ipc.error(f"Ignoring invalid IPC data from socket {conn_id}.") - self.got_invalid_data.emit(conn_id) + def _get_socket(self, warn=True): + """Get the current socket and ID for on_ready_read. + + Arguments: + warn: Whether to warn if no socket was found. + """ + if self._socket is None: # pragma: no cover + # This happens when doing a connection while another one is already + # active for some reason. + if self._old_socket is None: + if warn: + log.ipc.warning("In _get_socket with None socket and old_socket!") + return None, None + log.ipc.debug("In _get_socket with None socket!") + socket = self._old_socket + # hopefully accurate guess, but at least we have a debug log + socket_id = self._socket_id - 1 + else: + socket = self._socket + socket_id = self._socket_id + + if sip.isdeleted(socket): # pragma: no cover + log.ipc.warning("Ignoring deleted IPC socket") + return None, None + + return socket, socket_id @pyqtSlot() - def update_atime(self) -> None: + def on_ready_read(self): + """Read json data from the client.""" + self._timer.stop() + + socket, socket_id = self._get_socket() + while socket is not None and socket.canReadLine(): + data = bytes(socket.readLine()) + self.got_raw.emit(data) + log.ipc.debug("Read from socket {}: {!r}".format(socket_id, data)) + self._handle_data(data) + socket, socket_id = self._get_socket(warn=False) + + if self._socket is not None: + self._timer.start() + + @pyqtSlot() + def on_timeout(self): + """Cancel the current connection if it was idle for too long.""" + assert self._socket is not None + log.ipc.error("IPC connection timed out " + "(socket {}).".format(self._socket_id)) + self._socket.disconnectFromServer() + if self._socket is not None: # pragma: no cover + # on_socket_disconnected sets it to None + self._socket.waitForDisconnected(CONNECT_TIMEOUT) + if self._socket is not None: # pragma: no cover + # on_socket_disconnected sets it to None + self._socket.abort() + + @pyqtSlot() + def update_atime(self): """Update the atime of the socket file all few hours. From the XDG basedir spec: @@ -428,7 +435,7 @@ class IPCServer(QObject): self.listen() @pyqtSlot() - def shutdown(self) -> None: + def shutdown(self): """Shut down the IPC server cleanly.""" if self._server is None: # We can get called twice when using :restart -- there, IPC is shut down @@ -436,9 +443,13 @@ class IPCServer(QObject): # we get called again when the application is about to quit. return - log.ipc.debug("Shutting down IPC") - self.shutting_down.emit() + log.ipc.debug("Shutting down IPC (socket {})".format(self._socket_id)) + if self._socket is not None: + self._socket.deleteLater() + self._socket = None + + self._timer.stop() if self._atime_timer is not None: # pragma: no branch self._atime_timer.stop() try: @@ -452,13 +463,7 @@ class IPCServer(QObject): self._server = None -def send_to_running_instance( - socketname: int, - command: List[str], - target_arg: str, - *, - socket: Optional[QLocalSocket] = None, -) -> None: +def send_to_running_instance(socketname, command, target_arg, *, socket=None): """Try to send a commandline to a running instance. Blocks for CONNECT_TIMEOUT ms. @@ -510,14 +515,14 @@ def send_to_running_instance( return False -def display_error(exc: Exception, args: argparse.Namespace) -> None: +def display_error(exc, args): """Display a message box with an IPC error.""" error.handle_fatal_exc( exc, "Error while connecting to running instance!", no_err_windows=args.no_err_windows) -def send_or_listen(args: argparse.Namespace) -> None: +def send_or_listen(args): """Send the args to a running instance or start a new IPCServer. Args: From 38053466fd5404e9d1ffec5e9e83a559bfb97de3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 May 2024 20:51:16 +0200 Subject: [PATCH 059/403] Ignore timeout --- qutebrowser/misc/ipc.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 72159eab7..0dbb88ae0 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -168,6 +168,7 @@ class IPCServer(QObject): self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) + self._timer_start_time = None if utils.is_windows: # pragma: no cover self._atime_timer = None @@ -261,6 +262,7 @@ class IPCServer(QObject): log.ipc.debug("Client connected (socket {}).".format(self._socket_id)) self._socket = socket self._timer.start() + self._timer_start_time = time.monotonic() socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") @@ -393,11 +395,23 @@ class IPCServer(QObject): if self._socket is not None: self._timer.start() + self._timer_start_time = time.monotonic() @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" assert self._socket is not None + assert self._timer_start_time is not None + if ( + time.monotonic() - self._timer_start_time < READ_TIMEOUT / 1000 / 10 + and qtutils.version_check("6.7.0", exact=True, compiled=False) + and utils.is_windows + ): + # WORKAROUND for unknown Qt bug (?) where the timer triggers immediately + # https://github.com/qutebrowser/qutebrowser/issues/8191 + log.ipc.debug("Ignoring early on_timeout call") + return + log.ipc.error("IPC connection timed out " "(socket {}).".format(self._socket_id)) self._socket.disconnectFromServer() From 52346a19103bd1448c92f31bb7f84be6d6644bd3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 May 2024 10:39:05 +0200 Subject: [PATCH 060/403] Check all timers for early triggering --- qutebrowser/utils/usertypes.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 95b1f3a01..102f57725 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -7,6 +7,7 @@ import html import operator import enum +import time import dataclasses from typing import Optional, Sequence, TypeVar, Union @@ -443,6 +444,8 @@ class Timer(QTimer): def __init__(self, parent: QObject = None, name: str = None) -> None: super().__init__(parent) + self._start_time = None + self.timeout.connect(self._check_timeout_validity) if name is None: self._name = "unnamed" else: @@ -452,6 +455,17 @@ class Timer(QTimer): def __repr__(self) -> str: return utils.get_repr(self, name=self._name) + @pyqtSlot() + def _check_timeout_validity(self) -> None: + if self._start_time is None: + # manual emission? + return + elapsed = time.monotonic() - self._start_time + if elapsed < self.interval() / 1000 / 2: + log.misc.warning( + f"Timer {self._name} (id {self.timerId()} triggered too early: " + f"interval {self.interval()} but only {elapsed:.3f}s passed") + def setInterval(self, msec: int) -> None: """Extend setInterval to check for overflows.""" qtutils.check_overflow(msec, 'int') @@ -459,6 +473,7 @@ class Timer(QTimer): def start(self, msec: int = None) -> None: """Extend start to check for overflows.""" + self._start_time = time.monotonic() if msec is not None: qtutils.check_overflow(msec, 'int') super().start(msec) From 38abfdb8a00f51a5bcf1a12047d5417aabbb6e0c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 7 May 2024 13:07:13 +0200 Subject: [PATCH 061/403] check more timers --- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/completion/completer.py | 4 ++-- qutebrowser/mainwindow/messageview.py | 2 +- qutebrowser/mainwindow/statusbar/clock.py | 3 ++- qutebrowser/mainwindow/tabwidget.py | 2 +- qutebrowser/misc/httpclient.py | 4 ++-- qutebrowser/utils/usertypes.py | 4 ++-- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 19defb6d3..759e4a9e3 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -816,7 +816,7 @@ class WebEngineAudio(browsertab.AbstractAudio): # Implements the intended two-second delay specified at # https://doc.qt.io/archives/qt-5.14/qwebenginepage.html#recentlyAudibleChanged delay_ms = 2000 - self._silence_timer = QTimer(self) + self._silence_timer = usertypes.Timer(self) self._silence_timer.setSingleShot(True) self._silence_timer.setInterval(delay_ms) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 49a97c9cb..c6db54caa 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -12,7 +12,7 @@ from qutebrowser.qt.core import pyqtSlot, QObject, QTimer from qutebrowser.config import config from qutebrowser.commands import parser, cmdexc from qutebrowser.misc import objects, split -from qutebrowser.utils import log, utils, debug, objreg +from qutebrowser.utils import log, utils, debug, objreg, usertypes from qutebrowser.completion.models import miscmodels from qutebrowser.completion import completionwidget if TYPE_CHECKING: @@ -49,7 +49,7 @@ class Completer(QObject): super().__init__(parent) self._cmd = cmd self._win_id = win_id - self._timer = QTimer() + self._timer = usertypes.Timer() self._timer.setSingleShot(True) self._timer.setInterval(0) self._timer.timeout.connect(self._update_completion) diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 38d2a2f9e..8b2e65205 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -101,7 +101,7 @@ class MessageView(QWidget): self._vbox.setSpacing(0) self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - self._clear_timer = QTimer() + self._clear_timer = usertypes.Timer() self._clear_timer.timeout.connect(self.clear_messages) config.instance.changed.connect(self._set_clear_timer_interval) diff --git a/qutebrowser/mainwindow/statusbar/clock.py b/qutebrowser/mainwindow/statusbar/clock.py index aa2afe8a0..38550bad8 100644 --- a/qutebrowser/mainwindow/statusbar/clock.py +++ b/qutebrowser/mainwindow/statusbar/clock.py @@ -8,6 +8,7 @@ from datetime import datetime from qutebrowser.qt.core import Qt, QTimer from qutebrowser.mainwindow.statusbar import textbase +from qutebrowser.utils import usertypes class Clock(textbase.TextBase): @@ -20,7 +21,7 @@ class Clock(textbase.TextBase): super().__init__(parent, elidemode=Qt.TextElideMode.ElideNone) self.format = "" - self.timer = QTimer(self) + self.timer = usertypes.Timer(self) self.timer.timeout.connect(self._show_time) def _show_time(self): diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 42c31c97e..afbfa0a95 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -398,7 +398,7 @@ class TabBar(QTabBar): self.setStyle(self._our_style) self.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.vertical = False - self._auto_hide_timer = QTimer() + self._auto_hide_timer = usertypes.Timer() self._auto_hide_timer.setSingleShot(True) self._auto_hide_timer.timeout.connect(self.maybe_hide) self._on_show_switching_delay_changed() diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py index 6e1a0f577..19186ffb7 100644 --- a/qutebrowser/misc/httpclient.py +++ b/qutebrowser/misc/httpclient.py @@ -12,7 +12,7 @@ from qutebrowser.qt.core import pyqtSignal, QObject, QTimer from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkRequest, QNetworkReply) -from qutebrowser.utils import qtlog +from qutebrowser.utils import qtlog, usertypes class HTTPRequest(QNetworkRequest): @@ -85,7 +85,7 @@ class HTTPClient(QObject): if reply.isFinished(): self.on_reply_finished(reply) else: - timer = QTimer(self) + timer = usertypes.Timer(self) timer.setInterval(10000) timer.timeout.connect(reply.abort) timer.start() diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 102f57725..a81036c77 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -461,9 +461,9 @@ class Timer(QTimer): # manual emission? return elapsed = time.monotonic() - self._start_time - if elapsed < self.interval() / 1000 / 2: + if elapsed < self.interval() / 1000 / 2 and self._name != "ipc-timeout": log.misc.warning( - f"Timer {self._name} (id {self.timerId()} triggered too early: " + f"Timer {self._name} (id {self.timerId()}) triggered too early: " f"interval {self.interval()} but only {elapsed:.3f}s passed") def setInterval(self, msec: int) -> None: From c5fa5a0dc9442eacb9faf9f72caad193e99b5506 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 11 May 2024 12:47:51 +1200 Subject: [PATCH 062/403] lint: Add misc check for QTimer initialization Since we added some sanity checking in usertypes.Timer() around QTBUG-124496 it would be convenient if there was a reminder for future timer users to use our Timer object instead. Here's one! It's looking for QTimer initialisations, we are still allowing QTimer.singleShot(), although that probably can hit the same issue. It uses an end-of-line anchor in the regex so you can put a comment (any comment) on the end of the line to ignore the check. --- scripts/dev/misc_checks.py | 4 ++++ tests/unit/utils/test_qtutils.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index f10dc77bf..4b838b5fe 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -274,6 +274,10 @@ def check_spelling(args: argparse.Namespace) -> Optional[bool]: re.compile(r'qutebrowser is free software: you can redistribute'), "use 'SPDX-License-Identifier: GPL-3.0-or-later' instead", ), + ( + re.compile(r'QTimer\(.*\)$'), + "use usertypes.Timer() instead of a plain QTimer", + ), ] # Files which should be ignored, e.g. because they come from another diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index f1cf1e73e..0a3afa416 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -1118,13 +1118,13 @@ class TestQObjRepr: assert qtutils.qobj_repr(obj) == expected def test_class_name(self): - obj = QTimer() + obj = QTimer() # misc: ignore hidden = sip.cast(obj, QObject) expected = f"<{self._py_repr(hidden)}, className='QTimer'>" assert qtutils.qobj_repr(hidden) == expected def test_both(self): - obj = QTimer() + obj = QTimer() # misc: ignore obj.setObjectName("Pomodoro") hidden = sip.cast(obj, QObject) expected = f"<{self._py_repr(hidden)}, objectName='Pomodoro', className='QTimer'>" From 9c8a80798a91d1bcbe4a9b02ca217d076e459b39 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 11 May 2024 13:35:59 +1200 Subject: [PATCH 063/403] Use common timer validity check in usertypes.Timer Since we are now checking for early timer firings in usertype.Timer I figured why do it in two places, lets call the check method on Timer in the handler in the IPC class too. I also removed the platform and version check because while the bug only happens under those conditions the check to ensure a timer event was valid should work everywhere. Right? We can add them back of someone wants but I'm not sure I see a point. It would be nice if we could filter out invalid timer events in usertypes.Timer and not require the consuming code to have to do anything. But that would be more work, so lets wait until more places of code actually need to care about it, chances are it'll be fixed in Qt soon enough anyway. I also think usertypes.Timer should be restarting the time so it gets called at the proper time. But if we aren't filtering the events that would mean it gets called twice. --- qutebrowser/misc/ipc.py | 13 ++----------- qutebrowser/utils/usertypes.py | 34 +++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 0dbb88ae0..671ddd9ec 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -168,7 +168,6 @@ class IPCServer(QObject): self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) - self._timer_start_time = None if utils.is_windows: # pragma: no cover self._atime_timer = None @@ -262,7 +261,6 @@ class IPCServer(QObject): log.ipc.debug("Client connected (socket {}).".format(self._socket_id)) self._socket = socket self._timer.start() - self._timer_start_time = time.monotonic() socket.readyRead.connect(self.on_ready_read) if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") @@ -395,20 +393,13 @@ class IPCServer(QObject): if self._socket is not None: self._timer.start() - self._timer_start_time = time.monotonic() @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" assert self._socket is not None - assert self._timer_start_time is not None - if ( - time.monotonic() - self._timer_start_time < READ_TIMEOUT / 1000 / 10 - and qtutils.version_check("6.7.0", exact=True, compiled=False) - and utils.is_windows - ): - # WORKAROUND for unknown Qt bug (?) where the timer triggers immediately - # https://github.com/qutebrowser/qutebrowser/issues/8191 + if not self._timer.check_timeout_validity(): + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-124496 log.ipc.debug("Ignoring early on_timeout call") return diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index a81036c77..f67a76ff7 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -444,8 +444,8 @@ class Timer(QTimer): def __init__(self, parent: QObject = None, name: str = None) -> None: super().__init__(parent) - self._start_time = None - self.timeout.connect(self._check_timeout_validity) + self._start_time: Optional[float] = None + self.timeout.connect(self._validity_check_handler) if name is None: self._name = "unnamed" else: @@ -456,15 +456,31 @@ class Timer(QTimer): return utils.get_repr(self, name=self._name) @pyqtSlot() - def _check_timeout_validity(self) -> None: - if self._start_time is None: - # manual emission? - return - elapsed = time.monotonic() - self._start_time - if elapsed < self.interval() / 1000 / 2 and self._name != "ipc-timeout": + def _validity_check_handler(self) -> None: + if not self.check_timeout_validity() and self._start_time is not None: + elapsed = time.monotonic() - self._start_time log.misc.warning( f"Timer {self._name} (id {self.timerId()}) triggered too early: " - f"interval {self.interval()} but only {elapsed:.3f}s passed") + f"interval {self.interval()} but only {elapsed:.3f}s passed" + ) + + def check_timeout_validity(self) -> bool: + """Check to see if the timeout signal was fired at the expected time. + + WORKAROUND for https://bugreports.qt.io/browse/QTBUG-124496 + """ + if self._start_time is None: + # manual emission? + return True + + elapsed = time.monotonic() - self._start_time + # Checking for half the interval is pretty arbitrary. In the bug case + # the timer typically fires immediately since the expiry event is + # already pending when it is created. + if elapsed < self.interval() / 1000 / 2: + return False + + return True def setInterval(self, msec: int) -> None: """Extend setInterval to check for overflows.""" From f6e9295c50855f1478a42d9c211f6b5d22db2973 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 11 May 2024 13:45:21 +1200 Subject: [PATCH 064/403] remove unneeded QTimer imports --- qutebrowser/browser/webengine/webenginetab.py | 2 +- qutebrowser/completion/completer.py | 2 +- qutebrowser/mainwindow/messageview.py | 2 +- qutebrowser/mainwindow/statusbar/clock.py | 2 +- tests/unit/completion/test_completer.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 759e4a9e3..48ae7ea50 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -12,7 +12,7 @@ import re import html as html_utils from typing import cast, Union, Optional -from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUrl, +from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl, QObject, QByteArray) from qutebrowser.qt.network import QAuthenticator from qutebrowser.qt.webenginecore import QWebEnginePage, QWebEngineScript, QWebEngineHistory diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index c6db54caa..846fa7c22 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -7,7 +7,7 @@ import dataclasses from typing import TYPE_CHECKING -from qutebrowser.qt.core import pyqtSlot, QObject, QTimer +from qutebrowser.qt.core import pyqtSlot, QObject from qutebrowser.config import config from qutebrowser.commands import parser, cmdexc diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 8b2e65205..95bbed724 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -6,7 +6,7 @@ from typing import MutableSequence, Optional -from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QTimer, Qt +from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt from qutebrowser.qt.widgets import QWidget, QVBoxLayout, QLabel, QSizePolicy from qutebrowser.config import config, stylesheet diff --git a/qutebrowser/mainwindow/statusbar/clock.py b/qutebrowser/mainwindow/statusbar/clock.py index 38550bad8..604243935 100644 --- a/qutebrowser/mainwindow/statusbar/clock.py +++ b/qutebrowser/mainwindow/statusbar/clock.py @@ -5,7 +5,7 @@ """Clock displayed in the statusbar.""" from datetime import datetime -from qutebrowser.qt.core import Qt, QTimer +from qutebrowser.qt.core import Qt from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.utils import usertypes diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index add81c04d..5a012c634 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -55,7 +55,7 @@ def completion_widget_stub(): def completer_obj(qtbot, status_command_stub, config_stub, monkeypatch, stubs, completion_widget_stub): """Create the completer used for testing.""" - monkeypatch.setattr(completer, 'QTimer', stubs.InstaTimer) + monkeypatch.setattr(completer.usertypes, 'Timer', stubs.InstaTimer) config_stub.val.completion.show = 'auto' return completer.Completer(cmd=status_command_stub, win_id=0, parent=completion_widget_stub) From ccebbd6ebbb787107523d71e51eb36bcdfdde759 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 11 May 2024 14:17:11 +1200 Subject: [PATCH 065/403] adjust test strings for incrementing connection IDs --- tests/unit/misc/test_ipc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py index 79c2c7b7d..827ac828d 100644 --- a/tests/unit/misc/test_ipc.py +++ b/tests/unit/misc/test_ipc.py @@ -404,7 +404,7 @@ class TestOnError: socket.setErrorString("Connection refused.") with pytest.raises(ipc.Error, match=r"Error while handling IPC " - r"connection: Connection refused \(ConnectionRefusedError\)"): + r"connection [0-9]: Connection refused \(ConnectionRefusedError\)"): ipc_server.on_error(QLocalSocket.LocalSocketError.ConnectionRefusedError) @@ -439,7 +439,7 @@ class TestHandleConnection: ipc_server._server = FakeServer(socket) with pytest.raises(ipc.Error, match=r"Error while handling IPC " - r"connection: Error string \(ConnectionError\)"): + r"connection [0-9]: Error string \(ConnectionError\)"): ipc_server.handle_connection() assert "We got an error immediately." in caplog.messages From 5e4d0f1870929f946417adf3a72d18f4d9f4e8bb Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 11 May 2024 14:37:06 +1200 Subject: [PATCH 066/403] Add unit tests for early timer firing checking --- tests/unit/utils/usertypes/test_timer.py | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/unit/utils/usertypes/test_timer.py b/tests/unit/utils/usertypes/test_timer.py index c02f160b6..0793c826f 100644 --- a/tests/unit/utils/usertypes/test_timer.py +++ b/tests/unit/utils/usertypes/test_timer.py @@ -4,6 +4,9 @@ """Tests for Timer.""" +import logging +import fnmatch + import pytest from qutebrowser.qt.core import QObject @@ -65,3 +68,63 @@ def test_timeout_set_interval(qtbot): with qtbot.wait_signal(t.timeout, timeout=3000): t.setInterval(200) t.start() + + +@pytest.mark.parametrize( + "elapsed_ms,expected", + [ + (0, False,), + (1, False,), + (600, True,), + (999, True,), + (1000, True,), + ], +) +def test_early_timeout_check(qtbot, mocker, elapsed_ms, expected): + time_mock = mocker.patch("time.monotonic", autospec=True) + + t = usertypes.Timer() + t.setInterval(1000) # anything long enough to not actually fire + time_mock.return_value = 0 # assigned to _start_time in start() + t.start() + time_mock.return_value = elapsed_ms / 1000 # used for `elapsed` + + assert t.check_timeout_validity() is expected + + t.stop() + + +def test_early_timeout_handler(qtbot, mocker, caplog): + time_mock = mocker.patch("time.monotonic", autospec=True) + + t = usertypes.Timer(name="t") + t.setInterval(3) + t.setSingleShot(True) + time_mock.return_value = 0 + with caplog.at_level(logging.WARNING): + with qtbot.wait_signal(t.timeout, timeout=10): + t.start() + time_mock.return_value = 1 / 1000 + + assert len(caplog.messages) == 1 + assert fnmatch.fnmatch( + caplog.messages[-1], + "Timer t (id *) triggered too early: interval 3 but only 0.001s passed", + ) + + +def test_early_manual_fire(qtbot, mocker, caplog): + """Same as above but start() never gets called.""" + time_mock = mocker.patch("time.monotonic", autospec=True) + + t = usertypes.Timer(name="t") + t.setInterval(3) + t.setSingleShot(True) + time_mock.return_value = 0 + with caplog.at_level(logging.WARNING): + with qtbot.wait_signal(t.timeout, timeout=10): + t.timeout.emit() + time_mock.return_value = 1 / 1000 + + assert len(caplog.messages) == 0 + assert t.check_timeout_validity() From 669795058bd9a3679fae19d8b216f3c1c71c0ff6 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 11 May 2024 15:15:51 +1200 Subject: [PATCH 067/403] Handle "early timer" log messages in tests The `test_cleanup()` test for guiprocess was triggering the early timer warning messages because it was using a VeryCourse timer with a 100ms interval. Changed it to to a normal Course timer (the default) for that test. For the `ipc-timeout` timer we know that is happening in the tests on windows. It's being logged in lost of e2e tests, the elapsed times it's logging are between 0 and 0.020s. I'm not sure if it's the right thing to be changing the log level in production code or marking the messages as expected in test code. --- qutebrowser/utils/usertypes.py | 13 ++++++++++--- tests/unit/misc/test_guiprocess.py | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index f67a76ff7..d61d4aba7 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -9,6 +9,7 @@ import operator import enum import time import dataclasses +import logging from typing import Optional, Sequence, TypeVar, Union from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QTimer @@ -459,9 +460,15 @@ class Timer(QTimer): def _validity_check_handler(self) -> None: if not self.check_timeout_validity() and self._start_time is not None: elapsed = time.monotonic() - self._start_time - log.misc.warning( - f"Timer {self._name} (id {self.timerId()}) triggered too early: " - f"interval {self.interval()} but only {elapsed:.3f}s passed" + level = logging.WARNING + if utils.is_windows and self._name == "ipc-timeout": + level = logging.DEBUG + log.misc.log( + level, + ( + f"Timer {self._name} (id {self.timerId()}) triggered too early: " + f"interval {self.interval()} but only {elapsed:.3f}s passed" + ) ) def check_timeout_validity(self) -> bool: diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index d1bc6e7c1..7c4ff1a5d 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -9,7 +9,7 @@ import logging import signal import pytest -from qutebrowser.qt.core import QProcess, QUrl +from qutebrowser.qt.core import QProcess, QUrl, Qt from qutebrowser.misc import guiprocess from qutebrowser.utils import usertypes, utils, version @@ -534,6 +534,7 @@ def test_str(proc, py_proc): def test_cleanup(proc, py_proc, qtbot): + proc._cleanup_timer.setTimerType(Qt.TimerType.CoarseTimer) proc._cleanup_timer.setInterval(100) with qtbot.wait_signal(proc._cleanup_timer.timeout): From 68a8d618d6d738989c97166805cb8428a99d7ed6 Mon Sep 17 00:00:00 2001 From: toofar Date: Tue, 21 May 2024 08:15:15 +1200 Subject: [PATCH 068/403] update changelog for flaky windows qtimer issue --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e253e26a5..9173527d2 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -110,6 +110,8 @@ Fixed evident on Qt 6.6.0. (#7489) - The `colors.webpage.darkmode.threshold.foreground` setting (`.text` in older versions) now works correctly with Qt 6.4+. +- Worked around a minor issue around QTimers on Windows where the IPC server + could close the socket early. (#8191) [[v3.0.2]] From 1f2a0016b7bd4dcf1ec74e9cd73d8f27f0ae4f46 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 25 May 2024 09:40:06 +1200 Subject: [PATCH 069/403] put windows IPC fix changelog in correct release [ci skip] --- doc/changelog.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 9173527d2..e1e63f896 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,6 +55,8 @@ Fixed crash anymore (but QtWebEngine still might). - Fixed a rare crash in the completion widget when there was no selection model when we went to clear that, probably when leaving a mode. (#7901) +- Worked around a minor issue around QTimers on Windows where the IPC server + could close the socket early. (#8191) [[v3.1.1]] v3.1.1 (unreleased) @@ -110,8 +112,6 @@ Fixed evident on Qt 6.6.0. (#7489) - The `colors.webpage.darkmode.threshold.foreground` setting (`.text` in older versions) now works correctly with Qt 6.4+. -- Worked around a minor issue around QTimers on Windows where the IPC server - could close the socket early. (#8191) [[v3.0.2]] From 48d57cd5c8ff06bf5f4525a3aa1f76b54e0b2ec1 Mon Sep 17 00:00:00 2001 From: toofar Date: Wed, 15 May 2024 18:18:49 +1200 Subject: [PATCH 070/403] Add `Promise.withResolvers()` polyfill for pdf.js It's needed for both the main page (which we generate using jinja) and the worker script. Closes: https://github.com/qutebrowser/qutebrowser/issues/8170 --- qutebrowser/browser/pdfjs.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py index 841285deb..7062febb1 100644 --- a/qutebrowser/browser/pdfjs.py +++ b/qutebrowser/browser/pdfjs.py @@ -69,6 +69,21 @@ def generate_pdfjs_page(filename, url): return html +def _generate_polyfills(): + return """ + if (typeof Promise.withResolvers === 'undefined') { + Promise.withResolvers = function () { + let resolve, reject + const promise = new Promise((res, rej) => { + resolve = res + reject = rej + }) + return { promise, resolve, reject } + } + } + """ + + def _generate_pdfjs_script(filename): """Generate the script that shows the pdf with pdf.js. @@ -83,6 +98,8 @@ def _generate_pdfjs_script(filename): js_url = javascript.to_js(url.toString(urlutils.FormatOption.ENCODED)) return jinja.js_environment.from_string(""" + {{ polyfills }} + document.addEventListener("DOMContentLoaded", function() { if (typeof window.PDFJS !== 'undefined') { // v1.x @@ -104,7 +121,7 @@ def _generate_pdfjs_script(filename): }); } }); - """).render(url=js_url) + """).render(url=js_url, polyfills=_generate_polyfills()) def get_pdfjs_res_and_path(path): @@ -148,6 +165,14 @@ def get_pdfjs_res_and_path(path): log.misc.warning("OSError while reading PDF.js file: {}".format(e)) raise PDFJSNotFound(path) from None + if path == "build/pdf.worker.mjs": + content = b"\n".join( + [ + _generate_polyfills().encode("ascii"), + content, + ] + ) + return content, file_path From 22370b457f86b20c3389b609d0f9049cb4abe772 Mon Sep 17 00:00:00 2001 From: toofar Date: Wed, 15 May 2024 18:24:18 +1200 Subject: [PATCH 071/403] Install recent pdf.js in some CI jobs This installs pdf.js in a selection of CI jobs. Previously the PDF.js tests (in qutescheme.feature) were skipped in CI because it wasn't installed anywhere. There has been a couple of recent cases where pdf.js started depending on javascript features that are too new for even the most recent QtWebEngine to support. The aim of this commit is to catch that case. This doesn't add coverage for older webengine releases. This also incidentally updates the ace editor in these test jobs, since that is also updated by default by the update_3rdparty script. Hopefully that doesn't cause issues. The reasoning for installing on each type of job: *ubuntu jobs*: not installed - while our main test runs are on ubuntu there is an upstream issue where many assets used by pdf.js (like icons used in the toolbar) aren't packaged, see #7904. This causes warning messages because assets requested via qutescheme can't be found, which causes the tests to fail. We could a) install pdf.js from source instead of using the ubuntu one b) ignore the warning logs c) skip this environment and rely on tests elsewhere. I've chosen to do (c). I don't see a huge benefit in testing pdf.js across multiple environments if we aren't using it installed from the OS anyway. We could install from source but currently all the Qt < 6.5 tests are failing from some other JS error, and I think fixing that is out of scope of this issue. *docker Qt6*: installed - the archlinux pdfjs package works fine and we are only testing the most recent Qt versions because arch users are expected to stay up to date. *docker Qt5*: not installed - doesn't support JS features required by PDF.js. I guess we could install the legacy build from source here. I'm mostly worried about catching new breakages for this commit though *windows*: installed - we install pdf.js from source when making a release so it would be nice to do that in tests too. *macos*: not installed - the tests that were catching the breakages are end2end tests which we don't run on mac. And I think there was an error from the :versions tests here, don't remember. *bleeding edge*: installed - from source pdf.js tests fail on Qt < 6.5 with `Uncaught TypeError: Cannot read properties of null (reading 'mainContainer')` The `TestPDFJSVersion.test_real_file` unit tests currently fails because `version._pdfjs_version()` returns `unknown (bundled)`, not sure why. I think this is pre-existing and it also wasn't being run on CI. --- .github/workflows/bleeding.yml | 3 +++ .github/workflows/ci.yml | 3 +++ scripts/dev/ci/docker/Dockerfile.j2 | 1 + 3 files changed, 7 insertions(+) diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml index 2587d832b..47264b2e5 100644 --- a/.github/workflows/bleeding.yml +++ b/.github/workflows/bleeding.yml @@ -37,6 +37,9 @@ jobs: persist-credentials: false - name: Set up problem matchers run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}" + - name: Upgrade 3rd party assets + run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }}" + if: "endsWith(matrix.image, '-qt6')" - name: Run tox run: dbus-run-session tox -e ${{ matrix.testenv }} irc: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f62d2bdc5..f0d5f9f91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -214,6 +214,9 @@ jobs: run: | python -m pip install -U pip python -m pip install -U -r misc/requirements/requirements-tox.txt + - name: Upgrade 3rd party assets + run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }}" + if: "startsWith(matrix.os, 'windows-')" - name: "Run ${{ matrix.testenv }}" run: "dbus-run-session -- tox -e ${{ matrix.testenv }} -- ${{ matrix.args }}" if: "startsWith(matrix.os, 'ubuntu-')" diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index 4b958babd..d9f636376 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -16,6 +16,7 @@ RUN pacman -Su --noconfirm \ qt6-declarative \ {% if webengine %} qt6-webengine python-pyqt6-webengine \ + pdfjs \ {% else %}{{ 1/0 }}{% endif %} python-pyqt6 \ {% else %} From 7eb32a75507b6729cab826176df20d7eb90117d5 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 18 May 2024 21:04:03 +1200 Subject: [PATCH 072/403] update_3rdparty: move dict related imports into method I'm trying to update pdf.js in the bleeding edge CI jobs. It complains that either it can't find PyQt or it can't find yaml depending on how I invoke tox. Joy. Since dict stuff isn't run by default in this script hopefully that is the only broken import path and moving it into the function lets the pdfjs (and ace) bit of the script work fine. Actually, looking at the stack traces below, both of them are from dict related code! tox exec -re bleeding -- python scripts/dev/update_3rdparty.py --gh-token *** Traceback (most recent call last): File "/__w/qutebrowser/qutebrowser/scripts/dev/update_3rdparty.py", line 20, in from scripts import dictcli File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/dictcli.py", line 25, in from qutebrowser.browser.webengine import spell File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/browser/webengine/spell.py", line 14, in from qutebrowser.utils import log, message, standarddir File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/utils/message.py", line 15, in from qutebrowser.qt.core import pyqtSignal, pyqtBoundSignal, QObject File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/qt/core.py", line 17, in machinery.init_implicit() File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/qt/machinery.py", line 278, in init_implicit raise NoWrapperAvailableError(info) qutebrowser.qt.machinery.NoWrapperAvailableError: No Qt wrapper was importable. python scripts/dev/update_3rdparty.py --gh-token *** Traceback (most recent call last): File "/__w/qutebrowser/qutebrowser/scripts/dev/update_3rdparty.py", line 20, in from scripts import dictcli File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/dictcli.py", line 25, in from qutebrowser.browser.webengine import spell File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/browser/webengine/spell.py", line 14, in from qutebrowser.utils import log, message, standarddir File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/utils/message.py", line 17, in from qutebrowser.utils import usertypes, log File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/utils/usertypes.py", line 16, in from qutebrowser.utils import log, qtutils, utils File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/utils/qtutils.py", line 39, in from qutebrowser.utils import usertypes, utils File "/__w/qutebrowser/qutebrowser/scripts/dev/../../scripts/../qutebrowser/utils/utils.py", line 29, in import yaml ModuleNotFoundError: No module named 'yaml' --- scripts/dev/update_3rdparty.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index e47d01cb7..4d8a5e562 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -17,8 +17,6 @@ import sys sys.path.insert( 0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) -from scripts import dictcli -from qutebrowser.config import configdata def download_nsis_plugins(): @@ -160,6 +158,8 @@ def update_ace(): def test_dicts(): """Test available dictionaries.""" + from scripts import dictcli + from qutebrowser.config import configdata configdata.init() for lang in dictcli.available_languages(): print('Testing dictionary {}... '.format(lang.code), end='') From 3aaa3ce16d0b6865bf7eaf81b8fb8dc58e165982 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 18 May 2024 21:34:33 +1200 Subject: [PATCH 073/403] Adapt pdf.js unit test for changed string delims As of at the very least the latest version the version string looks like: const pdfjsVersion = "4.2.67"; So change the regex to allow double quotes too. --- qutebrowser/utils/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 775f57bd0..7955bc558 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -486,7 +486,7 @@ def _pdfjs_version() -> str: else: pdfjs_file = pdfjs_file.decode('utf-8') version_re = re.compile( - r"^ *(PDFJS\.version|(var|const) pdfjsVersion) = '(?P[^']+)';$", + r"^ *(PDFJS\.version|(var|const) pdfjsVersion) = ('|\")(?P[^']+)('|\");$", re.MULTILINE) match = version_re.search(pdfjs_file) From e86ef0b2fdbe75a12bf55f0c97ec19042831c69e Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 19 May 2024 11:39:37 +1200 Subject: [PATCH 074/403] update changlog entry for pdf.js fix --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e1e63f896..6c641b2c0 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -57,6 +57,8 @@ Fixed when we went to clear that, probably when leaving a mode. (#7901) - Worked around a minor issue around QTimers on Windows where the IPC server could close the socket early. (#8191) +- The latest PDF.js release (v4.2.67) is now supported when backed by + QtWebEngine 6.6+ (#8170) [[v3.1.1]] v3.1.1 (unreleased) From 04b2b0bdeae57cc301dc95d1147655f1c09a33b4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 25 May 2024 12:09:02 +0200 Subject: [PATCH 075/403] Revert "Use a proper increasing id for ipc logging" This reverts commit 7144e7211689938392c0abc469c26ddfe2a6ae7a. Turns out this adds complexity, but doesn't really help with debugging that much, and also needs us to guess what the correct ID might be. We should rather go for a nicer (but more invasive) cleanup by having a separate IPCConnection class that stores a socket and ID, see #8208. --- qutebrowser/misc/ipc.py | 48 ++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 671ddd9ec..eefa2e3f3 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -139,7 +139,6 @@ class IPCServer(QObject): _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. - _socket_id: An unique, incrementing ID for every socket. _socketname: The socketname to use. _atime_timer: Timer to update the atime of the socket regularly. @@ -181,7 +180,6 @@ class IPCServer(QObject): self._server.newConnection.connect(self.handle_connection) self._socket = None - self._socket_id = 0 self._old_socket = None if utils.is_windows: # pragma: no cover @@ -234,13 +232,11 @@ class IPCServer(QObject): log.ipc.debug("In on_error with None socket!") return self._timer.stop() - log.ipc.debug("Socket {}: error {}: {}".format( - self._socket_id, self._socket.error(), + log.ipc.debug("Socket 0x{:x}: error {}: {}".format( + id(self._socket), self._socket.error(), self._socket.errorString())) if err != QLocalSocket.LocalSocketError.PeerClosedError: - raise SocketError( - f"handling IPC connection {self._socket_id}", self._socket - ) + raise SocketError("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): @@ -249,16 +245,14 @@ class IPCServer(QObject): return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " - "still handling another one ({}).".format( - self._socket_id)) + "still handling another one (0x{:x}).".format( + id(self._socket))) return socket = qtutils.add_optional(self._server.nextPendingConnection()) if socket is None: log.ipc.debug("No new connection to handle.") return - - self._socket_id += 1 - log.ipc.debug("Client connected (socket {}).".format(self._socket_id)) + log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) self._socket = socket self._timer.start() socket.readyRead.connect(self.on_ready_read) @@ -285,7 +279,8 @@ class IPCServer(QObject): @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" - log.ipc.debug("Client disconnected from socket {}.".format(self._socket_id)) + log.ipc.debug("Client disconnected from socket 0x{:x}.".format( + id(self._socket))) self._timer.stop() if self._old_socket is not None: self._old_socket.deleteLater() @@ -297,8 +292,8 @@ class IPCServer(QObject): def _handle_invalid_data(self): """Handle invalid data we got from a QLocalSocket.""" assert self._socket is not None - log.ipc.error("Ignoring invalid IPC data from socket {}.".format( - self._socket_id)) + log.ipc.error("Ignoring invalid IPC data from socket 0x{:x}.".format( + id(self._socket))) self.got_invalid_data.emit() self._socket.errorOccurred.connect(self.on_error) self._socket.disconnectFromServer() @@ -352,7 +347,7 @@ class IPCServer(QObject): self.got_args.emit(args, target_arg, cwd) def _get_socket(self, warn=True): - """Get the current socket and ID for on_ready_read. + """Get the current socket for on_ready_read. Arguments: warn: Whether to warn if no socket was found. @@ -363,33 +358,31 @@ class IPCServer(QObject): if self._old_socket is None: if warn: log.ipc.warning("In _get_socket with None socket and old_socket!") - return None, None + return None log.ipc.debug("In _get_socket with None socket!") socket = self._old_socket - # hopefully accurate guess, but at least we have a debug log - socket_id = self._socket_id - 1 else: socket = self._socket - socket_id = self._socket_id if sip.isdeleted(socket): # pragma: no cover log.ipc.warning("Ignoring deleted IPC socket") - return None, None + return None - return socket, socket_id + return socket @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" self._timer.stop() - socket, socket_id = self._get_socket() + socket = self._get_socket() while socket is not None and socket.canReadLine(): data = bytes(socket.readLine()) self.got_raw.emit(data) - log.ipc.debug("Read from socket {}: {!r}".format(socket_id, data)) + log.ipc.debug("Read from socket 0x{:x}: {!r}".format( + id(socket), data)) self._handle_data(data) - socket, socket_id = self._get_socket(warn=False) + socket = self._get_socket(warn=False) if self._socket is not None: self._timer.start() @@ -404,7 +397,7 @@ class IPCServer(QObject): return log.ipc.error("IPC connection timed out " - "(socket {}).".format(self._socket_id)) + "(socket 0x{:x}).".format(id(self._socket))) self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None @@ -448,7 +441,8 @@ class IPCServer(QObject): # we get called again when the application is about to quit. return - log.ipc.debug("Shutting down IPC (socket {})".format(self._socket_id)) + log.ipc.debug("Shutting down IPC (socket 0x{:x})".format( + id(self._socket))) if self._socket is not None: self._socket.deleteLater() From af1d537970facb1e429d29a166bdb956ae98c052 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 25 May 2024 12:59:52 +0200 Subject: [PATCH 076/403] Try stabilizing 'Clicking on focused element' The problem was that 'qute-input focused' wasn't logged. Let's see if this helps. --- tests/end2end/features/misc.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 90ce5334a..6e606a195 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -507,7 +507,7 @@ Feature: Various utility commands. Scenario: Clicking on focused element When I open data/click_element.html - And I run :fake-key + And I run :jseval document.getElementById("qute-input").focus() And I wait for the javascript message "qute-input focused" And I run :click-element focused Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged From b41cc1d0fc36fbd8c5ba306c22abf1cd498f9ae0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 25 May 2024 13:12:49 +0200 Subject: [PATCH 077/403] Syntax simplifications - Drop trailing comma inside trivial tuple - Use r"""...""" for string containing ", as \" inside r"..." is taken literally (I'm surprised it works!) - Use ['"] instead of ('|") - Also adjust the inner [^'] to [^'"] for consistency --- qutebrowser/utils/version.py | 2 +- tests/unit/utils/usertypes/test_timer.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 7955bc558..7e15bf77e 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -486,7 +486,7 @@ def _pdfjs_version() -> str: else: pdfjs_file = pdfjs_file.decode('utf-8') version_re = re.compile( - r"^ *(PDFJS\.version|(var|const) pdfjsVersion) = ('|\")(?P[^']+)('|\");$", + r"""^ *(PDFJS\.version|(var|const) pdfjsVersion) = ['"](?P[^'"]+)['"];$""", re.MULTILINE) match = version_re.search(pdfjs_file) diff --git a/tests/unit/utils/usertypes/test_timer.py b/tests/unit/utils/usertypes/test_timer.py index 0793c826f..6aabc8c04 100644 --- a/tests/unit/utils/usertypes/test_timer.py +++ b/tests/unit/utils/usertypes/test_timer.py @@ -71,13 +71,13 @@ def test_timeout_set_interval(qtbot): @pytest.mark.parametrize( - "elapsed_ms,expected", + "elapsed_ms, expected", [ - (0, False,), - (1, False,), - (600, True,), - (999, True,), - (1000, True,), + (0, False), + (1, False), + (600, True), + (999, True), + (1000, True), ], ) def test_early_timeout_check(qtbot, mocker, elapsed_ms, expected): From 63b5708bae2427c9adbbbcfdac62d1bab9ee034c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 25 May 2024 14:49:51 +0200 Subject: [PATCH 078/403] Revert "adjust test strings for incrementing connection IDs" This reverts commit ccebbd6ebbb787107523d71e51eb36bcdfdde759. --- tests/unit/misc/test_ipc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py index 827ac828d..79c2c7b7d 100644 --- a/tests/unit/misc/test_ipc.py +++ b/tests/unit/misc/test_ipc.py @@ -404,7 +404,7 @@ class TestOnError: socket.setErrorString("Connection refused.") with pytest.raises(ipc.Error, match=r"Error while handling IPC " - r"connection [0-9]: Connection refused \(ConnectionRefusedError\)"): + r"connection: Connection refused \(ConnectionRefusedError\)"): ipc_server.on_error(QLocalSocket.LocalSocketError.ConnectionRefusedError) @@ -439,7 +439,7 @@ class TestHandleConnection: ipc_server._server = FakeServer(socket) with pytest.raises(ipc.Error, match=r"Error while handling IPC " - r"connection [0-9]: Error string \(ConnectionError\)"): + r"connection: Error string \(ConnectionError\)"): ipc_server.handle_connection() assert "We got an error immediately." in caplog.messages From 7d32df88cba0bfb75ee1a05d656eed6da253b1ab Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 25 May 2024 14:51:29 +0200 Subject: [PATCH 079/403] Adjust security patch version for Qt 6.7.1 --- qutebrowser/utils/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 7e15bf77e..2bb39fea0 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -625,7 +625,7 @@ class WebEngineVersions: ## Qt 6.7 utils.VersionNumber(6, 7): (_BASES[118], '122.0.6261.128'), # 2024-03-12 - utils.VersionNumber(6, 7, 1): (_BASES[118], '124.0.6367.78'), # (?) 2024-04-24 + utils.VersionNumber(6, 7, 1): (_BASES[118], '124.0.6367.202'), # ~2024-05-09 } def __post_init__(self) -> None: From 4577fcc8ebc188f36bc7f4f1e7aa9f457cdafc8d Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 27 May 2024 04:21:09 +0000 Subject: [PATCH 080/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 6 +++--- misc/requirements/requirements-mypy.txt | 6 +++--- misc/requirements/requirements-pyinstaller.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 6 +++--- misc/requirements/requirements-pyroma.txt | 6 +++--- misc/requirements/requirements-sphinx.txt | 4 ++-- misc/requirements/requirements-tests.txt | 12 ++++++------ misc/requirements/requirements-tox.txt | 2 +- requirements.txt | 2 +- 10 files changed, 25 insertions(+), 25 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 0b868a571..29c5e8452 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -6,4 +6,4 @@ importlib_metadata==7.1.0 packaging==24.0 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.18.2 +zipp==3.19.0 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 09cd3822c..a85bc136f 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -33,7 +33,7 @@ pyproject_hooks==1.1.0 PyQt-builder==1.16.2 python-dateutil==2.9.0.post0 readme_renderer==43.0 -requests==2.31.0 +requests==2.32.2 requests-toolbelt==1.0.0 rfc3986==2.0.0 rich==13.7.1 @@ -42,7 +42,7 @@ sip==6.8.3 six==1.16.0 tomli==2.0.1 twine==5.1.0 -typing_extensions==4.11.0 +typing_extensions==4.12.0 uritemplate==4.1.1 # urllib3==2.2.1 -zipp==3.18.2 +zipp==3.19.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 11b3b4894..120a94932 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -16,6 +16,6 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20240423 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 -types-setuptools==69.5.0.20240519 -typing_extensions==4.11.0 -zipp==3.18.2 +types-setuptools==70.0.0.20240524 +typing_extensions==4.12.0 +zipp==3.19.0 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 62cda742f..b78b0c41c 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -3,6 +3,6 @@ altgraph==0.17.4 importlib_metadata==7.1.0 packaging==24.0 -pyinstaller==6.6.0 +pyinstaller==6.7.0 pyinstaller-hooks-contrib==2024.6 -zipp==3.18.2 +zipp==3.19.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index af2d2695c..10793cfb0 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -14,13 +14,13 @@ pefile==2023.2.7 platformdirs==4.2.2 pycparser==2.22 PyJWT==2.8.0 -pylint==3.2.1 +pylint==3.2.2 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers -requests==2.31.0 +requests==2.32.2 six==1.16.0 tomli==2.0.1 tomlkit==0.12.5 -typing_extensions==4.11.0 +typing_extensions==4.12.0 uritemplate==4.1.1 # urllib3==2.2.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 8f4ca2e88..0a4e2fde7 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -10,8 +10,8 @@ packaging==24.0 Pygments==2.18.0 pyproject_hooks==1.1.0 pyroma==4.2 -requests==2.31.0 +requests==2.32.2 tomli==2.0.1 -trove-classifiers==2024.5.17 +trove-classifiers==2024.5.22 urllib3==2.2.1 -zipp==3.18.2 +zipp==3.19.0 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 5f0aa3323..1a21cf2ab 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -13,7 +13,7 @@ MarkupSafe==2.1.5 packaging==24.0 Pygments==2.18.0 pytz==2024.1 -requests==2.31.0 +requests==2.32.2 snowballstemmer==2.2.0 Sphinx==7.1.2 sphinxcontrib-applehelp==1.0.4 @@ -23,4 +23,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==2.2.1 -zipp==3.18.2 +zipp==3.19.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 140774d7d..2baab1711 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -7,13 +7,13 @@ certifi==2024.2.2 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 -coverage==7.5.1 +coverage==7.5.2 exceptiongroup==1.2.1 execnet==2.1.1 filelock==3.14.0 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.102.4 +hypothesis==6.102.6 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 @@ -42,15 +42,15 @@ pytest-rerunfailures==14.0 pytest-xdist==3.6.1 pytest-xvfb==3.0.0 PyVirtualDisplay==3.0 -requests==2.31.0 -requests-file==2.0.0 +requests==2.32.2 +requests-file==2.1.0 six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.5 tldextract==5.1.2 tomli==2.0.1 -typing_extensions==4.11.0 +typing_extensions==4.12.0 urllib3==2.2.1 vulture==2.11 Werkzeug==3.0.3 -zipp==3.18.2 +zipp==3.19.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 49ab17091..edc096488 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -10,7 +10,7 @@ pip==24.0 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.6.1 -setuptools==69.5.1 +setuptools==70.0.0 tomli==2.0.1 tox==4.15.0 virtualenv==20.26.2 diff --git a/requirements.txt b/requirements.txt index ff70a1c08..07574e4e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 PyYAML==6.0.1 -zipp==3.18.2 +zipp==3.19.0 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From d3dc1bf4c1610a97a8e0f1020892d7072653151a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 29 May 2024 08:45:28 +0200 Subject: [PATCH 081/403] Update changelog --- doc/changelog.asciidoc | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 6c641b2c0..f95e20c2d 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -19,6 +19,13 @@ breaking changes (such as renamed commands) can happen in minor releases. v3.2.0 (unreleased) ------------------- +Deprecated +~~~~~~~~~~ + +- This will be the last feature release supporting macOS 11 Big Sur. + Starting with qutebrowser v3.3.0, macOS 12 Monterey will be the oldest + supported version. + Added ~~~~~ @@ -32,6 +39,8 @@ Added Changed ~~~~~~~ +- Windows and macOS releases now ship with Qt 6.7.1, which is based on Chromium + 118.0.5993.220 with security patches up to 122.0.6261.128. - With QtWebEngine 6.7+, the `colors.webpage.darkmode.enabled` setting can now be changed at runtime and supports URL patterns (#8182). - A few more completions will now match search terms in any order: @@ -46,9 +55,9 @@ Fixed ~~~~~ - `input.insert_mode.auto_load` sometimes not triggering due to a race - condition. + condition. (#8145) - Worked around qutebrowser quitting when closing a KDE file dialog due to a Qt - bug. + bug. (#8143) - Trying to use qutebrowser after it's been deleted/moved on disk (e.g. after a Python upgrade) should now not crash anymore. - When the QtWebEngine resources dir couldn't be found, qutebrowser now doesn't @@ -60,11 +69,6 @@ Fixed - The latest PDF.js release (v4.2.67) is now supported when backed by QtWebEngine 6.6+ (#8170) -[[v3.1.1]] -v3.1.1 (unreleased) -------------------- - - [[v3.1.0]] v3.1.0 (2023-12-08) ------------------- From 3036be1300f95c86acfafd22dc0b6de1eac93063 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 29 May 2024 17:47:16 +0200 Subject: [PATCH 082/403] Fix copyright text --- qutebrowser/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 5df8d7a31..c22b0b541 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -5,9 +5,12 @@ """A keyboard-driven, vim-like browser based on Python and Qt.""" import os.path +import datetime + +_year = datetime.date.today().year __author__ = "Florian Bruhin" -__copyright__ = "Copyright 2014-2021 Florian Bruhin (The Compiler)" +__copyright__ = f"Copyright 2013-{_year} Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" From 56f14fba2ddea5509d6830397a9c49540f8f6aab Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 1 Jun 2024 13:34:30 +0200 Subject: [PATCH 083/403] Fix security patch version in changelog --- doc/changelog.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f95e20c2d..111e11ec9 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -40,7 +40,7 @@ Changed ~~~~~~~ - Windows and macOS releases now ship with Qt 6.7.1, which is based on Chromium - 118.0.5993.220 with security patches up to 122.0.6261.128. + 118.0.5993.220 with security patches up to 124.0.6367.202. - With QtWebEngine 6.7+, the `colors.webpage.darkmode.enabled` setting can now be changed at runtime and supports URL patterns (#8182). - A few more completions will now match search terms in any order: From af2d151175ef60cadc515882bcbe4d7ffebde11c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 1 Jun 2024 14:09:53 +0200 Subject: [PATCH 084/403] Make __init__.py work on old Python --- qutebrowser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index c22b0b541..ff2f94675 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -10,7 +10,7 @@ import datetime _year = datetime.date.today().year __author__ = "Florian Bruhin" -__copyright__ = f"Copyright 2013-{_year} Florian Bruhin (The Compiler)" +__copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year) __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" From b77cbbb2c3b9482351d332f677fe09ac9560ee7c Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 1 Oct 2023 18:40:44 +1300 Subject: [PATCH 085/403] Don't set completion model to None, just delete it To avoid a leak when calling `QTreeView.setModel(None)`, this commit switches to relying on the `model.destroyed` signal to make sure related state is cleaned up. Upstream bug: https://bugreports.qt.io/browse/QTBUG-49966 When you call `setModel(None)` on a QTreeView it causes a small memory leak of `QItemSelectionModel` objects created by the QTreeView's child QHeaderView object. `QAbstractItemView` will create a new `QItemSelectionModel` whenever a model is set, even if that model is `None`. When the new model is non-null the new selection model will be set to be deleted when the new model is, but when the new model is None the new selection model will be linked to the static `QAbstractItemModelPrivate::staticEmptyModel()`. Since that empty model lives forever, so do the related section models, unless callers take care to clean them up themselves. Both `QTreeView` and it's child `QHeaderView` implement `QAbstractItemView` and have this behaviour. For the `QTreeView` we were making sure to delete the old selection model ourselves (as of fe1215c74d731). But for the one created by the `QHeaderView` we can't get a reference it because `QTreeView.setModel()` would call `QHeaderView.setModel()` and then `QHeaderView.setSelectionModel()` right away to assign it's own selection model to the child, leaving no references to the selection model created by `QHeaderView.setModel()`. I was previously using `header.findChildren(QItemSelectionModel)` to clean up old orphaned selection models, but this approach is a lot simpler! To observe this for yourself you can plonk something like this in `set_model()` before the early return and switch between the old and new implementation and see how it changes behaviour. header = self.header() header_children = header.findChildren(QItemSelectionModel) our_children = self.findChildren(QItemSelectionModel) print(f"{len(our_children)=} {len(header_children)=}") You can also observer the selection models accumulating in Gammaray (https://github.com/KDAB/GammaRay/) if you just open and close the selection a lot and then filter the object view by "model". The relevant code is in `QTreeView` and `QAbstractItemView`'s `setModel()`, `setSlectionModel()` and `modelDestroyed()`. Bot mostly in the `setModels()` where you can see the relevant signals being connected and disconnected. https://code.qt.io/cgit/qt/qtbase.git/tree/src/widgets/itemviews/qtreeview.cpp#n179 Fixes: #7947 --- qutebrowser/completion/completionwidget.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 27e631662..652ea636d 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -364,15 +364,13 @@ class CompletionView(QTreeView): old_model = self.model() if old_model is not None and model is not old_model: old_model.deleteLater() - self._selection_model().deleteLater() - - self.setModel(model) if model is None: self._active = False self.hide() return + self.setModel(model) model.setParent(self) self._active = True self.pattern = None From 74e4476893405aa472fcaf67487eec7f6e7f3ea2 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 3 Jun 2024 04:23:36 +0000 Subject: [PATCH 086/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 12 ++++++------ misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pylint.txt | 6 +++--- misc/requirements/requirements-pyqt-6.7.txt | 5 +++-- misc/requirements/requirements-pyqt-6.txt | 5 +++-- misc/requirements/requirements-pyqt.txt | 5 +++-- misc/requirements/requirements-pyroma.txt | 6 +++--- misc/requirements/requirements-sphinx.txt | 6 +++--- misc/requirements/requirements-tests.txt | 12 ++++++------ requirements.txt | 2 +- 12 files changed, 35 insertions(+), 32 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 29c5e8452..8a6f386bf 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -6,4 +6,4 @@ importlib_metadata==7.1.0 packaging==24.0 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.19.0 +zipp==3.19.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index a85bc136f..b20409f94 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -backports.tarfile==1.1.1 +backports.tarfile==1.2.0 build==1.2.1 bump2version==1.0.1 -certifi==2024.2.2 +certifi==2024.6.2 cffi==1.16.0 charset-normalizer==3.3.2 cryptography==42.0.7 @@ -24,7 +24,7 @@ mdurl==0.1.2 more-itertools==10.2.0 nh3==0.2.17 packaging==24.0 -pkginfo==1.10.0 +pkginfo==1.11.0 pycparser==2.22 Pygments==2.18.0 PyJWT==2.8.0 @@ -33,7 +33,7 @@ pyproject_hooks==1.1.0 PyQt-builder==1.16.2 python-dateutil==2.9.0.post0 readme_renderer==43.0 -requests==2.32.2 +requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 rich==13.7.1 @@ -42,7 +42,7 @@ sip==6.8.3 six==1.16.0 tomli==2.0.1 twine==5.1.0 -typing_extensions==4.12.0 +typing_extensions==4.12.1 uritemplate==4.1.1 # urllib3==2.2.1 -zipp==3.19.0 +zipp==3.19.1 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 120a94932..7500fba34 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -17,5 +17,5 @@ types-docutils==0.21.0.20240423 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 types-setuptools==70.0.0.20240524 -typing_extensions==4.12.0 -zipp==3.19.0 +typing_extensions==4.12.1 +zipp==3.19.1 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index b78b0c41c..ec771d806 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -5,4 +5,4 @@ importlib_metadata==7.1.0 packaging==24.0 pyinstaller==6.7.0 pyinstaller-hooks-contrib==2024.6 -zipp==3.19.0 +zipp==3.19.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 10793cfb0..0074bc3a6 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py astroid==3.2.2 -certifi==2024.2.2 +certifi==2024.6.2 cffi==1.16.0 charset-normalizer==3.3.2 cryptography==42.0.7 @@ -17,10 +17,10 @@ PyJWT==2.8.0 pylint==3.2.2 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers -requests==2.32.2 +requests==2.32.3 six==1.16.0 tomli==2.0.1 tomlkit==0.12.5 -typing_extensions==4.12.0 +typing_extensions==4.12.1 uritemplate==4.1.1 # urllib3==2.2.1 diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index 3d5203985..2ba2ee9b0 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -1,8 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.0 -PyQt6-Qt6==6.7.0 +PyQt6-Qt6==6.7.1 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.0 +PyQt6-WebEngine-Qt6==6.7.1 +PyQt6-WebEngineSubwheel-Qt6==6.7.1 --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 3d5203985..2ba2ee9b0 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -1,8 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.0 -PyQt6-Qt6==6.7.0 +PyQt6-Qt6==6.7.1 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.0 +PyQt6-WebEngine-Qt6==6.7.1 +PyQt6-WebEngineSubwheel-Qt6==6.7.1 --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 3d5203985..2ba2ee9b0 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,8 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.0 -PyQt6-Qt6==6.7.0 +PyQt6-Qt6==6.7.1 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.0 +PyQt6-WebEngine-Qt6==6.7.1 +PyQt6-WebEngineSubwheel-Qt6==6.7.1 --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 0a4e2fde7..571e7f948 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py build==1.2.1 -certifi==2024.2.2 +certifi==2024.6.2 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 @@ -10,8 +10,8 @@ packaging==24.0 Pygments==2.18.0 pyproject_hooks==1.1.0 pyroma==4.2 -requests==2.32.2 +requests==2.32.3 tomli==2.0.1 trove-classifiers==2024.5.22 urllib3==2.2.1 -zipp==3.19.0 +zipp==3.19.1 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 1a21cf2ab..c0bcb1a9e 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -2,7 +2,7 @@ alabaster==0.7.13 Babel==2.15.0 -certifi==2024.2.2 +certifi==2024.6.2 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 @@ -13,7 +13,7 @@ MarkupSafe==2.1.5 packaging==24.0 Pygments==2.18.0 pytz==2024.1 -requests==2.32.2 +requests==2.32.3 snowballstemmer==2.2.0 Sphinx==7.1.2 sphinxcontrib-applehelp==1.0.4 @@ -23,4 +23,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==2.2.1 -zipp==3.19.0 +zipp==3.19.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 2baab1711..fab0bd7a0 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -3,17 +3,17 @@ attrs==23.2.0 beautifulsoup4==4.12.3 blinker==1.8.2 -certifi==2024.2.2 +certifi==2024.6.2 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 -coverage==7.5.2 +coverage==7.5.3 exceptiongroup==1.2.1 execnet==2.1.1 filelock==3.14.0 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.102.6 +hypothesis==6.103.0 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 @@ -42,15 +42,15 @@ pytest-rerunfailures==14.0 pytest-xdist==3.6.1 pytest-xvfb==3.0.0 PyVirtualDisplay==3.0 -requests==2.32.2 +requests==2.32.3 requests-file==2.1.0 six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.5 tldextract==5.1.2 tomli==2.0.1 -typing_extensions==4.12.0 +typing_extensions==4.12.1 urllib3==2.2.1 vulture==2.11 Werkzeug==3.0.3 -zipp==3.19.0 +zipp==3.19.1 diff --git a/requirements.txt b/requirements.txt index 07574e4e6..2f064a4ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 PyYAML==6.0.1 -zipp==3.19.0 +zipp==3.19.1 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From b45d98f1c60e4a5843808551f5b8b2016fbe2724 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 3 Jun 2024 18:20:44 +1200 Subject: [PATCH 087/403] update changelog URLs for WebEngineSubwheel --- scripts/dev/changelog_urls.json | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 645bb6385..a839f0291 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -105,6 +105,7 @@ "PyQt6-Qt6": "https://www.riverbankcomputing.com/news", "PyQt6-WebEngine": "https://www.riverbankcomputing.com/news", "PyQt6-WebEngine-Qt6": "https://www.riverbankcomputing.com/news", + "PyQt6-WebEngineSubwheel-Qt6": "https://www.riverbankcomputing.com/news", "PyQt6-sip": "https://www.riverbankcomputing.com/news", "Pygments": "https://pygments.org/docs/changelog/", "vulture": "https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md", From fb35cf9fdc4335d90c874e7bd42dbb4a3b588ea0 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 3 Jun 2024 18:23:09 +1200 Subject: [PATCH 088/403] Revert "Add Riverbank Computing as extra index for PyQt requirements" Mostly reverts the below, as the new release is on the main PyPI repo now. Keeps the change to misc_checks.py This reverts commit 470ec752e16fe4f4f1407b90ad5ce66da85a7b63. --- misc/requirements/requirements-pyqt-6.7.txt | 1 - misc/requirements/requirements-pyqt-6.7.txt-raw | 3 --- misc/requirements/requirements-pyqt-6.txt | 1 - misc/requirements/requirements-pyqt-6.txt-raw | 3 --- misc/requirements/requirements-pyqt.txt | 1 - misc/requirements/requirements-pyqt.txt-raw | 3 --- 6 files changed, 12 deletions(-) diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index 2ba2ee9b0..3df4abb77 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -6,4 +6,3 @@ PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.1 PyQt6-WebEngineSubwheel-Qt6==6.7.1 ---extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.7.txt-raw b/misc/requirements/requirements-pyqt-6.7.txt-raw index 7df2d49c6..98b1340b2 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt-raw +++ b/misc/requirements/requirements-pyqt-6.7.txt-raw @@ -2,6 +2,3 @@ PyQt6 >= 6.7, < 6.8 PyQt6-Qt6 >= 6.7, < 6.8 PyQt6-WebEngine >= 6.7, < 6.8 PyQt6-WebEngine-Qt6 >= 6.7, < 6.8 - -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 2ba2ee9b0..3df4abb77 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -6,4 +6,3 @@ PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.1 PyQt6-WebEngineSubwheel-Qt6==6.7.1 ---extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt-6.txt-raw b/misc/requirements/requirements-pyqt-6.txt-raw index 16cc342cd..68a5db685 100644 --- a/misc/requirements/requirements-pyqt-6.txt-raw +++ b/misc/requirements/requirements-pyqt-6.txt-raw @@ -2,6 +2,3 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine PyQt6-WebEngine-Qt6 - -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 2ba2ee9b0..3df4abb77 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -6,4 +6,3 @@ PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.1 PyQt6-WebEngineSubwheel-Qt6==6.7.1 ---extra-index-url https://www.riverbankcomputing.com/pypi/simple/ diff --git a/misc/requirements/requirements-pyqt.txt-raw b/misc/requirements/requirements-pyqt.txt-raw index 16cc342cd..68a5db685 100644 --- a/misc/requirements/requirements-pyqt.txt-raw +++ b/misc/requirements/requirements-pyqt.txt-raw @@ -2,6 +2,3 @@ PyQt6 PyQt6-Qt6 PyQt6-WebEngine PyQt6-WebEngine-Qt6 - -# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2024-April/045832.html -#@ add: --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ From ae7d7fb426790f2efdc7abba783ffee59e2c424b Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 3 Jun 2024 14:42:27 +0000 Subject: [PATCH 089/403] Release v3.2.0 --- .bumpversion.cfg | 2 +- doc/changelog.asciidoc | 2 +- misc/org.qutebrowser.qutebrowser.appdata.xml | 1 + qutebrowser/__init__.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 23de6e95a..61b0612f2 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.1.0 +current_version = 3.2.0 commit = True message = Release v{new_version} tag = True diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 111e11ec9..5a797fdc5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -16,7 +16,7 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Security` to invite users to upgrade in case of vulnerabilities. [[v3.2.0]] -v3.2.0 (unreleased) +v3.2.0 (2024-06-03) ------------------- Deprecated diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index 017303345..516c3b523 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,7 @@ + diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index ff2f94675..761f7cb75 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -14,7 +14,7 @@ __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year) __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "3.1.0" +__version__ = "3.2.0" __version_info__ = tuple(int(part) for part in __version__.split('.')) __description__ = "A keyboard-driven, vim-like browser based on Python and Qt." From 79942fa4660b562a6781c7a73a900c26125fcdba Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Jun 2024 16:37:41 +0200 Subject: [PATCH 090/403] scripts: Update data link for ua_fetch.py Same developer, just repo moved apparently --- scripts/dev/ua_fetch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dev/ua_fetch.py b/scripts/dev/ua_fetch.py index 2f094476e..956a0ed4c 100644 --- a/scripts/dev/ua_fetch.py +++ b/scripts/dev/ua_fetch.py @@ -7,7 +7,7 @@ """Fetch and print the most common user agents. This script fetches the most common user agents according to -https://github.com/Kikobeats/top-user-agents, and prints the most recent +https://github.com/microlinkhq/top-user-agents, and prints the most recent Chrome user agent for Windows, macOS and Linux. """ @@ -29,7 +29,7 @@ def wrap(ini, sub, string): # pylint: disable-next=missing-timeout -response = requests.get('https://raw.githubusercontent.com/Kikobeats/top-user-agents/master/index.json') +response = requests.get('https://raw.githubusercontent.com/microlinkhq/top-user-agents/master/src/index.json') if response.status_code != 200: print('Unable to fetch the user agent index', file=sys.stderr) From 60f5ce153a093150a4853916eaf3ebc2e490a0f6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 3 Jun 2024 16:38:36 +0200 Subject: [PATCH 091/403] Update user agent completions in config --- qutebrowser/config/configdata.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 322f88f6c..ecb420410 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -755,14 +755,14 @@ content.headers.user_agent: # Vim-protip: Place your cursor below this comment and run # :r!python scripts/dev/ua_fetch.py - - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 - (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" - - Chrome 117 macOS + (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" + - Chrome 125 macOS - - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, - like Gecko) Chrome/117.0.0.0 Safari/537.36" - - Chrome 117 Win10 + like Gecko) Chrome/125.0.0.0 Safari/537.36" + - Chrome 125 Win10 - - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like - Gecko) Chrome/117.0.0.0 Safari/537.36" - - Chrome 117 Linux + Gecko) Chrome/125.0.0.0 Safari/537.36" + - Chrome 125 Linux supports_pattern: true desc: | User agent to send. From 1e1af23d34371dabbe224b0c9649981a1e412377 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 4 Jun 2024 22:32:10 +0200 Subject: [PATCH 092/403] Fix earlyinit with no Qt available We need to wait with init_qtlog until after we know we have Qt available. Closes #8220 --- doc/changelog.asciidoc | 10 ++++++++++ qutebrowser/misc/earlyinit.py | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 5a797fdc5..c63a655c4 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,6 +15,16 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +[[v3.2.1]] +v3.2.1 (unreleased) +------------------- + +Fixed +~~~~~ + +- When the selected Qt wrapper is unavailable, qutebrowser now again shows a + GUI error message instead of only an exception in the terminal. + [[v3.2.0]] v3.2.0 (2024-06-03) ------------------- diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index a7bdb8252..c008286e6 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -337,11 +337,11 @@ def early_init(args): init_faulthandler() # Then we configure the selected Qt wrapper info = machinery.init(args) - # Init Qt logging after machinery is initialized - init_qtlog(args) # Here we check if QtCore is available, and if not, print a message to the # console or via Tk. check_qt_available(info) + # Init Qt logging after machinery is initialized + init_qtlog(args) # Now we can be sure QtCore is available, so we can print dialogs on # errors, so people only using the GUI notice them as well. check_libraries() From 44a631b43b6952cc1c03c0a592a0df331204f198 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 5 Jun 2024 22:44:00 +0200 Subject: [PATCH 093/403] doc: Update similar project list --- README.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index cbb3fdd64..b670e1800 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -252,9 +252,9 @@ main inspiration for qutebrowser) https://github.com/akhodakivskiy/VimFx[VimFx] (seems to offer a https://gir.st/blog/legacyfox.htm[hack] to run on modern Firefox releases), https://github.com/shinglyu/QuantumVim[QuantumVim], - https://github.com/ueokande/vim-vixen[Vim Vixen] (ESR only), + https://github.com/ueokande/vim-vixen[Vim Vixen], https://github.com/amedama41/vvimpulation[VVimpulation], - https://krabby.netlify.com/[Krabby] + https://krabby.netlify.app/[Krabby] * Chrome/Chromium addons: https://github.com/k2nr/ViChrome/[ViChrome], https://github.com/jinzhu/vrome[Vrome], From fe81422f944d821dfc2181d8263d7c26fd4d650c Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 3 Jun 2024 15:28:54 +1200 Subject: [PATCH 094/403] Ignore QItemSelectionModel no-op message. This starts happening reliably with the "Using session completion" end to end test after the previous change where I made it so we don't set the completion widget model to none, just call `deleteLater()` on the old one. The relevant part of the test is: And I run :session-save hello # This loads a completion model with one entry When I run :cmd-set-text -s :session-load # This one selects the entry, which clears the model because the # session completion only applies to the first word after the # command, which we've just selected. And I run :completion-item-focus next # This does nothing, since we have no completion model loaded, # not sure why it is in the test, presumably making sure no # exception is thrown. And I run :completion-item-focus next The log message is reliable in the test but it's a bit flaky to reproduce manually. I've reproduced it via `for f in 1 2;do qute-send -i /tmp/qutebrowser-basedir-npzdpupy/runtime/ipc-8075cf54f63b8e1bc05db34f41292c38 ":completion-item-focus next" ;done` if I manually go through the scenario a few times. Possibly it would reproduce easier if I pin it to one process using `taskset`. I think the reason for this is that the model is marked for deletion in the next event loop, then a signal goes out, then the selection model is marked for deletion on the next event loop. And possibly this only happens between the model and the selection model being deleted? --- pytest.ini | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pytest.ini b/pytest.ini index 71a6f606d..fac89d32d 100644 --- a/pytest.ini +++ b/pytest.ini @@ -72,6 +72,10 @@ qt_log_ignore = ^[^ ]*qtwebengine_dictionaries'$ # Qt 5 on Archlinux ^QSslSocket: cannot resolve .* + # Seems to happen after we try to complete immediately after clearing a + # model, for example, when no completion function is available for the + # current text pattern. + QItemSelectionModel: Selecting when no model has been set will result in a no-op. xfail_strict = true filterwarnings = error From 00daefff7e7940c8f0eb4ffab170322602922c16 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 3 Jun 2024 16:09:10 +1200 Subject: [PATCH 095/403] Add test to ensure `set_model(None)` cleans up We want to make sure that the selection model gets deleted when clearing the model, since we are switching from doing that directly to having it happen indirectly based off of signals we don't manage. Hopefully this doesn't end up to be flaky. I think we are depending on this happening in two different Qt even loop runs (`model.deleteLater()` in the first one then `selmod.deleteLater()` gets called from the `model.deleted` signal). I tried using `qtbot.wait_signals()` with a list but it segfaulted in some cleanup method. Perhaps is doesn't handle the deleted signal well? Alternately we could maybe just wait for the selmodel one and check `sip.isdelted(model)` to check the model is gone too. --- tests/unit/completion/test_completionwidget.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 387339f4f..85b117319 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -146,6 +146,18 @@ def test_completion_item_focus_no_model(which, completionview, model, qtbot): completionview.completion_item_focus(which) +def test_models_deleted_on_clear(completionview, model, qtbot): + """Ensure set_model(None) deleted the model and selmodel.""" + completionview.set_model(model) + selmod = completionview._selection_model() + completionview.set_model(None) + + with qtbot.wait_signal(model.destroyed): + pass + with qtbot.wait_signal(selmod.destroyed): + pass + + @pytest.mark.skip("Seems to disagree with reality, see #5897") def test_completion_item_focus_fetch(completionview, model, qtbot): """Test that on_next_prev_item moves the selection properly.""" From cc34d09393fd3058de8c9b6f56a07eda60a50913 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 10 Jun 2024 04:21:11 +0000 Subject: [PATCH 096/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 4 ++-- misc/requirements/requirements-dev.txt | 10 +++++----- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pyinstaller.txt | 8 ++++---- misc/requirements/requirements-pylint.txt | 6 +++--- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 4 ++-- misc/requirements/requirements-tests.txt | 12 ++++++------ misc/requirements/requirements-tox.txt | 4 ++-- requirements.txt | 2 +- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 8a6f386bf..b299f678f 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -3,7 +3,7 @@ build==1.2.1 check-manifest==0.49 importlib_metadata==7.1.0 -packaging==24.0 +packaging==24.1 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.19.1 +zipp==3.19.2 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index b20409f94..86347f6d0 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -6,7 +6,7 @@ bump2version==1.0.1 certifi==2024.6.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==42.0.7 +cryptography==42.0.8 docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 @@ -23,8 +23,8 @@ markdown-it-py==3.0.0 mdurl==0.1.2 more-itertools==10.2.0 nh3==0.2.17 -packaging==24.0 -pkginfo==1.11.0 +packaging==24.1 +pkginfo==1.11.1 pycparser==2.22 Pygments==2.18.0 PyJWT==2.8.0 @@ -42,7 +42,7 @@ sip==6.8.3 six==1.16.0 tomli==2.0.1 twine==5.1.0 -typing_extensions==4.12.1 +typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.1 -zipp==3.19.1 +zipp==3.19.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 7500fba34..3d4842c5b 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -17,5 +17,5 @@ types-docutils==0.21.0.20240423 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 types-setuptools==70.0.0.20240524 -typing_extensions==4.12.1 -zipp==3.19.1 +typing_extensions==4.12.2 +zipp==3.19.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index ec771d806..572306db0 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -2,7 +2,7 @@ altgraph==0.17.4 importlib_metadata==7.1.0 -packaging==24.0 -pyinstaller==6.7.0 -pyinstaller-hooks-contrib==2024.6 -zipp==3.19.1 +packaging==24.1 +pyinstaller==6.8.0 +pyinstaller-hooks-contrib==2024.7 +zipp==3.19.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 0074bc3a6..b5e7c1a9d 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -4,7 +4,7 @@ astroid==3.2.2 certifi==2024.6.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==42.0.7 +cryptography==42.0.8 dill==0.3.8 github3.py==4.0.1 idna==3.7 @@ -14,13 +14,13 @@ pefile==2023.2.7 platformdirs==4.2.2 pycparser==2.22 PyJWT==2.8.0 -pylint==3.2.2 +pylint==3.2.3 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 six==1.16.0 tomli==2.0.1 tomlkit==0.12.5 -typing_extensions==4.12.1 +typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 571e7f948..e6bdd171c 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -6,7 +6,7 @@ charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 importlib_metadata==7.1.0 -packaging==24.0 +packaging==24.1 Pygments==2.18.0 pyproject_hooks==1.1.0 pyroma==4.2 @@ -14,4 +14,4 @@ requests==2.32.3 tomli==2.0.1 trove-classifiers==2024.5.22 urllib3==2.2.1 -zipp==3.19.1 +zipp==3.19.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index c0bcb1a9e..ae4afbeaa 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -10,7 +10,7 @@ imagesize==1.4.1 importlib_metadata==7.1.0 Jinja2==3.1.4 MarkupSafe==2.1.5 -packaging==24.0 +packaging==24.1 Pygments==2.18.0 pytz==2024.1 requests==2.32.3 @@ -23,4 +23,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==2.2.1 -zipp==3.19.1 +zipp==3.19.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index fab0bd7a0..eb3f0556a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -13,7 +13,7 @@ execnet==2.1.1 filelock==3.14.0 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.103.0 +hypothesis==6.103.1 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 @@ -24,14 +24,14 @@ Mako==1.3.5 manhole==1.8.0 # MarkupSafe==2.1.5 more-itertools==10.2.0 -packaging==24.0 +packaging==24.1 parse==1.20.1 parse-type==0.6.2 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 -pytest==8.2.1 -pytest-bdd==7.1.2 +pytest==8.2.2 +pytest-bdd==7.2.0 pytest-benchmark==4.0.0 pytest-cov==5.0.0 pytest-instafail==0.5.0 @@ -49,8 +49,8 @@ sortedcontainers==2.4.0 soupsieve==2.5 tldextract==5.1.2 tomli==2.0.1 -typing_extensions==4.12.1 +typing_extensions==4.12.2 urllib3==2.2.1 vulture==2.11 Werkzeug==3.0.3 -zipp==3.19.1 +zipp==3.19.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index edc096488..dd013ad8f 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -5,13 +5,13 @@ chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 filelock==3.14.0 -packaging==24.0 +packaging==24.1 pip==24.0 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.6.1 setuptools==70.0.0 tomli==2.0.1 -tox==4.15.0 +tox==4.15.1 virtualenv==20.26.2 wheel==0.43.0 diff --git a/requirements.txt b/requirements.txt index 2f064a4ab..969a4d2b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 PyYAML==6.0.1 -zipp==3.19.1 +zipp==3.19.2 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From 27164d0d6ebb2eee6de4123fe6956e3a8571c8f9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 11 Jun 2024 23:01:47 +0200 Subject: [PATCH 097/403] Build separate Apple Silicon release With GitHub Actions now providing macOS 14 runners with M1 chips, we can build a separate Apple Silicon release there and upload it. Universal wheels are currently not possible, see #8229 for details. Closes #6478 --- .github/workflows/ci.yml | 5 +++++ .github/workflows/nightly.yml | 10 ++++++++-- .github/workflows/release.yml | 1 + doc/changelog.asciidoc | 10 ++++++++++ scripts/dev/build_release.py | 8 +++++++- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f0d5f9f91..b8b3372fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,6 +183,11 @@ jobs: os: macos-12 python: "3.12" args: "tests/unit" # Only run unit tests on macOS + ### macOS Sonoma (M1 runner) + - testenv: py312-pyqt67 + os: macos-14 + python: "3.12" + args: "tests/unit" # Only run unit tests on macOS ### Windows - testenv: py312-pyqt67 os: windows-2019 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index b326c2ad6..5b798d589 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -30,14 +30,20 @@ jobs: name: qt5-windows-debug - os: macos-11 toxenv: build-release - name: macos + name: macos-intel + - os: macos-14 + toxenv: build-release + name: macos-apple-silicon - os: windows-2019 toxenv: build-release name: windows - os: macos-11 args: --debug toxenv: build-release - name: macos-debug + name: macos-debug-intel + - os: macos-14 + toxenv: build-release + name: macos-debug-apple-silicon - os: windows-2019 args: --debug toxenv: build-release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa8b3b2ef..2b278010b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -127,6 +127,7 @@ jobs: matrix: include: - os: macos-11 + - os: macos-14 - os: windows-2019 - os: ubuntu-20.04 runs-on: "${{ matrix.os }}" diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index c63a655c4..dcc8ef562 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,6 +15,16 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +[[v3.3.0]] +v3.3.0 (unreleased) +------------------- + +Added +~~~~~ + +- There is now a separate macOS release built for Apple Silicon. A Universal + Binary might follow with a later release. + [[v3.2.1]] v3.2.1 (unreleased) ------------------- diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 40cedc2e8..3511bc9ca 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -16,6 +16,7 @@ import subprocess import argparse import tarfile import tempfile +import platform import collections import dataclasses import re @@ -301,8 +302,10 @@ def build_mac( dmg_makefile_path = REPO_ROOT / "scripts" / "dev" / "Makefile-dmg" subprocess.run(['make', '-f', dmg_makefile_path], check=True) + arch = platform.machine() suffix = "-debug" if debug else "" suffix += "-qt5" if qt5 else "" + suffix += f"-{arch}" dmg_path = dist_path / f'qutebrowser-{qutebrowser.__version__}{suffix}.dmg' pathlib.Path('qutebrowser.dmg').rename(dmg_path) @@ -322,11 +325,14 @@ def build_mac( except PermissionError as e: print(f"Failed to remove tempdir: {e}") + arch_to_desc = {"x86_64": "Intel", "arm64": "Apple Silicon"} + desc_arch = arch_to_desc[arch] + return [ Artifact( path=dmg_path, mimetype='application/x-apple-diskimage', - description='macOS .dmg' + description=f'macOS .dmg ({desc_arch})' ) ] From 3eeeaa5b483c97cb6d3ea666e5b25e0a3952f13d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 11 Jun 2024 22:14:44 +0200 Subject: [PATCH 098/403] scripts: Push to experimental repo if on experiments CI --- scripts/dev/build_release.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 3511bc9ca..ed653316b 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -683,7 +683,8 @@ def main() -> None: parser.add_argument('--qt5', action='store_true', required=False, help="Build against PyQt5") parser.add_argument('--experimental', action='store_true', required=False, - help="Upload to experiments repo and test PyPI") + default=os.environ.get("GITHUB_REPOSITORY") == "qutebrowser/experiments", + help="Upload to experiments repo and test PyPI. Set automatically if on qutebrowser/experiments CI.") args = parser.parse_args() utils.change_cwd() From 8bacbad1d6758880015e997159c359bd07f7a728 Mon Sep 17 00:00:00 2001 From: "Bernhard M. Wiedemann" Date: Fri, 14 Jun 2024 12:07:32 +0200 Subject: [PATCH 099/403] Fix tests after year 2036 Background: As part of my work on reproducible builds for openSUSE, I check that software still gives identical build results in the future. The usual offset is +16 years, because that is how long I expect some software will be used in some places. This showed up failing tests in our package build. See https://reproducible-builds.org/ for why this matters. --- tests/unit/browser/webkit/test_cookies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/browser/webkit/test_cookies.py b/tests/unit/browser/webkit/test_cookies.py index ad614aee0..70fa6bc62 100644 --- a/tests/unit/browser/webkit/test_cookies.py +++ b/tests/unit/browser/webkit/test_cookies.py @@ -14,8 +14,8 @@ from qutebrowser.misc import lineparser, objects pytestmark = pytest.mark.usefixtures('data_tmpdir') -COOKIE1 = b'foo1=bar; expires=Tue, 01-Jan-2036 08:00:01 GMT' -COOKIE2 = b'foo2=bar; expires=Tue, 01-Jan-2036 08:00:01 GMT' +COOKIE1 = b'foo1=bar; expires=Tue, 01-Jan-2999 08:00:01 GMT' +COOKIE2 = b'foo2=bar; expires=Tue, 01-Jan-2999 08:00:01 GMT' SESSION_COOKIE = b'foo3=bar' EXPIRED_COOKIE = b'foo4=bar; expires=Sat, 01-Jan-2000 08:00:01 GMT' From 167d7ac87d185fd6ce708250d719c8c71fed63d6 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 16 Jun 2024 11:24:23 +1200 Subject: [PATCH 100/403] update changelog for qute-pass idna --- doc/changelog.asciidoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index dcc8ef562..ac57243fd 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -25,6 +25,13 @@ Added - There is now a separate macOS release built for Apple Silicon. A Universal Binary might follow with a later release. +Changed +~~~~~~~ + +- The qute-pass userscript now has better support for internationalized domain + names when using the pass backend - both domain names and secret paths are + normalized before comparing (#8133) + [[v3.2.1]] v3.2.1 (unreleased) ------------------- From 03f5ad870a5610ecf6b138fcc96a703e078a4ea0 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 17 Jun 2024 04:20:19 +0000 Subject: [PATCH 101/403] Update dependencies --- misc/requirements/requirements-dev.txt | 2 +- misc/requirements/requirements-flake8.txt | 4 ++-- misc/requirements/requirements-tests.txt | 8 ++++---- misc/requirements/requirements-tox.txt | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 86347f6d0..0e269a497 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -21,7 +21,7 @@ keyring==25.2.1 manhole==1.8.0 markdown-it-py==3.0.0 mdurl==0.1.2 -more-itertools==10.2.0 +more-itertools==10.3.0 nh3==0.2.17 packaging==24.1 pkginfo==1.11.1 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 3044ce83b..923732b61 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py attrs==23.2.0 -flake8==7.0.0 +flake8==7.1.0 flake8-bugbear==24.4.26 flake8-builtins==2.5.0 flake8-comprehensions==3.14.0 @@ -16,7 +16,7 @@ flake8-tidy-imports==4.10.0 flake8-tuple==0.4.1 mccabe==0.7.0 pep8-naming==0.14.1 -pycodestyle==2.11.1 +pycodestyle==2.12.0 pydocstyle==6.3.0 pyflakes==3.2.0 six==1.16.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index eb3f0556a..da2184caa 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -10,10 +10,10 @@ click==8.1.7 coverage==7.5.3 exceptiongroup==1.2.1 execnet==2.1.1 -filelock==3.14.0 +filelock==3.15.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.103.1 +hypothesis==6.103.2 idna==3.7 importlib_metadata==7.1.0 iniconfig==2.0.0 @@ -23,9 +23,9 @@ jaraco.functools==4.0.1 Mako==1.3.5 manhole==1.8.0 # MarkupSafe==2.1.5 -more-itertools==10.2.0 +more-itertools==10.3.0 packaging==24.1 -parse==1.20.1 +parse==1.20.2 parse-type==0.6.2 pluggy==1.5.0 py-cpuinfo==9.0.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index dd013ad8f..3526962a2 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,7 +4,7 @@ cachetools==5.3.3 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 -filelock==3.14.0 +filelock==3.15.1 packaging==24.1 pip==24.0 platformdirs==4.2.2 From 74ccf05be1b1c086889b7db8191fdca9e77593fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:11:57 +0000 Subject: [PATCH 102/403] Bump docker/build-push-action from 5 to 6 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9dc925e29..0947af2b8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -32,7 +32,7 @@ jobs: with: username: qutebrowser password: ${{ secrets.DOCKER_TOKEN }} - - uses: docker/build-push-action@v5 + - uses: docker/build-push-action@v6 with: file: scripts/dev/ci/docker/Dockerfile context: . From 3221e6ca4905e03d387abb11380702b8988cde79 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Jun 2024 16:29:56 +0200 Subject: [PATCH 103/403] Update Chromium version information --- qutebrowser/utils/version.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 2bb39fea0..63c03dafa 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -548,6 +548,7 @@ class WebEngineVersions: 108: '108.0.5359.220', # ~2022-12-23 112: '112.0.5615.213', # ~2023-04-18 118: '118.0.5993.220', # ~2023-10-24 + # 122: '122.?.????.???', # ~2024-??-?? } _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, Tuple[str, Optional[str]]]] = { @@ -626,6 +627,10 @@ class WebEngineVersions: ## Qt 6.7 utils.VersionNumber(6, 7): (_BASES[118], '122.0.6261.128'), # 2024-03-12 utils.VersionNumber(6, 7, 1): (_BASES[118], '124.0.6367.202'), # ~2024-05-09 + utils.VersionNumber(6, 7, 2): (_BASES[118], '125.0.6422.142'), # 2024-05-30 + + ## Qt 6.8 + # utils.VersionNumber(6, 8): (_BASES[122], '???'), # 2024-03-12 } def __post_init__(self) -> None: From 7bbef811f3c00320a29ea6c78dc2e45e1016839b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 18 Jun 2024 16:36:11 +0200 Subject: [PATCH 104/403] Add debugging output to security version test --- tests/unit/utils/test_version.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 5d2863100..a79f42385 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -1085,6 +1085,7 @@ class TestWebEngineVersions: except ImportError: pytest.skip("Requires QtWebEngine 6.3+") + print(version.qtwebengine_versions()) # useful when adding new versions inferred = version.WebEngineVersions.from_webengine( qWebEngineVersion(), source="API") assert inferred.chromium_security == qWebEngineChromiumSecurityPatchVersion() From 6fd66619ba180609d2d1d43b5aa50de51f0f9cc6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 20 Jun 2024 11:10:34 +0200 Subject: [PATCH 105/403] Switch to new Archlinux testing layout should fix Docker package generation and maybe nightly builds --- scripts/dev/ci/docker/Dockerfile.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index d9f636376..d6ca11f9f 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -2,7 +2,7 @@ FROM archlinux:latest RUN pacman-key --init && pacman-key --populate {% if unstable %} -RUN sed -i '/^# after the header/a[kde-unstable]\nInclude = /etc/pacman.d/mirrorlist\n\n[testing]\nInclude = /etc/pacman.d/mirrorlist\n\n[community-testing]\nInclude = /etc/pacman.d/mirrorlist' /etc/pacman.conf +RUN sed -i '/^# after the header/a[kde-unstable]\nInclude = /etc/pacman.d/mirrorlist\n\n[core-testing]\nInclude = /etc/pacman.d/mirrorlist\n\n[extra-testing]\nInclude = /etc/pacman.d/mirrorlist' /etc/pacman.conf {% endif %} RUN pacman -Sy --noconfirm archlinux-keyring RUN pacman -Su --noconfirm \ From dfae071aede0250b85d1d5db250206fe904b6309 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 20 Jun 2024 17:51:56 +0200 Subject: [PATCH 106/403] ci: Try installing OpenSSL 1.1 for Qt 5 docker Only OpenSSL 3 is installed currently, and for some reason, Qt 5 started failing in CI complaining that it only found OpenSSL 3 and not 1.1... --- scripts/dev/ci/docker/Dockerfile.j2 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index d6ca11f9f..042455b13 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -22,6 +22,7 @@ RUN pacman -Su --noconfirm \ {% else %} qt5-base \ qt5-declarative \ + openssl-1.1 \ {% if webengine %} qt5-webengine \ python-pyqtwebengine \ @@ -69,6 +70,7 @@ RUN useradd user -u 1001 && \ chown user:users /home/user USER user WORKDIR /home/user +RUN git config --global --add safe.directory /outside/.git CMD git clone /outside qutebrowser.git && \ cd qutebrowser.git && \ From fcd68efd3c2a283bcc4379f753ae99e61339c593 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 22 Jun 2024 16:34:32 +1200 Subject: [PATCH 107/403] update changelog for 7879 and 7950 Move the entry about stripping query params up to the next minor release and move it into the "changed" section, instead of "fixed". --- doc/changelog.asciidoc | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index a22cf07f4..b8f02e998 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -31,6 +31,16 @@ Changed - The qute-pass userscript now has better support for internationalized domain names when using the pass backend - both domain names and secret paths are normalized before comparing (#8133) +- Ignored URL query parameters (via `url.yank_ignored_parameters`) are now + respected when yanking any URL (for example, through hints with `hint links + yank`). The `{url:yank}` substitution has also been added as a version of + `{url}` that respects ignored URL query parameters. (#7879) + +Fixed +~~~~~ + +- A minor memory leak of QItemSelectionModels triggered by closing the + completion dialog has been resolved. (#7950) [[v3.2.1]] v3.2.1 (unreleased) @@ -180,10 +190,6 @@ Fixed - Setting `url.auto_search` to `dns` works correctly now with Qt 6. - Counts passed via keypresses now have a digit limit (4300) to avoid exceptions due to cats sleeping on numpads. (#7834) -- Ignored URL query parameters (via `url.yank_ignored_parameters`) are now - respected when yanking any URL (for example, through hints with `hint links - yank`). The `{url:yank}` substitution has also been added as a version of - `{url}` that respects ignored URL query parameters. (#7879) - Navigating via hints to a remote URL from a file:// one works again. (#7847) - The timers related to the tab audible indicator and the auto follow timeout no longer accumulate connections over time. (#7888) From c57a280ef0dfd83f9712614180d30228aed84ff9 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 23 Jun 2024 10:17:21 +1200 Subject: [PATCH 108/403] update docs for url:yank addition --- doc/help/commands.asciidoc | 2 ++ doc/help/settings.asciidoc | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 4d1610970..be017a2c3 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -17,6 +17,8 @@ For command arguments, there are also some variables you can use: - `{url:host}`, `{url:domain}`, `{url:auth}`, `{url:scheme}`, `{url:username}`, `{url:password}`, `{url:port}`, `{url:path}` and `{url:query}` expand to the respective parts of the current URL +- `{url:yank}` expands to the URL of the current page but strips all the query + parameters in the `url.yank_ignored_parameters` setting. - `{title}` expands to the current page's title - `{clipboard}` expands to the clipboard contents - `{primary}` expands to the primary selection contents diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index f2a5062c2..b51df3f2f 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -355,7 +355,7 @@ |<>|Open base URL of the searchengine if a searchengine shortcut is invoked without parameters. |<>|Search engines which can be used via the address bar. |<>|Page(s) to open at the start. -|<>|URL parameters to strip with `:yank url`. +|<>|URL parameters to strip when yanking a URL. |<>|Hide the window decoration. |<>|Format to use for the window title. The same placeholders like for |<>|Set the main window background to transparent. @@ -739,12 +739,12 @@ Default: * +pass:[xO]+: +pass:[cmd-set-text :open -b -r {url:pretty}]+ * +pass:[xo]+: +pass:[cmd-set-text -s :open -b]+ * +pass:[yD]+: +pass:[yank domain -s]+ -* +pass:[yM]+: +pass:[yank inline [{title}\]({url}) -s]+ +* +pass:[yM]+: +pass:[yank inline [{title}\]({url:yank}) -s]+ * +pass:[yP]+: +pass:[yank pretty-url -s]+ * +pass:[yT]+: +pass:[yank title -s]+ * +pass:[yY]+: +pass:[yank -s]+ * +pass:[yd]+: +pass:[yank domain]+ -* +pass:[ym]+: +pass:[yank inline [{title}\]({url})]+ +* +pass:[ym]+: +pass:[yank inline [{title}\]({url:yank})]+ * +pass:[yp]+: +pass:[yank pretty-url]+ * +pass:[yt]+: +pass:[yank title]+ * +pass:[yy]+: +pass:[yank]+ @@ -4693,7 +4693,7 @@ Default: +pass:[https://start.duckduckgo.com]+ [[url.yank_ignored_parameters]] === url.yank_ignored_parameters -URL parameters to strip with `:yank url`. +URL parameters to strip when yanking a URL. Type: <> From 52632d0f994edbf8682e76790142007ac026cfd2 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 23 Jun 2024 10:56:16 +1200 Subject: [PATCH 109/403] make Qt 6.8 security patch version match arch unstable The security patch numbers reported by Qt are not accurate yet (haven't been updated on the dev branch since 6.7 was released), but this makes the test pass and it will tell us when it needs updating again. Relates to: https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2175936721 --- qutebrowser/utils/version.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 63c03dafa..015566a27 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -630,7 +630,9 @@ class WebEngineVersions: utils.VersionNumber(6, 7, 2): (_BASES[118], '125.0.6422.142'), # 2024-05-30 ## Qt 6.8 - # utils.VersionNumber(6, 8): (_BASES[122], '???'), # 2024-03-12 + # WORKAROUND for 6.8 chromium not being up to date yet. These numbers + # will need to be bumped at least once before it's released. + utils.VersionNumber(6, 8): (_BASES[118], '122.0.6261.128'), # 2024-03-12 } def __post_init__(self) -> None: From 39f8ce51309b24b86c95002c4cca730ce26f39e5 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 23 Jun 2024 11:37:15 +1200 Subject: [PATCH 110/403] XFail permission tests that have changed behavior on 6.8 WebEngine now has it's own mechanism to remember permission, and it's turned on by default. We can't disable it until PyQt picks up the new `QWebEngineProfile::setPersistentPermissionsPolicy()`. So the first test that prompts for a permission will persist that setting and later ones will fail because they don't get the prompt they expect. For now lets set them to xfail while we figure out what to do with permission persisting for actual users. That we we can reduce the noise in the test results! The WebEngine permissions persistence mechanism doesn't seem to respect whatever we are doing to separate storage per-basedir. Testing with a temp basedir like so: python3 -m qutebrowser -T https://web-push-book.gauntface.com/demos/notification-examples/ I see this file has been created under my home directory: $ cat ~/.local/share/qutebrowser/qutebrowser/QtWebEngine/Default/permissions.json {"Notifications":{"https://web-push-book.gauntface.com/":true}} I've raised an issue upstream about that here: https://bugreports.qt.io/browse/QTBUG-126595 We've got two things to think about regarding how to deal with this new on-by-default feature: 1. what do we do for the tests? We can Disable the feature (if on new enough PyQt) or add a test setup step that ... restarts the browser and deletes the permissions.json. That's not great 2. what do we do for real users? See below By default I would recommend disabling the webengine one since we already have our own. BUT we can't actually disable it until PyQt updates with the new APIs, which can take a while, and it's pretty likely people will be using the new Qt version before PyQt updates. So it would be best to figure out what we can do before that! Can we make it respect the basedir data path? Can we make it write to some fake file we throwaway? chmod +i? Hopefully Qt makes the permission JSON respect the data path we set and then at the very least we can remove the JSON file after a permission is set. It'll still be a change in behavior for users on Qt 6.8 and PyQt 6.7 though as it'll likely remember permissions within a browser instance by default, which isn't the case for our implementation currently. Related to: https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2175949686 --- pytest.ini | 1 + tests/end2end/conftest.py | 9 ++++++++- tests/end2end/features/prompts.feature | 21 ++++++++++++--------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/pytest.ini b/pytest.ini index fac89d32d..da826c901 100644 --- a/pytest.ini +++ b/pytest.ini @@ -41,6 +41,7 @@ markers = qt6_only: Tests which should only run with Qt 6 qt5_xfail: Tests which fail with Qt 5 qt6_xfail: Tests which fail with Qt 6 + qt68_beta1_xfail: Fails on Qt 6.8 beta 1 qt_log_level_fail = WARNING qt_log_ignore = # GitHub Actions diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index ab973175d..ffc5e9b4e 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -12,6 +12,7 @@ import pstats import operator import pytest +from qutebrowser.qt import machinery from qutebrowser.qt.core import PYQT_VERSION, QCoreApplication pytest.register_assert_rewrite('end2end.fixtures') @@ -23,7 +24,7 @@ from end2end.fixtures.quteprocess import (quteproc_process, quteproc, quteproc_new) from end2end.fixtures.testprocess import pytest_runtest_makereport # pylint: enable=unused-import -from qutebrowser.utils import qtutils, utils +from qutebrowser.utils import qtutils, utils, version def pytest_configure(config): @@ -186,6 +187,12 @@ def pytest_collection_modifyitems(config, items): 'Skipped on Windows', pytest.mark.skipif, utils.is_windows), + ('qt68_beta1_xfail', + "Fails on Qt 6.8 beta 1", + pytest.mark.xfail, + machinery.IS_QT6 and version.qtwebengine_versions( + avoid_init=True + ).webengine == utils.VersionNumber(6, 8) and version.PYQT_WEBENGINE_VERSION_STR < '6.8.0'), ] for item in items: diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 43199fa3b..33e9e3abf 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -112,7 +112,7 @@ Feature: Prompts Then the javascript message "notification permission granted" should be logged And "Added quickmark test for *" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: Async question interrupted by blocking one Given I have a fresh instance When I set content.notifications.enabled to ask @@ -251,6 +251,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "geolocation permission denied" should be logged + @qt68_beta1_xfail Scenario: geolocation with ask -> false When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -259,6 +260,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "geolocation permission denied" should be logged + @qt68_beta1_xfail Scenario: geolocation with ask -> false and save When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -268,6 +270,7 @@ Feature: Prompts Then the javascript message "geolocation permission denied" should be logged And the per-domain option content.geolocation should be set to false for http://localhost:(port) + @qt68_beta1_xfail Scenario: geolocation with ask -> abort When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -278,7 +281,7 @@ Feature: Prompts # Notifications - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: Always rejecting notifications Given I have a fresh instance When I set content.notifications.enabled to false @@ -286,7 +289,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "notification permission denied" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: Always accepting notifications Given I have a fresh instance When I set content.notifications.enabled to true @@ -294,7 +297,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "notification permission granted" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: notifications with ask -> false Given I have a fresh instance When I set content.notifications.enabled to ask @@ -304,7 +307,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "notification permission denied" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: notifications with ask -> false and save Given I have a fresh instance When I set content.notifications.enabled to ask @@ -315,7 +318,7 @@ Feature: Prompts Then the javascript message "notification permission denied" should be logged And the per-domain option content.notifications.enabled should be set to false for http://localhost:(port) - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: notifications with ask -> true Given I have a fresh instance When I set content.notifications.enabled to ask @@ -325,7 +328,7 @@ Feature: Prompts And I run :prompt-accept yes Then the javascript message "notification permission granted" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: notifications with ask -> true and save Given I have a fresh instance When I set content.notifications.enabled to ask @@ -347,7 +350,7 @@ Feature: Prompts And I run :mode-leave Then the javascript message "notification permission aborted" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: answering notification after closing tab Given I have a fresh instance When I set content.notifications.enabled to ask @@ -521,7 +524,7 @@ Feature: Prompts # https://github.com/qutebrowser/qutebrowser/issues/1249#issuecomment-175205531 # https://github.com/qutebrowser/qutebrowser/pull/2054#issuecomment-258285544 - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_xfail Scenario: Interrupting SSL prompt during a notification prompt Given I have a fresh instance When I set content.notifications.enabled to ask From 9ffa98535f7465cb3f93db5fb5b1beab2769813c Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 23 Jun 2024 15:49:48 +1200 Subject: [PATCH 111/403] Ignore QSaveFile::commit warning message in tests The test (`test_failing_flush()`) is deliberately trying to write to a close file. This is a new error in Qt 6.8 it seems. Ignore just the specific file for this test in case it pops up somewhere else later. --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index da826c901..f400b276f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -77,6 +77,7 @@ qt_log_ignore = # model, for example, when no completion function is available for the # current text pattern. QItemSelectionModel: Selecting when no model has been set will result in a no-op. + ^QSaveFile::commit: File \(.*/test_failing_flush0/foo\) is not open$ xfail_strict = true filterwarnings = error From 68755ed850b1d41d488e86bf2c6f988b69b8bef3 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 23 Jun 2024 16:39:38 +1200 Subject: [PATCH 112/403] Change Qt6.8 failing permission test xfail to "skip" The "Async question interrupted by async one" test was failing on CI but not locally. Not sure why, changing the tests to skip instead of xfail. The downside is that we won't bet a notification when upstream fixes the issues, hopefully they mark the QTBUG as closed and we see it that way. I think we do have to do something else to deal with this persistent permission thing anyway, assuming they don't change it to be off-by-default, so I'm sure we'll be looking in this area again! They'll at the very least be re-enabled when we get a PyQt 6.8. --- pytest.ini | 2 +- tests/end2end/conftest.py | 2 +- tests/end2end/features/prompts.feature | 26 +++++++++++++------------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pytest.ini b/pytest.ini index f400b276f..6c8d29ff9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -41,7 +41,7 @@ markers = qt6_only: Tests which should only run with Qt 6 qt5_xfail: Tests which fail with Qt 5 qt6_xfail: Tests which fail with Qt 6 - qt68_beta1_xfail: Fails on Qt 6.8 beta 1 + qt68_beta1_skip: Fails on Qt 6.8 beta 1 qt_log_level_fail = WARNING qt_log_ignore = # GitHub Actions diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index ffc5e9b4e..066c5cd1b 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -187,7 +187,7 @@ def pytest_collection_modifyitems(config, items): 'Skipped on Windows', pytest.mark.skipif, utils.is_windows), - ('qt68_beta1_xfail', + ('qt68_beta1_skip', # WORKAROUND: Qt6.8b1 https://bugreports.qt.io/browse/QTBUG-126595 "Fails on Qt 6.8 beta 1", pytest.mark.xfail, machinery.IS_QT6 and version.qtwebengine_versions( diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 33e9e3abf..f8f5af8d7 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -96,7 +96,7 @@ Feature: Prompts Then the javascript message "Alert done" should be logged And the javascript message "notification permission granted" should be logged - @qtwebkit_skip + @qtwebkit_skip @qt68_beta1_skip Scenario: Async question interrupted by async one Given I have a fresh instance When I set content.notifications.enabled to ask @@ -112,7 +112,7 @@ Feature: Prompts Then the javascript message "notification permission granted" should be logged And "Added quickmark test for *" should be logged - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: Async question interrupted by blocking one Given I have a fresh instance When I set content.notifications.enabled to ask @@ -251,7 +251,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "geolocation permission denied" should be logged - @qt68_beta1_xfail + @qt68_beta1_skip Scenario: geolocation with ask -> false When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -260,7 +260,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "geolocation permission denied" should be logged - @qt68_beta1_xfail + @qt68_beta1_skip Scenario: geolocation with ask -> false and save When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -270,7 +270,7 @@ Feature: Prompts Then the javascript message "geolocation permission denied" should be logged And the per-domain option content.geolocation should be set to false for http://localhost:(port) - @qt68_beta1_xfail + @qt68_beta1_skip Scenario: geolocation with ask -> abort When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -281,7 +281,7 @@ Feature: Prompts # Notifications - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: Always rejecting notifications Given I have a fresh instance When I set content.notifications.enabled to false @@ -289,7 +289,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "notification permission denied" should be logged - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: Always accepting notifications Given I have a fresh instance When I set content.notifications.enabled to true @@ -297,7 +297,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "notification permission granted" should be logged - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: notifications with ask -> false Given I have a fresh instance When I set content.notifications.enabled to ask @@ -307,7 +307,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "notification permission denied" should be logged - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: notifications with ask -> false and save Given I have a fresh instance When I set content.notifications.enabled to ask @@ -318,7 +318,7 @@ Feature: Prompts Then the javascript message "notification permission denied" should be logged And the per-domain option content.notifications.enabled should be set to false for http://localhost:(port) - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: notifications with ask -> true Given I have a fresh instance When I set content.notifications.enabled to ask @@ -328,7 +328,7 @@ Feature: Prompts And I run :prompt-accept yes Then the javascript message "notification permission granted" should be logged - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: notifications with ask -> true and save Given I have a fresh instance When I set content.notifications.enabled to ask @@ -350,7 +350,7 @@ Feature: Prompts And I run :mode-leave Then the javascript message "notification permission aborted" should be logged - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: answering notification after closing tab Given I have a fresh instance When I set content.notifications.enabled to ask @@ -524,7 +524,7 @@ Feature: Prompts # https://github.com/qutebrowser/qutebrowser/issues/1249#issuecomment-175205531 # https://github.com/qutebrowser/qutebrowser/pull/2054#issuecomment-258285544 - @qtwebkit_skip @qt68_beta1_xfail + @qtwebkit_skip @qt68_beta1_skip Scenario: Interrupting SSL prompt during a notification prompt Given I have a fresh instance When I set content.notifications.enabled to ask From d0422a982fe03c4ae608ca5962024c07066fbf3c Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 6 Apr 2024 16:49:54 +1300 Subject: [PATCH 113/403] Tips for contributors to run the webkit backend My virtualenv I used to run webkit has rotted long ago and I don't remember how I set it up. There is a PyQtWebKit project on PyPI but I don't know who that's published by. So I figured I would write some notes for myself on using the docker container used for CI instead. I chose to mount the current directory (which is presumably a qutebrowser checkout!) directly into the container instead of cloning it so I could have quicker feedback between making code changes and running tests. Then there's a couple of things that stem from that. Since the user in the container is different from the one in the host we have to move some things that are normally written to the current directory to be written elsewhere. There are other ways to approach this (eg you can add `-u $(id -u)` to the docker command line, although that makes things a bit confusing in the container) but arguably it's good for the container not to be able to write to the host, hence making that volume read only. The TOX_WORK_DIR trick is from [here](https://github.com/tox-dev/tox/issues/20), apart from with `{toxinidir}` in it too because the pyroma env was failing with just `.tox`, saying the pyroma binary needed to be in the allowlist, possibly it was doing full path matching without normalizing. The hypothesis folks [here](https://github.com/HypothesisWorks/hypothesis/issues/2367#issuecomment-595524571) say if you want to override the examples DB location with an env var to do it yourself. It's actually only a warning from hypothesis, it says it falls back to an in-memory DB, but I guess the tests run with warnings-are-errors. You can also pass `database=None` to make hypothesis skip example storage altogether. I'm using tox to run commands in a virtualenv with the right stuff in it because, uh, because I was copying the CI workflow actually. I just found out about the `exec` subcommand to override the `commands` defined for the env, neat! One point of awkwardness about that is that since we are using the PyQt from the OS we need any virtualenv we use to have access to the OS packages, which isn't the default for virtualenvs created by tox. The text envs use the link_pyqt script for that but if you are using this container and the first thing you do is run `tox exec` then that wouldn't have been run. So I'm setting `VIRTUALENV_SYSTEM_SITE_PACKAGES` to tell tox to always make the system packages available in the virtualenvs it manages. I did try using the mkvenv script instead of tox but it complained when trying to install the current directory in editable mode because setup.py tries to write to a git-commit-id file. --- doc/contributing.asciidoc | 22 ++++++++++++++++++++++ tests/conftest.py | 13 ++++++++++++- tox.ini | 2 ++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 144117677..630a96db7 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -192,6 +192,28 @@ specific one you can set either of a) the environment variable QUTE_TESTS_BACKEN , or b) the command line argument --qute-backend, to the desired backend (webkit/webengine). +If you need an environment with webkit installed to do testing while we still +support it (see #4039) you can re-use the docker container used for the CI +test runs which has PyQt5Webkit installed from the archlinux package archives. +Examples: + +---- +# Get a bash shell in the docker container with +# a) the current directory mounted at /work in the container +# b) the container using the X11 display :27 (for example, a Xephyr instance) from the host +# c) the tox and hypothesis dirs set to somewhere in the container that it can write to +# d) the system site packages available in the tox venv so you can use PyQt +# from the OS without having to run the link_pyqt script +docker run -it -v $PWD:/work:ro -w /work -e QUTE_TESTS_BACKEND=webkit -e DISPLAY=:27 -v /tmp/.X11-unix:/tmp/.X11-unix -e TOX_WORK_DIR="/home/user/.tox" -e HYPOTHESIS_EXAMPLES_DIR="/home/user/.hypothesis/examples" -e VIRTUALENV_SYSTEM_SITE_PACKAGES=True qutebrowser/ci:archlinux-webkit bash + +# Start a qutebrowser temporary basedir in the appropriate tox environment to +# play with +tox exec -e py-qt5 -- python3 -m qutebrowser -T --backend webkit + +# Run tests, passing positional args through to pytest. +tox -e py-qt5 -- tests/unit +---- + Profiling ~~~~~~~~~ diff --git a/tests/conftest.py b/tests/conftest.py index ddacc3db1..c834e62a0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ import ssl import pytest import hypothesis +import hypothesis.database pytest.register_assert_rewrite('helpers') @@ -33,10 +34,19 @@ _qute_scheme_handler = None # Set hypothesis settings +hypotheses_optional_kwargs = {} +if "HYPOTHESIS_EXAMPLES_DIR" in os.environ: + hypotheses_optional_kwargs[ + "database" + ] = hypothesis.database.DirectoryBasedExampleDatabase( + os.environ["HYPOTHESIS_EXAMPLES_DIR"] + ) + hypothesis.settings.register_profile( 'default', hypothesis.settings( deadline=600, suppress_health_check=[hypothesis.HealthCheck.function_scoped_fixture], + **hypotheses_optional_kwargs, ) ) hypothesis.settings.register_profile( @@ -45,7 +55,8 @@ hypothesis.settings.register_profile( suppress_health_check=[ hypothesis.HealthCheck.function_scoped_fixture, hypothesis.HealthCheck.too_slow - ] + ], + **hypotheses_optional_kwargs, ) ) hypothesis.settings.load_profile('ci' if testutils.ON_CI else 'default') diff --git a/tox.ini b/tox.ini index f7454740b..5afe211b4 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,7 @@ envlist = py38-pyqt515-cov,mypy-pyqt5,misc,vulture,flake8,pylint,pyroma,check-ma distshare = {toxworkdir} skipsdist = true minversion = 3.20 +toxworkdir={env:TOX_WORK_DIR:{toxinidir}/.tox} [testenv] setenv = @@ -30,6 +31,7 @@ passenv = QT_QUICK_BACKEND FORCE_COLOR DBUS_SESSION_BUS_ADDRESS + HYPOTHESIS_EXAMPLES_DIR basepython = py: {env:PYTHON:python3} py3: {env:PYTHON:python3} From 514026053df665709ffa68e320b20a976b18ee25 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 24 Jun 2024 04:19:39 +0000 Subject: [PATCH 114/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 8 ++++---- misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 4 ++-- misc/requirements/requirements-tests.txt | 10 +++++----- misc/requirements/requirements-tox.txt | 10 +++++----- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index b299f678f..1da93a7ff 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -2,7 +2,7 @@ build==1.2.1 check-manifest==0.49 -importlib_metadata==7.1.0 +importlib_metadata==7.2.1 packaging==24.1 pyproject_hooks==1.1.0 tomli==2.0.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 0e269a497..1c21e9d61 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -11,7 +11,7 @@ docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 idna==3.7 -importlib_metadata==7.1.0 +importlib_metadata==7.2.1 importlib_resources==6.4.0 jaraco.classes==3.4.0 jaraco.context==5.3.0 @@ -30,7 +30,7 @@ Pygments==2.18.0 PyJWT==2.8.0 Pympler==1.0.1 pyproject_hooks==1.1.0 -PyQt-builder==1.16.2 +PyQt-builder==1.16.3 python-dateutil==2.9.0.post0 readme_renderer==43.0 requests==2.32.3 @@ -38,11 +38,11 @@ requests-toolbelt==1.0.0 rfc3986==2.0.0 rich==13.7.1 SecretStorage==3.3.3 -sip==6.8.3 +sip==6.8.5 six==1.16.0 tomli==2.0.1 twine==5.1.0 typing_extensions==4.12.2 uritemplate==4.1.1 -# urllib3==2.2.1 +# urllib3==2.2.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 572306db0..a73fe39db 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py altgraph==0.17.4 -importlib_metadata==7.1.0 +importlib_metadata==7.2.1 packaging==24.1 pyinstaller==6.8.0 pyinstaller-hooks-contrib==2024.7 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index b5e7c1a9d..261a29b14 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -23,4 +23,4 @@ tomli==2.0.1 tomlkit==0.12.5 typing_extensions==4.12.2 uritemplate==4.1.1 -# urllib3==2.2.1 +# urllib3==2.2.2 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index e6bdd171c..c2383b6f3 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -5,7 +5,7 @@ certifi==2024.6.2 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 -importlib_metadata==7.1.0 +importlib_metadata==7.2.1 packaging==24.1 Pygments==2.18.0 pyproject_hooks==1.1.0 @@ -13,5 +13,5 @@ pyroma==4.2 requests==2.32.3 tomli==2.0.1 trove-classifiers==2024.5.22 -urllib3==2.2.1 +urllib3==2.2.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index ae4afbeaa..21b717581 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -7,7 +7,7 @@ charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 imagesize==1.4.1 -importlib_metadata==7.1.0 +importlib_metadata==7.2.1 Jinja2==3.1.4 MarkupSafe==2.1.5 packaging==24.1 @@ -22,5 +22,5 @@ sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -urllib3==2.2.1 +urllib3==2.2.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index da2184caa..a62a229a9 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -7,15 +7,15 @@ certifi==2024.6.2 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 -coverage==7.5.3 +coverage==7.5.4 exceptiongroup==1.2.1 execnet==2.1.1 -filelock==3.15.1 +filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.103.2 +hypothesis==6.103.3 idna==3.7 -importlib_metadata==7.1.0 +importlib_metadata==7.2.1 iniconfig==2.0.0 itsdangerous==2.2.0 jaraco.functools==4.0.1 @@ -50,7 +50,7 @@ soupsieve==2.5 tldextract==5.1.2 tomli==2.0.1 typing_extensions==4.12.2 -urllib3==2.2.1 +urllib3==2.2.2 vulture==2.11 Werkzeug==3.0.3 zipp==3.19.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 3526962a2..f4291553d 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,14 +4,14 @@ cachetools==5.3.3 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 -filelock==3.15.1 +filelock==3.15.4 packaging==24.1 -pip==24.0 +pip==24.1 platformdirs==4.2.2 pluggy==1.5.0 -pyproject-api==1.6.1 -setuptools==70.0.0 +pyproject-api==1.7.1 +setuptools==70.1.0 tomli==2.0.1 tox==4.15.1 -virtualenv==20.26.2 +virtualenv==20.26.3 wheel==0.43.0 From d0cc8d4fe9a0b20c8aeea8222085356b6c854b54 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 08:14:20 +0200 Subject: [PATCH 115/403] tests: Update private logging API usage for Python 3.13 See https://github.com/python/cpython/issues/109461 Using it as a context manager already works fine in earlier Python versions too. See #8205 --- tests/unit/utils/test_log.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 03ace4009..701d6f669 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -27,8 +27,7 @@ def restore_loggers(): """ logging.captureWarnings(False) logger_dict = logging.getLogger().manager.loggerDict - logging._acquireLock() - try: + with logging._lock: saved_handlers = logging._handlers.copy() saved_handler_list = logging._handlerList[:] saved_loggers = saved_loggers = logger_dict.copy() @@ -37,8 +36,6 @@ def restore_loggers(): logger_states = {} for name in saved_loggers: logger_states[name] = getattr(saved_loggers[name], 'disabled', None) - finally: - logging._releaseLock() root_logger = logging.getLogger("") root_handlers = root_logger.handlers[:] From 4f66661ff8a5912b3305a0f85cbe79f163f13da3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 08:57:36 +0200 Subject: [PATCH 116/403] Update sip changelog URL --- scripts/dev/changelog_urls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index a839f0291..cc841d6db 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -100,7 +100,7 @@ "PyQt-builder": "https://pyqt-builder.readthedocs.io/en/stable/releases.html", "PyQt5-sip": "https://www.riverbankcomputing.com/news", "PyQt5-stubs": "https://github.com/python-qt-tools/PyQt5-stubs/blob/master/CHANGELOG.md", - "sip": "https://www.riverbankcomputing.com/news", + "sip": "https://python-sip.readthedocs.io/en/stable/releases.html", "PyQt6": "https://www.riverbankcomputing.com/news", "PyQt6-Qt6": "https://www.riverbankcomputing.com/news", "PyQt6-WebEngine": "https://www.riverbankcomputing.com/news", From d34bbada0393e929025660baf9d70afa92639bfb Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Tue, 25 Jun 2024 07:11:53 +0000 Subject: [PATCH 117/403] Update dependencies --- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pyqt-6.7.txt | 6 +++--- misc/requirements/requirements-pyqt-6.txt | 6 +++--- misc/requirements/requirements-pyqt.txt | 6 +++--- misc/requirements/requirements-tests.txt | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 3d4842c5b..6c58fad02 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -6,7 +6,7 @@ importlib_resources==6.4.0 Jinja2==3.1.4 lxml==5.2.2 MarkupSafe==2.1.5 -mypy==1.10.0 +mypy==1.10.1 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 @@ -16,6 +16,6 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20240423 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 -types-setuptools==70.0.0.20240524 +types-setuptools==70.1.0.20240625 typing_extensions==4.12.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index 3df4abb77..d42d1dfdc 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.0 -PyQt6-Qt6==6.7.1 +PyQt6-Qt6==6.7.2 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.1 -PyQt6-WebEngineSubwheel-Qt6==6.7.1 +PyQt6-WebEngine-Qt6==6.7.2 +PyQt6-WebEngineSubwheel-Qt6==6.7.2 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 3df4abb77..d42d1dfdc 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.0 -PyQt6-Qt6==6.7.1 +PyQt6-Qt6==6.7.2 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.1 -PyQt6-WebEngineSubwheel-Qt6==6.7.1 +PyQt6-WebEngine-Qt6==6.7.2 +PyQt6-WebEngineSubwheel-Qt6==6.7.2 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 3df4abb77..d42d1dfdc 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.0 -PyQt6-Qt6==6.7.1 +PyQt6-Qt6==6.7.2 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.1 -PyQt6-WebEngineSubwheel-Qt6==6.7.1 +PyQt6-WebEngine-Qt6==6.7.2 +PyQt6-WebEngineSubwheel-Qt6==6.7.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index a62a229a9..6d59fffbd 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -13,7 +13,7 @@ execnet==2.1.1 filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.103.3 +hypothesis==6.104.0 idna==3.7 importlib_metadata==7.2.1 iniconfig==2.0.0 From c836004dc0827776d044d76bda56d3f6550f5f33 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 09:32:47 +0200 Subject: [PATCH 118/403] Update mypy changelog URL Seems to contain patch versions too --- scripts/dev/changelog_urls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index cc841d6db..b8f0ac50d 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -22,7 +22,7 @@ "Flask": "https://flask.palletsprojects.com/en/latest/changes/", "Mako": "https://docs.makotemplates.org/en/latest/changelog.html", "hypothesis": "https://hypothesis.readthedocs.io/en/latest/changes.html", - "mypy": "https://mypy-lang.blogspot.com/", + "mypy": "https://github.com/python/mypy/blob/master/CHANGELOG.md", "types-PyYAML": "https://github.com/python/typeshed/commits/main/stubs/PyYAML", "types-colorama": "https://github.com/python/typeshed/commits/main/stubs/colorama", "types-docutils": "https://github.com/python/typeshed/commits/main/stubs/docutils", From 307245c8cf23d0ec060418a1b23118fca04a343b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 10:06:27 +0200 Subject: [PATCH 119/403] Update changelog --- doc/changelog.asciidoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b8f02e998..3eec99c4b 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -46,6 +46,12 @@ Fixed v3.2.1 (unreleased) ------------------- +Changed +~~~~~~~ + +- Windows and macOS releases now bundle Qt 6.7.2, which includes security fixes + up to Chromium 125.0.6422.142. + Fixed ~~~~~ From 547530e33ce154a7fabade9930b654e91db5f9cf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 10:40:46 +0200 Subject: [PATCH 120/403] Move apple silicon releases to v3.2.1 --- doc/changelog.asciidoc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 3eec99c4b..711704596 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -19,12 +19,6 @@ breaking changes (such as renamed commands) can happen in minor releases. v3.3.0 (unreleased) ------------------- -Added -~~~~~ - -- There is now a separate macOS release built for Apple Silicon. A Universal - Binary might follow with a later release. - Changed ~~~~~~~ @@ -46,6 +40,12 @@ Fixed v3.2.1 (unreleased) ------------------- +Added +~~~~~ + +- There is now a separate macOS release built for Apple Silicon. A Universal + Binary might follow with a later release. + Changed ~~~~~~~ From f9cfef973acc02910ef9bde141fa96e0e50eceab Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Tue, 25 Jun 2024 09:07:51 +0000 Subject: [PATCH 121/403] Release v3.2.1 (cherry picked from commit 8cb4556245fb1f63f06f5607bcd332e9a97e9ba1) --- .bumpversion.cfg | 2 +- doc/changelog.asciidoc | 4 ++-- misc/org.qutebrowser.qutebrowser.appdata.xml | 1 + qutebrowser/__init__.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 61b0612f2..e77e4bbab 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.2.0 +current_version = 3.2.1 commit = True message = Release v{new_version} tag = True diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 711704596..b668d7d59 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -16,7 +16,7 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Security` to invite users to upgrade in case of vulnerabilities. [[v3.3.0]] -v3.3.0 (unreleased) +v3.3.0 (2024-06-25) ------------------- Changed @@ -37,7 +37,7 @@ Fixed completion dialog has been resolved. (#7950) [[v3.2.1]] -v3.2.1 (unreleased) +v3.2.1 (2024-06-25) ------------------- Added diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index 516c3b523..d0422d911 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,7 @@ + diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index 761f7cb75..ed7e3fe5c 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -14,7 +14,7 @@ __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year) __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "3.2.0" +__version__ = "3.2.1" __version_info__ = tuple(int(part) for part in __version__.split('.')) __description__ = "A keyboard-driven, vim-like browser based on Python and Qt." From f377fd36d92bff69247e3809e91e709d34e880f4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 11:19:55 +0200 Subject: [PATCH 122/403] Fix up changelog Cherry-picking 21ee2d093ab84d722f1e0d6be890dd9c2b3e4323 resulted in an accidental v3.3.0 section in the changelog. --- doc/changelog.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b668d7d59..63e2a1cc7 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -16,7 +16,7 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Security` to invite users to upgrade in case of vulnerabilities. [[v3.3.0]] -v3.3.0 (2024-06-25) +v3.3.0 (unreleased) ------------------- Changed From df1066b833410dde049662afb5a6e1a30e3a5197 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 26 Jun 2024 17:41:30 +0200 Subject: [PATCH 123/403] Replace all logging._acquireLock usages Follow-up to d0cc8d4, see #8205 --- tests/unit/utils/test_log.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index 701d6f669..8af04486f 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -53,8 +53,8 @@ def restore_loggers(): if not isinstance(h, _pytest.logging.LogCaptureHandler): # https://github.com/qutebrowser/qutebrowser/issues/856 root_logger.addHandler(h) - logging._acquireLock() - try: + + with logging._lock: logging._levelToName.clear() logging._levelToName.update(saved_level_to_name) logging._nameToLevel.clear() @@ -68,8 +68,6 @@ def restore_loggers(): for name, state in logger_states.items(): if state is not None: saved_loggers[name].disabled = state - finally: - logging._releaseLock() @pytest.fixture(scope='session') From b78fc5765abebffa2fd3ef94b012c6e5f422c9c0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 27 Jun 2024 22:04:40 +0200 Subject: [PATCH 124/403] ci: Drop macOS 11 Will be dropped on GitHub Actions tomorrow: https://github.blog/changelog/2024-05-20-actions-upcoming-changes-to-github-hosted-macos-runners/ For unit tests, we now run them on macOS 13 instead, thus testing on all three macOS versions we currently support. For releases, this forces us to now support macOS 12 as the oldest supported version and drop macOS 11 support. Thus, we should not have a v3.2.2 release. Not backporting this commit so CI fails there rather than silently bumping up requirements. --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/nightly.yml | 8 ++++---- .github/workflows/release.yml | 2 +- doc/changelog.asciidoc | 6 ++++++ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8b3372fb..60f9d12ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -173,16 +173,16 @@ jobs: - testenv: py312-pyqt67 os: ubuntu-22.04 python: "3.12" - ### macOS Big Sur - - testenv: py312-pyqt67 - os: macos-11 - python: "3.12" - args: "tests/unit" # Only run unit tests on macOS ### macOS Monterey - testenv: py312-pyqt67 os: macos-12 python: "3.12" args: "tests/unit" # Only run unit tests on macOS + ### macOS Ventura + - testenv: py312-pyqt67 + os: macos-13 + python: "3.12" + args: "tests/unit" # Only run unit tests on macOS ### macOS Sonoma (M1 runner) - testenv: py312-pyqt67 os: macos-14 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5b798d589..d4e106bc2 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -14,13 +14,13 @@ jobs: fail-fast: false matrix: include: - - os: macos-11 + - os: macos-12 toxenv: build-release-qt5 name: qt5-macos - os: windows-2019 toxenv: build-release-qt5 name: qt5-windows - - os: macos-11 + - os: macos-12 args: --debug toxenv: build-release-qt5 name: qt5-macos-debug @@ -28,7 +28,7 @@ jobs: args: --debug toxenv: build-release-qt5 name: qt5-windows-debug - - os: macos-11 + - os: macos-12 toxenv: build-release name: macos-intel - os: macos-14 @@ -37,7 +37,7 @@ jobs: - os: windows-2019 toxenv: build-release name: windows - - os: macos-11 + - os: macos-12 args: --debug toxenv: build-release name: macos-debug-intel diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2b278010b..826970384 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -126,7 +126,7 @@ jobs: strategy: matrix: include: - - os: macos-11 + - os: macos-12 - os: macos-14 - os: windows-2019 - os: ubuntu-20.04 diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 63e2a1cc7..ae9bfc342 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -19,6 +19,12 @@ breaking changes (such as renamed commands) can happen in minor releases. v3.3.0 (unreleased) ------------------- +Removed +~~~~~~~ + +- Support for macOS 11 Big Sur is dropped. Binaries are now built on macOS 12 + Monterey and are unlikely to still run on older macOS versions. + Changed ~~~~~~~ From b241b0360bc053b21b51677aca8d55b7937fc322 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 29 Jun 2024 20:47:59 +0200 Subject: [PATCH 125/403] ci: Install a newer Python 3.10 for QtWebKit image Starting with the upgrade to Hypothesis 6.103.4 we got hangs when pytest exits. This is caused by: https://github.com/HypothesisWorks/hypothesis/pull/4013 combined with: https://github.com/python/cpython/issues/102126 which was fixed in Python 3.10.11, but the latest 3.10 packaged by Archlinux was 3.10.10. Thus, we instead build a newer 3.10 from the AUR. This bumps the build time up to about 20 minutes on my machine, which is probably acceptable since those are nightly builds only anyways. We could probably half that by disabling --enable-optimization, but that would be at the cost of making the actual test runs (which run more often) slower. Closes #8247 --- scripts/dev/ci/docker/Dockerfile.j2 | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index 042455b13..eef7f190a 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -36,11 +36,14 @@ RUN pacman -Su --noconfirm \ libyaml \ xorg-xdpyinfo +RUN useradd user -u 1001 && \ + mkdir /home/user && \ + chown user:users /home/user + {% if not webengine %} RUN pacman -U --noconfirm \ https://archive.archlinux.org/packages/q/qt5-webkit/qt5-webkit-5.212.0alpha4-18-x86_64.pkg.tar.zst \ https://archive.archlinux.org/packages/p/python-pyqt5/python-pyqt5-5.15.7-2-x86_64.pkg.tar.zst \ - https://archive.archlinux.org/packages/p/python/python-3.10.10-1-x86_64.pkg.tar.zst \ https://archive.archlinux.org/packages/i/icu/icu-72.1-2-x86_64.pkg.tar.zst \ https://archive.archlinux.org/packages/l/libxml2/libxml2-2.10.4-4-x86_64.pkg.tar.zst \ https://archive.archlinux.org/packages/q/qt5-base/qt5-base-5.15.10%2Bkde%2Br129-3-x86_64.pkg.tar.zst \ @@ -49,8 +52,15 @@ RUN pacman -U --noconfirm \ https://archive.archlinux.org/packages/q/qt5-sensors/qt5-sensors-5.15.10-1-x86_64.pkg.tar.zst \ https://archive.archlinux.org/packages/q/qt5-location/qt5-location-5.15.10%2Bkde%2Br5-1-x86_64.pkg.tar.zst \ https://archive.archlinux.org/packages/q/qt5-webchannel/qt5-webchannel-5.15.10%2Bkde%2Br3-1-x86_64.pkg.tar.zst +RUN pacman -S --noconfirm base-devel -RUN python3 -m ensurepip +RUN echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # needed for makepkg +USER user +RUN cd ~ && \ + git clone https://aur.archlinux.org/python310.git && \ + cd python310 && \ + makepkg -si --noconfirm +USER root RUN python3 -m pip install tox pyqt5-sip {% endif %} @@ -65,9 +75,6 @@ RUN python3 -m pip install tox pyqt5-sip RUN python3 -c "from {{ pyqt_module }} import QtWebKit, QtWebKitWidgets" {% endif %} -RUN useradd user -u 1001 && \ - mkdir /home/user && \ - chown user:users /home/user USER user WORKDIR /home/user RUN git config --global --add safe.directory /outside/.git From 4d2361288d5fcbc5d9ff05faae46139f0cbfae9e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 29 Jun 2024 21:59:43 +0200 Subject: [PATCH 126/403] ci: Fix pip/python usage for QtWebKit I'm sure I tested this locally before pushing b241b0360bc053b21b51677aca8d55b7937fc322 but now it's broken? This seems to fix things now. See #8247. --- scripts/dev/ci/docker/Dockerfile.j2 | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index eef7f190a..257ffecb9 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -61,7 +61,8 @@ RUN cd ~ && \ cd python310 && \ makepkg -si --noconfirm USER root -RUN python3 -m pip install tox pyqt5-sip +RUN python3.10 -m ensurepip +RUN python3.10 -m pip install tox pyqt5-sip {% endif %} {% if qt6 %} @@ -70,9 +71,11 @@ RUN python3 -m pip install tox pyqt5-sip {% set pyqt_module = 'PyQt5' %} {% endif %} {% if webengine %} - RUN python3 -c "from {{ pyqt_module }} import QtWebEngineCore, QtWebEngineWidgets" + {% set python = 'python3' %} + RUN {{ python }} -c "from {{ pyqt_module }} import QtWebEngineCore, QtWebEngineWidgets" {% else %} - RUN python3 -c "from {{ pyqt_module }} import QtWebKit, QtWebKitWidgets" + {% set python = 'python3.10' %} + RUN {{ python }} -c "from {{ pyqt_module }} import QtWebKit, QtWebKitWidgets" {% endif %} USER user @@ -81,4 +84,4 @@ RUN git config --global --add safe.directory /outside/.git CMD git clone /outside qutebrowser.git && \ cd qutebrowser.git && \ - tox -e {% if qt6 %}py-qt6{% else %}py-qt5{% endif %} + {{ python }} -m tox -e {% if qt6 %}py-qt6{% else %}py-qt5{% endif %} From 95075d20ff3038edc163378aad63accb4ec0427a Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 1 Jul 2024 04:21:28 +0000 Subject: [PATCH 127/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 8 ++++---- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyroma.txt | 2 +- misc/requirements/requirements-sphinx.txt | 2 +- misc/requirements/requirements-tests.txt | 4 ++-- misc/requirements/requirements-tox.txt | 4 ++-- 10 files changed, 16 insertions(+), 16 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 1da93a7ff..5648e13de 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -2,7 +2,7 @@ build==1.2.1 check-manifest==0.49 -importlib_metadata==7.2.1 +importlib_metadata==8.0.0 packaging==24.1 pyproject_hooks==1.1.0 tomli==2.0.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 1c21e9d61..5f4770afc 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -11,7 +11,7 @@ docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 idna==3.7 -importlib_metadata==7.2.1 +importlib_metadata==8.0.0 importlib_resources==6.4.0 jaraco.classes==3.4.0 jaraco.context==5.3.0 @@ -24,11 +24,11 @@ mdurl==0.1.2 more-itertools==10.3.0 nh3==0.2.17 packaging==24.1 -pkginfo==1.11.1 +pkginfo==1.10.0 pycparser==2.22 Pygments==2.18.0 PyJWT==2.8.0 -Pympler==1.0.1 +Pympler==1.1 pyproject_hooks==1.1.0 PyQt-builder==1.16.3 python-dateutil==2.9.0.post0 @@ -41,7 +41,7 @@ SecretStorage==3.3.3 sip==6.8.5 six==1.16.0 tomli==2.0.1 -twine==5.1.0 +twine==5.1.1 typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.2 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 923732b61..6e233690f 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -4,7 +4,7 @@ attrs==23.2.0 flake8==7.1.0 flake8-bugbear==24.4.26 flake8-builtins==2.5.0 -flake8-comprehensions==3.14.0 +flake8-comprehensions==3.15.0 flake8-debugger==4.1.2 flake8-deprecated==2.2.1 flake8-docstrings==1.7.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 6c58fad02..52c4093ad 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==5.2.0 -diff_cover==9.0.0 +diff_cover==9.1.0 importlib_resources==6.4.0 Jinja2==3.1.4 lxml==5.2.2 @@ -16,6 +16,6 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20240423 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 -types-setuptools==70.1.0.20240625 +types-setuptools==70.1.0.20240627 typing_extensions==4.12.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index a73fe39db..af045e2ea 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py altgraph==0.17.4 -importlib_metadata==7.2.1 +importlib_metadata==8.0.0 packaging==24.1 pyinstaller==6.8.0 pyinstaller-hooks-contrib==2024.7 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 261a29b14..1d8bcb1ad 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -14,7 +14,7 @@ pefile==2023.2.7 platformdirs==4.2.2 pycparser==2.22 PyJWT==2.8.0 -pylint==3.2.3 +pylint==3.2.5 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index c2383b6f3..96295355f 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -5,7 +5,7 @@ certifi==2024.6.2 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 -importlib_metadata==7.2.1 +importlib_metadata==8.0.0 packaging==24.1 Pygments==2.18.0 pyproject_hooks==1.1.0 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 21b717581..878c93a95 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -7,7 +7,7 @@ charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 imagesize==1.4.1 -importlib_metadata==7.2.1 +importlib_metadata==8.0.0 Jinja2==3.1.4 MarkupSafe==2.1.5 packaging==24.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 6d59fffbd..424337a97 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -13,9 +13,9 @@ execnet==2.1.1 filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.104.0 +hypothesis==6.104.2 idna==3.7 -importlib_metadata==7.2.1 +importlib_metadata==8.0.0 iniconfig==2.0.0 itsdangerous==2.2.0 jaraco.functools==4.0.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index f4291553d..f6d30722f 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -6,11 +6,11 @@ colorama==0.4.6 distlib==0.3.8 filelock==3.15.4 packaging==24.1 -pip==24.1 +pip==24.1.1 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.7.1 -setuptools==70.1.0 +setuptools==70.1.1 tomli==2.0.1 tox==4.15.1 virtualenv==20.26.3 From aae05613b540a4c804ee12525fc2b7e8a8d68475 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jul 2024 16:43:09 +0200 Subject: [PATCH 128/403] style: Use dict.fromkeys() Added in flake8-comprehensions 3.15.0: https://github.com/adamchainz/flake8-comprehensions/blob/main/CHANGELOG.rst --- qutebrowser/config/configtypes.py | 2 +- qutebrowser/utils/log.py | 2 +- tests/unit/browser/webkit/test_webkitelem.py | 2 +- tests/unit/utils/test_version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index e789437d3..60faf9bc3 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1558,7 +1558,7 @@ class FormatString(BaseType): _validate_encoding(self.encoding, value) try: - value.format(**{k: '' for k in self.fields}) + value.format(**dict.fromkeys(self.fields, "")) except (KeyError, IndexError, AttributeError) as e: raise configexc.ValidationError(value, "Invalid placeholder " "{}".format(e)) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 9695ec5a2..5029ce3d7 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -547,7 +547,7 @@ class ColoredFormatter(logging.Formatter): log_color = LOG_COLORS[record.levelname] color_dict['log_color'] = COLOR_ESCAPES[log_color] else: - color_dict = {color: '' for color in COLOR_ESCAPES} + color_dict = dict.fromkeys(COLOR_ESCAPES, "") color_dict['reset'] = '' color_dict['log_color'] = '' record.__dict__.update(color_dict) diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index 90211f5b3..1c829f89c 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -82,7 +82,7 @@ def get_webelem(geometry=None, frame=None, *, null=False, style=None, if attributes is None: pass elif not isinstance(attributes, collections.abc.Mapping): - attribute_dict.update({e: None for e in attributes}) + attribute_dict.update(dict.fromkeys(attributes)) else: attribute_dict.update(attributes) diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index a79f42385..149b1d855 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -623,7 +623,7 @@ def test_path_info(monkeypatch, equal): @pytest.fixture def import_fake(stubs, monkeypatch): """Fixture to patch imports using ImportFake.""" - fake = stubs.ImportFake({mod: True for mod in version.MODULE_INFO}, monkeypatch) + fake = stubs.ImportFake(dict.fromkeys(version.MODULE_INFO, True), monkeypatch) fake.patch() return fake From 5f50586ca4a777aecbff78ff87e8b64fdeae8a59 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 1 Jul 2024 16:44:56 +0200 Subject: [PATCH 129/403] style: Deal with unreachable code and pylint https://pylint.pycqa.org/en/latest/whatsnew/3/3.2/index.html#what-s-new-in-pylint-3-2-4 --- qutebrowser/config/configdata.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 2377841ef..90486f702 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -94,7 +94,6 @@ def _parse_yaml_type( _raise_invalid_node(name, 'type', node) try: - # pylint: disable=possibly-used-before-assignment typ = getattr(configtypes, type_name) except AttributeError: raise AttributeError("Did not find type {} for {}".format( @@ -183,7 +182,6 @@ def _parse_yaml_backends( elif isinstance(node, dict): return _parse_yaml_backends_dict(name, node) _raise_invalid_node(name, 'backends', node) - raise utils.Unreachable def _read_yaml( From a5c5cdb08b0dafd30214ca837e6fbb907060846b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 2 Jul 2024 13:40:15 +0200 Subject: [PATCH 130/403] Fix some typos Thanks to https://github.com/crate-ci/typos --- qutebrowser/browser/webelem.py | 6 +++--- qutebrowser/completion/completionwidget.py | 2 +- qutebrowser/components/utils/blockutils.py | 2 +- qutebrowser/mainwindow/tabwidget.py | 2 +- qutebrowser/misc/earlyinit.py | 2 +- qutebrowser/misc/httpclient.py | 2 +- tests/unit/browser/test_history.py | 2 +- tests/unit/utils/test_utils.py | 10 +++++----- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 721ab83df..556623ee5 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -22,9 +22,9 @@ if TYPE_CHECKING: JsValueType = Union[int, float, str, None] if machinery.IS_QT6: - KeybordModifierType = Qt.KeyboardModifier + KeyboardModifierType = Qt.KeyboardModifier else: - KeybordModifierType = Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] + KeyboardModifierType = Union[Qt.KeyboardModifiers, Qt.KeyboardModifier] class Error(Exception): @@ -336,7 +336,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a log.webelem.debug("Sending fake click to {!r} at position {} with " "target {}".format(self, pos, click_target)) - target_modifiers: Dict[usertypes.ClickTarget, KeybordModifierType] = { + target_modifiers: Dict[usertypes.ClickTarget, KeyboardModifierType] = { usertypes.ClickTarget.normal: Qt.KeyboardModifier.NoModifier, usertypes.ClickTarget.window: Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier, usertypes.ClickTarget.tab: Qt.KeyboardModifier.ControlModifier, diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index 652ea636d..c7e549f46 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -4,7 +4,7 @@ """Completion view for statusbar command section. -Defines a CompletionView which uses CompletionFiterModel and CompletionModel +Defines a CompletionView which uses CompletionFilterModel and CompletionModel subclasses to provide completions. """ diff --git a/qutebrowser/components/utils/blockutils.py b/qutebrowser/components/utils/blockutils.py index 369b0eee5..a65085949 100644 --- a/qutebrowser/components/utils/blockutils.py +++ b/qutebrowser/components/utils/blockutils.py @@ -75,7 +75,7 @@ class BlocklistDownloads(QObject): if not self._in_progress and not self._finished: # The in-progress list is empty but we still haven't called the # completion callback yet. This happens when all downloads finish - # before we've set `_finished_registering_dowloads` to False. + # before we've set `_finished_registering_downloads` to False. self._finished = True self.all_downloads_finished.emit(self._done_count) diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index afbfa0a95..6cc466c93 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -696,7 +696,7 @@ class TabBar(QTabBar): # Re-do the text elision that the base QTabBar does, but using a text # rectangle computed by out TabBarStyle. With Qt6 the base class ends - # up using QCommonStyle directly for that which has a different opinon + # up using QCommonStyle directly for that which has a different opinion # of how vertical tabs should work. text_rect = self._our_style.subElementRect( QStyle.SubElement.SE_TabBarTabText, diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index c008286e6..06de668b0 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -175,7 +175,7 @@ def qt_version(qversion=None, qt_version_str=None): def get_qt_version(): - """Get the Qt version, or None if too old for QLibaryInfo.version().""" + """Get the Qt version, or None if too old for QLibraryInfo.version().""" try: from qutebrowser.qt.core import QLibraryInfo return QLibraryInfo.version().normalized() diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py index 19186ffb7..a6a6025c3 100644 --- a/qutebrowser/misc/httpclient.py +++ b/qutebrowser/misc/httpclient.py @@ -16,7 +16,7 @@ from qutebrowser.utils import qtlog, usertypes class HTTPRequest(QNetworkRequest): - """A QNetworkRquest that follows (secure) redirects by default.""" + """A QNetworkRequest that follows (secure) redirects by default.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 7b35fa149..e46c685f4 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -224,7 +224,7 @@ class TestAdd: assert list(web_history) assert not list(web_history.completion) - def test_no_immedate_duplicates(self, web_history, mock_time): + def test_no_immediate_duplicates(self, web_history, mock_time): url = QUrl("http://example.com") url2 = QUrl("http://example2.com") web_history.add_from_tab(QUrl(url), QUrl(url), 'title') diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 77805c6dc..51255aa61 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -533,7 +533,7 @@ class TestIsEnum: assert not utils.is_enum(23) -class SentinalException(Exception): +class SentinelException(Exception): pass @@ -543,7 +543,7 @@ class TestRaises: def do_raise(self): """Helper function which raises an exception.""" - raise SentinalException + raise SentinelException def do_nothing(self): """Helper function which does nothing.""" @@ -562,15 +562,15 @@ class TestRaises: def test_no_args_true(self): """Test with no args and an exception which gets raised.""" - assert utils.raises(SentinalException, self.do_raise) + assert utils.raises(SentinelException, self.do_raise) def test_no_args_false(self): """Test with no args and an exception which does not get raised.""" - assert not utils.raises(SentinalException, self.do_nothing) + assert not utils.raises(SentinelException, self.do_nothing) def test_unrelated_exception(self): """Test with an unrelated exception.""" - with pytest.raises(SentinalException): + with pytest.raises(SentinelException): utils.raises(ValueError, self.do_raise) From 2dbb518a8da60e964553549df7b0e46874a1c23a Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 8 Jul 2024 04:20:22 +0000 Subject: [PATCH 131/403] Update dependencies --- misc/requirements/requirements-dev.txt | 6 +++--- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pyinstaller.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyqt-5.15.txt | 4 ++-- misc/requirements/requirements-pyqt-5.txt | 4 ++-- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 2 +- misc/requirements/requirements-tests.txt | 6 +++--- misc/requirements/requirements-tox.txt | 6 +++--- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 5f4770afc..61d30b975 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -3,7 +3,7 @@ backports.tarfile==1.2.0 build==1.2.1 bump2version==1.0.1 -certifi==2024.6.2 +certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 cryptography==42.0.8 @@ -18,11 +18,11 @@ jaraco.context==5.3.0 jaraco.functools==4.0.1 jeepney==0.8.0 keyring==25.2.1 -manhole==1.8.0 +manhole==1.8.1 markdown-it-py==3.0.0 mdurl==0.1.2 more-itertools==10.3.0 -nh3==0.2.17 +nh3==0.2.18 packaging==24.1 pkginfo==1.10.0 pycparser==2.22 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 52c4093ad..15d749b66 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -13,9 +13,9 @@ Pygments==2.18.0 PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-colorama==0.4.15.20240311 -types-docutils==0.21.0.20240423 +types-docutils==0.21.0.20240708 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 -types-setuptools==70.1.0.20240627 +types-setuptools==70.2.0.20240704 typing_extensions==4.12.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index af045e2ea..8444867c3 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -3,6 +3,6 @@ altgraph==0.17.4 importlib_metadata==8.0.0 packaging==24.1 -pyinstaller==6.8.0 +pyinstaller==6.9.0 pyinstaller-hooks-contrib==2024.7 zipp==3.19.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 1d8bcb1ad..e2ef4d61c 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py astroid==3.2.2 -certifi==2024.6.2 +certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 cryptography==42.0.8 diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt index 5f9e4828e..76b702bf7 100644 --- a/misc/requirements/requirements-pyqt-5.15.txt +++ b/misc/requirements/requirements-pyqt-5.15.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.15.10 # rq.filter: < 5.16 -PyQt5-Qt5==5.15.2 +PyQt5-Qt5==5.15.14 PyQt5-sip==12.13.0 PyQtWebEngine==5.15.6 # rq.filter: < 5.16 -PyQtWebEngine-Qt5==5.15.2 +PyQtWebEngine-Qt5==5.15.14 diff --git a/misc/requirements/requirements-pyqt-5.txt b/misc/requirements/requirements-pyqt-5.txt index e8ee2b9c7..94a11998e 100644 --- a/misc/requirements/requirements-pyqt-5.txt +++ b/misc/requirements/requirements-pyqt-5.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.15.10 -PyQt5-Qt5==5.15.2 +PyQt5-Qt5==5.15.14 PyQt5-sip==12.13.0 PyQtWebEngine==5.15.6 -PyQtWebEngine-Qt5==5.15.2 +PyQtWebEngine-Qt5==5.15.14 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 96295355f..a2084b1ea 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py build==1.2.1 -certifi==2024.6.2 +certifi==2024.7.4 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 @@ -12,6 +12,6 @@ pyproject_hooks==1.1.0 pyroma==4.2 requests==2.32.3 tomli==2.0.1 -trove-classifiers==2024.5.22 +trove-classifiers==2024.7.2 urllib3==2.2.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 878c93a95..45892321f 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -2,7 +2,7 @@ alabaster==0.7.13 Babel==2.15.0 -certifi==2024.6.2 +certifi==2024.7.4 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 424337a97..c515f304c 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -3,7 +3,7 @@ attrs==23.2.0 beautifulsoup4==4.12.3 blinker==1.8.2 -certifi==2024.6.2 +certifi==2024.7.4 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 @@ -13,7 +13,7 @@ execnet==2.1.1 filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.104.2 +hypothesis==6.105.1 idna==3.7 importlib_metadata==8.0.0 iniconfig==2.0.0 @@ -21,7 +21,7 @@ itsdangerous==2.2.0 jaraco.functools==4.0.1 # Jinja2==3.1.4 Mako==1.3.5 -manhole==1.8.0 +manhole==1.8.1 # MarkupSafe==2.1.5 more-itertools==10.3.0 packaging==24.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index f6d30722f..7841d6165 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -6,12 +6,12 @@ colorama==0.4.6 distlib==0.3.8 filelock==3.15.4 packaging==24.1 -pip==24.1.1 +pip==24.1.2 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.7.1 -setuptools==70.1.1 +setuptools==70.2.0 tomli==2.0.1 -tox==4.15.1 +tox==4.16.0 virtualenv==20.26.3 wheel==0.43.0 From d9b93496568e669802594b01c54da5ad2aac990b Mon Sep 17 00:00:00 2001 From: owl Date: Wed, 10 Jul 2024 21:45:02 +0200 Subject: [PATCH 132/403] Add setting to disable Google Hangouts extension Fixes #8257 --- doc/changelog.asciidoc | 6 ++++++ doc/help/settings.asciidoc | 15 +++++++++++++++ qutebrowser/config/configdata.yml | 13 +++++++++++++ qutebrowser/misc/pakjoy.py | 2 ++ 4 files changed, 36 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index ae9bfc342..da0643023 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -19,6 +19,12 @@ breaking changes (such as renamed commands) can happen in minor releases. v3.3.0 (unreleased) ------------------- +Added +~~~~~ + +- Added the `qt.workarounds.disable_hangouts_extension` setting, + for disabling the Google Hangouts extension built into Chromium/QtWebEngine. + Removed ~~~~~~~ diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index b51df3f2f..0bc76ec90 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -302,6 +302,7 @@ |<>|Force software rendering for QtWebEngine. |<>|Turn on Qt HighDPI scaling. |<>|Disable accelerated 2d canvas to avoid graphical glitches. +|<>|Disable the Hangouts extension. |<>|Work around locale parsing issues in QtWebEngine 5.15.3. |<>|Delete the QtWebEngine Service Worker directory on every start. |<>|When/how to show the scrollbar. @@ -3993,6 +3994,20 @@ Valid values: Default: +pass:[auto]+ +[[qt.workarounds.disable_hangouts_extension]] +=== qt.workarounds.disable_hangouts_extension +Disable the Hangouts extension. +The Hangouts extension provides additional APIs for Google domains only. +Hangouts has been replaced with Meet, which appears to work without this extension. + +This setting requires a restart. + +This setting is only available with the QtWebEngine backend. + +Type: <> + +Default: +pass:[false]+ + [[qt.workarounds.locale]] === qt.workarounds.locale Work around locale parsing issues in QtWebEngine 5.15.3. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 9e3334173..7991b89bc 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -404,6 +404,19 @@ qt.workarounds.disable_accelerated_2d_canvas: So far these glitches only occur on some Intel graphics devices. +qt.workarounds.disable_hangouts_extension: + type: Bool + default: false + backend: QtWebEngine + restart: true + desc: >- + Disable the Hangouts extension. + + The Hangouts extension provides additional APIs for Google domains only. + + Hangouts has been replaced with Meet, + which appears to work without this extension. + ## auto_save auto_save.interval: diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index c0e6b4d0c..f914a372a 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -208,6 +208,8 @@ def copy_webengine_resources() -> Optional[pathlib.Path]: and versions.webengine < utils.VersionNumber(6, 5, 3) and config.val.colors.webpage.darkmode.enabled ) + # https://github.com/qutebrowser/qutebrowser/issues/8257 + or config.val.qt.workarounds.disable_hangouts_extension ): # No patching needed return None From bcf792a0dca48035623439ef951a5e7d8b0981e0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jul 2024 15:28:38 +0200 Subject: [PATCH 133/403] Add more hardcoded IDs and tests --- qutebrowser/misc/pakjoy.py | 8 ++++++-- tests/unit/misc/test_pakjoy.py | 25 +++++++++++++------------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index f914a372a..7ae0526e7 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -38,8 +38,12 @@ from qutebrowser.utils import qtutils, standarddir, version, utils, log HANGOUTS_MARKER = b"// Extension ID: nkeimhogjdpnpccoofpliimaahmaaome" HANGOUTS_IDS = [ - 36197, # QtWebEngine 6.5, as found by toofar - 34897, # QtWebEngine 6.4 + 41262, # QtWebEngine 6.7 + 36197, # QtWebEngine 6.6 + 34897, # QtWebEngine 6.5 + 32707, # QtWebEngine 6.4 + 27537, # QtWebEngine 6.3 + 23607, # QtWebEngine 6.2 ] PAK_VERSION = 5 RESOURCES_ENV_VAR = "QTWEBENGINE_RESOURCES_PATH" diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index 59185a380..9dae21ccb 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -25,17 +25,6 @@ pytestmark = pytest.mark.usefixtures("cache_tmpdir") versions = version.qtwebengine_versions(avoid_init=True) -# Used to skip happy path tests with the real resources file. -# -# Since we don't know how reliably the Google Meet hangouts extensions is -# reliably in the resource files, and this quirk is only targeting 6.6 -# anyway. -skip_if_unsupported = pytest.mark.skipif( - versions.webengine != utils.VersionNumber(6, 6), - reason="Code under test only runs on 6.6", -) - - @pytest.fixture(autouse=True) def prepare_env(qapp, monkeypatch): monkeypatch.setattr(pakjoy.objects, "qapp", qapp) @@ -206,7 +195,6 @@ def read_patched_manifest(): class TestWithRealResourcesFile: """Tests that use the real pak file form the Qt installation.""" - @skip_if_unsupported def test_happy_path(self): # Go through the full patching processes with the real resources file from # the current installation. Make sure our replacement string is in it @@ -266,6 +254,19 @@ class TestWithRealResourcesFile: "Not applying quirks. Expected location: " ) + def test_hardcoded_ids(self): + """Make sure we hardcoded the currently valid ID. + + This avoids users having to iterate through the whole resource file on + every start. It will probably break on every QtWebEngine upgrade and can + be fixed by adding the respective ID to HANGOUTS_IDS. + """ + resources_dir = pakjoy._find_webengine_resources() + file_to_patch = resources_dir / pakjoy.PAK_FILENAME + with open(file_to_patch, "rb") as f: + parser = pakjoy.PakParser(f) + assert parser.manifest_entry.resource_id in pakjoy.HANGOUTS_IDS + def json_manifest_factory(extension_id=pakjoy.HANGOUTS_MARKER, url=pakjoy.TARGET_URL): assert isinstance(extension_id, bytes) From 2088ecebd94a3c80fb9928c5364aee39b0c77bde Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jul 2024 16:00:52 +0200 Subject: [PATCH 134/403] pakjoy: Add more hardcoded IDs and fix tests --- qutebrowser/misc/pakjoy.py | 4 ++++ tests/unit/misc/test_pakjoy.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index 7ae0526e7..f0c7d75b7 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -38,12 +38,16 @@ from qutebrowser.utils import qtutils, standarddir, version, utils, log HANGOUTS_MARKER = b"// Extension ID: nkeimhogjdpnpccoofpliimaahmaaome" HANGOUTS_IDS = [ + # Linux 41262, # QtWebEngine 6.7 36197, # QtWebEngine 6.6 34897, # QtWebEngine 6.5 32707, # QtWebEngine 6.4 27537, # QtWebEngine 6.3 23607, # QtWebEngine 6.2 + + 248, # macOS + 381, # Windows ] PAK_VERSION = 5 RESOURCES_ENV_VAR = "QTWEBENGINE_RESOURCES_PATH" diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index 9dae21ccb..7955c1712 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -195,6 +195,7 @@ def read_patched_manifest(): class TestWithRealResourcesFile: """Tests that use the real pak file form the Qt installation.""" + @pytest.mark.qt6_only def test_happy_path(self): # Go through the full patching processes with the real resources file from # the current installation. Make sure our replacement string is in it @@ -254,6 +255,7 @@ class TestWithRealResourcesFile: "Not applying quirks. Expected location: " ) + @pytest.mark.qt6_only def test_hardcoded_ids(self): """Make sure we hardcoded the currently valid ID. From 564293fb6e87ef6601afc7a5174c6fd416d0aa82 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jul 2024 16:33:14 +0200 Subject: [PATCH 135/403] Update pakjoy setting description --- doc/help/settings.asciidoc | 1 + qutebrowser/config/configdata.yml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 0bc76ec90..1ff5580c6 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -3999,6 +3999,7 @@ Default: +pass:[auto]+ Disable the Hangouts extension. The Hangouts extension provides additional APIs for Google domains only. Hangouts has been replaced with Meet, which appears to work without this extension. +Note this setting gets ignored and the Hangouts extension is always disabled to avoid crashes on Qt 6.5.0 to 6.5.3 if dark mode is enabled, as well as on Qt 6.6.0. This setting requires a restart. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 7991b89bc..786981563 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -417,6 +417,10 @@ qt.workarounds.disable_hangouts_extension: Hangouts has been replaced with Meet, which appears to work without this extension. + Note this setting gets ignored and the Hangouts extension is always + disabled to avoid crashes on Qt 6.5.0 to 6.5.3 if dark mode is enabled, + as well as on Qt 6.6.0. + ## auto_save auto_save.interval: From 67aeae367c244a7ddab20a3613a0a5b7b6c8eb2d Mon Sep 17 00:00:00 2001 From: owl Date: Sun, 14 Jul 2024 21:32:25 +0200 Subject: [PATCH 136/403] pakjoy: show error message if workaround was explicitly requested and failed --- qutebrowser/misc/pakjoy.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index f0c7d75b7..114bd2f72 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -34,7 +34,7 @@ from typing import ClassVar, IO, Optional, Dict, Tuple, Iterator from qutebrowser.config import config from qutebrowser.misc import binparsing, objects -from qutebrowser.utils import qtutils, standarddir, version, utils, log +from qutebrowser.utils import qtutils, standarddir, version, utils, log, message HANGOUTS_MARKER = b"// Extension ID: nkeimhogjdpnpccoofpliimaahmaaome" HANGOUTS_IDS = [ @@ -234,7 +234,8 @@ def copy_webengine_resources() -> Optional[pathlib.Path]: def _patch(file_to_patch: pathlib.Path) -> None: """Apply any patches to the given pak file.""" if not file_to_patch.exists(): - log.misc.error( + _error( + None, "Resource pak doesn't exist at expected location! " f"Not applying quirks. Expected location: {file_to_patch}" ) @@ -247,8 +248,22 @@ def _patch(file_to_patch: pathlib.Path) -> None: offset = parser.find_patch_offset() binparsing.safe_seek(f, offset) f.write(REPLACEMENT_URL) - except binparsing.ParseError: - log.misc.exception("Failed to apply quirk to resources pak.") + except binparsing.ParseError as e: + _error(e, "Failed to apply quirk to resources pak.") + + +def _error(exc: Optional[BaseException], text: str) -> None: + if config.val.qt.workarounds.disable_hangouts_extension: + # Explicitly requested -> hard error + lines = ["Failed to disable Hangouts extension"] + if exc is None: + lines.append(str(exc)) + message.error("\n".join(lines)) + elif exc is None: + # Best effort -> just log + log.misc.error(text) + else: + log.misc.exception(text) @contextlib.contextmanager @@ -263,8 +278,8 @@ def patch_webengine() -> Iterator[None]: # Still calling this on Qt != 6.6 so that the directory is cleaned up # when not needed anymore. webengine_resources_path = copy_webengine_resources() - except OSError: - log.misc.exception("Failed to copy webengine resources, not applying quirk") + except OSError as e: + _error(e, "Failed to copy webengine resources, not applying quirk") yield return From dfcb6e3f7c91f5a618dac195220a558c23517fb5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 14 Jul 2024 22:04:56 +0200 Subject: [PATCH 137/403] pakjoy: Improve error message and add test --- qutebrowser/misc/pakjoy.py | 2 +- tests/unit/misc/test_pakjoy.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index 114bd2f72..787c5c916 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -255,7 +255,7 @@ def _patch(file_to_patch: pathlib.Path) -> None: def _error(exc: Optional[BaseException], text: str) -> None: if config.val.qt.workarounds.disable_hangouts_extension: # Explicitly requested -> hard error - lines = ["Failed to disable Hangouts extension"] + lines = ["Failed to disable Hangouts extension:", text] if exc is None: lines.append(str(exc)) message.error("\n".join(lines)) diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index 7955c1712..bc41dfb0d 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -13,7 +13,7 @@ import shutil import pytest from qutebrowser.misc import pakjoy, binparsing -from qutebrowser.utils import utils, version, standarddir +from qutebrowser.utils import utils, version, standarddir, usertypes pytest.importorskip("qutebrowser.qt.webenginecore") @@ -397,7 +397,9 @@ class TestWithConstructedResourcesFile: ): parser.find_patch_offset() - def test_url_not_found_high_level(self, cache_tmpdir, caplog, affected_version): + @pytest.mark.parametrize("explicit", [True, False]) + def test_url_not_found_high_level(self, cache_tmpdir, caplog, affected_version, config_stub, message_mock, explicit): + config_stub.val.qt.workarounds.disable_hangouts_extension = explicit buffer = pak_factory(entries=[json_manifest_factory(url=b"example.com")]) # Write bytes to file so we can test pakjoy._patch() @@ -405,10 +407,18 @@ class TestWithConstructedResourcesFile: with open(tmpfile, "wb") as fd: fd.write(buffer.read()) - with caplog.at_level(logging.ERROR, "misc"): + logger = "message" if explicit else "misc" + with caplog.at_level(logging.ERROR, logger): pakjoy._patch(tmpfile) - assert caplog.messages == ["Failed to apply quirk to resources pak."] + if explicit: + msg = message_mock.getmsg(usertypes.MessageLevel.error) + assert msg.text == ( + "Failed to disable Hangouts extension:\n" + "Failed to apply quirk to resources pak." + ) + else: + assert caplog.messages[-1] == "Failed to apply quirk to resources pak." @pytest.fixture def resources_path( From 6e84639276c70b14d76d0e46046b57adf6a68a34 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 14 Jul 2024 22:14:00 +0200 Subject: [PATCH 138/403] pakjoy: Test behavior when explicitly enabled --- tests/unit/misc/test_pakjoy.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index bc41dfb0d..6f79b81b5 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -67,7 +67,7 @@ def affected_version(monkeypatch: pytest.MonkeyPatch, request: pytest.FixtureReq @pytest.mark.parametrize("workdir_exists", [True, False]) -def test_version_gate(cache_tmpdir, unaffected_version, mocker, workdir_exists): +def test_version_gate(cache_tmpdir, unaffected_version, mocker, config_stub, workdir_exists): workdir = cache_tmpdir / pakjoy.CACHE_DIR_NAME if workdir_exists: workdir.mkdir() @@ -81,7 +81,9 @@ def test_version_gate(cache_tmpdir, unaffected_version, mocker, workdir_exists): assert not workdir.exists() -def test_escape_hatch(affected_version, mocker, monkeypatch): +@pytest.mark.parametrize("explicit", [True, False]) +def test_escape_hatch(affected_version, mocker, monkeypatch, config_stub, explicit): + config_stub.val.qt.workarounds.disable_hangouts_extension = explicit fake_open = mocker.patch("qutebrowser.misc.pakjoy.open") monkeypatch.setenv(pakjoy.DISABLE_ENV_VAR, "1") @@ -450,6 +452,12 @@ class TestWithConstructedResourcesFile: ) assert pakjoy.RESOURCES_ENV_VAR not in os.environ + def test_explicitly_enabled(self, monkeypatch: pytest.MonkeyPatch, config_stub): + patch_version(monkeypatch, utils.VersionNumber(6, 7)) # unaffected + config_stub.val.qt.workarounds.disable_hangouts_extension = True + with pakjoy.patch_webengine(): + assert pakjoy.RESOURCES_ENV_VAR in os.environ + def test_preset_env_var( self, resources_path: pathlib.Path, From a2c8ebf2575f20d8e38822f6beb8baa17f70d92b Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 15 Jul 2024 04:21:46 +0000 Subject: [PATCH 139/403] Update dependencies --- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 4 ++-- misc/requirements/requirements-pyqt-5.15.2.txt | 2 +- misc/requirements/requirements-pyqt-5.15.txt | 2 +- misc/requirements/requirements-pyqt-5.txt | 2 +- misc/requirements/requirements-pyqt-6.2.txt | 2 +- misc/requirements/requirements-pyqt-6.3.txt | 2 +- misc/requirements/requirements-pyqt-6.4.txt | 2 +- misc/requirements/requirements-pyqt-6.5.txt | 2 +- misc/requirements/requirements-pyqt-6.6.txt | 2 +- misc/requirements/requirements-pyqt-6.7.txt | 2 +- misc/requirements/requirements-pyqt-6.txt | 2 +- misc/requirements/requirements-pyqt.txt | 2 +- misc/requirements/requirements-tests.txt | 6 +++--- misc/requirements/requirements-tox.txt | 2 +- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 61d30b975..3e25d7b9b 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -30,7 +30,7 @@ Pygments==2.18.0 PyJWT==2.8.0 Pympler==1.1 pyproject_hooks==1.1.0 -PyQt-builder==1.16.3 +PyQt-builder==1.16.4 python-dateutil==2.9.0.post0 readme_renderer==43.0 requests==2.32.3 @@ -38,7 +38,7 @@ requests-toolbelt==1.0.0 rfc3986==2.0.0 rich==13.7.1 SecretStorage==3.3.3 -sip==6.8.5 +sip==6.8.6 six==1.16.0 tomli==2.0.1 twine==5.1.1 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 15d749b66..67bddc858 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -13,9 +13,9 @@ Pygments==2.18.0 PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-colorama==0.4.15.20240311 -types-docutils==0.21.0.20240708 +types-docutils==0.21.0.20240711 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240311 -types-setuptools==70.2.0.20240704 +types-setuptools==70.3.0.20240710 typing_extensions==4.12.2 zipp==3.19.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index e2ef4d61c..b7705a127 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==3.2.2 +astroid==3.2.3 certifi==2024.7.4 cffi==1.16.0 charset-normalizer==3.3.2 @@ -20,7 +20,7 @@ python-dateutil==2.9.0.post0 requests==2.32.3 six==1.16.0 tomli==2.0.1 -tomlkit==0.12.5 +tomlkit==0.13.0 typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.2 diff --git a/misc/requirements/requirements-pyqt-5.15.2.txt b/misc/requirements/requirements-pyqt-5.15.2.txt index 41f75871e..f075381bb 100644 --- a/misc/requirements/requirements-pyqt-5.15.2.txt +++ b/misc/requirements/requirements-pyqt-5.15.2.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.15.2 # rq.filter: == 5.15.2 -PyQt5-sip==12.13.0 +PyQt5_sip==12.15.0 PyQtWebEngine==5.15.2 # rq.filter: == 5.15.2 diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt index 76b702bf7..00bd02351 100644 --- a/misc/requirements/requirements-pyqt-5.15.txt +++ b/misc/requirements/requirements-pyqt-5.15.txt @@ -2,6 +2,6 @@ PyQt5==5.15.10 # rq.filter: < 5.16 PyQt5-Qt5==5.15.14 -PyQt5-sip==12.13.0 +PyQt5_sip==12.15.0 PyQtWebEngine==5.15.6 # rq.filter: < 5.16 PyQtWebEngine-Qt5==5.15.14 diff --git a/misc/requirements/requirements-pyqt-5.txt b/misc/requirements/requirements-pyqt-5.txt index 94a11998e..46d68a5a2 100644 --- a/misc/requirements/requirements-pyqt-5.txt +++ b/misc/requirements/requirements-pyqt-5.txt @@ -2,6 +2,6 @@ PyQt5==5.15.10 PyQt5-Qt5==5.15.14 -PyQt5-sip==12.13.0 +PyQt5_sip==12.15.0 PyQtWebEngine==5.15.6 PyQtWebEngine-Qt5==5.15.14 diff --git a/misc/requirements/requirements-pyqt-6.2.txt b/misc/requirements/requirements-pyqt-6.2.txt index e90769ddd..876a309d7 100644 --- a/misc/requirements/requirements-pyqt-6.2.txt +++ b/misc/requirements/requirements-pyqt-6.2.txt @@ -2,6 +2,6 @@ PyQt6==6.2.3 PyQt6-Qt6==6.2.4 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.2.1 PyQt6-WebEngine-Qt6==6.2.4 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.3.txt b/misc/requirements/requirements-pyqt-6.3.txt index d82c623c3..ec4cd307e 100644 --- a/misc/requirements/requirements-pyqt-6.3.txt +++ b/misc/requirements/requirements-pyqt-6.3.txt @@ -2,6 +2,6 @@ PyQt6==6.3.1 PyQt6-Qt6==6.3.2 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.3.1 PyQt6-WebEngine-Qt6==6.3.2 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.4.txt b/misc/requirements/requirements-pyqt-6.4.txt index b52e8a511..7e3f0c07e 100644 --- a/misc/requirements/requirements-pyqt-6.4.txt +++ b/misc/requirements/requirements-pyqt-6.4.txt @@ -2,6 +2,6 @@ PyQt6==6.4.2 PyQt6-Qt6==6.4.3 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.4.0 PyQt6-WebEngine-Qt6==6.4.3 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.5.txt b/misc/requirements/requirements-pyqt-6.5.txt index 5dca9ab74..dafa1b9bc 100644 --- a/misc/requirements/requirements-pyqt-6.5.txt +++ b/misc/requirements/requirements-pyqt-6.5.txt @@ -2,6 +2,6 @@ PyQt6==6.5.3 PyQt6-Qt6==6.5.3 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.5.0 PyQt6-WebEngine-Qt6==6.5.3 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.6.txt b/misc/requirements/requirements-pyqt-6.6.txt index 02f1a325f..b0463bfab 100644 --- a/misc/requirements/requirements-pyqt-6.6.txt +++ b/misc/requirements/requirements-pyqt-6.6.txt @@ -2,6 +2,6 @@ PyQt6==6.6.1 PyQt6-Qt6==6.6.3 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.6.0 PyQt6-WebEngine-Qt6==6.6.3 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index d42d1dfdc..218e995e8 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -2,7 +2,7 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.2 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.2 PyQt6-WebEngineSubwheel-Qt6==6.7.2 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index d42d1dfdc..218e995e8 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -2,7 +2,7 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.2 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.2 PyQt6-WebEngineSubwheel-Qt6==6.7.2 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index d42d1dfdc..218e995e8 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -2,7 +2,7 @@ PyQt6==6.7.0 PyQt6-Qt6==6.7.2 -PyQt6-sip==13.6.0 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.2 PyQt6-WebEngineSubwheel-Qt6==6.7.2 +PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c515f304c..4d37426d1 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -7,13 +7,13 @@ certifi==2024.7.4 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 -coverage==7.5.4 -exceptiongroup==1.2.1 +coverage==7.6.0 +exceptiongroup==1.2.2 execnet==2.1.1 filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.105.1 +hypothesis==6.108.2 idna==3.7 importlib_metadata==8.0.0 iniconfig==2.0.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 7841d6165..2628de1bd 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -10,7 +10,7 @@ pip==24.1.2 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.7.1 -setuptools==70.2.0 +setuptools==70.3.0 tomli==2.0.1 tox==4.16.0 virtualenv==20.26.3 From 086c1b4c085d8064f3c133f68ca2caf5ebf098c6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 15 Jul 2024 08:09:13 +0200 Subject: [PATCH 140/403] scripts: Adjust PyQt[56]-sip package names --- scripts/dev/changelog_urls.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index b8f0ac50d..7f0c265b4 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -98,7 +98,7 @@ "PyQtWebEngine": "https://www.riverbankcomputing.com/news", "PyQtWebEngine-Qt5": "https://www.riverbankcomputing.com/news", "PyQt-builder": "https://pyqt-builder.readthedocs.io/en/stable/releases.html", - "PyQt5-sip": "https://www.riverbankcomputing.com/news", + "PyQt5_sip": "https://www.riverbankcomputing.com/news", "PyQt5-stubs": "https://github.com/python-qt-tools/PyQt5-stubs/blob/master/CHANGELOG.md", "sip": "https://python-sip.readthedocs.io/en/stable/releases.html", "PyQt6": "https://www.riverbankcomputing.com/news", @@ -106,7 +106,7 @@ "PyQt6-WebEngine": "https://www.riverbankcomputing.com/news", "PyQt6-WebEngine-Qt6": "https://www.riverbankcomputing.com/news", "PyQt6-WebEngineSubwheel-Qt6": "https://www.riverbankcomputing.com/news", - "PyQt6-sip": "https://www.riverbankcomputing.com/news", + "PyQt6_sip": "https://www.riverbankcomputing.com/news", "Pygments": "https://pygments.org/docs/changelog/", "vulture": "https://github.com/jendrikseipp/vulture/blob/main/CHANGELOG.md", "distlib": "https://github.com/pypa/distlib/blob/master/CHANGES.rst", From 5003929cdd39557eba5192e50932ac878c990d95 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 15 Jul 2024 08:11:10 +0200 Subject: [PATCH 141/403] pakjoy: Fix Qt 5 tests --- tests/unit/misc/test_pakjoy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index 6f79b81b5..61d1fc3df 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -452,6 +452,7 @@ class TestWithConstructedResourcesFile: ) assert pakjoy.RESOURCES_ENV_VAR not in os.environ + @pytest.mark.qt6_only def test_explicitly_enabled(self, monkeypatch: pytest.MonkeyPatch, config_stub): patch_version(monkeypatch, utils.VersionNumber(6, 7)) # unaffected config_stub.val.qt.workarounds.disable_hangouts_extension = True From d65489da9151d9da6e42f08348418ebafb6e8118 Mon Sep 17 00:00:00 2001 From: Fernando Ramos Date: Sun, 21 Jul 2024 21:23:43 +0200 Subject: [PATCH 142/403] Update link to match patterns documentation --- doc/help/configuring.asciidoc | 2 +- doc/help/settings.asciidoc | 2 +- qutebrowser/config/configtypes.py | 5 +++-- qutebrowser/utils/urlmatch.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index d61743040..54f4f34f9 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -31,7 +31,7 @@ patterns. The link:settings{outfilesuffix}[settings documentation] marks such settings with "This setting supports URL patterns. The syntax is based on Chromium's -https://developer.chrome.com/docs/extensions/mv3/match_patterns/[URL pattern syntax]. +https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns/[URL pattern syntax]. As an extension, the scheme and path can be left off as a short-hand syntax, so `example.com` is equivalent to `*://example.com/*`. diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 9bae037f2..49015c50c 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -4874,6 +4874,6 @@ See the setting's valid values for more information on allowed values. |Url|A URL as a string. |UrlPattern|A match pattern for a URL. -See https://developer.chrome.com/apps/match_patterns for the allowed syntax. +See https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns for the allowed syntax. |VerticalPosition|The position of the download bar. |============== diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index e789437d3..7be8cdd52 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -1984,8 +1984,9 @@ class UrlPattern(BaseType): """A match pattern for a URL. - See https://developer.chrome.com/apps/match_patterns for the allowed - syntax. + See + https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns + for the allowed syntax. """ def to_py( diff --git a/qutebrowser/utils/urlmatch.py b/qutebrowser/utils/urlmatch.py index 620e4d143..55b0037dc 100644 --- a/qutebrowser/utils/urlmatch.py +++ b/qutebrowser/utils/urlmatch.py @@ -5,7 +5,7 @@ """A Chromium-like URL matching pattern. See: -https://developer.chrome.com/apps/match_patterns +https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns https://cs.chromium.org/chromium/src/extensions/common/url_pattern.cc https://cs.chromium.org/chromium/src/extensions/common/url_pattern.h From c3f34a8a3962b83945782317b82d801380dabe23 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Jul 2024 11:20:17 +1200 Subject: [PATCH 143/403] update docs and changelog for URL match patterns link --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index da0643023..609cf6ab7 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -47,6 +47,8 @@ Fixed - A minor memory leak of QItemSelectionModels triggered by closing the completion dialog has been resolved. (#7950) +- The link to the chrome https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns/[URL match pattern] + documentation in our settings docs now loads a live page again. (#8268) [[v3.2.1]] v3.2.1 (2024-06-25) From 779bb739202711096a6a85f69e143ba74ab237d3 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 28 Jul 2024 16:00:17 +1200 Subject: [PATCH 144/403] Update pakjoy and chromium versions for Qt6.8 Looks like the kde-unstable arch repo has updated again. It says 6.8.0beta2-1. I guess the number might change again in the future, still a couple of months to go before release. --- qutebrowser/misc/pakjoy.py | 1 + qutebrowser/utils/version.py | 6 ++---- tests/unit/misc/test_pakjoy.py | 7 ++++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index 787c5c916..b2ebbc4b3 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -39,6 +39,7 @@ from qutebrowser.utils import qtutils, standarddir, version, utils, log, message HANGOUTS_MARKER = b"// Extension ID: nkeimhogjdpnpccoofpliimaahmaaome" HANGOUTS_IDS = [ # Linux + 43722, # QtWebEngine 6.8 41262, # QtWebEngine 6.7 36197, # QtWebEngine 6.6 34897, # QtWebEngine 6.5 diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 015566a27..ea794597c 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -548,7 +548,7 @@ class WebEngineVersions: 108: '108.0.5359.220', # ~2022-12-23 112: '112.0.5615.213', # ~2023-04-18 118: '118.0.5993.220', # ~2023-10-24 - # 122: '122.?.????.???', # ~2024-??-?? + 122: '122.0.6261.171', # ~2024-??-?? } _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, Tuple[str, Optional[str]]]] = { @@ -630,9 +630,7 @@ class WebEngineVersions: utils.VersionNumber(6, 7, 2): (_BASES[118], '125.0.6422.142'), # 2024-05-30 ## Qt 6.8 - # WORKAROUND for 6.8 chromium not being up to date yet. These numbers - # will need to be bumped at least once before it's released. - utils.VersionNumber(6, 8): (_BASES[118], '122.0.6261.128'), # 2024-03-12 + utils.VersionNumber(6, 8): (_BASES[122], '124.0.6367.202'), # 2024-??-?? } def __post_init__(self) -> None: diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py index 61d1fc3df..3065243f0 100644 --- a/tests/unit/misc/test_pakjoy.py +++ b/tests/unit/misc/test_pakjoy.py @@ -269,7 +269,12 @@ class TestWithRealResourcesFile: file_to_patch = resources_dir / pakjoy.PAK_FILENAME with open(file_to_patch, "rb") as f: parser = pakjoy.PakParser(f) - assert parser.manifest_entry.resource_id in pakjoy.HANGOUTS_IDS + error_msg = ( + "Encountered hangouts extension with resource ID which isn't in pakjoy.HANGOUTS_IDS: " + f"found_resource_id={parser.manifest_entry.resource_id} " + f"webengine_version={versions.webengine}" + ) + assert parser.manifest_entry.resource_id in pakjoy.HANGOUTS_IDS, error_msg def json_manifest_factory(extension_id=pakjoy.HANGOUTS_MARKER, url=pakjoy.TARGET_URL): From 43fa657f55c08256c1f8fe39b9c1348f499f4831 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 28 Jul 2024 18:58:29 +1200 Subject: [PATCH 145/403] Make failed subframe styling error matching more flexible In the Qt6.8.0-beta2 release for some reason the error message now looks like; Failed to style frame: Failed to read a named property '_qutebrowser' from 'Window': Blocked a frame with origin "http://localhost:35549" from accessing a cross-origin frame. It seems to have an extra "Failed to read a named property '_qutebrowser' from 'Window'" before the "Blocked a frame ..." bit. Seems like maybe a nested exception situation? Not sure what's going on there but the exception is still being caught, which is the point of the test. Hopefully we don't have more issues with subframes cropping up... --- tests/end2end/features/misc.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 6e606a195..c66d89d40 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -151,7 +151,7 @@ Feature: Various utility commands. When I load a third-party iframe # rerun set_css in stylesheet.js And I set content.user_stylesheets to [] - Then the javascript message "Failed to style frame: Blocked a frame with origin * from accessing *" should be logged + Then the javascript message "Failed to style frame:* Blocked a frame with origin * from accessing *" should be logged # :debug-webaction From b9441cad45bf5995fc1a98a57886ad98be9d0618 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 4 Aug 2024 21:00:50 +0200 Subject: [PATCH 146/403] Fix crash when the renderer process terminates for an unknown reason With PyQt 6, this gets represented as QWebEnginePage.RenderProcessTerminationStatus(-1) which is != -1, thus leading to a KeyError. Updating to a RendererProcessTerminationStatus enum value works fine on both PyQt5 and PyQt6. --- doc/changelog.asciidoc | 2 ++ qutebrowser/browser/webengine/webenginetab.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 609cf6ab7..b99363ec0 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -49,6 +49,8 @@ Fixed completion dialog has been resolved. (#7950) - The link to the chrome https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns/[URL match pattern] documentation in our settings docs now loads a live page again. (#8268) +- A rare crash when on Qt 6, a renderer process terminates with an unknown + termination reason. [[v3.2.1]] v3.2.1 (2024-06-25) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 48ae7ea50..04ed7c409 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -1513,7 +1513,7 @@ class WebEngineTab(browsertab.AbstractTab): browsertab.TerminationStatus.crashed, QWebEnginePage.RenderProcessTerminationStatus.KilledTerminationStatus: browsertab.TerminationStatus.killed, - -1: + QWebEnginePage.RenderProcessTerminationStatus(-1): browsertab.TerminationStatus.unknown, } self.renderer_process_terminated.emit(status_map[status], exitcode) From 59b2cc71ce06d043f87df59f13af9f1ed91c8190 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 12 Aug 2024 04:21:43 +0000 Subject: [PATCH 147/403] Update dependencies --- .../requirements-check-manifest.txt | 4 +-- misc/requirements/requirements-dev.txt | 22 ++++++++++------ misc/requirements/requirements-docs.txt | 2 +- misc/requirements/requirements-flake8.txt | 6 ++--- misc/requirements/requirements-mypy.txt | 14 +++++------ .../requirements/requirements-pyinstaller.txt | 8 +++--- misc/requirements/requirements-pylint.txt | 10 ++++---- misc/requirements/requirements-pyqt-5.15.txt | 4 +-- misc/requirements/requirements-pyqt-5.txt | 4 +-- misc/requirements/requirements-pyqt-6.7.txt | 2 +- misc/requirements/requirements-pyqt-6.txt | 2 +- misc/requirements/requirements-pyqt.txt | 2 +- misc/requirements/requirements-pyroma.txt | 4 +-- misc/requirements/requirements-sphinx.txt | 6 ++--- misc/requirements/requirements-tests.txt | 25 +++++++++++++------ misc/requirements/requirements-tox.txt | 10 ++++---- misc/requirements/requirements-yamllint.txt | 2 +- requirements.txt | 4 +-- 18 files changed, 73 insertions(+), 58 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 5648e13de..d86f50b78 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -2,8 +2,8 @@ build==1.2.1 check-manifest==0.49 -importlib_metadata==8.0.0 +importlib_metadata==8.2.0 packaging==24.1 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.19.2 +zipp==3.20.0 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 3e25d7b9b..1ffa897f9 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,33 +1,38 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py +autocommand==2.2.2 backports.tarfile==1.2.0 build==1.2.1 bump2version==1.0.1 certifi==2024.7.4 -cffi==1.16.0 +cffi==1.17.0 charset-normalizer==3.3.2 -cryptography==42.0.8 +cryptography==43.0.0 docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 idna==3.7 -importlib_metadata==8.0.0 +importlib_metadata==8.2.0 importlib_resources==6.4.0 +inflect==7.3.1 jaraco.classes==3.4.0 jaraco.context==5.3.0 -jaraco.functools==4.0.1 +jaraco.functools==4.0.2 +jaraco.text==3.12.1 jeepney==0.8.0 -keyring==25.2.1 +keyring==25.3.0 manhole==1.8.1 markdown-it-py==3.0.0 mdurl==0.1.2 -more-itertools==10.3.0 +more-itertools==10.4.0 nh3==0.2.18 +ordered-set==4.1.0 packaging==24.1 pkginfo==1.10.0 +platformdirs==4.2.2 pycparser==2.22 Pygments==2.18.0 -PyJWT==2.8.0 +PyJWT==2.9.0 Pympler==1.1 pyproject_hooks==1.1.0 PyQt-builder==1.16.4 @@ -42,7 +47,8 @@ sip==6.8.6 six==1.16.0 tomli==2.0.1 twine==5.1.1 +typeguard==4.3.0 typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.2 -zipp==3.19.2 +zipp==3.20.0 diff --git a/misc/requirements/requirements-docs.txt b/misc/requirements/requirements-docs.txt index d2d35d758..50a00d64d 100644 --- a/misc/requirements/requirements-docs.txt +++ b/misc/requirements/requirements-docs.txt @@ -1,3 +1,3 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -asciidoc==10.2.0 +asciidoc==10.2.1 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 6e233690f..ab5d61413 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -attrs==23.2.0 -flake8==7.1.0 +attrs==24.2.0 +flake8==7.1.1 flake8-bugbear==24.4.26 flake8-builtins==2.5.0 flake8-comprehensions==3.15.0 @@ -16,7 +16,7 @@ flake8-tidy-imports==4.10.0 flake8-tuple==0.4.1 mccabe==0.7.0 pep8-naming==0.14.1 -pycodestyle==2.12.0 +pycodestyle==2.12.1 pydocstyle==6.3.0 pyflakes==3.2.0 six==1.16.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 67bddc858..613795358 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,21 +1,21 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==5.2.0 -diff_cover==9.1.0 +diff_cover==9.1.1 importlib_resources==6.4.0 Jinja2==3.1.4 -lxml==5.2.2 +lxml==5.3.0 MarkupSafe==2.1.5 -mypy==1.10.1 +mypy==1.11.1 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-colorama==0.4.15.20240311 -types-docutils==0.21.0.20240711 +types-docutils==0.21.0.20240724 types-Pygments==2.18.0.20240506 -types-PyYAML==6.0.12.20240311 -types-setuptools==70.3.0.20240710 +types-PyYAML==6.0.12.20240808 +types-setuptools==71.1.0.20240806 typing_extensions==4.12.2 -zipp==3.19.2 +zipp==3.20.0 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 8444867c3..406f77a6c 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py altgraph==0.17.4 -importlib_metadata==8.0.0 +importlib_metadata==8.2.0 packaging==24.1 -pyinstaller==6.9.0 -pyinstaller-hooks-contrib==2024.7 -zipp==3.19.2 +pyinstaller==6.10.0 +pyinstaller-hooks-contrib==2024.8 +zipp==3.20.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index b7705a127..583313a2e 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,10 +1,10 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==3.2.3 +astroid==3.2.4 certifi==2024.7.4 -cffi==1.16.0 +cffi==1.17.0 charset-normalizer==3.3.2 -cryptography==42.0.8 +cryptography==43.0.0 dill==0.3.8 github3.py==4.0.1 idna==3.7 @@ -13,8 +13,8 @@ mccabe==0.7.0 pefile==2023.2.7 platformdirs==4.2.2 pycparser==2.22 -PyJWT==2.8.0 -pylint==3.2.5 +PyJWT==2.9.0 +pylint==3.2.6 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt index 00bd02351..e2473d653 100644 --- a/misc/requirements/requirements-pyqt-5.15.txt +++ b/misc/requirements/requirements-pyqt-5.15.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.15.10 # rq.filter: < 5.16 +PyQt5==5.15.11 # rq.filter: < 5.16 PyQt5-Qt5==5.15.14 PyQt5_sip==12.15.0 -PyQtWebEngine==5.15.6 # rq.filter: < 5.16 +PyQtWebEngine==5.15.7 # rq.filter: < 5.16 PyQtWebEngine-Qt5==5.15.14 diff --git a/misc/requirements/requirements-pyqt-5.txt b/misc/requirements/requirements-pyqt-5.txt index 46d68a5a2..f3a57b692 100644 --- a/misc/requirements/requirements-pyqt-5.txt +++ b/misc/requirements/requirements-pyqt-5.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.15.10 +PyQt5==5.15.11 PyQt5-Qt5==5.15.14 PyQt5_sip==12.15.0 -PyQtWebEngine==5.15.6 +PyQtWebEngine==5.15.7 PyQtWebEngine-Qt5==5.15.14 diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index 218e995e8..64e171c1b 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.7.0 +PyQt6==6.7.1 PyQt6-Qt6==6.7.2 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.2 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 218e995e8..64e171c1b 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.7.0 +PyQt6==6.7.1 PyQt6-Qt6==6.7.2 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.2 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 218e995e8..64e171c1b 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt6==6.7.0 +PyQt6==6.7.1 PyQt6-Qt6==6.7.2 PyQt6-WebEngine==6.7.0 PyQt6-WebEngine-Qt6==6.7.2 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index a2084b1ea..26478433c 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -5,7 +5,7 @@ certifi==2024.7.4 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 -importlib_metadata==8.0.0 +importlib_metadata==8.2.0 packaging==24.1 Pygments==2.18.0 pyproject_hooks==1.1.0 @@ -14,4 +14,4 @@ requests==2.32.3 tomli==2.0.1 trove-classifiers==2024.7.2 urllib3==2.2.2 -zipp==3.19.2 +zipp==3.20.0 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 45892321f..fa15e97fb 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -1,13 +1,13 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py alabaster==0.7.13 -Babel==2.15.0 +babel==2.16.0 certifi==2024.7.4 charset-normalizer==3.3.2 docutils==0.20.1 idna==3.7 imagesize==1.4.1 -importlib_metadata==8.0.0 +importlib_metadata==8.2.0 Jinja2==3.1.4 MarkupSafe==2.1.5 packaging==24.1 @@ -23,4 +23,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==2.2.2 -zipp==3.19.2 +zipp==3.20.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 4d37426d1..8514f7730 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,36 +1,44 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -attrs==23.2.0 +attrs==24.2.0 +autocommand==2.2.2 +backports.tarfile==1.2.0 beautifulsoup4==4.12.3 blinker==1.8.2 certifi==2024.7.4 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 -coverage==7.6.0 +coverage==7.6.1 exceptiongroup==1.2.2 execnet==2.1.1 filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.108.2 +hypothesis==6.111.0 idna==3.7 -importlib_metadata==8.0.0 +importlib_metadata==8.2.0 +importlib_resources==6.4.0 +inflect==7.3.1 iniconfig==2.0.0 itsdangerous==2.2.0 -jaraco.functools==4.0.1 +jaraco.context==5.3.0 +jaraco.functools==4.0.2 +jaraco.text==3.12.1 # Jinja2==3.1.4 Mako==1.3.5 manhole==1.8.1 # MarkupSafe==2.1.5 -more-itertools==10.3.0 +more-itertools==10.4.0 +ordered-set==4.1.0 packaging==24.1 parse==1.20.2 parse-type==0.6.2 +platformdirs==4.2.2 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 -pytest==8.2.2 +pytest==8.3.2 pytest-bdd==7.2.0 pytest-benchmark==4.0.0 pytest-cov==5.0.0 @@ -49,8 +57,9 @@ sortedcontainers==2.4.0 soupsieve==2.5 tldextract==5.1.2 tomli==2.0.1 +typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.2 vulture==2.11 Werkzeug==3.0.3 -zipp==3.19.2 +zipp==3.20.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 2628de1bd..e310a6573 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,17 +1,17 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -cachetools==5.3.3 +cachetools==5.4.0 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 filelock==3.15.4 packaging==24.1 -pip==24.1.2 +pip==24.2 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.7.1 -setuptools==70.3.0 +setuptools==72.1.0 tomli==2.0.1 -tox==4.16.0 +tox==4.17.1 virtualenv==20.26.3 -wheel==0.43.0 +wheel==0.44.0 diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt index 4fb649ec4..a501b1d56 100644 --- a/misc/requirements/requirements-yamllint.txt +++ b/misc/requirements/requirements-yamllint.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py pathspec==0.12.1 -PyYAML==6.0.1 +PyYAML==6.0.2 yamllint==1.35.1 diff --git a/requirements.txt b/requirements.txt index 969a4d2b8..cc5071ca9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,8 @@ importlib_resources==6.4.0 ; python_version=="3.8.*" Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 -PyYAML==6.0.1 -zipp==3.19.2 +PyYAML==6.0.2 +zipp==3.20.0 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From e2f718a5180c5fb89b4cab8d3a42242ab59ae229 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Jul 2024 15:56:54 +1200 Subject: [PATCH 148/403] Adjust some type hints to better match parent classes mypy 1.11 has stricter checking of the type signature overridden methods: https://github.com/python/mypy/blob/master/CHANGELOG.md#stricter-checks-for-untyped-overrides There's a couple of places where I added type hints and had to duplicate the default kwarg value from the parent. In `completionmodel.py` it was complaining that the type signature of `parent()` didn't match that of `QAbstractItemModel` and `QObject`. I've changed it to be happy, and incidently made it so the positional arg is optional, otherwise it's impossible to call `QObject.parent()`. Options that I see: 1. support both variant of parent() - what I've done, the technically correct solution 2. have the two overload definitions but in the actual implementation make the positional argument required - would mean one overload signature was a lie, but would make it more clear how to call `CompletionModel.parent() 3. do type: ignore[override] and leave it as it was In the end I don't expect there to be many callers of `CompletionModel.parent(child)`. I also added a few more function type hints to `completionmodel.py` while I was there. Not all of them though! In `objreg.py` I expanded the user of `_IndexType` because as 7b9d70203fa say, the window register uses int as the key. --- qutebrowser/browser/downloads.py | 2 +- qutebrowser/browser/network/proxy.py | 6 ++--- .../completion/models/completionmodel.py | 25 +++++++++++++------ qutebrowser/utils/objreg.py | 6 ++--- 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index 28f20b0ef..fbbf4ff11 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -1264,7 +1264,7 @@ class DownloadModel(QAbstractListModel): else: return "" - def data(self, index, role): + def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any: """Download data from DownloadManager.""" if not index.isValid(): return None diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index 62872d68e..5b29c29fc 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -4,10 +4,10 @@ """Handling of proxies.""" -from typing import Optional +from typing import Optional, List from qutebrowser.qt.core import QUrl, pyqtSlot -from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory +from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory, QNetworkProxyQuery from qutebrowser.config import config, configtypes from qutebrowser.utils import message, usertypes, urlutils, utils, qtutils @@ -71,7 +71,7 @@ class ProxyFactory(QNetworkProxyFactory): capabilities &= ~lookup_cap proxy.setCapabilities(capabilities) - def queryProxy(self, query): + def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> List[QNetworkProxy]: """Get the QNetworkProxies for a query. Args: diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 6ddf27dcf..808608e85 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -4,9 +4,9 @@ """A model that proxies access to one or more completion categories.""" -from typing import MutableSequence +from typing import MutableSequence, overload, Optional, Any -from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel +from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel, QObject from qutebrowser.utils import log, qtutils, utils from qutebrowser.api import cmdutils @@ -30,7 +30,7 @@ class CompletionModel(QAbstractItemModel): self.column_widths = column_widths self._categories: MutableSequence[QAbstractItemModel] = [] - def _cat_from_idx(self, index): + def _cat_from_idx(self, index: QModelIndex): """Return the category pointed to by the given index. Args: @@ -48,7 +48,7 @@ class CompletionModel(QAbstractItemModel): """Add a completion category to the model.""" self._categories.append(cat) - def data(self, index, role=Qt.ItemDataRole.DisplayRole): + def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole) -> Any: """Return the item data for index. Override QAbstractItemModel::data. @@ -74,7 +74,7 @@ class CompletionModel(QAbstractItemModel): idx = cat.index(index.row(), index.column()) return cat.data(idx) - def flags(self, index): + def flags(self, index: QModelIndex) -> Qt.ItemFlag: """Return the item flags for index. Override QAbstractItemModel::flags. @@ -91,7 +91,7 @@ class CompletionModel(QAbstractItemModel): # category return Qt.ItemFlag.NoItemFlags - def index(self, row, col, parent=QModelIndex()): + def index(self, row: int, col: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: """Get an index into the model. Override QAbstractItemModel::index. @@ -108,7 +108,15 @@ class CompletionModel(QAbstractItemModel): return self.createIndex(row, col, self._categories[parent.row()]) return self.createIndex(row, col, None) - def parent(self, index): + @overload + def parent(self, index: QModelIndex) -> QModelIndex: + ... + + @overload + def parent(self) -> Optional[QObject]: + ... + + def parent(self, index=None): """Get an index to the parent of the given index. Override QAbstractItemModel::parent. @@ -116,6 +124,9 @@ class CompletionModel(QAbstractItemModel): Args: index: The QModelIndex to get the parent index for. """ + if not index: + return QObject.parent(self) + parent_cat = index.internalPointer() if not parent_cat: # categories have no parent diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index c0715d90a..8a3489d09 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -77,7 +77,7 @@ class ObjectRegistry(collections.UserDict): # type: ignore[type-arg] super().__setitem__(name, obj) - def __delitem__(self, name: str) -> None: + def __delitem__(self, name: _IndexType) -> None: """Extend __delitem__ to disconnect the destroyed signal.""" self._disconnect_destroyed(name) super().__delitem__(name) @@ -101,7 +101,7 @@ class ObjectRegistry(collections.UserDict): # type: ignore[type-arg] pass del partial_objs[name] - def on_destroyed(self, name: str) -> None: + def on_destroyed(self, name: _IndexType) -> None: """Schedule removing of a destroyed QObject. We don't remove the destroyed object immediately because it might still @@ -111,7 +111,7 @@ class ObjectRegistry(collections.UserDict): # type: ignore[type-arg] log.destroy.debug("schedule removal: {}".format(name)) QTimer.singleShot(0, functools.partial(self._on_destroyed, name)) - def _on_destroyed(self, name: str) -> None: + def _on_destroyed(self, name: _IndexType) -> None: """Remove a destroyed QObject.""" log.destroy.debug("removed: {}".format(name)) if not hasattr(self, 'data'): From b91e14264347f78a6e027692aeb48f869351668d Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Jul 2024 17:31:10 +1200 Subject: [PATCH 149/403] Remove `callback` arg to webkit print preview mypy 1.11 has new and improved support for checking partial functions, and it works great! It says: qutebrowser/components/misccommands.py: note: In function "_print_preview": qutebrowser/components/misccommands.py:74: error: Unexpected keyword argument "callback" for "to_printer" of "AbstractPrinting" [call-arg] diag.paintRequested.connect(functools.partial( ^ qutebrowser/browser/browsertab.py:269: note: "to_printer" of "AbstractPrinting" defined here We indeed removed the callback arg in 377749c76f7080507dc64 And running `:print --preview` on webkit crashes with: TypeError: WebKitPrinting.to_printer() got an unexpected keyword argument 'callback' With this change print preview works again (on webkit), which I'm a little surprised by! --- qutebrowser/components/misccommands.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py index e3ffb82d0..0d8fa0b2e 100644 --- a/qutebrowser/components/misccommands.py +++ b/qutebrowser/components/misccommands.py @@ -9,7 +9,6 @@ import os import signal -import functools import logging import pathlib from typing import Optional, Sequence, Callable @@ -60,10 +59,6 @@ def stop(tab: Optional[apitypes.Tab]) -> None: def _print_preview(tab: apitypes.Tab) -> None: """Show a print preview.""" - def print_callback(ok: bool) -> None: - if not ok: - message.error("Printing failed!") - tab.printing.check_preview_support() diag = QPrintPreviewDialog(tab) diag.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose) @@ -71,8 +66,7 @@ def _print_preview(tab: apitypes.Tab) -> None: diag.windowFlags() | Qt.WindowType.WindowMaximizeButtonHint | Qt.WindowType.WindowMinimizeButtonHint) - diag.paintRequested.connect(functools.partial( - tab.printing.to_printer, callback=print_callback)) + diag.paintRequested.connect(tab.printing.to_printer) diag.exec() From 98f85cbfcc63cb7cdeed2c6f0657e832a917e581 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Jul 2024 17:46:53 +1200 Subject: [PATCH 150/403] Fix more type hints New mypy 3.11 update got smarter and raise some issues, they appear to be correct in all cases. There are several `type: ignore[unreachable]` comments in conditionals on `sys.stderr` being None, which were introduce in a comment specifically to handle a case where `sys.stderr` could be None. So presumable the ignore comments were just to shut mypy up when it was making mistakes. In `debug.py` it was complaining that the class handling branch was unreachable, because the type hint was overly restrictive. We do indeed handle both classes and objects. `log.py` got some extra Optional annotations around a variable that isn't set if `sys.stderr` is None. --- qutebrowser/misc/crashsignal.py | 2 +- qutebrowser/utils/debug.py | 3 ++- qutebrowser/utils/log.py | 16 ++++++++++------ qutebrowser/utils/utils.py | 8 ++++---- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 05e5806df..c33ae1173 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -178,7 +178,7 @@ class CrashHandler(QObject): if sys.__stderr__ is not None: faulthandler.enable(sys.__stderr__) else: - faulthandler.disable() # type: ignore[unreachable] + faulthandler.disable() try: self._crash_log_file.close() os.remove(self._crash_log_file.name) diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 433e2274f..230c965ef 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -38,7 +38,7 @@ def log_events(klass: Type[QObject]) -> Type[QObject]: return klass -def log_signals(obj: QObject) -> QObject: +def log_signals(obj: Union[QObject, Type[QObject]]) -> Union[QObject, Type[QObject]]: """Log all signals of an object or class. Can be used as class decorator. @@ -80,6 +80,7 @@ def log_signals(obj: QObject) -> QObject: obj.__init__ = new_init else: + assert isinstance(obj, QObject) connect_log_slot(obj) return obj diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 5029ce3d7..b8d1a0b92 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -240,7 +240,7 @@ def _init_handlers( force_color: bool, json_logging: bool, ram_capacity: int -) -> Tuple["logging.StreamHandler[TextIO]", Optional['RAMHandler']]: +) -> Tuple[Optional["logging.StreamHandler[TextIO]"], Optional['RAMHandler']]: """Init log handlers. Args: @@ -255,7 +255,7 @@ def _init_handlers( level, color, force_color, json_logging) if sys.stderr is None: - console_handler = None # type: ignore[unreachable] + console_handler = None else: strip = False if force_color else None if use_colorama: @@ -293,9 +293,13 @@ def _init_formatters( level: int, color: bool, force_color: bool, - json_logging: bool -) -> Tuple[Union['JSONFormatter', 'ColoredFormatter'], - 'ColoredFormatter', 'HTMLFormatter', bool]: + json_logging: bool, +) -> Tuple[ + Union['JSONFormatter', Optional['ColoredFormatter']], + 'ColoredFormatter', + 'HTMLFormatter', + bool, +]: """Init log formatters. Args: @@ -318,7 +322,7 @@ def _init_formatters( use_colorama = False if sys.stderr is None: - console_formatter = None # type: ignore[unreachable] + console_formatter = None return console_formatter, ram_formatter, html_formatter, use_colorama if json_logging: diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 13ccf5ca2..cf340e444 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -265,16 +265,16 @@ def fake_io(write_func: Callable[[str], int]) -> Iterator[None]: old_stderr = sys.stderr fake_stderr = FakeIOStream(write_func) fake_stdout = FakeIOStream(write_func) - sys.stderr = fake_stderr # type: ignore[assignment] - sys.stdout = fake_stdout # type: ignore[assignment] + sys.stderr = fake_stderr + sys.stdout = fake_stdout try: yield finally: # If the code we did run did change sys.stdout/sys.stderr, we leave it # unchanged. Otherwise, we reset it. - if sys.stdout is fake_stdout: # type: ignore[comparison-overlap] + if sys.stdout is fake_stdout: sys.stdout = old_stdout - if sys.stderr is fake_stderr: # type: ignore[comparison-overlap] + if sys.stderr is fake_stderr: sys.stderr = old_stderr From aceef82b241e69a83ac9fb113049ffa8dca39026 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 27 Jul 2024 17:51:58 +1200 Subject: [PATCH 151/403] mypy: Attempt to extract base class from completion categories The methods in `completionmodel.py` dealing with completion categories were annotated with `QAbstractItemModel`. In mypy's latest 3.11 update it correctly pointed out that there is code relying on some attributes, like `name`, being on the categories but `QAbstractItemModel` didn't implement those attributes. This commit adds a new base class for completion categories which defines the extra attributes we expect. It also changes the type hints to ensure all list categories inherit from it. There is a couple of downsides to the current implementation: * It's using multiple inheritance * the completionmodel code currently expects categories to have all the methods of `QAbstractItemModel` plus a few other attributes. Each of the categories inherit from a different Qt model, so we can't just remove the Qt model from their class definition. * trying to extract the Qt models to a `widget` class is way too much work to fit in a dependency update, and I'm not sure it'll be the right thing to do because the categories are primarily Qt models, so we would have have to proxy most methods. Perhaps if they added their extra metadata to a central registry or something * I tried using a typing.Protocol for BaseCategory but when trying to make it also inherit from QAbstractItemModel it got grumpy at me * It doesn't enforce that the attributes are actually set * it makes mypy happy that they are there, but there is nothing warning child classes they have forgotten to set them. Mypy does at least warn about categories that don't inherit from `BaseCategory` so implementors will hopefully go there an look at it. * Apparently you can do some stuff with abstract properties, that might even have type hinting support. But that's a bit much for me to want to pile in there tonight At lest the type hints in `completionmodel.py` are more correct now! --- qutebrowser/completion/models/__init__.py | 18 ++++++++++++++++++ .../completion/models/completionmodel.py | 7 ++++--- .../completion/models/filepathcategory.py | 3 ++- qutebrowser/completion/models/histcategory.py | 4 ++-- qutebrowser/completion/models/listcategory.py | 4 ++-- qutebrowser/completion/models/urlmodel.py | 7 +++---- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/qutebrowser/completion/models/__init__.py b/qutebrowser/completion/models/__init__.py index 5a19f438f..4fd45e160 100644 --- a/qutebrowser/completion/models/__init__.py +++ b/qutebrowser/completion/models/__init__.py @@ -3,3 +3,21 @@ # SPDX-License-Identifier: GPL-3.0-or-later """Models for the command completion.""" + +from typing import Sequence, Optional +from qutebrowser.completion.models.util import DeleteFuncType +from qutebrowser.qt.core import QAbstractItemModel + + +class BaseCategory(QAbstractItemModel): + """Abstract base class for categories of CompletionModels. + + Extends QAbstractItemModel with a few attributes we expect to be present. + + TODO: actually enforce that child classes set these variables, either via + mypy (how) or turning these variables into abstract properties, eg https://stackoverflow.com/a/50381071 + """ + + name: str + columns_to_filter: Sequence[int] + delete_func: Optional[DeleteFuncType] = None diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 808608e85..98301747b 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -10,6 +10,7 @@ from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel, QObject from qutebrowser.utils import log, qtutils, utils from qutebrowser.api import cmdutils +from qutebrowser.completion.models import BaseCategory class CompletionModel(QAbstractItemModel): @@ -28,9 +29,9 @@ class CompletionModel(QAbstractItemModel): def __init__(self, *, column_widths=(30, 70, 0), parent=None): super().__init__(parent) self.column_widths = column_widths - self._categories: MutableSequence[QAbstractItemModel] = [] + self._categories: MutableSequence[BaseCategory] = [] - def _cat_from_idx(self, index: QModelIndex): + def _cat_from_idx(self, index: QModelIndex) -> Optional[BaseCategory]: """Return the category pointed to by the given index. Args: @@ -44,7 +45,7 @@ class CompletionModel(QAbstractItemModel): return self._categories[index.row()] return None - def add_category(self, cat): + def add_category(self, cat: BaseCategory) -> None: """Add a completion category to the model.""" self._categories.append(cat) diff --git a/qutebrowser/completion/models/filepathcategory.py b/qutebrowser/completion/models/filepathcategory.py index 1f4c04dea..23ad0d173 100644 --- a/qutebrowser/completion/models/filepathcategory.py +++ b/qutebrowser/completion/models/filepathcategory.py @@ -18,11 +18,12 @@ from typing import List, Optional, Iterable from qutebrowser.qt.core import QAbstractListModel, QModelIndex, QObject, Qt, QUrl +from qutebrowser.completion.models import BaseCategory from qutebrowser.config import config from qutebrowser.utils import log -class FilePathCategory(QAbstractListModel): +class FilePathCategory(QAbstractListModel, BaseCategory): """Represent filesystem paths matching a pattern.""" def __init__(self, name: str, parent: QObject = None) -> None: diff --git a/qutebrowser/completion/models/histcategory.py b/qutebrowser/completion/models/histcategory.py index 2e54eae91..5b79b4ade 100644 --- a/qutebrowser/completion/models/histcategory.py +++ b/qutebrowser/completion/models/histcategory.py @@ -12,10 +12,10 @@ from qutebrowser.qt.widgets import QWidget from qutebrowser.misc import sql from qutebrowser.utils import debug, message, log from qutebrowser.config import config -from qutebrowser.completion.models import util +from qutebrowser.completion.models import util, BaseCategory -class HistoryCategory(QSqlQueryModel): +class HistoryCategory(QSqlQueryModel, BaseCategory): """A completion category that queries the SQL history store.""" diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index f92679cc6..10639f47d 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -11,11 +11,11 @@ from qutebrowser.qt.core import QSortFilterProxyModel, QRegularExpression from qutebrowser.qt.gui import QStandardItem, QStandardItemModel from qutebrowser.qt.widgets import QWidget -from qutebrowser.completion.models import util +from qutebrowser.completion.models import util, BaseCategory from qutebrowser.utils import qtutils, log -class ListCategory(QSortFilterProxyModel): +class ListCategory(QSortFilterProxyModel, BaseCategory): """Expose a list of items as a category for the CompletionModel.""" diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 0d6428348..10bee0393 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -6,10 +6,9 @@ from typing import Dict, Sequence -from qutebrowser.qt.core import QAbstractItemModel - from qutebrowser.completion.models import (completionmodel, filepathcategory, - listcategory, histcategory) + listcategory, histcategory, + BaseCategory) from qutebrowser.browser import history from qutebrowser.utils import log, objreg from qutebrowser.config import config @@ -59,7 +58,7 @@ def url(*, info): in sorted(config.val.url.searchengines.items()) if k != 'DEFAULT'] categories = config.val.completion.open_categories - models: Dict[str, QAbstractItemModel] = {} + models: Dict[str, BaseCategory] = {} if searchengines and 'searchengines' in categories: models['searchengines'] = listcategory.ListCategory( From 0050fc99dcf7750a4a360a254de49ede433488b0 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 28 Jul 2024 11:02:06 +1200 Subject: [PATCH 152/403] mypy: adapt new type hints to pyqt5 Ah! I'm having flashbacks to last year. 1. pyqt5 has plural enum names = define conditional type variable 2. pyqt5 doesn't wrap all the nullable things in Optional = sneakily make the existing overload function signature conditional. There might be some other was to solve this, not sure. I know we have qtutils.add_optional() but in this case it's complaining that the signature doesn't match the parent. Narrowing or widening the type of the returned object doesn't affect the function signature. Possibly we could define our own type variable MaybeOptional... --- .../completion/models/completionmodel.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 98301747b..5a85f7281 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -4,8 +4,9 @@ """A model that proxies access to one or more completion categories.""" -from typing import MutableSequence, overload, Optional, Any +from typing import MutableSequence, overload, Optional, Any, cast +from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel, QObject from qutebrowser.utils import log, qtutils, utils @@ -13,6 +14,12 @@ from qutebrowser.api import cmdutils from qutebrowser.completion.models import BaseCategory +if machinery.IS_QT5: + _FlagType = Qt.ItemFlags +else: + _FlagType = Qt.ItemFlag + + class CompletionModel(QAbstractItemModel): """A model that proxies access to one or more completion categories. @@ -75,7 +82,7 @@ class CompletionModel(QAbstractItemModel): idx = cat.index(index.row(), index.column()) return cat.data(idx) - def flags(self, index: QModelIndex) -> Qt.ItemFlag: + def flags(self, index: QModelIndex) -> _FlagType: """Return the item flags for index. Override QAbstractItemModel::flags. @@ -83,14 +90,14 @@ class CompletionModel(QAbstractItemModel): Return: The item flags, or Qt.ItemFlag.NoItemFlags on error. """ if not index.isValid(): - return Qt.ItemFlag.NoItemFlags + return cast(_FlagType, Qt.ItemFlag.NoItemFlags) if index.parent().isValid(): # item return (Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemNeverHasChildren) else: # category - return Qt.ItemFlag.NoItemFlags + return cast(_FlagType, Qt.ItemFlag.NoItemFlags) def index(self, row: int, col: int, parent: QModelIndex = QModelIndex()) -> QModelIndex: """Get an index into the model. @@ -113,9 +120,15 @@ class CompletionModel(QAbstractItemModel): def parent(self, index: QModelIndex) -> QModelIndex: ... - @overload - def parent(self) -> Optional[QObject]: - ... + if machinery.IS_QT5: + @overload + def parent(self) -> QObject: + ... + + else: + @overload + def parent(self) -> Optional[QObject]: + ... def parent(self, index=None): """Get an index to the parent of the given index. From 096a4edec201c4af2d023e4e9db831a12eff0d07 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 10 Aug 2024 19:26:41 +1200 Subject: [PATCH 153/403] Add changelog URLs Few new vendored packages showing up from setuptools for environments where pkg_resources is being imported for some reasons. I don't think these requirements should be in our requirements files, they aren't direct dependancies and they aren't declared as dependancies of setuptools (and we are currently excluding setuptools from our requirements files anyway, although apparently that is not the right thing to do these days). These are actually not installed as normal packages by are vendored packages shipped with setuptools. Options I see to deal with them: 1. suck it up and add them to the compiled requirements files * not ideal, but should be harmless. They are real packages that the setuptools authors have chose to use 2. exclude these new packages using the markers in comments * maybe, seems like it could lead to issues in the future if any of these packages start getting declared as proper dependancies 3. find out where pkg_resources is being imported and stop it * I don't seem to be able to reproduce this behaviour locally, even when using a py3.8 docker container. And we are literally only running `pip freeze` via subprocess, what could the difference be? * I don't particularly want to delve into the arcane python packaging stuff, it seems to be layers and layers of very specific issues and old vendored packages 4. stop using pip freeze to compile requirements files and just compute them based off of the raw files themselves * Don't give us the chance to use stuff that we don't depend on but happens to be installed. We get other nice things with this too This commit does (1). I'll open a follow up PR to do (4). --- scripts/dev/changelog_urls.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 7f0c265b4..2702b7bd2 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -146,6 +146,11 @@ "jaraco.classes": "https://jaracoclasses.readthedocs.io/en/latest/history.html", "jaraco.context": "https://jaracocontext.readthedocs.io/en/latest/history.html", "jaraco.functools": "https://jaracofunctools.readthedocs.io/en/latest/history.html", + "jaraco.text": "https://jaracotext.readthedocs.io/en/latest/history.html", + "autocommand": "https://github.com/Lucretiel/autocommand/releases", + "inflect": "https://inflect.readthedocs.io/en/latest/history.html", + "ordered-set": "https://github.com/rspeer/ordered-set/blob/master/CHANGELOG.md", + "typeguard": "https://typeguard.readthedocs.io/en/latest/versionhistory.html", "backports.tarfile": "https://github.com/jaraco/backports.tarfile/blob/main/NEWS.rst", "pkginfo": "https://bazaar.launchpad.net/~tseaver/pkginfo/trunk/view/head:/CHANGES.txt", "readme_renderer": "https://github.com/pypa/readme_renderer/blob/main/CHANGES.rst", From 452408870b7f41b6defd3fe5766ca42291015912 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 12 Aug 2024 18:58:59 +1200 Subject: [PATCH 154/403] adjust babel changelog (case change) --- scripts/dev/changelog_urls.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 2702b7bd2..87f5e6f68 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -68,7 +68,7 @@ "more-itertools": "https://github.com/more-itertools/more-itertools/blob/master/docs/versions.rst", "pydocstyle": "https://www.pydocstyle.org/en/latest/release_notes.html", "Sphinx": "https://www.sphinx-doc.org/en/master/changes.html", - "Babel": "https://github.com/python-babel/babel/blob/master/CHANGES.rst", + "babel": "https://github.com/python-babel/babel/blob/master/CHANGES.rst", "alabaster": "https://alabaster.readthedocs.io/en/latest/changelog.html", "imagesize": "https://github.com/shibukawa/imagesize_py/commits/master", "pytz": "https://mm.icann.org/pipermail/tz-announce/", From ba210f52f1436f79aff4dfa6436759aaa812f595 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Aug 2024 11:58:50 +0200 Subject: [PATCH 155/403] Simplify type annotation See #8269 --- qutebrowser/utils/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index b8d1a0b92..aa3ea4123 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -295,7 +295,7 @@ def _init_formatters( force_color: bool, json_logging: bool, ) -> Tuple[ - Union['JSONFormatter', Optional['ColoredFormatter']], + Union['JSONFormatter', 'ColoredFormatter', None], 'ColoredFormatter', 'HTMLFormatter', bool, From f3459a8f145e26ff398cf7fa0ec91187e87066f0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Aug 2024 16:03:54 +0200 Subject: [PATCH 156/403] Reset PyInstaller environment on :restart Starting with PyInstaller 6.10 (6.9?), we're supposed to tell PyInstaller when we restart our application (and a subprocess should outlive this process). In their words: The above requirement was introduced in PyInstaller 6.9, which changed the way the bootloader treats a process spawned via the same executable as its parent process. Whereas previously the default assumption was that it is running a new instance of (the same) program, the new assumption is that the spawned process is some sort of a worker subprocess that can reuse the already-unpacked resources. This change was done because the worker-process scenarios are more common, and more difficult to explicitly accommodate across various multiprocessing frameworks and other code that spawns worker processes via sys.executable. https://pyinstaller.org/en/stable/common-issues-and-pitfalls.html#independent-subprocess https://pyinstaller.org/en/stable/CHANGES.html (6.10) While from a quick test on Windows, things still worked without setting the variable (possibly because we don't use a onefile build), it still seems reasonable to do what PyInstaller recommends doing. Follow-up to #8269. --- qutebrowser/misc/quitter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qutebrowser/misc/quitter.py b/qutebrowser/misc/quitter.py index 825acfcd8..9fe743414 100644 --- a/qutebrowser/misc/quitter.py +++ b/qutebrowser/misc/quitter.py @@ -177,10 +177,17 @@ class Quitter(QObject): assert ipc.server is not None ipc.server.shutdown() + if hasattr(sys, 'frozen'): + # https://pyinstaller.org/en/stable/common-issues-and-pitfalls.html#independent-subprocess + env = os.environ.copy() + env["PYINSTALLER_RESET_ENVIRONMENT"] = "1" + else: + env = None + # Open a new process and immediately shutdown the existing one try: args = self._get_restart_args(pages, session, override_args) - proc = subprocess.Popen(args) # pylint: disable=consider-using-with + proc = subprocess.Popen(args, env=env) # pylint: disable=consider-using-with except OSError: log.destroy.exception("Failed to restart") return False From a3238eb494c8f3c35c6f423dcc3017902326ff01 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 26 Apr 2024 12:04:06 +1200 Subject: [PATCH 157/403] upload e2e failure screenshots as artifacts This commit takes a screenshot of the active browser window when an end2end test fails. When running on CI a zip file of screenshots will be attached to the run summary as an artifact. When run locally screenshots will be left in /$TMPDIR/pytest-screenshots/. The screenshot is of the Xvfb screen that the tests are running under. If there are multiple windows open it will likely only show the active window because a) we aren't running with a window manager b) the Xvfb display is, by default, the same size as the browser window. I'm not sure if xvfb is used on the Window runs in CI. We could fall back to trying to take screenshots if not running under xvfb but I'm a bit wary of an automatic feature that takes screenshots of people's desktops when running locally. Even if they just to to /tmp/ it might be surprising. We can change it later if it turns out we need to try to take screenshots in more cases. I'm using pillow ImageGrab, the same as pyvirtualdisplay.smartdisplay. I'm getting the display number from the pytest-xvfb plugin and formatting it appropriately (pyvirtualdisplay has an already formatted one which is used by the smartdisplay, but that's not accessible). Pillow is now a requirement for running the tests. I thought about making it gracefully not required but I'm not sure how to inform the user with a warning from pytest, or if they would even want one. Maybe we could add a config thing to allow not taking screenshots? I had to bump the colordepth config for pytest-xvfb otherwise pillow complained that the default 16bit color depth wasn't supported as it only supports 24bit, see https://github.com/python-pillow/Pillow/blob/1138ea5370cbda5eb328ec949 8c314d376c81265/src/display.c#L898 I'm saving screenshots to a temp dir because I don't want to put them in my workdir when running locally. I want to clear the directory for each run so that you don't get confused by looking at old images. I'm not 100% sure about the lifecycle of the process classes though. Eg if we have two processes they might both try to create the output directory. I'm using pytest.session.stash to save the directory so perhaps the lifecycle of the stash will handle that? Not sure. Ideally the images would be uploaded somewhere where we could click through and open them in the browser without having to download a zip file, but I'm not sure how to achieve that. It would be nice to print in the test logs that a screenshot was saved and where to. Just so you could copy paste the filename instead of having to match the sanitized filename against failing test names. But I don't know how to log stuff from this stage in the pytest lifecycle. TODO: * I'm not sure that the screenshot captures the whole browser window? Maybe the browser windows is bigger than the X11 display? Closes: #7625 --- .github/workflows/bleeding.yml | 15 ++++++ .github/workflows/ci.yml | 30 ++++++++++++ .../requirements-tests-bleeding.txt | 1 + misc/requirements/requirements-tests.txt | 1 + misc/requirements/requirements-tests.txt-raw | 1 + pytest.ini | 1 + scripts/dev/changelog_urls.json | 3 +- tests/end2end/fixtures/quteprocess.py | 46 +++++++++++++++++++ tests/end2end/fixtures/test_quteprocess.py | 14 +++++- tox.ini | 1 + 10 files changed, 111 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml index 47264b2e5..97d95adde 100644 --- a/.github/workflows/bleeding.yml +++ b/.github/workflows/bleeding.yml @@ -42,6 +42,21 @@ jobs: if: "endsWith(matrix.image, '-qt6')" - name: Run tox run: dbus-run-session tox -e ${{ matrix.testenv }} + - name: Gather info + id: info + run: | + echo "date=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" + echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + shell: bash + if: failure() + - name: Upload screenshots + uses: actions/upload-artifact@v4 + with: + name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}" + path: | + ${{ runner.temp }}/pytest-screenshots/*.png + if-no-files-found: ignore + if: failure() irc: timeout-minutes: 2 continue-on-error: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60f9d12ad..94f561999 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,21 @@ jobs: run: "python scripts/dev/ci/problemmatchers.py tests ${{ runner.temp }}" - name: Run tox run: "dbus-run-session -- tox -e ${{ matrix.testenv }}" + - name: Gather info + id: info + run: | + echo "date=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" + echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + shell: bash + if: failure() + - name: Upload screenshots + uses: actions/upload-artifact@v4 + with: + name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}" + path: | + ${{ runner.temp }}/pytest-screenshots/*.png + if-no-files-found: ignore + if: failure() tests: if: "!contains(github.event.head_commit.message, '[ci skip]')" @@ -236,6 +251,21 @@ jobs: uses: codecov/codecov-action@v3 with: name: "${{ matrix.testenv }}" + - name: Gather info + id: info + run: | + echo "date=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" + echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" + shell: bash + if: failure() + - name: Upload screenshots + uses: actions/upload-artifact@v4 + with: + name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}" + path: | + ${{ runner.temp }}/pytest-screenshots/*.png + if-no-files-found: ignore + if: failure() codeql: if: "!contains(github.event.head_commit.message, '[ci skip]')" diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt index f1ad30158..10369fc30 100644 --- a/misc/requirements/requirements-tests-bleeding.txt +++ b/misc/requirements/requirements-tests-bleeding.txt @@ -20,6 +20,7 @@ git+https://github.com/pygments/pygments.git git+https://github.com/pytest-dev/pytest-repeat.git git+https://github.com/pytest-dev/pytest-cov.git git+https://github.com/The-Compiler/pytest-xvfb.git +git+https://github.com/python-pillow/Pillow.git git+https://github.com/pytest-dev/pytest-xdist.git git+https://github.com/john-kurkowski/tldextract diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 8514f7730..10c66cc46 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -35,6 +35,7 @@ packaging==24.1 parse==1.20.2 parse-type==0.6.2 platformdirs==4.2.2 +pillow==10.4.0 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 54e036106..d37002954 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -25,6 +25,7 @@ pytest-cov # To avoid windows from popping up pytest-xvfb PyVirtualDisplay +pillow # To run on multiple cores with -n pytest-xdist diff --git a/pytest.ini b/pytest.ini index 6c8d29ff9..0129da083 100644 --- a/pytest.ini +++ b/pytest.ini @@ -85,3 +85,4 @@ filterwarnings = # Python 3.12: https://github.com/ionelmc/pytest-benchmark/issues/240 (fixed but not released) ignore:(datetime\.)?datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\. Use timezone-aware objects to represent datetimes in UTC. (datetime\.)?datetime\.now\(datetime\.UTC\)\.:DeprecationWarning:pytest_benchmark\.utils faulthandler_timeout = 90 +xvfb_colordepth = 24 diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 87f5e6f68..cfd82ad06 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -168,5 +168,6 @@ "mdurl": "https://github.com/executablebooks/mdurl/commits/master", "blinker": "https://blinker.readthedocs.io/en/stable/#changes", "exceptiongroup": "https://github.com/agronholm/exceptiongroup/blob/main/CHANGES.rst", - "nh3": "https://github.com/messense/nh3/commits/main" + "nh3": "https://github.com/messense/nh3/commits/main", + "pillow": "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst" } diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index b1e4bbaab..0d6edfda7 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -5,8 +5,10 @@ """Fixtures to run qutebrowser in a QProcess and communicate.""" import pathlib +import os import re import sys +import shutil import time import datetime import logging @@ -18,6 +20,7 @@ import json import yaml import pytest +from PIL.ImageGrab import grab from qutebrowser.qt.core import pyqtSignal, QUrl, QPoint from qutebrowser.qt.gui import QImage, QColor @@ -541,6 +544,8 @@ class QuteProc(testprocess.Process): except AttributeError: pass else: + if call.failed: + self._take_x11_screenshot_of_failed_test() if call.failed or hasattr(call, 'wasxfail') or call.skipped: super().after_test() return @@ -868,6 +873,47 @@ class QuteProc(testprocess.Process): self.send_cmd(cmd.format('no-scroll-filtering')) self.send_cmd(cmd.format('log-scroll-pos')) + def _get_x11_screenshot_directory(self): + screenshot_path = self.request.session.stash.get("screenshot_path", None) + if screenshot_path: + return screenshot_path + + temp_path = os.environ.get("RUNNER_TEMP", tempfile.gettempdir()) + screenshot_path = pathlib.Path(temp_path) / "pytest-screenshots" + if screenshot_path.exists(): + shutil.rmtree(screenshot_path) + screenshot_path.mkdir() + + self.request.session.stash["screenshot_path"] = screenshot_path + return screenshot_path + + def _take_x11_screenshot_of_failed_test(self): + # Take a basic X11 image grab using pillow. If we want to do something + # fancy like autocropping see pyvirtualdisplay smartdisplay for + # inspiration. + xvfb = self.request.getfixturevalue('xvfb') + if not xvfb: + # Likely we are being run with --no-xvfb + return + + img = grab(xdisplay=f":{xvfb.display}") + + current_test = self.request.node.nodeid + fname = f"{datetime.datetime.now().isoformat()}-{current_test.replace('/', '_')}.png" + # upload-artifacts says it doesn't allow these characters if it sees + # one of them. + bad_chars = '":<>|*?\r\n' + for char in bad_chars: + fname = fname.replace(char, "_") + + # TODO: + # 1. Keep old directories around? + # 2. Will different runs in parallel in CI clobber the folder? Might + # have to put them in subdirs with the process ID if so. + # 4. Log a "screenshot saved to ..." message? + fpath = self._get_x11_screenshot_directory() / fname + img.save(fpath) + class YamlLoader(yaml.SafeLoader): diff --git a/tests/end2end/fixtures/test_quteprocess.py b/tests/end2end/fixtures/test_quteprocess.py index ec0cd55ac..00266fc32 100644 --- a/tests/end2end/fixtures/test_quteprocess.py +++ b/tests/end2end/fixtures/test_quteprocess.py @@ -98,8 +98,9 @@ def test_quteproc_error_message(qtbot, quteproc, cmd, request_mock): quteproc.after_test() -def test_quteproc_error_message_did_fail(qtbot, quteproc, request_mock): +def test_quteproc_error_message_did_fail(qtbot, quteproc, request_mock, monkeypatch): """Make sure the test does not fail on teardown if the main test failed.""" + monkeypatch.setattr(quteproc, "_take_x11_screenshot_of_failed_test", lambda: None) request_mock.node.rep_call.failed = True with qtbot.wait_signal(quteproc.got_error): quteproc.send_cmd(':message-error test') @@ -108,6 +109,17 @@ def test_quteproc_error_message_did_fail(qtbot, quteproc, request_mock): quteproc.after_test() +def test_quteproc_screenshot_on_fail(qtbot, quteproc, request_mock, monkeypatch, mocker): + """Make sure we call the method to take a screenshot to test failure.""" + take_screenshot_spy = mocker.Mock() + monkeypatch.setattr( + quteproc, "_take_x11_screenshot_of_failed_test", take_screenshot_spy + ) + request_mock.node.rep_call.failed = True + quteproc.after_test() + take_screenshot_spy.assert_called_once() + + def test_quteproc_skip_via_js(qtbot, quteproc): with pytest.raises(pytest.skip.Exception, match='test'): quteproc.send_cmd(':jseval console.log("[SKIP] test");') diff --git a/tox.ini b/tox.ini index 5afe211b4..607b19fb3 100644 --- a/tox.ini +++ b/tox.ini @@ -31,6 +31,7 @@ passenv = QT_QUICK_BACKEND FORCE_COLOR DBUS_SESSION_BUS_ADDRESS + RUNNER_TEMP HYPOTHESIS_EXAMPLES_DIR basepython = py: {env:PYTHON:python3} From 78b6fd5cad0d52dbfce8391a0a494b3a637a9545 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 26 Apr 2024 19:10:46 +1200 Subject: [PATCH 158/403] Position e2e browser at top left corner of screen When taking screenshots of the test process running under xvfb it's offset from the top left corner, the default geometry of qutebrowser is 800x600+50+50. The default size of pytest-xvfb is 800x600, which means part of the browser window is outside the X11 screen and doesn't get captured. This commit duplicates the width and height from the default geometry in mainwindow.py but sets the x and y offsets to zero so that the browser window is fully contained within the X11 window. --- tests/end2end/fixtures/quteprocess.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 0d6edfda7..8d234f1c7 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -415,7 +415,8 @@ class QuteProc(testprocess.Process): '--debug-flag', 'werror', '--debug-flag', 'test-notification-service', '--debug-flag', 'caret', - '--qt-flag', 'disable-features=PaintHoldingCrossOrigin'] + '--qt-flag', 'disable-features=PaintHoldingCrossOrigin', + '--qt-arg', 'geometry', '800x600+0+0'] if self.request.config.webengine and testutils.disable_seccomp_bpf_sandbox(): args += testutils.DISABLE_SECCOMP_BPF_ARGS From 557b2f37fdea5ee573015be820a38663af7b7031 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 25 May 2024 15:46:22 +1200 Subject: [PATCH 159/403] Don't clobber screenshot dir when running with xdist Ohhh! I didn't realize the e2e tests where running in parallel already. Interesting. Anyhow, use the builtin `filelock` module to make sure different test processes in the same session don't re-create the screenshot directory. This is based on advice here: https://pytest-xdist.readthedocs.io/en/latest/how-to.html#making-session-scoped-fixtures-execute-only-once I'm saving the lock object to the session stash because it seems the lock is released when the FileLock object is destroyed. So this ties it's lifecycle to the test session lifecycle, with xdist hopefully all the tests processes live for the whole run. --- tests/end2end/fixtures/quteprocess.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 8d234f1c7..2b9602074 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -17,6 +17,7 @@ import contextlib import itertools import collections import json +import filelock import yaml import pytest @@ -881,11 +882,26 @@ class QuteProc(testprocess.Process): temp_path = os.environ.get("RUNNER_TEMP", tempfile.gettempdir()) screenshot_path = pathlib.Path(temp_path) / "pytest-screenshots" + + lock = filelock.FileLock(screenshot_path / ".pytest.lock") if screenshot_path.exists(): - shutil.rmtree(screenshot_path) - screenshot_path.mkdir() + # Clean and re-create dir for a new run. Except if the lock file + # is being held then we are running in parallel. + try: + lock.acquire(blocking=False) + except filelock.Timeout: + pass + else: + lock.release() + shutil.rmtree(screenshot_path) + screenshot_path.mkdir() + lock.acquire() + else: + screenshot_path.mkdir() + lock.acquire() self.request.session.stash["screenshot_path"] = screenshot_path + self.request.session.stash["screenshot_lock"] = lock return screenshot_path def _take_x11_screenshot_of_failed_test(self): @@ -908,10 +924,9 @@ class QuteProc(testprocess.Process): fname = fname.replace(char, "_") # TODO: - # 1. Keep old directories around? - # 2. Will different runs in parallel in CI clobber the folder? Might - # have to put them in subdirs with the process ID if so. - # 4. Log a "screenshot saved to ..." message? + # 1. Log a "screenshot saved to ..." message so that people know where + # to go look for them when running locally? Using pytest-print? Or + # add an FYI to the report summary? fpath = self._get_x11_screenshot_directory() / fname img.save(fpath) From 2fcd6eafc4e74899caca09a8a76e9b16c54b4c1e Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 17 Aug 2024 14:16:37 +1200 Subject: [PATCH 160/403] Save screenshots to tmp_path, move stuff into fixtures Saving screenshots to the temp directories managed by pytest means we don't have to worry about cleaning up from previous runs because pytest will create a new folder for each run. Now that we aren't cleaning stuff up means we don't have to worry about workers clobbering each other because all they are going to do is write to files with the names of tests which have already been distributed amongst them. Moving to the pytest temp dirs instead of a hardcoded one also means that it'll be less obvious to users where the screenshots are. Pytest doesn't seem to have much UX around pointing people to interesting artifacts in the "temp" dir. So I'll have another look at adding this information to the test report. Since this implementation is now more tightly couple with pytest I've pulled some code out of the QuteProc process into fixtures. TODO: * add screenshot locations to test report * adapt GHA zip file creation to get files from /tmp/pytest-of-$user/pytest-current/pytest-screenshots * review filenames to see if pytest does a good enough sanitization for us, from what I've seen it doesn't slugify that path of the tests, and it tends to truncate names. I think having the full test path in the filenames could be useful for people who download the zip file from the github actions to investigate CI failures --- tests/end2end/conftest.py | 9 ++- tests/end2end/fixtures/quteprocess.py | 104 ++++++++++++-------------- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 066c5cd1b..7b17d0d5c 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -18,10 +18,15 @@ from qutebrowser.qt.core import PYQT_VERSION, QCoreApplication pytest.register_assert_rewrite('end2end.fixtures') # pylint: disable=unused-import +# Import fixtures that the bdd tests rely on. from end2end.fixtures.notificationserver import notification_server from end2end.fixtures.webserver import server, server_per_test, server2, ssl_server -from end2end.fixtures.quteprocess import (quteproc_process, quteproc, - quteproc_new) +from end2end.fixtures.quteprocess import ( + quteproc_process, quteproc, + quteproc_new, + screenshot_dir, + take_x11_screenshot, +) from end2end.fixtures.testprocess import pytest_runtest_makereport # pylint: enable=unused-import from qutebrowser.utils import qtutils, utils, version diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 2b9602074..5d5af7c53 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -8,7 +8,6 @@ import pathlib import os import re import sys -import shutil import time import datetime import logging @@ -17,7 +16,6 @@ import contextlib import itertools import collections import json -import filelock import yaml import pytest @@ -875,60 +873,9 @@ class QuteProc(testprocess.Process): self.send_cmd(cmd.format('no-scroll-filtering')) self.send_cmd(cmd.format('log-scroll-pos')) - def _get_x11_screenshot_directory(self): - screenshot_path = self.request.session.stash.get("screenshot_path", None) - if screenshot_path: - return screenshot_path - - temp_path = os.environ.get("RUNNER_TEMP", tempfile.gettempdir()) - screenshot_path = pathlib.Path(temp_path) / "pytest-screenshots" - - lock = filelock.FileLock(screenshot_path / ".pytest.lock") - if screenshot_path.exists(): - # Clean and re-create dir for a new run. Except if the lock file - # is being held then we are running in parallel. - try: - lock.acquire(blocking=False) - except filelock.Timeout: - pass - else: - lock.release() - shutil.rmtree(screenshot_path) - screenshot_path.mkdir() - lock.acquire() - else: - screenshot_path.mkdir() - lock.acquire() - - self.request.session.stash["screenshot_path"] = screenshot_path - self.request.session.stash["screenshot_lock"] = lock - return screenshot_path - def _take_x11_screenshot_of_failed_test(self): - # Take a basic X11 image grab using pillow. If we want to do something - # fancy like autocropping see pyvirtualdisplay smartdisplay for - # inspiration. - xvfb = self.request.getfixturevalue('xvfb') - if not xvfb: - # Likely we are being run with --no-xvfb - return - - img = grab(xdisplay=f":{xvfb.display}") - - current_test = self.request.node.nodeid - fname = f"{datetime.datetime.now().isoformat()}-{current_test.replace('/', '_')}.png" - # upload-artifacts says it doesn't allow these characters if it sees - # one of them. - bad_chars = '":<>|*?\r\n' - for char in bad_chars: - fname = fname.replace(char, "_") - - # TODO: - # 1. Log a "screenshot saved to ..." message so that people know where - # to go look for them when running locally? Using pytest-print? Or - # add an FYI to the report summary? - fpath = self._get_x11_screenshot_directory() / fname - img.save(fpath) + fixture = self.request.getfixturevalue('take_x11_screenshot') + fixture() class YamlLoader(yaml.SafeLoader): @@ -970,6 +917,51 @@ def _xpath_escape(text): return 'concat({})'.format(', '.join(parts)) +@pytest.fixture +def screenshot_dir(request, tmp_path_factory): + """Return the path of a directory to save e2e screenshots in.""" + path = tmp_path_factory.getbasetemp() + if "PYTEST_XDIST_WORKER" in os.environ: + # If we are running under xdist remove the per-worker directory + # (like "popen-gw0") so the user doesn't have to search through + # multiple folders for the screenshot they are looking for. + path = path.parent + path /= "pytest-screenshots" + path.mkdir(exist_ok=True) + return path + + +@pytest.fixture +def take_x11_screenshot(request, screenshot_dir, xvfb): + """Take a screenshot of the current pytest-xvfb display. + + Screenshots are saved to the location of the `screenshot_dir` fixture. + """ + def doit(): + if not xvfb: + # Likely we are being run with --no-xvfb + return + + img = grab(xdisplay=f":{xvfb.display}") + + current_test = request.node.nodeid + fname = f"{datetime.datetime.now().isoformat()}-{current_test.replace('/', '_')}.png" + # upload-artifacts says it doesn't allow these characters if it sees + # one of them. + bad_chars = '":<>|*?\r\n' + for char in bad_chars: + fname = fname.replace(char, "_") + + # TODO: + # 1. Log a "screenshot saved to ..." message so that people know where + # to go look for them when running locally? Using pytest-print? Or + # add an FYI to the report summary? + fpath = screenshot_dir / fname + img.save(fpath) + + return doit + + @pytest.fixture(scope='module') def quteproc_process(qapp, server, request): """Fixture for qutebrowser process which is started once per file.""" @@ -981,7 +973,7 @@ def quteproc_process(qapp, server, request): @pytest.fixture -def quteproc(quteproc_process, server, request): +def quteproc(quteproc_process, server, request, take_x11_screenshot): """Per-test qutebrowser fixture which uses the per-file process.""" request.node._quteproc_log = quteproc_process.captured_log quteproc_process.before_test() From 0c3807b04a121f9a23fa707ce668dcd517e1ee46 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 17 Aug 2024 14:51:19 +1200 Subject: [PATCH 161/403] Add pytest report section listing e2e screenshots I would like it to be obvious to contributors who run the tests locally that there are screenshots of the processes under test that they can examine. I don't think it's obvious that there could be useful files sitting round in a temp directory. This commit adds the screenshot file paths to a user property on failed tests then adds a custom report section that pulls that lists those properties. That way when there is errors users will get the paths to the images printed out alongside the report of failed tests. I find it difficult to navigate the internals of pytest. I tried various ways of printing information and getting that information to methods that could do the printing but couldn't get anything to work. I ended up entirely copying this SO post which worked really well for attaching information to test results in a place that is accessable to the reporting hook: https://stackoverflow.com/a/64822668 It's added to the end of the existing terminal report hook, because while it seems you can have two of those hooks, things can get pretty confusing with interleaved reports and not all of them running every time. --------------------- End2end screenshots available in: /tmp/pytest-of-user/pytest-56/pytest-screenshots --------------------- 2024-08-17T14_49_35.896940-tests_end2end_features_test_utilcmds_bdd.py__test_cmdrepeatlast_with_modeswitching_command_deleting.png 2024-08-17T14_49_37.391229-tests_end2end_features_test_completion_bdd.py__test_deleting_an_open_tab_via_the_completion.png =================================================== short test summary info ==================================================== FAILED tests/end2end/features/test_utilcmds_bdd.py::test_cmdrepeatlast_with_modeswitching_command_deleting - AssertionError: assert 'http://local...ata/hello.txt' == 'http://local...ata/sello.txt' FAILED tests/end2end/features/test_completion_bdd.py::test_deleting_an_open_tab_via_the_completion - AssertionError: assert 'http://local...ata/hello.txt' == 'http://local...ata/sello.txt' ====================================================== 2 failed in 5.18s ======================================================= From adding debug messages I can see: RUNNER_TEMP=/home/runner/work/_temp /tmp/pytest-of-runner/pytest-0/pytest-screenshots Means that I don't think GHA will be able to collect the temp files because they are not being written to the temp dir being mounted in from outside. I think that's the case anyway. Might have to pass --basetemp=$RUNNER_TEMP to pytest or set TEMP or something. TODO --- tests/conftest.py | 23 ++++++++++++++++++++++- tests/end2end/fixtures/quteprocess.py | 7 ++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c834e62a0..15607b6a1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -372,7 +372,8 @@ def pytest_runtest_makereport(item, call): @pytest.hookimpl(hookwrapper=True) def pytest_terminal_summary(terminalreporter): - """Group benchmark results on CI.""" + """Add custom pytest summary sections.""" + # Group benchmark results on CI. if testutils.ON_CI: terminalreporter.write_line( testutils.gha_group_begin('Benchmark results')) @@ -380,3 +381,23 @@ def pytest_terminal_summary(terminalreporter): terminalreporter.write_line(testutils.gha_group_end()) else: yield + + # List any screenshots of failed end2end tests that were generated during + # the run. Screenshots are captured from QuteProc.after_test() + properties = lambda report: dict(report.user_properties) + reports = [ + report + for report in terminalreporter.getreports("") + if "screenshot" in properties(report) + ] + screenshots = [ + pathlib.Path(properties(report)["screenshot"]) + for report in reports + ] + + if screenshots: + terminalreporter.ensure_newline() + screenshot_dir = screenshots[0].parent + terminalreporter.section(f"End2end screenshots available in: {screenshot_dir}", sep="-", blue=True, bold=True) + for screenshot in screenshots: + terminalreporter.line(screenshot.parts[-1]) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 5d5af7c53..8cac6c4f6 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -932,7 +932,7 @@ def screenshot_dir(request, tmp_path_factory): @pytest.fixture -def take_x11_screenshot(request, screenshot_dir, xvfb): +def take_x11_screenshot(request, screenshot_dir, record_property, xvfb): """Take a screenshot of the current pytest-xvfb display. Screenshots are saved to the location of the `screenshot_dir` fixture. @@ -952,13 +952,10 @@ def take_x11_screenshot(request, screenshot_dir, xvfb): for char in bad_chars: fname = fname.replace(char, "_") - # TODO: - # 1. Log a "screenshot saved to ..." message so that people know where - # to go look for them when running locally? Using pytest-print? Or - # add an FYI to the report summary? fpath = screenshot_dir / fname img.save(fpath) + record_property("screenshot", str(fpath)) return doit From cc85d6130345b6dbc41b5fd81be32419e44e2684 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 18 Aug 2024 11:26:37 +1200 Subject: [PATCH 162/403] Teach sanity check in tests about temp dirs under HOME On GitHub the RUNNER_TEMP dir is inside the user's home directory. I think the spirit of the check is making sure you aren't touching stuff like ~/.config/qutebrowser/ in the tests, if it's within a specified tempdir it should be fine --- qutebrowser/utils/standarddir.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 026376dc2..1eb296e50 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -10,6 +10,7 @@ import sys import contextlib import enum import argparse +import tempfile from typing import Iterator, Optional, Dict from qutebrowser.qt.core import QStandardPaths @@ -311,14 +312,15 @@ def _create(path: str) -> None: 0700. If the destination directory exists already the permissions should not be changed. """ - if APPNAME == 'qute_test' and path.startswith('/home'): # pragma: no cover - for k, v in os.environ.items(): - if k == 'HOME' or k.startswith('XDG_'): - log.init.debug(f"{k} = {v}") - raise AssertionError( - "Trying to create directory inside /home during " - "tests, this should not happen." - ) + if APPNAME == 'qute_test': + if path.startswith('/home') and not path.startswith(tempfile.gettempdir()): # pragma: no cover + for k, v in os.environ.items(): + if k == 'HOME' or k.startswith('XDG_'): + log.init.debug(f"{k} = {v}") + raise AssertionError( + "Trying to create directory inside /home during " + "tests, this should not happen." + ) os.makedirs(path, 0o700, exist_ok=True) From 914227ca1c703ce28242b86b08e5e350d75ebdbf Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 18 Aug 2024 11:45:51 +1200 Subject: [PATCH 163/403] Set TMPDIR to RUNNER_TEMP on CI The upload artifact action can't collect artifacts from /tmp/ in the test runners. So now that we are writing the screenshots that we want to collect later to the pytest `tmp_path` location we need to make sure that lives somewhere the later actions can find it. Pytest uses `tempfile.gettempdir()` to find the temp dir, and that respects a number of environment variables including `TMPDIR`. This commits sets TMPDIR to RUNNER_TEMP in in our test runners to make pytest uses the temp dir that's mounted into the action containers. For the docker based runners I can use the `env` map, but for the ubuntu ones it didn't let me expand `${{ runner.temp }}` in the end map under `step`, so I'm writing it to the env file for the runner instead. It failed to parse the action yaml and said: > Unrecognized named-value: 'runner'. Located at position 1 within expression: runner.temp For the user part of the `pytest-of-$user` directory, I looked at the new screenshot related test summary lines to see what the user was called. `runner` on the ubuntu containers and `user` in our docker containers. Pytest maintains the "pytest-current" symlink to the latest temp folder. --- .github/workflows/bleeding.yml | 3 ++- .github/workflows/ci.yml | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml index 97d95adde..ce4531b00 100644 --- a/.github/workflows/bleeding.yml +++ b/.github/workflows/bleeding.yml @@ -27,6 +27,7 @@ jobs: PY_COLORS: "1" DOCKER: "${{ matrix.image }}" CI: true + TMPDIR: "${{ runner.temp }}" volumes: # Hardcoded because we can't use ${{ runner.temp }} here apparently. - /home/runner/work/_temp/:/home/runner/work/_temp/ @@ -54,7 +55,7 @@ jobs: with: name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}" path: | - ${{ runner.temp }}/pytest-screenshots/*.png + ${{ runner.temp }}/pytest-of-user/pytest-current/pytest-screenshots/*.png if-no-files-found: ignore if: failure() irc: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94f561999..1e0c84bc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,6 +107,7 @@ jobs: DOCKER: "${{ matrix.image }}" CI: true PYTEST_ADDOPTS: "--color=yes" + TMPDIR: "${{ runner.temp }}" volumes: # Hardcoded because we can't use ${{ runner.temp }} here apparently. - /home/runner/work/_temp/:/home/runner/work/_temp/ @@ -131,7 +132,7 @@ jobs: with: name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}" path: | - ${{ runner.temp }}/pytest-screenshots/*.png + ${{ runner.temp }}/pytest-of-user/pytest-current/pytest-screenshots/*.png if-no-files-found: ignore if: failure() @@ -237,6 +238,8 @@ jobs: - name: Upgrade 3rd party assets run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }}" if: "startsWith(matrix.os, 'windows-')" + - name: "Set TMPDIR for pytest" + run: 'echo "TMPDIR=${{ runner.temp }}" >> "$GITHUB_ENV"' - name: "Run ${{ matrix.testenv }}" run: "dbus-run-session -- tox -e ${{ matrix.testenv }} -- ${{ matrix.args }}" if: "startsWith(matrix.os, 'ubuntu-')" @@ -263,7 +266,7 @@ jobs: with: name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}" path: | - ${{ runner.temp }}/pytest-screenshots/*.png + ${{ runner.temp }}/pytest-of-runner/pytest-current/pytest-screenshots/*.png if-no-files-found: ignore if: failure() From 622b98df1251ec035ab6de9032e48dfb0dfb5373 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 22 Jun 2024 17:59:31 +1200 Subject: [PATCH 164/403] update changelog for e2e test screenshots --- doc/changelog.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index b99363ec0..2e745909a 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -30,6 +30,10 @@ Removed - Support for macOS 11 Big Sur is dropped. Binaries are now built on macOS 12 Monterey and are unlikely to still run on older macOS versions. +- Failed end2end tests will now save screenshots of the browser window when + run under xvfb (the default on linux). Screenshots will be under + `$TEMP/pytest-current/pytest-screenshots/` or attached to the GitHub actions + run as an artifact. (#7625) Changed ~~~~~~~ From 451cc6fd5626147d538cfe63188c01ca5d36de7b Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 18 Aug 2024 13:23:02 +1200 Subject: [PATCH 165/403] Refer to mkvenv script by full path in install docs Might help with people copying and pasting commands. I don't think the script installs itself in bin/ in the virtualenv it creates? Closes: #8263 --- doc/install.asciidoc | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 98cc6fb05..1954a662a 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -45,7 +45,7 @@ by Debian's security support. It's recommended to <> with a newer PyQt/Qt binary instead. If you need proprietary codec support or use an architecture not supported by Qt binaries, starting with Ubuntu 22.04 and Debian Bookworm, it's possible to -install Qt 6 via apt. By using `mkvenv.py` with `--pyqt-type link` you get a +install Qt 6 via apt. By using `scripts/mkvenv.py` with `--pyqt-type link` you get a newer qutebrowser running with: - Ubuntu 22.04, Linux Mint 21: QtWebEngine 6.2.4 (based on Chromium 90 from mid-2021) @@ -64,9 +64,9 @@ Additional hints However, Qt 6.5 https://www.qt.io/blog/moving-to-openssl-3-in-binary-builds-starting-from-qt-6.5-beta-2[moved to OpenSSL 3] for its binary builds. Thus, you will either need to live with `:adblock-update` and `:download` being broken, or use `--pyqt-version 6.4` for - the `mkvenv.py` script to get an older Qt. + the `scripts/mkvenv.py` script to get an older Qt. - If running from git, run the following to generate the documentation for the - `:help` command (the `mkvenv.py` script used with a virtualenv install already does + `:help` command (the `scripts/mkvenv.py` script used with a virtualenv install already does this for you): + ---- @@ -398,7 +398,7 @@ location for a particular application, rather than being installed globally. The `scripts/mkvenv.py` script in this repository can be used to create a virtualenv for qutebrowser and install it (including all dependencies) there. The next couple of sections will explain the most common use-cases - run -`mkvenv.py` with `--help` to see all available options. +`scripts/mkvenv.py` with `--help` to see all available options. Getting the repository ~~~~~~~~~~~~~~~~~~~~~~ @@ -442,8 +442,8 @@ See the next section for an alternative install method which might help with those issues but result in an older Qt version. You can specify a Qt/PyQt version with the `--pyqt-version` flag, see -`mkvenv.py --help` for a list of available versions. By default, the latest -version which plays well with qutebrowser is used. +`scripts/mkvenv.py --help` for a list of available versions. By default, the +latest version which plays well with qutebrowser is used. NOTE: If the Qt smoke test fails with a _"This application failed to start because no Qt platform plugin could be initialized."_ message, most likely a @@ -453,22 +453,24 @@ failed on ..._ line for details. Installing dependencies (system-wide Qt) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Alternatively, you can use `mkvenv.py --pyqt-type link` to symlink your local -PyQt/Qt install instead of installing PyQt in the virtualenv. However, unless -you have a new QtWebKit or QtWebEngine available, qutebrowser will not work. It -also typically means you'll be using an older release of QtWebEngine. +Alternatively, you can use `scripts/mkvenv.py --pyqt-type link` to symlink +your local PyQt/Qt install instead of installing PyQt in the virtualenv. +However, unless you have a new QtWebKit or QtWebEngine available, qutebrowser +will not work. It also typically means you'll be using an older release of +QtWebEngine. On Windows, run `set PYTHON=C:\path\to\python.exe` (CMD) or `$Env:PYTHON = "..."` (Powershell) first. -There is a third mode, `mkvenv.py --pyqt-type source` which uses a system-wide -Qt but builds PyQt from source. In most scenarios, this shouldn't be needed. +There is a third mode, `scripts/mkvenv.py --pyqt-type source` which uses a +system-wide Qt but builds PyQt from source. In most scenarios, this shouldn't +be needed. Creating a wrapper script ~~~~~~~~~~~~~~~~~~~~~~~~~ -Running `mkvenv.py` does not install a system-wide `qutebrowser` script. You can -launch qutebrowser by doing: +Running `scripts/mkvenv.py` does not install a system-wide `qutebrowser` +script. You can launch qutebrowser by doing: ---- .venv/bin/python3 -m qutebrowser @@ -485,9 +487,9 @@ You can create a simple wrapper script to start qutebrowser somewhere in your Updating ~~~~~~~~ -If you cloned the git repository, run `mkvenv.py --update` which will take care -of updating the code (via `git pull`) and recreating the environment with the -newest dependencies. +If you cloned the git repository, run `scripts/mkvenv.py --update` which will +take care of updating the code (via `git pull`) and recreating the environment +with the newest dependencies. Alternatively, you can update your local copy of the code (e.g. by pulling the git repo, or extracting a new version) and the virtualenv should automatically From 130479e2bd3b35cadd906bc63f07c6868d47f686 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 22 Aug 2024 15:10:54 +0200 Subject: [PATCH 166/403] Add missing copyright / license headers to qutebrowser.qt Done via: reuse annotate \ --exclude-year \ -c 'Florian Bruhin (The Compiler) ' \ --license="GPL-3.0-or-later" \ qutebrowser/qt/*.py --- qutebrowser/qt/__init__.py | 3 +++ qutebrowser/qt/_core_pyqtproperty.py | 4 ++++ qutebrowser/qt/core.py | 4 ++++ qutebrowser/qt/dbus.py | 4 ++++ qutebrowser/qt/gui.py | 4 ++++ qutebrowser/qt/machinery.py | 4 ++++ qutebrowser/qt/network.py | 4 ++++ qutebrowser/qt/opengl.py | 4 ++++ qutebrowser/qt/printsupport.py | 4 ++++ qutebrowser/qt/qml.py | 4 ++++ qutebrowser/qt/sip.py | 4 ++++ qutebrowser/qt/sql.py | 4 ++++ qutebrowser/qt/test.py | 4 ++++ qutebrowser/qt/webenginecore.py | 4 ++++ qutebrowser/qt/webenginewidgets.py | 4 ++++ qutebrowser/qt/webkit.py | 4 ++++ qutebrowser/qt/webkitwidgets.py | 4 ++++ qutebrowser/qt/widgets.py | 4 ++++ 18 files changed, 71 insertions(+) diff --git a/qutebrowser/qt/__init__.py b/qutebrowser/qt/__init__.py index e69de29bb..113e06b0b 100644 --- a/qutebrowser/qt/__init__.py +++ b/qutebrowser/qt/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/qutebrowser/qt/_core_pyqtproperty.py b/qutebrowser/qt/_core_pyqtproperty.py index c2078c403..02c1cab97 100644 --- a/qutebrowser/qt/_core_pyqtproperty.py +++ b/qutebrowser/qt/_core_pyqtproperty.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + """WORKAROUND for missing pyqtProperty typing, ported from PyQt5-stubs: FIXME:mypy PyQt6-stubs issue diff --git a/qutebrowser/qt/core.py b/qutebrowser/qt/core.py index 87a253218..f6e8b5a93 100644 --- a/qutebrowser/qt/core.py +++ b/qutebrowser/qt/core.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt Core. diff --git a/qutebrowser/qt/dbus.py b/qutebrowser/qt/dbus.py index d3b22a747..81658faf0 100644 --- a/qutebrowser/qt/dbus.py +++ b/qutebrowser/qt/dbus.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt DBus. diff --git a/qutebrowser/qt/gui.py b/qutebrowser/qt/gui.py index dc5fbb23c..5f35f694e 100644 --- a/qutebrowser/qt/gui.py +++ b/qutebrowser/qt/gui.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import,unused-import """Wrapped Qt imports for Qt Gui. diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index 45a1f6598..dcb3b3243 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pyright: reportConstantRedefinition=false """Qt wrapper selection. diff --git a/qutebrowser/qt/network.py b/qutebrowser/qt/network.py index 7b194affc..dad42f733 100644 --- a/qutebrowser/qt/network.py +++ b/qutebrowser/qt/network.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt Network. diff --git a/qutebrowser/qt/opengl.py b/qutebrowser/qt/opengl.py index bc5a31c11..8191f03d7 100644 --- a/qutebrowser/qt/opengl.py +++ b/qutebrowser/qt/opengl.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-import,unused-wildcard-import """Wrapped Qt imports for Qt OpenGL. diff --git a/qutebrowser/qt/printsupport.py b/qutebrowser/qt/printsupport.py index 08358d417..af0dc1c25 100644 --- a/qutebrowser/qt/printsupport.py +++ b/qutebrowser/qt/printsupport.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt Print Support. diff --git a/qutebrowser/qt/qml.py b/qutebrowser/qt/qml.py index 9202667e2..112003f57 100644 --- a/qutebrowser/qt/qml.py +++ b/qutebrowser/qt/qml.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt QML. diff --git a/qutebrowser/qt/sip.py b/qutebrowser/qt/sip.py index 1eb21bc27..3616b3505 100644 --- a/qutebrowser/qt/sip.py +++ b/qutebrowser/qt/sip.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=wildcard-import,unused-wildcard-import """Wrapped Qt imports for PyQt5.sip/PyQt6.sip. diff --git a/qutebrowser/qt/sql.py b/qutebrowser/qt/sql.py index 4d969936b..ea617668c 100644 --- a/qutebrowser/qt/sql.py +++ b/qutebrowser/qt/sql.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt SQL. diff --git a/qutebrowser/qt/test.py b/qutebrowser/qt/test.py index 3c1bcfdff..2ec4488ae 100644 --- a/qutebrowser/qt/test.py +++ b/qutebrowser/qt/test.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt Test. diff --git a/qutebrowser/qt/webenginecore.py b/qutebrowser/qt/webenginecore.py index afd76e38c..026e9af32 100644 --- a/qutebrowser/qt/webenginecore.py +++ b/qutebrowser/qt/webenginecore.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import,unused-import """Wrapped Qt imports for Qt WebEngine Core. diff --git a/qutebrowser/qt/webenginewidgets.py b/qutebrowser/qt/webenginewidgets.py index 88758cf23..a6d512fd6 100644 --- a/qutebrowser/qt/webenginewidgets.py +++ b/qutebrowser/qt/webenginewidgets.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt WebEngine Widgets. diff --git a/qutebrowser/qt/webkit.py b/qutebrowser/qt/webkit.py index c4b0bb7ae..79aa9dba1 100644 --- a/qutebrowser/qt/webkit.py +++ b/qutebrowser/qt/webkit.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=wildcard-import """Wrapped Qt imports for Qt WebKit. diff --git a/qutebrowser/qt/webkitwidgets.py b/qutebrowser/qt/webkitwidgets.py index 5b790dcc7..a040a45f8 100644 --- a/qutebrowser/qt/webkitwidgets.py +++ b/qutebrowser/qt/webkitwidgets.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=wildcard-import,no-else-raise """Wrapped Qt imports for Qt WebKit Widgets. diff --git a/qutebrowser/qt/widgets.py b/qutebrowser/qt/widgets.py index f82ec2e3b..1e77412f4 100644 --- a/qutebrowser/qt/widgets.py +++ b/qutebrowser/qt/widgets.py @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) +# +# SPDX-License-Identifier: GPL-3.0-or-later + # pylint: disable=import-error,wildcard-import,unused-wildcard-import """Wrapped Qt imports for Qt Widgets. From 213a163623db8033a1b99b39dc737159d88ad18f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 23 Aug 2024 21:44:10 +0200 Subject: [PATCH 167/403] test: Ignore new libEGL warnings Seem to fail all tests on Archlinux-unstable --- tests/end2end/fixtures/quteprocess.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index b1e4bbaab..0bb09e632 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -77,6 +77,10 @@ def is_ignored_lowlevel_message(message): 'DRI3 not available', # Webkit on arch with a newer mesa 'MESA: error: ZINK: failed to load libvulkan.so.1', + + # GitHub Actions with Archlinux unstable packages + 'libEGL warning: DRI3: Screen seems not DRI3 capable', + 'libEGL warning: egl: failed to create dri2 screen', ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) From ea5d15ad2ed49329b0f2fdf6dc0511a18caa6a08 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 24 Aug 2024 13:11:55 +1200 Subject: [PATCH 168/403] Remove timestamp and test path from screenshot names. Hopefully now that we have reporting in the test results, and pytest retaining of old directories, we don't have to encode so much information in the filenames to help you make sense of them. Previously the png filenames looked like this: 2024-08-24T12_42_11.160556-tests_end2end_features_test_completion_bdd.py__test_deleting_an_open_tab_via_the_completion.png Now they just have the individual test name, eg: test_deleting_an_open_tab_via_the_completion.png The two times people will want to look at these files and I want to make sure they can find what they are looking for are: * running the tests locally * the directory with the images is printed out right above the pytest summary, hopefully that is a clear enough reference to the tests and that has the full path to the tests, not just the name * if people run multiple test runs and want to find older images they will have to know, or guess, how the pytest temp dir naming scheme works, or go back in their terminal scrollback * when downloading images as artifacts to debug tests * The zip files with images from a job currently have names like end2end-screenshots-2024-08-18-fef13d4-py310-pyqt65-ubuntu-22.04.zip * Hopefully that zip file name is specific enough * I'm not sure if the individual filenames with just test name in them are specific enough for this case. But presumably people will be looking at the run logs in CI anywhere and will be able to match up a failing test with the screenshot easy enough Pytest appears to sanitize test names enough for upload-artifact. Couldn't see any docs on it, but I put all the characters it complains about in a BDD test name and they all go stripped out. --- tests/end2end/fixtures/quteprocess.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 8cac6c4f6..17e32167c 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -943,16 +943,7 @@ def take_x11_screenshot(request, screenshot_dir, record_property, xvfb): return img = grab(xdisplay=f":{xvfb.display}") - - current_test = request.node.nodeid - fname = f"{datetime.datetime.now().isoformat()}-{current_test.replace('/', '_')}.png" - # upload-artifacts says it doesn't allow these characters if it sees - # one of them. - bad_chars = '":<>|*?\r\n' - for char in bad_chars: - fname = fname.replace(char, "_") - - fpath = screenshot_dir / fname + fpath = screenshot_dir / f"{request.node.name}.png" img.save(fpath) record_property("screenshot", str(fpath)) From 3852f12091a6eeba1b0896257cee1e33b1a9d53f Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 24 Aug 2024 21:22:58 +1200 Subject: [PATCH 169/403] Don't list each screenshot name in pytest report This bit is printed right about the test result summary, so now that the file names are just test names, printing them out just above the full test paths in the results seems a bit redundant. The section header prints out the file path with the screenshots and that's the important part. It looks fine to me printing a section header without any section contents. Example: -------------------- End2end screenshots available in: /tmp/pytest-of-user/pytest-108/pytest-screenshots --------------------- =================================================== short test summary info ==================================================== FAILED tests/end2end/features/test_completion_bdd.py::test_deleting_an_ornpen_tab_via_the_completion - AssertionError: assert 'http://local...ata/hello.txt' == 'http://local...ata/sello.txt' FAILED tests/unit/utils/test_resources.py::TestReadFile::test_glob_deleting_resources_subdir[True-pathlib] - AssertionError: assert ['html/subdir...ir-file.html'] == ['html/subdir...ir-sile.html'] FAILED tests/unit/utils/test_resources.py::TestReadFile::test_glob_deleting_resources_subdir[False-zipfile] - AssertionError: assert ['html/subdir...ir-file.html'] == ['html/subdir...ir-sile.html'] FAILED tests/unit/utils/test_resources.py::TestReadFile::test_glob_deleting_resources_subdir[True-zipfile] - AssertionError: assert ['html/subdir...ir-file.html'] == ['html/subdir...ir-sile.html'] FAILED tests/end2end/features/test_utilcmds_bdd.py::test_cmdrepeatlast_with_modeswitching_command_deleting - AssertionError: assert 'http://local...ata/hello.txt' == 'http://local...ata/sello.txt' FAILED tests/unit/utils/test_resources.py::TestReadFile::test_glob_deleting_resources_subdir[False-pathlib] - AssertionError: assert ['html/subdir...ir-file.html'] == ['html/subdir...ir-sile.html'] =========================================== 6 failed, 23 passed, 8 skipped in 22.59s =========================================== --- tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 15607b6a1..89f32fed1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -399,5 +399,3 @@ def pytest_terminal_summary(terminalreporter): terminalreporter.ensure_newline() screenshot_dir = screenshots[0].parent terminalreporter.section(f"End2end screenshots available in: {screenshot_dir}", sep="-", blue=True, bold=True) - for screenshot in screenshots: - terminalreporter.line(screenshot.parts[-1]) From 433074c6817daa2fe25b0f9609af180ce3688dbe Mon Sep 17 00:00:00 2001 From: toofar Date: Tue, 3 Sep 2024 18:32:57 +1200 Subject: [PATCH 170/403] Add importlib_resources to tests requirements file as workaround Currently the dependency update job is failing[1] because one of the tests installs multiple requirements files before running the tests and it claims they have conflicting versions of `importlib_resources` (6.4.0 vs 6.4.4). 6.4.0 is in the pinned files and there is a 6.4.4 available. Looking though the logs the first time I see importlib_resources==6.4.0 is when printing the requirements for the `test` requirements file. But it's not mentioned at all when installing that file. Which makes me think it found it's way into the virtualenv by some other means. Looking at git blame for the test requirements lock file, it looks like importlib_resources was introduced in https://github.com/qutebrowser/qutebrowser/pull/8269 and indeed I can see version 6.4.0 in setuptools vendored folder[2]. So it looks like this is another issue caused by setuptools adding their vendored packages into sys.path. Options I can see for resolving this: a. add importlib_resources as a dependency in requirements.txt-raw so that we always pull down the newest one, even though we don't need it b. add an @ignore line for importlib_resources * I think in the unlikely event we end up needing it then it being ignored might be hard to spot c. drop python 3.8 support d. switch to a requirements compilation method that doesn't use `pip freeze` I've chosen (a) here because I think it's less surprising than (b), less work than (c) and I already have a PR up for (d). And it's only pulled down for 3.8 anyhow, so we'll drop this workaround when we drop that. [1]: https://github.com/qutebrowser/qutebrowser/actions/runs/10660624684/job/29544897516 [2]: https://github.com/pypa/setuptools/tree/main/setuptools/_vendor --- misc/requirements/requirements-tests.txt-raw | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 54e036106..923ccf7e3 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -31,4 +31,11 @@ pytest-xdist # Needed to test misc/userscripts/qute-lastpass tldextract +# importlib_resources==6.4.0 is being included in the lock file via +# setuptools' vendored dependencies and conflicting with the more up to date +# one pulled down by other requirements files. Include it here even though we +# don't need to to make sure we at least get an up to date one. +importlib_resources +#@ markers: importlib_resources python_version=="3.8.*" + #@ ignore: Jinja2, MarkupSafe, colorama From adf39e9f72c230dcf13f58fafdc3e83ba67fe024 Mon Sep 17 00:00:00 2001 From: toofar Date: Tue, 3 Sep 2024 19:17:03 +1200 Subject: [PATCH 171/403] also update jaroco-context as a workaround see previous commit --- misc/requirements/requirements-tests.txt-raw | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 923ccf7e3..44dfdafe9 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -31,11 +31,13 @@ pytest-xdist # Needed to test misc/userscripts/qute-lastpass tldextract -# importlib_resources==6.4.0 is being included in the lock file via -# setuptools' vendored dependencies and conflicting with the more up to date -# one pulled down by other requirements files. Include it here even though we -# don't need to to make sure we at least get an up to date one. +# importlib_resources==6.4.0 and context==5.3.0 are being included in the lock +# file via setuptools' vendored dependencies and conflicting with the more up +# to date one pulled down by other requirements files. +# Include them here even though we don't need them to make sure we at least +# get an up to date version. importlib_resources #@ markers: importlib_resources python_version=="3.8.*" +jaraco.context #@ ignore: Jinja2, MarkupSafe, colorama From 16781b5b0967aa03fa6458f9d5338ca7a4052820 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Tue, 3 Sep 2024 07:26:01 +0000 Subject: [PATCH 172/403] Update dependencies --- .../requirements-check-manifest.txt | 4 ++-- misc/requirements/requirements-dev.txt | 15 +++++++------ misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-mypy.txt | 8 +++---- .../requirements/requirements-pyinstaller.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 10 ++++----- misc/requirements/requirements-pyroma.txt | 8 +++---- misc/requirements/requirements-sphinx.txt | 8 +++---- misc/requirements/requirements-tests.txt | 21 +++++++++---------- misc/requirements/requirements-tox.txt | 6 +++--- requirements.txt | 4 ++-- 11 files changed, 44 insertions(+), 46 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index d86f50b78..13fa61d34 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -2,8 +2,8 @@ build==1.2.1 check-manifest==0.49 -importlib_metadata==8.2.0 +importlib_metadata==8.4.0 packaging==24.1 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.20.0 +zipp==3.20.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 1ffa897f9..b94e8329d 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -4,19 +4,19 @@ autocommand==2.2.2 backports.tarfile==1.2.0 build==1.2.1 bump2version==1.0.1 -certifi==2024.7.4 +certifi==2024.8.30 cffi==1.17.0 charset-normalizer==3.3.2 cryptography==43.0.0 docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 -idna==3.7 -importlib_metadata==8.2.0 -importlib_resources==6.4.0 +idna==3.8 +importlib_metadata==8.4.0 +importlib_resources==6.4.4 inflect==7.3.1 jaraco.classes==3.4.0 -jaraco.context==5.3.0 +jaraco.context==6.0.1 jaraco.functools==4.0.2 jaraco.text==3.12.1 jeepney==0.8.0 @@ -26,7 +26,6 @@ markdown-it-py==3.0.0 mdurl==0.1.2 more-itertools==10.4.0 nh3==0.2.18 -ordered-set==4.1.0 packaging==24.1 pkginfo==1.10.0 platformdirs==4.2.2 @@ -41,7 +40,7 @@ readme_renderer==43.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.7.1 +rich==13.8.0 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 @@ -51,4 +50,4 @@ typeguard==4.3.0 typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.2 -zipp==3.20.0 +zipp==3.20.1 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index ab5d61413..456c3be34 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==24.2.0 flake8==7.1.1 -flake8-bugbear==24.4.26 +flake8-bugbear==24.8.19 flake8-builtins==2.5.0 flake8-comprehensions==3.15.0 flake8-debugger==4.1.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 613795358..dc1bd4dc2 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -2,11 +2,11 @@ chardet==5.2.0 diff_cover==9.1.1 -importlib_resources==6.4.0 +importlib_resources==6.4.4 Jinja2==3.1.4 lxml==5.3.0 MarkupSafe==2.1.5 -mypy==1.11.1 +mypy==1.11.2 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 @@ -16,6 +16,6 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20240724 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240808 -types-setuptools==71.1.0.20240806 +types-setuptools==74.0.0.20240831 typing_extensions==4.12.2 -zipp==3.20.0 +zipp==3.20.1 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 406f77a6c..a58dbf628 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py altgraph==0.17.4 -importlib_metadata==8.2.0 +importlib_metadata==8.4.0 packaging==24.1 pyinstaller==6.10.0 pyinstaller-hooks-contrib==2024.8 -zipp==3.20.0 +zipp==3.20.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 583313a2e..b065600ec 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,26 +1,26 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py astroid==3.2.4 -certifi==2024.7.4 +certifi==2024.8.30 cffi==1.17.0 charset-normalizer==3.3.2 cryptography==43.0.0 dill==0.3.8 github3.py==4.0.1 -idna==3.7 +idna==3.8 isort==5.13.2 mccabe==0.7.0 -pefile==2023.2.7 +pefile==2024.8.26 platformdirs==4.2.2 pycparser==2.22 PyJWT==2.9.0 -pylint==3.2.6 +pylint==3.2.7 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 six==1.16.0 tomli==2.0.1 -tomlkit==0.13.0 +tomlkit==0.13.2 typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.2 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 26478433c..993ef342b 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,11 +1,11 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py build==1.2.1 -certifi==2024.7.4 +certifi==2024.8.30 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.7 -importlib_metadata==8.2.0 +idna==3.8 +importlib_metadata==8.4.0 packaging==24.1 Pygments==2.18.0 pyproject_hooks==1.1.0 @@ -14,4 +14,4 @@ requests==2.32.3 tomli==2.0.1 trove-classifiers==2024.7.2 urllib3==2.2.2 -zipp==3.20.0 +zipp==3.20.1 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index fa15e97fb..a834dbd72 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -2,12 +2,12 @@ alabaster==0.7.13 babel==2.16.0 -certifi==2024.7.4 +certifi==2024.8.30 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.7 +idna==3.8 imagesize==1.4.1 -importlib_metadata==8.2.0 +importlib_metadata==8.4.0 Jinja2==3.1.4 MarkupSafe==2.1.5 packaging==24.1 @@ -23,4 +23,4 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 urllib3==2.2.2 -zipp==3.20.0 +zipp==3.20.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 8514f7730..c17fba1cf 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -5,7 +5,7 @@ autocommand==2.2.2 backports.tarfile==1.2.0 beautifulsoup4==4.12.3 blinker==1.8.2 -certifi==2024.7.4 +certifi==2024.8.30 charset-normalizer==3.3.2 cheroot==10.0.1 click==8.1.7 @@ -15,14 +15,14 @@ execnet==2.1.1 filelock==3.15.4 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.111.0 -idna==3.7 -importlib_metadata==8.2.0 -importlib_resources==6.4.0 +hypothesis==6.111.2 +idna==3.8 +importlib_metadata==8.4.0 +importlib_resources==6.4.4 ; python_version=="3.8.*" inflect==7.3.1 iniconfig==2.0.0 itsdangerous==2.2.0 -jaraco.context==5.3.0 +jaraco.context==6.0.1 jaraco.functools==4.0.2 jaraco.text==3.12.1 # Jinja2==3.1.4 @@ -30,10 +30,9 @@ Mako==1.3.5 manhole==1.8.1 # MarkupSafe==2.1.5 more-itertools==10.4.0 -ordered-set==4.1.0 packaging==24.1 parse==1.20.2 -parse-type==0.6.2 +parse_type==0.6.3 platformdirs==4.2.2 pluggy==1.5.0 py-cpuinfo==9.0.0 @@ -54,12 +53,12 @@ requests==2.32.3 requests-file==2.1.0 six==1.16.0 sortedcontainers==2.4.0 -soupsieve==2.5 +soupsieve==2.6 tldextract==5.1.2 tomli==2.0.1 typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.2 vulture==2.11 -Werkzeug==3.0.3 -zipp==3.20.0 +Werkzeug==3.0.4 +zipp==3.20.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index e310a6573..6f89cc043 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -cachetools==5.4.0 +cachetools==5.5.0 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 @@ -10,8 +10,8 @@ pip==24.2 platformdirs==4.2.2 pluggy==1.5.0 pyproject-api==1.7.1 -setuptools==72.1.0 +setuptools==74.1.0 tomli==2.0.1 -tox==4.17.1 +tox==4.18.0 virtualenv==20.26.3 wheel==0.44.0 diff --git a/requirements.txt b/requirements.txt index cc5071ca9..0a4145b06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,12 @@ adblock==0.6.0 colorama==0.4.6 -importlib_resources==6.4.0 ; python_version=="3.8.*" +importlib_resources==6.4.4 ; python_version=="3.8.*" Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 PyYAML==6.0.2 -zipp==3.20.0 +zipp==3.20.1 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From d49dd3d48ff5b1333e0ce438d7eeca0f8f303194 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 6 Sep 2024 17:43:12 +1200 Subject: [PATCH 173/403] fix changelog urls --- scripts/dev/changelog_urls.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 87f5e6f68..fc1b24603 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -16,7 +16,7 @@ "Werkzeug": "https://werkzeug.palletsprojects.com/en/latest/changes/", "click": "https://click.palletsprojects.com/en/latest/changes/", "itsdangerous": "https://itsdangerous.palletsprojects.com/en/latest/changes/", - "parse-type": "https://github.com/jenisys/parse_type/blob/main/CHANGES.txt", + "parse_type": "https://github.com/jenisys/parse_type/blob/main/CHANGES.txt", "sortedcontainers": "https://github.com/grantjenks/python-sortedcontainers/blob/master/HISTORY.rst", "soupsieve": "https://facelessuser.github.io/soupsieve/about/changelog/", "Flask": "https://flask.palletsprojects.com/en/latest/changes/", @@ -149,7 +149,6 @@ "jaraco.text": "https://jaracotext.readthedocs.io/en/latest/history.html", "autocommand": "https://github.com/Lucretiel/autocommand/releases", "inflect": "https://inflect.readthedocs.io/en/latest/history.html", - "ordered-set": "https://github.com/rspeer/ordered-set/blob/master/CHANGELOG.md", "typeguard": "https://typeguard.readthedocs.io/en/latest/versionhistory.html", "backports.tarfile": "https://github.com/jaraco/backports.tarfile/blob/main/NEWS.rst", "pkginfo": "https://bazaar.launchpad.net/~tseaver/pkginfo/trunk/view/head:/CHANGES.txt", From 4ba0e00bbd2e53646ccf31863b6423de9299a3d2 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 6 Sep 2024 18:13:21 +1200 Subject: [PATCH 174/403] Ignore no dictionary errors on CI The message is: The following paths were searched for Qt WebEngine dictionaries: /tmp/qutebrowser-basedir-qrhbqblr/data/qtwebengine_dictionaries but could not find it. Spellchecking can not be enabled. Tests are failing on "Logged unexpected errors". --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index 6c8d29ff9..3a280e345 100644 --- a/pytest.ini +++ b/pytest.ini @@ -78,6 +78,7 @@ qt_log_ignore = # current text pattern. QItemSelectionModel: Selecting when no model has been set will result in a no-op. ^QSaveFile::commit: File \(.*/test_failing_flush0/foo\) is not open$ + ^The following paths were searched for Qt WebEngine dictionaries:.* xfail_strict = true filterwarnings = error From 3331a4cc6aa51066dc1237a7f703af13cd69a0f2 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 6 Sep 2024 19:35:51 +1200 Subject: [PATCH 175/403] Dump 6.8 beta4 security patch version --- qutebrowser/utils/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index ea794597c..113c99894 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -630,7 +630,7 @@ class WebEngineVersions: utils.VersionNumber(6, 7, 2): (_BASES[118], '125.0.6422.142'), # 2024-05-30 ## Qt 6.8 - utils.VersionNumber(6, 8): (_BASES[122], '124.0.6367.202'), # 2024-??-?? + utils.VersionNumber(6, 8): (_BASES[122], '127.0.6533.99'), # 2024-??-?? } def __post_init__(self) -> None: From a05cbe4f30396cb3379b22fecfa7027c1dd6a200 Mon Sep 17 00:00:00 2001 From: toofar Date: Fri, 6 Sep 2024 19:53:06 +1200 Subject: [PATCH 176/403] Adjust permission tests for changes to 6.8 permission storage feature Qt have updated their permission storage feature so it respects our the setting our basedir feature uses, so now all the tests that use "Given I have a fresh instance" are passing. The remaining failing ones do pass if I make them run in a fresh instance, but I am leaving them as xfail because a) opening a new instance is slow b) the new upstream behaviour causes a regression in the qutebrowser behavior (you don't get re-prompted where you would have been previously) so I feel like it is correct for some tests to be failing! We have to set AskEveryTime at some point and we can address them then. --- pytest.ini | 2 +- tests/end2end/conftest.py | 4 ++-- tests/end2end/features/prompts.feature | 26 +++++++++++++------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/pytest.ini b/pytest.ini index 3a280e345..beb5c6b8c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -41,7 +41,7 @@ markers = qt6_only: Tests which should only run with Qt 6 qt5_xfail: Tests which fail with Qt 5 qt6_xfail: Tests which fail with Qt 6 - qt68_beta1_skip: Fails on Qt 6.8 beta 1 + qt68_beta4_skip: Fails on Qt 6.8 beta 4 qt_log_level_fail = WARNING qt_log_ignore = # GitHub Actions diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 066c5cd1b..6bd7302ac 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -187,8 +187,8 @@ def pytest_collection_modifyitems(config, items): 'Skipped on Windows', pytest.mark.skipif, utils.is_windows), - ('qt68_beta1_skip', # WORKAROUND: Qt6.8b1 https://bugreports.qt.io/browse/QTBUG-126595 - "Fails on Qt 6.8 beta 1", + ('qt68_beta4_skip', # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2184542226 + "Fails on Qt 6.8 beta 4", pytest.mark.xfail, machinery.IS_QT6 and version.qtwebengine_versions( avoid_init=True diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index f8f5af8d7..9e2062d13 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -96,7 +96,7 @@ Feature: Prompts Then the javascript message "Alert done" should be logged And the javascript message "notification permission granted" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: Async question interrupted by async one Given I have a fresh instance When I set content.notifications.enabled to ask @@ -112,7 +112,7 @@ Feature: Prompts Then the javascript message "notification permission granted" should be logged And "Added quickmark test for *" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: Async question interrupted by blocking one Given I have a fresh instance When I set content.notifications.enabled to ask @@ -251,7 +251,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "geolocation permission denied" should be logged - @qt68_beta1_skip + @qt68_beta4_skip Scenario: geolocation with ask -> false When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -260,7 +260,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "geolocation permission denied" should be logged - @qt68_beta1_skip + @qt68_beta4_skip Scenario: geolocation with ask -> false and save When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -270,7 +270,7 @@ Feature: Prompts Then the javascript message "geolocation permission denied" should be logged And the per-domain option content.geolocation should be set to false for http://localhost:(port) - @qt68_beta1_skip + @qt68_beta4_skip Scenario: geolocation with ask -> abort When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -281,7 +281,7 @@ Feature: Prompts # Notifications - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: Always rejecting notifications Given I have a fresh instance When I set content.notifications.enabled to false @@ -289,7 +289,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "notification permission denied" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: Always accepting notifications Given I have a fresh instance When I set content.notifications.enabled to true @@ -297,7 +297,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "notification permission granted" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: notifications with ask -> false Given I have a fresh instance When I set content.notifications.enabled to ask @@ -307,7 +307,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "notification permission denied" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: notifications with ask -> false and save Given I have a fresh instance When I set content.notifications.enabled to ask @@ -318,7 +318,7 @@ Feature: Prompts Then the javascript message "notification permission denied" should be logged And the per-domain option content.notifications.enabled should be set to false for http://localhost:(port) - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: notifications with ask -> true Given I have a fresh instance When I set content.notifications.enabled to ask @@ -328,7 +328,7 @@ Feature: Prompts And I run :prompt-accept yes Then the javascript message "notification permission granted" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: notifications with ask -> true and save Given I have a fresh instance When I set content.notifications.enabled to ask @@ -350,7 +350,7 @@ Feature: Prompts And I run :mode-leave Then the javascript message "notification permission aborted" should be logged - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: answering notification after closing tab Given I have a fresh instance When I set content.notifications.enabled to ask @@ -524,7 +524,7 @@ Feature: Prompts # https://github.com/qutebrowser/qutebrowser/issues/1249#issuecomment-175205531 # https://github.com/qutebrowser/qutebrowser/pull/2054#issuecomment-258285544 - @qtwebkit_skip @qt68_beta1_skip + @qtwebkit_skip Scenario: Interrupting SSL prompt during a notification prompt Given I have a fresh instance When I set content.notifications.enabled to ask From cff456f2321be62f1ef208a0aee8c17b0b3d70a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:43:12 +0000 Subject: [PATCH 177/403] Bump peter-evans/create-pull-request from 6 to 7 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/recompile-requirements.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/recompile-requirements.yml b/.github/workflows/recompile-requirements.yml index 6d42c3137..2ef79ad8d 100644 --- a/.github/workflows/recompile-requirements.yml +++ b/.github/workflows/recompile-requirements.yml @@ -41,7 +41,7 @@ jobs: - name: Run qutebrowser smoke test run: "xvfb-run .venv/bin/python3 -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ':later 500 quit'" - name: Create pull request - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: committer: qutebrowser bot author: qutebrowser bot From db83a82fe118c3cf4a4696901bf4f29cf6361165 Mon Sep 17 00:00:00 2001 From: toofar Date: Tue, 17 Sep 2024 20:35:42 +1200 Subject: [PATCH 178/403] Include platformdirs in dev requirements as a workaround See 433074c6817daa2, this is the same cause. An older version of a package being included in requirements files because setuptools injects its vendored packages into sys.path and we use pip freeze to build lock files. Then when you install two requirements files at the same time they end up having conflicting versions. This at least means we include the latest version, which will do until we move to a method of generating lock files that just works off of the raw requirements file. --- misc/requirements/requirements-dev.txt-raw | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/misc/requirements/requirements-dev.txt-raw b/misc/requirements/requirements-dev.txt-raw index 261f4459f..fc991474c 100644 --- a/misc/requirements/requirements-dev.txt-raw +++ b/misc/requirements/requirements-dev.txt-raw @@ -7,5 +7,9 @@ pyqt-builder build twine +# Included to override setuptools' vendored version that is being included in +# the lock file by pip freeze. +platformdirs + # Already included via test requirements #@ ignore: urllib3 From 78a74a2e2afe0bc0ff93473c20294c5f79093a7e Mon Sep 17 00:00:00 2001 From: toofar Date: Tue, 17 Sep 2024 23:04:25 +1200 Subject: [PATCH 179/403] Include platformdirs in test requirements as a workaround too See the previous commit db83a82fe118c --- misc/requirements/requirements-tests.txt-raw | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 44dfdafe9..535424495 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -31,13 +31,15 @@ pytest-xdist # Needed to test misc/userscripts/qute-lastpass tldextract -# importlib_resources==6.4.0 and context==5.3.0 are being included in the lock -# file via setuptools' vendored dependencies and conflicting with the more up -# to date one pulled down by other requirements files. +# importlib_resources==6.4.0, jaraco.context and platformdirs are being +# included in the lock file via setuptools' vendored dependencies and +# conflicting with the more up to date one pulled down by other requirements +# files. # Include them here even though we don't need them to make sure we at least # get an up to date version. importlib_resources #@ markers: importlib_resources python_version=="3.8.*" jaraco.context +platformdirs #@ ignore: Jinja2, MarkupSafe, colorama From 61eb05d04379e3eea97d24bf9652eaf7d4786381 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Tue, 17 Sep 2024 11:14:19 +0000 Subject: [PATCH 180/403] Update dependencies --- .../requirements-check-manifest.txt | 6 ++--- misc/requirements/requirements-dev.txt | 23 ++++++++++--------- misc/requirements/requirements-mypy.txt | 12 +++++----- .../requirements/requirements-pyinstaller.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 10 ++++---- misc/requirements/requirements-pyqt-5.15.txt | 4 ++-- misc/requirements/requirements-pyqt-5.txt | 4 ++-- misc/requirements/requirements-pyroma.txt | 12 +++++----- misc/requirements/requirements-sphinx.txt | 10 ++++---- misc/requirements/requirements-tests.txt | 21 +++++++++-------- misc/requirements/requirements-tox.txt | 10 ++++---- requirements.txt | 4 ++-- 12 files changed, 61 insertions(+), 59 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 13fa61d34..82552b858 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.2.1 +build==1.2.2 check-manifest==0.49 -importlib_metadata==8.4.0 +importlib_metadata==8.5.0 packaging==24.1 pyproject_hooks==1.1.0 tomli==2.0.1 -zipp==3.20.1 +zipp==3.20.2 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index b94e8329d..a11708a6d 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -2,20 +2,21 @@ autocommand==2.2.2 backports.tarfile==1.2.0 -build==1.2.1 +build==1.2.2 bump2version==1.0.1 certifi==2024.8.30 -cffi==1.17.0 +cffi==1.17.1 charset-normalizer==3.3.2 -cryptography==43.0.0 +cryptography==43.0.1 docutils==0.20.1 github3.py==4.0.1 hunter==3.7.0 -idna==3.8 -importlib_metadata==8.4.0 -importlib_resources==6.4.4 +idna==3.10 +importlib_metadata==8.5.0 +importlib_resources==6.4.5 inflect==7.3.1 jaraco.classes==3.4.0 +jaraco.collections==5.1.0 jaraco.context==6.0.1 jaraco.functools==4.0.2 jaraco.text==3.12.1 @@ -24,11 +25,11 @@ keyring==25.3.0 manhole==1.8.1 markdown-it-py==3.0.0 mdurl==0.1.2 -more-itertools==10.4.0 +more-itertools==10.5.0 nh3==0.2.18 packaging==24.1 pkginfo==1.10.0 -platformdirs==4.2.2 +platformdirs==4.3.3 pycparser==2.22 Pygments==2.18.0 PyJWT==2.9.0 @@ -40,7 +41,7 @@ readme_renderer==43.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.8.0 +rich==13.8.1 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 @@ -49,5 +50,5 @@ twine==5.1.1 typeguard==4.3.0 typing_extensions==4.12.2 uritemplate==4.1.1 -# urllib3==2.2.2 -zipp==3.20.1 +# urllib3==2.2.3 +zipp==3.20.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index dc1bd4dc2..5a6eb2344 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py chardet==5.2.0 -diff_cover==9.1.1 -importlib_resources==6.4.4 +diff_cover==9.2.0 +importlib_resources==6.4.5 Jinja2==3.1.4 lxml==5.3.0 MarkupSafe==2.1.5 @@ -13,9 +13,9 @@ Pygments==2.18.0 PyQt5-stubs==5.15.6.0 tomli==2.0.1 types-colorama==0.4.15.20240311 -types-docutils==0.21.0.20240724 +types-docutils==0.21.0.20240907 types-Pygments==2.18.0.20240506 -types-PyYAML==6.0.12.20240808 -types-setuptools==74.0.0.20240831 +types-PyYAML==6.0.12.20240917 +types-setuptools==75.1.0.20240917 typing_extensions==4.12.2 -zipp==3.20.1 +zipp==3.20.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index a58dbf628..d7e92fc61 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py altgraph==0.17.4 -importlib_metadata==8.4.0 +importlib_metadata==8.5.0 packaging==24.1 pyinstaller==6.10.0 pyinstaller-hooks-contrib==2024.8 -zipp==3.20.1 +zipp==3.20.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index b065600ec..0d70d19c8 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -2,16 +2,16 @@ astroid==3.2.4 certifi==2024.8.30 -cffi==1.17.0 +cffi==1.17.1 charset-normalizer==3.3.2 -cryptography==43.0.0 +cryptography==43.0.1 dill==0.3.8 github3.py==4.0.1 -idna==3.8 +idna==3.10 isort==5.13.2 mccabe==0.7.0 pefile==2024.8.26 -platformdirs==4.2.2 +platformdirs==4.3.3 pycparser==2.22 PyJWT==2.9.0 pylint==3.2.7 @@ -23,4 +23,4 @@ tomli==2.0.1 tomlkit==0.13.2 typing_extensions==4.12.2 uritemplate==4.1.1 -# urllib3==2.2.2 +# urllib3==2.2.3 diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt index e2473d653..428932f34 100644 --- a/misc/requirements/requirements-pyqt-5.15.txt +++ b/misc/requirements/requirements-pyqt-5.15.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.15.11 # rq.filter: < 5.16 -PyQt5-Qt5==5.15.14 +PyQt5-Qt5==5.15.15 PyQt5_sip==12.15.0 PyQtWebEngine==5.15.7 # rq.filter: < 5.16 -PyQtWebEngine-Qt5==5.15.14 +PyQtWebEngine-Qt5==5.15.15 diff --git a/misc/requirements/requirements-pyqt-5.txt b/misc/requirements/requirements-pyqt-5.txt index f3a57b692..50b47a513 100644 --- a/misc/requirements/requirements-pyqt-5.txt +++ b/misc/requirements/requirements-pyqt-5.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt5==5.15.11 -PyQt5-Qt5==5.15.14 +PyQt5-Qt5==5.15.15 PyQt5_sip==12.15.0 PyQtWebEngine==5.15.7 -PyQtWebEngine-Qt5==5.15.14 +PyQtWebEngine-Qt5==5.15.15 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 993ef342b..b9410f09d 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,17 +1,17 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.2.1 +build==1.2.2 certifi==2024.8.30 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.8 -importlib_metadata==8.4.0 +idna==3.10 +importlib_metadata==8.5.0 packaging==24.1 Pygments==2.18.0 pyproject_hooks==1.1.0 pyroma==4.2 requests==2.32.3 tomli==2.0.1 -trove-classifiers==2024.7.2 -urllib3==2.2.2 -zipp==3.20.1 +trove-classifiers==2024.9.12 +urllib3==2.2.3 +zipp==3.20.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index a834dbd72..2f0ba5b9a 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -5,14 +5,14 @@ babel==2.16.0 certifi==2024.8.30 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.8 +idna==3.10 imagesize==1.4.1 -importlib_metadata==8.4.0 +importlib_metadata==8.5.0 Jinja2==3.1.4 MarkupSafe==2.1.5 packaging==24.1 Pygments==2.18.0 -pytz==2024.1 +pytz==2024.2 requests==2.32.3 snowballstemmer==2.2.0 Sphinx==7.1.2 @@ -22,5 +22,5 @@ sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 -urllib3==2.2.2 -zipp==3.20.1 +urllib3==2.2.3 +zipp==3.20.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c17fba1cf..4332150b8 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -12,16 +12,17 @@ click==8.1.7 coverage==7.6.1 exceptiongroup==1.2.2 execnet==2.1.1 -filelock==3.15.4 +filelock==3.16.0 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.111.2 -idna==3.8 -importlib_metadata==8.4.0 -importlib_resources==6.4.4 ; python_version=="3.8.*" +hypothesis==6.112.1 +idna==3.10 +importlib_metadata==8.5.0 +importlib_resources==6.4.5 ; python_version=="3.8.*" inflect==7.3.1 iniconfig==2.0.0 itsdangerous==2.2.0 +jaraco.collections==5.1.0 jaraco.context==6.0.1 jaraco.functools==4.0.2 jaraco.text==3.12.1 @@ -29,15 +30,15 @@ jaraco.text==3.12.1 Mako==1.3.5 manhole==1.8.1 # MarkupSafe==2.1.5 -more-itertools==10.4.0 +more-itertools==10.5.0 packaging==24.1 parse==1.20.2 parse_type==0.6.3 -platformdirs==4.2.2 +platformdirs==4.3.3 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 -pytest==8.3.2 +pytest==8.3.3 pytest-bdd==7.2.0 pytest-benchmark==4.0.0 pytest-cov==5.0.0 @@ -58,7 +59,7 @@ tldextract==5.1.2 tomli==2.0.1 typeguard==4.3.0 typing_extensions==4.12.2 -urllib3==2.2.2 +urllib3==2.2.3 vulture==2.11 Werkzeug==3.0.4 -zipp==3.20.1 +zipp==3.20.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 6f89cc043..dd4c6231f 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,14 +4,14 @@ cachetools==5.5.0 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 -filelock==3.15.4 +filelock==3.16.0 packaging==24.1 pip==24.2 -platformdirs==4.2.2 +platformdirs==4.3.3 pluggy==1.5.0 pyproject-api==1.7.1 -setuptools==74.1.0 +setuptools==75.1.0 tomli==2.0.1 -tox==4.18.0 -virtualenv==20.26.3 +tox==4.18.1 +virtualenv==20.26.4 wheel==0.44.0 diff --git a/requirements.txt b/requirements.txt index 0a4145b06..4102f1af6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,12 @@ adblock==0.6.0 colorama==0.4.6 -importlib_resources==6.4.4 ; python_version=="3.8.*" +importlib_resources==6.4.5 ; python_version=="3.8.*" Jinja2==3.1.4 MarkupSafe==2.1.5 Pygments==2.18.0 PyYAML==6.0.2 -zipp==3.20.1 +zipp==3.20.2 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From a409f9acf7d3045725ea89e1c6ca0cabc61ef301 Mon Sep 17 00:00:00 2001 From: toofar Date: Wed, 18 Sep 2024 11:35:30 +1200 Subject: [PATCH 181/403] add changelog for jaraco.collections --- scripts/dev/changelog_urls.json | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index fc1b24603..7b0fcd8fb 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -147,6 +147,7 @@ "jaraco.context": "https://jaracocontext.readthedocs.io/en/latest/history.html", "jaraco.functools": "https://jaracofunctools.readthedocs.io/en/latest/history.html", "jaraco.text": "https://jaracotext.readthedocs.io/en/latest/history.html", + "jaraco.collections": "https://jaracocollections.readthedocs.io/en/latest/history.html", "autocommand": "https://github.com/Lucretiel/autocommand/releases", "inflect": "https://inflect.readthedocs.io/en/latest/history.html", "typeguard": "https://typeguard.readthedocs.io/en/latest/versionhistory.html", From 2a75b341b80e5ddd0d433a3d04186e34c26baff1 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 23 Sep 2024 04:19:57 +0000 Subject: [PATCH 182/403] Update dependencies --- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-tests.txt | 8 ++++---- misc/requirements/requirements-tox.txt | 10 +++++----- misc/requirements/requirements-vulture.txt | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index a11708a6d..0bdf736de 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -21,7 +21,7 @@ jaraco.context==6.0.1 jaraco.functools==4.0.2 jaraco.text==3.12.1 jeepney==0.8.0 -keyring==25.3.0 +keyring==25.4.1 manhole==1.8.1 markdown-it-py==3.0.0 mdurl==0.1.2 @@ -29,7 +29,7 @@ more-itertools==10.5.0 nh3==0.2.18 packaging==24.1 pkginfo==1.10.0 -platformdirs==4.3.3 +platformdirs==4.3.6 pycparser==2.22 Pygments==2.18.0 PyJWT==2.9.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 0d70d19c8..ca7fbc05f 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -11,7 +11,7 @@ idna==3.10 isort==5.13.2 mccabe==0.7.0 pefile==2024.8.26 -platformdirs==4.3.3 +platformdirs==4.3.6 pycparser==2.22 PyJWT==2.9.0 pylint==3.2.7 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 4332150b8..9f70bb671 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -12,7 +12,7 @@ click==8.1.7 coverage==7.6.1 exceptiongroup==1.2.2 execnet==2.1.1 -filelock==3.16.0 +filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 hypothesis==6.112.1 @@ -34,12 +34,12 @@ more-itertools==10.5.0 packaging==24.1 parse==1.20.2 parse_type==0.6.3 -platformdirs==4.3.3 +platformdirs==4.3.6 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 pytest==8.3.3 -pytest-bdd==7.2.0 +pytest-bdd==7.3.0 pytest-benchmark==4.0.0 pytest-cov==5.0.0 pytest-instafail==0.5.0 @@ -60,6 +60,6 @@ tomli==2.0.1 typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 -vulture==2.11 +vulture==2.12 Werkzeug==3.0.4 zipp==3.20.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index dd4c6231f..d95e3c0f9 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -4,14 +4,14 @@ cachetools==5.5.0 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 -filelock==3.16.0 +filelock==3.16.1 packaging==24.1 pip==24.2 -platformdirs==4.3.3 +platformdirs==4.3.6 pluggy==1.5.0 -pyproject-api==1.7.1 +pyproject-api==1.8.0 setuptools==75.1.0 tomli==2.0.1 -tox==4.18.1 -virtualenv==20.26.4 +tox==4.20.0 +virtualenv==20.26.5 wheel==0.44.0 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index a7d37e73a..09294ac8d 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py tomli==2.0.1 -vulture==2.11 +vulture==2.12 From 1cc408b77ffee49f285af4458f2bd79cc2a0adb2 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 30 Sep 2024 04:20:13 +0000 Subject: [PATCH 183/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyqt-6.7.txt | 6 +++--- misc/requirements/requirements-pyqt-6.txt | 6 +++--- misc/requirements/requirements-pyqt.txt | 6 +++--- misc/requirements/requirements-pyroma.txt | 2 +- misc/requirements/requirements-tests.txt | 4 ++-- misc/requirements/requirements-tox.txt | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 82552b858..d4d85c486 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -4,6 +4,6 @@ build==1.2.2 check-manifest==0.49 importlib_metadata==8.5.0 packaging==24.1 -pyproject_hooks==1.1.0 +pyproject_hooks==1.2.0 tomli==2.0.1 zipp==3.20.2 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 0bdf736de..063738395 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -18,7 +18,7 @@ inflect==7.3.1 jaraco.classes==3.4.0 jaraco.collections==5.1.0 jaraco.context==6.0.1 -jaraco.functools==4.0.2 +jaraco.functools==4.1.0 jaraco.text==3.12.1 jeepney==0.8.0 keyring==25.4.1 @@ -34,7 +34,7 @@ pycparser==2.22 Pygments==2.18.0 PyJWT==2.9.0 Pympler==1.1 -pyproject_hooks==1.1.0 +pyproject_hooks==1.2.0 PyQt-builder==1.16.4 python-dateutil==2.9.0.post0 readme_renderer==43.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index ca7fbc05f..d053a6673 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -5,7 +5,7 @@ certifi==2024.8.30 cffi==1.17.1 charset-normalizer==3.3.2 cryptography==43.0.1 -dill==0.3.8 +dill==0.3.9 github3.py==4.0.1 idna==3.10 isort==5.13.2 diff --git a/misc/requirements/requirements-pyqt-6.7.txt b/misc/requirements/requirements-pyqt-6.7.txt index 64e171c1b..46501eb16 100644 --- a/misc/requirements/requirements-pyqt-6.7.txt +++ b/misc/requirements/requirements-pyqt-6.7.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.1 -PyQt6-Qt6==6.7.2 +PyQt6-Qt6==6.7.3 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.2 -PyQt6-WebEngineSubwheel-Qt6==6.7.2 +PyQt6-WebEngine-Qt6==6.7.3 +PyQt6-WebEngineSubwheel-Qt6==6.7.3 PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 64e171c1b..46501eb16 100644 --- a/misc/requirements/requirements-pyqt-6.txt +++ b/misc/requirements/requirements-pyqt-6.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.1 -PyQt6-Qt6==6.7.2 +PyQt6-Qt6==6.7.3 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.2 -PyQt6-WebEngineSubwheel-Qt6==6.7.2 +PyQt6-WebEngine-Qt6==6.7.3 +PyQt6-WebEngineSubwheel-Qt6==6.7.3 PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 64e171c1b..46501eb16 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py PyQt6==6.7.1 -PyQt6-Qt6==6.7.2 +PyQt6-Qt6==6.7.3 PyQt6-WebEngine==6.7.0 -PyQt6-WebEngine-Qt6==6.7.2 -PyQt6-WebEngineSubwheel-Qt6==6.7.2 +PyQt6-WebEngine-Qt6==6.7.3 +PyQt6-WebEngineSubwheel-Qt6==6.7.3 PyQt6_sip==13.8.0 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index b9410f09d..223ada625 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -8,7 +8,7 @@ idna==3.10 importlib_metadata==8.5.0 packaging==24.1 Pygments==2.18.0 -pyproject_hooks==1.1.0 +pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 tomli==2.0.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 9f70bb671..2e8f1f067 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -15,7 +15,7 @@ execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.112.1 +hypothesis==6.112.2 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 ; python_version=="3.8.*" @@ -24,7 +24,7 @@ iniconfig==2.0.0 itsdangerous==2.2.0 jaraco.collections==5.1.0 jaraco.context==6.0.1 -jaraco.functools==4.0.2 +jaraco.functools==4.1.0 jaraco.text==3.12.1 # Jinja2==3.1.4 Mako==1.3.5 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index d95e3c0f9..f66945528 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -13,5 +13,5 @@ pyproject-api==1.8.0 setuptools==75.1.0 tomli==2.0.1 tox==4.20.0 -virtualenv==20.26.5 +virtualenv==20.26.6 wheel==0.44.0 From 0b6db054992aa4bdc40e5e1185d26c9b30876bb1 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 30 Sep 2024 15:14:49 +0200 Subject: [PATCH 184/403] =?UTF-8?q?=F0=9F=93=9D=20Mention=20`kerberos`=20U?= =?UTF-8?q?SE-flag=20on=20Gentoo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This flag is vital for the allow-list configuration to be picked up. It should be set globally and `dev-qt/qtwebengine` should be recompiled after it's enabled. Ref #8313. --- doc/install.asciidoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 1954a662a..20dc29597 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -162,6 +162,13 @@ need to turn off the `bindist` flag for `dev-qt/qtwebengine`. See the https://wiki.gentoo.org/wiki/Qutebrowser#USE_flags[Gentoo Wiki] for more information. +To be able to use Kerberos authentication, you will need to turn on the +`kerberos` USE-flag system-wide and re-emerge `dev-qt/qtwebengine` after that. + +See the +https://wiki.gentoo.org/wiki/Qutebrowser#Kerberos_authentication_does_not_work[ +Troubleshooting section in Gentoo Wiki] for more information. + On Void Linux ------------- From 0b0eb46b55a0539aa4269e5381bc012285909995 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 5 Oct 2024 12:03:35 +1300 Subject: [PATCH 185/403] update expected security patch version for Qt 6.7.3 --- qutebrowser/utils/version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 113c99894..deb2aaea5 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -628,9 +628,10 @@ class WebEngineVersions: utils.VersionNumber(6, 7): (_BASES[118], '122.0.6261.128'), # 2024-03-12 utils.VersionNumber(6, 7, 1): (_BASES[118], '124.0.6367.202'), # ~2024-05-09 utils.VersionNumber(6, 7, 2): (_BASES[118], '125.0.6422.142'), # 2024-05-30 + utils.VersionNumber(6, 7, 3): (_BASES[118], '129.0.6668.58'), # 2024-09-27 ## Qt 6.8 - utils.VersionNumber(6, 8): (_BASES[122], '127.0.6533.99'), # 2024-??-?? + utils.VersionNumber(6, 8): (_BASES[122], '129.0.6668.58'), # 2024-??-?? } def __post_init__(self) -> None: From fc9fe750331255c4cd439a6866791654d6fd948c Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 5 Oct 2024 13:29:50 +1300 Subject: [PATCH 186/403] Remove qt5 nightly jobs They require maintenance, but we don't have a great need for Qt5 builds. See https://github.com/qutebrowser/qutebrowser/issues/8260 All the Qt5 switches are still in tox, the build release script and the nsis installer. --- .github/workflows/nightly.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d4e106bc2..07d8812b9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -14,20 +14,6 @@ jobs: fail-fast: false matrix: include: - - os: macos-12 - toxenv: build-release-qt5 - name: qt5-macos - - os: windows-2019 - toxenv: build-release-qt5 - name: qt5-windows - - os: macos-12 - args: --debug - toxenv: build-release-qt5 - name: qt5-macos-debug - - os: windows-2019 - args: --debug - toxenv: build-release-qt5 - name: qt5-windows-debug - os: macos-12 toxenv: build-release name: macos-intel From d24a4c5ab0b330d5d7c9ed32049e235999d1763a Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 5 Oct 2024 13:34:03 +1300 Subject: [PATCH 187/403] Remove Qt5 switches from release building scripts etc I just searched for qt5 and deleted stuff. EZ. Will leave on a branch for a bit and see if I feel like testing this at all, otherwise maybe leave this stuff in here and make it not called. Not 100% sure that we need to remove all this stuff when we just want the CI to go green. But tbh if we don't need to make Qt5 releases then we don't need it. Better to be bold and pull it out than have to work around it in the future. And we can always revert the commit. --- misc/nsis/install.nsh | 31 +++++++-------------- misc/nsis/qutebrowser.nsi | 3 -- scripts/dev/build_release.py | 53 +++++++++++------------------------- 3 files changed, 26 insertions(+), 61 deletions(-) diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh index 5086dcb0d..db23a6f51 100755 --- a/misc/nsis/install.nsh +++ b/misc/nsis/install.nsh @@ -435,29 +435,18 @@ Function .onInit ${If} ${RunningX64} ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks GetWinVer $R0 Major - !if "${QT5}" == "True" - IntCmpU $R0 6 0 _os_check_fail _os_check_pass - GetWinVer $R1 Minor - IntCmpU $R1 2 _os_check_pass _os_check_fail _os_check_pass - !else - IntCmpU $R0 10 0 _os_check_fail _os_check_pass - GetWinVer $R1 Build - ${If} $R1 >= 22000 ; Windows 11 21H2 - Goto _os_check_pass - ${ElseIf} $R1 >= 14393 ; Windows 10 1607 - ${AndIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 - Goto _os_check_pass - ${EndIf} - !endif + IntCmpU $R0 10 0 _os_check_fail _os_check_pass + GetWinVer $R1 Build + ${If} $R1 >= 22000 ; Windows 11 21H2 + Goto _os_check_pass + ${ElseIf} $R1 >= 14393 ; Windows 10 1607 + ${AndIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 + Goto _os_check_pass + ${EndIf} ${EndIf} _os_check_fail: - !if "${QT5}" == "True" - MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ - version of Windows 8 or later." - !else - MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ - version of Windows 10 1607 or later." - !endif + MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ + version of Windows 10 1607 or later." Abort _os_check_pass: diff --git a/misc/nsis/qutebrowser.nsi b/misc/nsis/qutebrowser.nsi index bd5156e83..dcdb047f6 100755 --- a/misc/nsis/qutebrowser.nsi +++ b/misc/nsis/qutebrowser.nsi @@ -131,9 +131,6 @@ ShowUninstDetails hide !define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}-${VERSION}" !endif -; If not defined, assume Qt6 (requires a more recent windows version) -!define /ifndef QT5 "False" - ; Pack the exe header with upx if UPX is defined. !ifdef UPX !packhdr "$%TEMP%\exehead.tmp" '"upx" "--ultra-brute" "$%TEMP%\exehead.tmp"' diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index ed653316b..08ce3c419 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -125,7 +125,7 @@ def _smoke_test_run( return subprocess.run(argv, check=True, capture_output=True) -def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None: +def smoke_test(executable: pathlib.Path, debug: bool) -> None: """Try starting the given qutebrowser executable.""" stdout_whitelist = [] stderr_whitelist = [ @@ -164,18 +164,15 @@ def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None: (r'\[.*:ERROR:command_buffer_proxy_impl.cc\([0-9]*\)\] ' r'ContextResult::kTransientFailure: Failed to send ' r'.*CreateCommandBuffer\.'), + # FIXME:qt6 Qt 6.3 on macOS + r'[0-9:]* WARNING: Incompatible version of OpenSSL', + r'[0-9:]* WARNING: Qt WebEngine resources not found at .*', + (r'[0-9:]* WARNING: Installed Qt WebEngine locales directory not found at ' + r'location /qtwebengine_locales\. Trying application directory\.\.\.'), + # Qt 6.7, only seen on macos for some reason + (r'.*Path override failed for key base::DIR_APP_DICTIONARIES ' + r"and path '.*/qtwebengine_dictionaries'"), ]) - if not qt5: - stderr_whitelist.extend([ - # FIXME:qt6 Qt 6.3 on macOS - r'[0-9:]* WARNING: Incompatible version of OpenSSL', - r'[0-9:]* WARNING: Qt WebEngine resources not found at .*', - (r'[0-9:]* WARNING: Installed Qt WebEngine locales directory not found at ' - r'location /qtwebengine_locales\. Trying application directory\.\.\.'), - # Qt 6.7, only seen on macos for some reason - (r'.*Path override failed for key base::DIR_APP_DICTIONARIES ' - r"and path '.*/qtwebengine_dictionaries'"), - ]) elif IS_WINDOWS: stderr_whitelist.extend([ # Windows N: @@ -267,7 +264,6 @@ def _mac_bin_path(base: pathlib.Path) -> pathlib.Path: def build_mac( *, gh_token: Optional[str], - qt5: bool, skip_packaging: bool, debug: bool, ) -> List[Artifact]: @@ -282,18 +278,18 @@ def build_mac( shutil.rmtree(d, ignore_errors=True) utils.print_title("Updating 3rdparty content") - update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=qt5, fancy_dmg=False, + update_3rdparty.run(ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building .app via pyinstaller") - call_tox(f'pyinstaller{"-qt5" if qt5 else ""}', '-r', debug=debug) + call_tox('pyinstaller', '-r', debug=debug) utils.print_title("Verifying .app") verify_mac_app() dist_path = pathlib.Path("dist") utils.print_title("Running pre-dmg smoke test") - smoke_test(_mac_bin_path(dist_path), debug=debug, qt5=qt5) + smoke_test(_mac_bin_path(dist_path), debug=debug) if skip_packaging: return [] @@ -304,7 +300,6 @@ def build_mac( arch = platform.machine() suffix = "-debug" if debug else "" - suffix += "-qt5" if qt5 else "" suffix += f"-{arch}" dmg_path = dist_path / f'qutebrowser-{qutebrowser.__version__}{suffix}.dmg' pathlib.Path('qutebrowser.dmg').rename(dmg_path) @@ -317,7 +312,7 @@ def build_mac( subprocess.run(['hdiutil', 'attach', dmg_path, '-mountpoint', tmp_path], check=True) try: - smoke_test(_mac_bin_path(tmp_path), debug=debug, qt5=qt5) + smoke_test(_mac_bin_path(tmp_path), debug=debug) finally: print("Waiting 10s for dmg to be detachable...") time.sleep(10) @@ -355,7 +350,6 @@ def _get_windows_python_path() -> pathlib.Path: def _build_windows_single( *, - qt5: bool, skip_packaging: bool, debug: bool, ) -> List[Artifact]: @@ -367,9 +361,7 @@ def _build_windows_single( _maybe_remove(out_path) python = _get_windows_python_path() - # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872? - suffix = "-qt5" if qt5 else "" - call_tox(f'pyinstaller{suffix}', '-r', python=python, debug=debug) + call_tox('pyinstaller', '-r', python=python, debug=debug) out_pyinstaller = dist_path / "qutebrowser" shutil.move(out_pyinstaller, out_path) @@ -379,7 +371,7 @@ def _build_windows_single( verify_windows_exe(exe_path) utils.print_title("Running smoke test") - smoke_test(exe_path, debug=debug, qt5=qt5) + smoke_test(exe_path, debug=debug) if skip_packaging: return [] @@ -388,19 +380,17 @@ def _build_windows_single( return _package_windows_single( out_path=out_path, debug=debug, - qt5=qt5, ) def build_windows( *, gh_token: str, skip_packaging: bool, - qt5: bool, debug: bool, ) -> List[Artifact]: """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") - update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=qt5, + update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, gh_token=gh_token) utils.print_title("Building Windows binaries") @@ -412,7 +402,6 @@ def build_windows( artifacts = _build_windows_single( skip_packaging=skip_packaging, debug=debug, - qt5=qt5, ) return artifacts @@ -421,7 +410,6 @@ def _package_windows_single( *, out_path: pathlib.Path, debug: bool, - qt5: bool, ) -> List[Artifact]: """Build the given installer/zip for windows.""" artifacts = [] @@ -430,7 +418,6 @@ def _package_windows_single( utils.print_subtitle("Building installer...") subprocess.run(['makensis.exe', f'/DVERSION={qutebrowser.__version__}', - f'/DQT5={qt5}', 'misc/nsis/qutebrowser.nsi'], check=True) name_parts = [ @@ -439,8 +426,6 @@ def _package_windows_single( ] if debug: name_parts.append('debug') - if qt5: - name_parts.append('qt5') name_parts.append('amd64') # FIXME:qt6 temporary until new installer name = '-'.join(name_parts) + '.exe' @@ -460,8 +445,6 @@ def _package_windows_single( ] if debug: zip_name_parts.append('debug') - if qt5: - zip_name_parts.append('qt5') zip_name = '-'.join(zip_name_parts) + '.zip' zip_path = dist_path / zip_name @@ -680,8 +663,6 @@ def main() -> None: help="Skip Windows installer/zip generation or macOS DMG.") parser.add_argument('--debug', action='store_true', required=False, help="Build a debug build.") - parser.add_argument('--qt5', action='store_true', required=False, - help="Build against PyQt5") parser.add_argument('--experimental', action='store_true', required=False, default=os.environ.get("GITHUB_REPOSITORY") == "qutebrowser/experiments", help="Upload to experiments repo and test PyPI. Set automatically if on qutebrowser/experiments CI.") @@ -712,14 +693,12 @@ def main() -> None: artifacts = build_windows( gh_token=gh_token, skip_packaging=args.skip_packaging, - qt5=args.qt5, debug=args.debug, ) elif IS_MACOS: artifacts = build_mac( gh_token=gh_token, skip_packaging=args.skip_packaging, - qt5=args.qt5, debug=args.debug, ) else: From 3a48111e5348c289e9472623ae7af84e4e2ae824 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 5 Oct 2024 13:45:18 +1300 Subject: [PATCH 188/403] update node version for eslint Github is updating all their actions to node 20, may as well do the same here? Not strong need to update it, just spotted this. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e0c84bc7..8d95a363e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: python-version: '3.10' - uses: actions/setup-node@v4 with: - node-version: '16.x' + node-version: '20.x' if: "matrix.testenv == 'eslint'" - name: Set up problem matchers run: "python scripts/dev/ci/problemmatchers.py ${{ matrix.testenv }} ${{ runner.temp }}" From dea648ccd8b838e692850439a9f6ac57d21d503e Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 7 Oct 2024 04:22:07 +0000 Subject: [PATCH 189/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 4 ++-- misc/requirements/requirements-dev.txt | 6 +++--- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-tests.txt | 10 +++++----- misc/requirements/requirements-tox.txt | 5 +++-- misc/requirements/requirements-vulture.txt | 4 ++-- 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index d4d85c486..1490661f4 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -1,9 +1,9 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.2.2 +build==1.2.2.post1 check-manifest==0.49 importlib_metadata==8.5.0 packaging==24.1 pyproject_hooks==1.2.0 -tomli==2.0.1 +tomli==2.0.2 zipp==3.20.2 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 063738395..19d0dd9b7 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -2,7 +2,7 @@ autocommand==2.2.2 backports.tarfile==1.2.0 -build==1.2.2 +build==1.2.2.post1 bump2version==1.0.1 certifi==2024.8.30 cffi==1.17.1 @@ -41,11 +41,11 @@ readme_renderer==43.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.8.1 +rich==13.9.2 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 -tomli==2.0.1 +tomli==2.0.2 twine==5.1.1 typeguard==4.3.0 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 5a6eb2344..30a44e2b2 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -11,9 +11,9 @@ mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 PyQt5-stubs==5.15.6.0 -tomli==2.0.1 +tomli==2.0.2 types-colorama==0.4.15.20240311 -types-docutils==0.21.0.20240907 +types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 types-setuptools==75.1.0.20240917 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index d053a6673..a19d6194b 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -19,7 +19,7 @@ python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 six==1.16.0 -tomli==2.0.1 +tomli==2.0.2 tomlkit==0.13.2 typing_extensions==4.12.2 uritemplate==4.1.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 223ada625..e69e41bdd 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.2.2 +build==1.2.2.post1 certifi==2024.8.30 charset-normalizer==3.3.2 docutils==0.20.1 @@ -11,7 +11,7 @@ Pygments==2.18.0 pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 -tomli==2.0.1 +tomli==2.0.2 trove-classifiers==2024.9.12 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index dcd27b11a..71cc11c6e 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -15,7 +15,7 @@ execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.112.2 +hypothesis==6.112.4 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 ; python_version=="3.8.*" @@ -33,9 +33,9 @@ manhole==1.8.1 more-itertools==10.5.0 packaging==24.1 parse==1.20.2 -parse_type==0.6.3 -platformdirs==4.3.6 +parse_type==0.6.4 pillow==10.4.0 +platformdirs==4.3.6 pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 @@ -57,10 +57,10 @@ six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.6 tldextract==5.1.2 -tomli==2.0.1 +tomli==2.0.2 typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 -vulture==2.12 +vulture==2.13 Werkzeug==3.0.4 zipp==3.20.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index f66945528..1b604a16f 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -11,7 +11,8 @@ platformdirs==4.3.6 pluggy==1.5.0 pyproject-api==1.8.0 setuptools==75.1.0 -tomli==2.0.1 -tox==4.20.0 +tomli==2.0.2 +tox==4.21.2 +typing_extensions==4.12.2 virtualenv==20.26.6 wheel==0.44.0 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 09294ac8d..bdedceb1d 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -tomli==2.0.1 -vulture==2.12 +tomli==2.0.2 +vulture==2.13 From 52a8576d7ccdd3516ffed6f162692fc95b2b6311 Mon Sep 17 00:00:00 2001 From: bitraid Date: Wed, 9 Oct 2024 21:40:37 +1300 Subject: [PATCH 190/403] Simplify OS version checks in nsis installer 22000 is the earliest win11 build, so no need to check for that separately. AtLeastWin11 is from https://github.com/kichik/nsis/blob/master/Include/WinVer.nsh#L552 Remove fail label since all failures fall through. --- misc/nsis/install.nsh | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh index db23a6f51..7f11f560d 100755 --- a/misc/nsis/install.nsh +++ b/misc/nsis/install.nsh @@ -432,19 +432,16 @@ Function .onInit StrCpy $KeepReg 1 ; OS version check - ${If} ${RunningX64} - ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks - GetWinVer $R0 Major - IntCmpU $R0 10 0 _os_check_fail _os_check_pass - GetWinVer $R1 Build - ${If} $R1 >= 22000 ; Windows 11 21H2 - Goto _os_check_pass - ${ElseIf} $R1 >= 14393 ; Windows 10 1607 - ${AndIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 - Goto _os_check_pass - ${EndIf} + ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks + ; https://learn.microsoft.com/en-us/windows/release-health/release-information + ; https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information + ${If} ${AtLeastWin11} + Goto _os_check_pass + ${ElseIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 + ${AndIf} ${AtLeastWin10} + ${AndIf} ${AtLeastBuild} 14393 ; Windows 10 1607 (also in error message below) + Goto _os_check_pass ${EndIf} - _os_check_fail: MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ version of Windows 10 1607 or later." Abort From 7d1d6459e0c7434d9310f75571090666a4cfd1ff Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Sat, 12 Oct 2024 19:23:16 +0000 Subject: [PATCH 191/403] Release v3.3.0 --- .bumpversion.cfg | 2 +- doc/changelog.asciidoc | 2 +- misc/org.qutebrowser.qutebrowser.appdata.xml | 1 + qutebrowser/__init__.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index e77e4bbab..9c6446b85 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.2.1 +current_version = 3.3.0 commit = True message = Release v{new_version} tag = True diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 2e745909a..21e15abf7 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -16,7 +16,7 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Security` to invite users to upgrade in case of vulnerabilities. [[v3.3.0]] -v3.3.0 (unreleased) +v3.3.0 (2024-10-12) ------------------- Added diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index d0422d911..a751d888e 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,7 @@ + diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index ed7e3fe5c..d653cb839 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -14,7 +14,7 @@ __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year) __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "3.2.1" +__version__ = "3.3.0" __version_info__ = tuple(int(part) for part in __version__.split('.')) __description__ = "A keyboard-driven, vim-like browser based on Python and Qt." From 5057c9a2ca34db3c2f0b1b1ae566c88a712da402 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 21:16:32 +0200 Subject: [PATCH 192/403] Update content.headers.user_agent completions --- qutebrowser/config/configdata.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 786981563..58bd92934 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -772,14 +772,14 @@ content.headers.user_agent: # Vim-protip: Place your cursor below this comment and run # :r!python scripts/dev/ua_fetch.py - - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 - (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" - - Chrome 125 macOS + (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36" + - Chrome 129 macOS - - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, - like Gecko) Chrome/125.0.0.0 Safari/537.36" - - Chrome 125 Win10 + like Gecko) Chrome/129.0.0.0 Safari/537.36" + - Chrome 129 Win10 - - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like - Gecko) Chrome/125.0.0.0 Safari/537.36" - - Chrome 125 Linux + Gecko) Chrome/129.0.0.0 Safari/537.36" + - Chrome 129 Linux supports_pattern: true desc: | User agent to send. From 28480f394b143a11af8143e983ce5887a2ae3089 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 21:19:03 +0200 Subject: [PATCH 193/403] Update the Firefox UA for quirks See #5182 --- doc/changelog.asciidoc | 1 + doc/contributing.asciidoc | 3 ++- qutebrowser/browser/webengine/webenginesettings.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 21e15abf7..2a1ac04b2 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -55,6 +55,7 @@ Fixed documentation in our settings docs now loads a live page again. (#8268) - A rare crash when on Qt 6, a renderer process terminates with an unknown termination reason. +- Updated the workaround for Google sign-in issues. [[v3.2.1]] v3.2.1 (2024-06-25) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 630a96db7..0bd72f594 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -792,7 +792,8 @@ qutebrowser release * Make sure there are no unstaged changes and the tests are green. * Make sure all issues with the related milestone are closed. * Mark the https://github.com/qutebrowser/qutebrowser/milestones[milestone] as closed. -* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`. +* Consider updating the completions for `content.headers.user_agent` in `configdata.yml` + and the Firefox UA in `qutebrowser/browser/webengine/webenginesettings.py`. * Minor release: Consider updating some files from main: - `misc/requirements/` and `requirements.txt` - `scripts/` diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index fd0d8c8de..a9dd3e5ef 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -452,7 +452,7 @@ def _init_site_specific_quirks(): "AppleWebKit/{webkit_version} (KHTML, like Gecko) " "{upstream_browser_key}/{upstream_browser_version} " "Safari/{webkit_version}") - firefox_ua = "Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0" + firefox_ua = "Mozilla/5.0 ({os_info}; rv:131.0) Gecko/20100101 Firefox/131.0" def maybe_newer_chrome_ua(at_least_version): """Return a new UA if our current chrome version isn't at least at_least_version.""" From 5a8964dc48693a2fec84cfcae6292596f1bf75a3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 21:22:36 +0200 Subject: [PATCH 194/403] Update changelog --- doc/changelog.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 2a1ac04b2..a3e65eac1 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -45,6 +45,8 @@ Changed respected when yanking any URL (for example, through hints with `hint links yank`). The `{url:yank}` substitution has also been added as a version of `{url}` that respects ignored URL query parameters. (#7879) +- Windows and macOS releases now bundle Qt 6.7.3, which includes security fixes + up to Chromium 129.0.6668.58. Fixed ~~~~~ From 7d6ea4b58b13c7a4bbead8063bc71256042e4611 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 21:37:17 +0200 Subject: [PATCH 195/403] Fix up changelog --- doc/changelog.asciidoc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index a3e65eac1..19e5f1ede 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,6 +15,15 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +[[v3.3.1]] +v3.3.1 (unreleased) +------------------- + +Fixed +~~~~~ + +- Updated the workaround for Google sign-in issues. + [[v3.3.0]] v3.3.0 (2024-10-12) ------------------- From 94dce5f1d4c390c643018c2c3f8a5ffe9ae158d5 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 21:38:20 +0200 Subject: [PATCH 196/403] Update release checklist --- doc/contributing.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 0bd72f594..c77d93f18 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -789,7 +789,8 @@ New PyQt release qutebrowser release ~~~~~~~~~~~~~~~~~~~ -* Make sure there are no unstaged changes and the tests are green. +* Make sure there are no unstaged or unpushed changes. +* Make sure CI is reasonably green. * Make sure all issues with the related milestone are closed. * Mark the https://github.com/qutebrowser/qutebrowser/milestones[milestone] as closed. * Consider updating the completions for `content.headers.user_agent` in `configdata.yml` From eacdca5a36cd1116b4f554fb6300bd2601f3f5f5 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Sat, 12 Oct 2024 19:40:56 +0000 Subject: [PATCH 197/403] Release v3.3.1 (cherry picked from commit fc0d7e08bc550255e7d3decfc791fe7c55f90ba1) --- .bumpversion.cfg | 2 +- doc/changelog.asciidoc | 2 +- misc/org.qutebrowser.qutebrowser.appdata.xml | 1 + qutebrowser/__init__.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 9c6446b85..11288aa7e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 3.3.0 +current_version = 3.3.1 commit = True message = Release v{new_version} tag = True diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 19e5f1ede..f9f8048dd 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -16,7 +16,7 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Security` to invite users to upgrade in case of vulnerabilities. [[v3.3.1]] -v3.3.1 (unreleased) +v3.3.1 (2024-10-12) ------------------- Fixed diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index a751d888e..448332961 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,7 @@ + diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index d653cb839..c2d292209 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -14,7 +14,7 @@ __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year) __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "3.3.0" +__version__ = "3.3.1" __version_info__ = tuple(int(part) for part in __version__.split('.')) __description__ = "A keyboard-driven, vim-like browser based on Python and Qt." From cc73134ead92702820ec7f351df6653df31713d9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 21:50:07 +0200 Subject: [PATCH 198/403] Add tenative v3.4.0 changelog --- doc/changelog.asciidoc | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index f9f8048dd..689c08753 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -15,6 +15,27 @@ breaking changes (such as renamed commands) can happen in minor releases. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +[[v3.4.0]] +v3.4.0 (unreleased) +------------------- + +Deprecated +~~~~~~~~~~ + +- **In either this (v3.4.0) or the next release (v3.5.0)**, support for Python + 3.8 will be dropped, and Python 3.9 will be required. +- **In the next release (v3.5.0)**, support for macOS 12 Monterey will be + dropped, and binaries will be built on macOS 13 Ventura. + +Added +~~~~~ + +- **Planned:** Full support for Qt 6.8, with Windows/macOS binaries being built + on Qt 6.8.0. +- **Planned:** Full support for Python 3.13, with Windows/macOS binaries using + it if possible. + + [[v3.3.1]] v3.3.1 (2024-10-12) ------------------- From 775db2caef69d4271416ed36f977dc4771e45e67 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 22:09:57 +0200 Subject: [PATCH 199/403] Update Chromium security patch versions --- qutebrowser/utils/version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index deb2aaea5..298eba9ca 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -628,10 +628,10 @@ class WebEngineVersions: utils.VersionNumber(6, 7): (_BASES[118], '122.0.6261.128'), # 2024-03-12 utils.VersionNumber(6, 7, 1): (_BASES[118], '124.0.6367.202'), # ~2024-05-09 utils.VersionNumber(6, 7, 2): (_BASES[118], '125.0.6422.142'), # 2024-05-30 - utils.VersionNumber(6, 7, 3): (_BASES[118], '129.0.6668.58'), # 2024-09-27 + utils.VersionNumber(6, 7, 3): (_BASES[118], '129.0.6668.58'), # 2024-09-17 ## Qt 6.8 - utils.VersionNumber(6, 8): (_BASES[122], '129.0.6668.58'), # 2024-??-?? + utils.VersionNumber(6, 8): (_BASES[122], '129.0.6668.70'), # 2024-09-24 } def __post_init__(self) -> None: From 4d2aa13db3e55e908ebd3951a499f337cb317d7e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 12 Oct 2024 22:18:24 +0200 Subject: [PATCH 200/403] Fix pdf.js downloading tests See https://github.com/mozilla/pdf.js/commit/ee89bd1c39c39327df0a4837728d5a9e30734ad9 which was part of PDF.js v4.7.76 (2024-10-06). This should work both with the old and new version. --- tests/end2end/features/downloads.feature | 2 +- tests/end2end/features/qutescheme.feature | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index a1bbee870..58a1e498a 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -645,7 +645,7 @@ Feature: Downloading things from a website. And I set content.pdfjs to true And I open data/misc/test.pdf without waiting And I wait until PDF.js is ready - And I run :click-element id download + And I run :jseval (document.getElementById("downloadButton") || document.getElementById("download")).click() And I clear the log And I wait until the download is finished # We get viewer.html as name on QtWebKit... diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature index e99b9af9d..f05e58eb8 100644 --- a/tests/end2end/features/qutescheme.feature +++ b/tests/end2end/features/qutescheme.feature @@ -190,7 +190,7 @@ Feature: Special qute:// pages And I set downloads.location.prompt to true And I open data/misc/test.pdf without waiting And I wait until PDF.js is ready - And I run :jseval document.getElementById("download").click() + And I run :jseval (document.getElementById("downloadButton") || document.getElementById("download")).click() And I wait for "Asking question option=None text=* title='Save file to:'>, *" in the log And I run :mode-leave Then no crash should happen From 7475d385aced5bb29f99bb79785b5d01ea24108f Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 13 Oct 2024 14:10:43 +1300 Subject: [PATCH 201/403] Comment out failing test assertion for now Ref: https://github.com/qutebrowser/qutebrowser/issues/8330 --- tests/unit/browser/webengine/test_webenginesettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/browser/webengine/test_webenginesettings.py b/tests/unit/browser/webengine/test_webenginesettings.py index ecfe65041..59243e20e 100644 --- a/tests/unit/browser/webengine/test_webenginesettings.py +++ b/tests/unit/browser/webengine/test_webenginesettings.py @@ -133,7 +133,7 @@ def test_existing_dict(config_stub, monkeypatch, global_settings, config_stub.val.spellcheck.languages = ['en-US'] webenginesettings._update_settings('spellcheck.languages') for profile in [default_profile, private_profile]: - assert profile.isSpellCheckEnabled() + #assert profile.isSpellCheckEnabled() assert profile.spellCheckLanguages() == ['en-US-8-0'] From dfd4fffaac1b0d8b7d059d99668b1ef6d3ab38ec Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 13 Oct 2024 17:59:00 +1300 Subject: [PATCH 202/403] Add test around remembering permissions across restart Qt 6.8 has its own permission grant persistence features. This means that if you accept a permission prompt in qutebrowser, and don't save it, it will be remembered by webengine anyway and you won't be re-prompted again. This test demonstrates that behaviour by temporarily granting a permission, restarting the browser in the same basedir, then seeing if we get prompted for the permission again or not. If not it fails on the "Asking question" line. We can't do much about re-prompting for a permission in the same browser instance (Qt saves the permission grant in memory too) but we can clean up the persisted permission files on browser starts so it doesn't remember it forever. At that point the skip mark can be removed from this test. --- tests/end2end/test_invocations.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index ccf4534dd..fb992fc63 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -636,6 +636,34 @@ def test_cookies_store(quteproc_new, request, short_tmpdir, store): quteproc_new.wait_for_quit() +@pytest.mark.qt68_beta4_skip +def test_permission_prompt_across_restart(quteproc_new, request, short_tmpdir): + # Start test process + args = _base_args(request.config) + [ + '--basedir', str(short_tmpdir), + '-s', 'content.notifications.enabled', 'ask', + ] + quteproc_new.start(args) + + def notification_prompt(answer): + quteproc_new.open_path('data/prompt/notifications.html') + quteproc_new.send_cmd(':click-element id button') + quteproc_new.wait_for(message='Asking question *') + quteproc_new.send_cmd(f':prompt-accept {answer}') + + # Make sure we are prompted the first time we are opened in this basedir + notification_prompt('yes') + quteproc_new.wait_for_js('notification permission granted') + + # Restart with same basedir + quteproc_new.send_cmd(':quit') + quteproc_new.wait_for_quit() + quteproc_new.start(args) + + # We should be re-prompted in the new instance + notification_prompt('no') + + # The 'colors' dictionaries in the parametrize decorator below have (QtWebEngine # version, CPU architecture) as keys. Either of those (or both) can be None to # say "on all other Qt versions" or "on all other CPU architectures". From 0ab1e3b757ceaea0bd4d354d61d0bb2c84652ec2 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 13 Oct 2024 18:52:29 +1300 Subject: [PATCH 203/403] Clear webengine's permissions.json on start To avoid WebEngine remembering granted permissions across restarts, remove their persistence file when we start up. This is only technically required when Qt=>6.8 and PyQt<6.8. But we only take action if the file exists anyway, so it's safe enough to run all the time and that means less conditional code to test ;) There are a few options for where we could do this cleanup, I'm choosing to do it at the latest point possible, which is right before we set `setPersistentStoragePath()`, since the permissions manager is re-initialized after that, see https://bugreports.qt.io/browse/QTBUG-126595 TODO: * call the new setPersistentPermissionsPolicy API when PyQt>=6.8 --- .../browser/webengine/webenginesettings.py | 21 +++++++++++++++++++ tests/end2end/test_invocations.py | 1 - 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index a9dd3e5ef..b4f6f8a1b 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -392,6 +392,26 @@ def _init_profile(profile: QWebEngineProfile) -> None: _global_settings.init_settings() +def _clear_webengine_permissions_json(): + """Remove QtWebEngine's persistent permissions file, if present. + + We have our own permissions feature and don't integrate with their one. + This only needs to be called when you are on Qt6.8 but PyQt<6.8, since if + we have access to the `setPersistentPermissionsPolicy()` we will use that + to disable the Qt feature. + This needs to be called before we call `setPersistentStoragePath()` + because Qt will load the file during that. + """ + permissions_file = pathlib.Path(standarddir.data()) / "webengine" / "permissions.json" + if permissions_file.exists(): + try: + permissions_file.unlink() + except OSError as err: + log.init.warning( + f"Error while cleaning up webengine permissions file: {err}" + ) + + def _init_default_profile(): """Init the default QWebEngineProfile.""" global default_profile @@ -414,6 +434,7 @@ def _init_default_profile(): f" Early version: {non_ua_version}\n" f" Real version: {ua_version}") + _clear_webengine_permissions_json() default_profile.setCachePath( os.path.join(standarddir.cache(), 'webengine')) default_profile.setPersistentStoragePath( diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index fb992fc63..a5a03ecf6 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -636,7 +636,6 @@ def test_cookies_store(quteproc_new, request, short_tmpdir, store): quteproc_new.wait_for_quit() -@pytest.mark.qt68_beta4_skip def test_permission_prompt_across_restart(quteproc_new, request, short_tmpdir): # Start test process args = _base_args(request.config) + [ From 6093306ff51f62880f808e9e5a0c06383e29d947 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Sun, 13 Oct 2024 14:54:29 +0000 Subject: [PATCH 204/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 2 +- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 2 +- misc/requirements/requirements-tests.txt | 4 ++-- misc/requirements/requirements-tox.txt | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 1490661f4..349e4399b 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py build==1.2.2.post1 -check-manifest==0.49 +check-manifest==0.50 importlib_metadata==8.5.0 packaging==24.1 pyproject_hooks==1.2.0 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 19d0dd9b7..2d9404147 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -6,7 +6,7 @@ build==1.2.2.post1 bump2version==1.0.1 certifi==2024.8.30 cffi==1.17.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cryptography==43.0.1 docutils==0.20.1 github3.py==4.0.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index a19d6194b..d2c903ce6 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -3,7 +3,7 @@ astroid==3.2.4 certifi==2024.8.30 cffi==1.17.1 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cryptography==43.0.1 dill==0.3.9 github3.py==4.0.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index e69e41bdd..0df8ecead 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -2,7 +2,7 @@ build==1.2.2.post1 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 docutils==0.20.1 idna==3.10 importlib_metadata==8.5.0 @@ -12,6 +12,6 @@ pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 tomli==2.0.2 -trove-classifiers==2024.9.12 +trove-classifiers==2024.10.13 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 2f0ba5b9a..8bc582b4f 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -3,7 +3,7 @@ alabaster==0.7.13 babel==2.16.0 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 docutils==0.20.1 idna==3.10 imagesize==1.4.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 71cc11c6e..c04bb74d6 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -6,7 +6,7 @@ backports.tarfile==1.2.0 beautifulsoup4==4.12.3 blinker==1.8.2 certifi==2024.8.30 -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 cheroot==10.0.1 click==8.1.7 coverage==7.6.1 @@ -15,7 +15,7 @@ execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.112.4 +hypothesis==6.113.0 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 ; python_version=="3.8.*" diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 1b604a16f..3eef4d652 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -3,7 +3,7 @@ cachetools==5.5.0 chardet==5.2.0 colorama==0.4.6 -distlib==0.3.8 +distlib==0.3.9 filelock==3.16.1 packaging==24.1 pip==24.2 From bd3774dfc82e679d64e708022bbc8b484214d7ab Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:40:49 +0200 Subject: [PATCH 205/403] Drop Python 3.8 from tox/CI --- .github/workflows/ci.yml | 18 +++++++++--------- .github/workflows/recompile-requirements.yml | 4 ++-- .github/workflows/release.yml | 1 - doc/contributing.asciidoc | 14 +++++++------- tox.ini | 3 +-- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e0c84bc7..6f44b5756 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,10 +144,10 @@ jobs: fail-fast: false matrix: include: - ### PyQt 5.15.2 (Python 3.8) - - testenv: py38-pyqt5152 + ### PyQt 5.15.2 (Python 3.9) + - testenv: py39-pyqt5152 os: ubuntu-20.04 - python: "3.8" + python: "3.9" ### PyQt 5.15 (Python 3.10, with coverage) # FIXME:qt6 # - testenv: py310-pyqt515-cov @@ -157,14 +157,14 @@ jobs: - testenv: py311-pyqt515 os: ubuntu-20.04 python: "3.11" - ### PyQt 6.2 (Python 3.8) - - testenv: py38-pyqt62 + ### PyQt 6.2 (Python 3.9) + - testenv: py39-pyqt62 os: ubuntu-20.04 - python: "3.8" - ### PyQt 6.3 (Python 3.8) - - testenv: py38-pyqt63 + python: "3.9" + ### PyQt 6.3 (Python 3.9) + - testenv: py39-pyqt63 os: ubuntu-20.04 - python: "3.8" + python: "3.9" ## PyQt 6.4 (Python 3.9) - testenv: py39-pyqt64 os: ubuntu-20.04 diff --git a/.github/workflows/recompile-requirements.yml b/.github/workflows/recompile-requirements.yml index 6d42c3137..2b91fd5c9 100644 --- a/.github/workflows/recompile-requirements.yml +++ b/.github/workflows/recompile-requirements.yml @@ -21,10 +21,10 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - name: Set up Python 3.8 + - name: Set up Python 3.9 uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.9' - name: Recompile requirements run: "python3 scripts/dev/recompile_requirements.py ${{ github.event.input.environments }}" id: requirements diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 826970384..e7eab6371 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,7 +19,6 @@ on: default: '3.12' type: choice options: - - '3.8' - '3.9' - '3.10' - '3.11' diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index c77d93f18..6e609334e 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -111,9 +111,9 @@ unittests and several linters/checkers. Currently, the following tox environments are available: * Tests using https://www.pytest.org[pytest]: - - `py38`, `py39`, ...: Run pytest for python 3.8/3.9/... with the system-wide PyQt. - - `py38-pyqt515`, ..., `py38-pyqt65`: Run pytest with the given PyQt version (`py39-*` etc. also works). - - `py38-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too). + - `py39`, `py310`, ...: Run pytest for python 3.9/3.10/... with the system-wide PyQt. + - `py39-pyqt515`, ..., `py39-pyqt65`: Run pytest with the given PyQt version (`py310-*` etc. also works). + - `py39-pyqt515-cov`: Run with coverage support (other Python/PyQt versions work too). * `flake8`: Run various linting checks via https://pypi.python.org/pypi/flake8[flake8]. * `vulture`: Run https://pypi.python.org/pypi/vulture[vulture] to find unused code portions. @@ -171,16 +171,16 @@ Examples: ---- # run only pytest tests which failed in last run: -tox -e py38 -- --lf +tox -e py39 -- --lf # run only the end2end feature tests: -tox -e py38 -- tests/end2end/features +tox -e py39 -- tests/end2end/features # run everything with undo in the generated name, based on the scenario text -tox -e py38 -- tests/end2end/features/test_tabs_bdd.py -k undo +tox -e py39 -- tests/end2end/features/test_tabs_bdd.py -k undo # run coverage test for specific file (updates htmlcov/index.html) -tox -e py38-cov -- tests/unit/browser/test_webelem.py +tox -e py39-cov -- tests/unit/browser/test_webelem.py ---- Specifying the backend for tests diff --git a/tox.ini b/tox.ini index 607b19fb3..274638969 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py38-pyqt515-cov,mypy-pyqt5,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint,yamllint,actionlint +envlist = py39-pyqt515-cov,mypy-pyqt5,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint,yamllint,actionlint distshare = {toxworkdir} skipsdist = true minversion = 3.20 @@ -36,7 +36,6 @@ passenv = basepython = py: {env:PYTHON:python3} py3: {env:PYTHON:python3} - py38: {env:PYTHON:python3.8} py39: {env:PYTHON:python3.9} py310: {env:PYTHON:python3.10} py311: {env:PYTHON:python3.11} From 3288ec8598d9a124e4e5e932c17b47a3da5eed32 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:41:39 +0200 Subject: [PATCH 206/403] Adjust Python versions in setup.py --- setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index feb949595..9562a8e22 100755 --- a/setup.py +++ b/setup.py @@ -57,9 +57,8 @@ try: entry_points={'gui_scripts': ['qutebrowser = qutebrowser.qutebrowser:main']}, zip_safe=True, - install_requires=['jinja2', 'PyYAML', - 'importlib_resources>=1.1.0; python_version < "3.9"'], - python_requires='>=3.8', + install_requires=['jinja2', 'PyYAML'], + python_requires='>=3.9', name='qutebrowser', version=_get_constant('version'), description=_get_constant('description'), @@ -81,10 +80,10 @@ try: 'Operating System :: MacOS', 'Operating System :: POSIX :: BSD', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Internet', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Browsers', From 5337882657de995a639cab7a0cea5464f8443886 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 15:48:38 +0200 Subject: [PATCH 207/403] Adjust linters for dropping Python 3.8 --- .flake8 | 2 +- .mypy.ini | 2 +- .pylintrc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.flake8 b/.flake8 index 6c4dd923e..8bf2b3efd 100644 --- a/.flake8 +++ b/.flake8 @@ -58,7 +58,7 @@ ignore = PT004, PT011, PT012 -min-version = 3.8.0 +min-version = 3.9.0 max-complexity = 12 per-file-ignores = qutebrowser/api/hook.py : N801 diff --git a/.mypy.ini b/.mypy.ini index 81f69a09e..59a3f8670 100644 --- a/.mypy.ini +++ b/.mypy.ini @@ -1,5 +1,5 @@ [mypy] -python_version = 3.8 +python_version = 3.9 ### --strict warn_unused_configs = True diff --git a/.pylintrc b/.pylintrc index a6784c0e4..f304cf1ac 100644 --- a/.pylintrc +++ b/.pylintrc @@ -16,7 +16,7 @@ load-plugins=qute_pylint.config, pylint.extensions.dunder persistent=n -py-version=3.8 +py-version=3.9 [MESSAGES CONTROL] enable=all From eb67b20417b11cb5a58addfbd4d46175f8406542 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:42:41 +0200 Subject: [PATCH 208/403] Update docs for Python 3.8 drop --- README.asciidoc | 6 +----- doc/changelog.asciidoc | 7 +++++-- doc/install.asciidoc | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index b670e1800..9567473ee 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -84,7 +84,7 @@ Requirements The following software and libraries are required to run qutebrowser: -* https://www.python.org/[Python] 3.8 or newer +* https://www.python.org/[Python] 3.9 or newer * https://www.qt.io/[Qt], either 6.2.0 or newer, or 5.15.0 or newer, with the following modules: - QtCore / qtbase - QtQuick (part of qtbase or qtdeclarative in some distributions) @@ -105,10 +105,6 @@ websites and using it for transmission of sensitive data._ * https://palletsprojects.com/p/jinja/[jinja2] * https://github.com/yaml/pyyaml[PyYAML] -On Python 3.8, the following backport is also required: - -* https://importlib-resources.readthedocs.io/[importlib_resources] - On macOS, the following libraries are also required: * https://pyobjc.readthedocs.io/en/latest/[pyobjc-core and pyobjc-framework-Cocoa] diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 689c08753..49688dac1 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -22,11 +22,14 @@ v3.4.0 (unreleased) Deprecated ~~~~~~~~~~ -- **In either this (v3.4.0) or the next release (v3.5.0)**, support for Python - 3.8 will be dropped, and Python 3.9 will be required. - **In the next release (v3.5.0)**, support for macOS 12 Monterey will be dropped, and binaries will be built on macOS 13 Ventura. +Removed +~~~~~~~ + +- Support for Python 3.8 is dropped, and Python 3.9 is now required. + Added ~~~~~ diff --git a/doc/install.asciidoc b/doc/install.asciidoc index 20dc29597..75c9b9777 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -438,7 +438,7 @@ This installs all needed Python dependencies in a `.venv` subfolder This comes with an up-to-date Qt/PyQt including a pre-compiled QtWebEngine binary, but has a few caveats: -- Make sure your `python3` is Python 3.8 or newer, otherwise you'll get a "No +- Make sure your `python3` is Python 3.9 or newer, otherwise you'll get a "No matching distribution found" error and/or qutebrowser will not run. - It only works on 64-bit x86 systems, with other architectures you'll get the same error. From bcff1e90eac4c821134a4f6ebd4349ca9a166283 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:53:06 +0200 Subject: [PATCH 209/403] Update checkpyver for 3.8 drop --- qutebrowser/misc/checkpyver.py | 4 ++-- qutebrowser/misc/earlyinit.py | 2 +- tests/end2end/test_invocations.py | 2 +- tests/unit/misc/test_checkpyver.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/qutebrowser/misc/checkpyver.py b/qutebrowser/misc/checkpyver.py index 596a7803a..e93a124e5 100644 --- a/qutebrowser/misc/checkpyver.py +++ b/qutebrowser/misc/checkpyver.py @@ -28,11 +28,11 @@ except ImportError: # pragma: no cover # to stderr. def check_python_version(): """Check if correct python version is run.""" - if sys.hexversion < 0x03080000: + if sys.hexversion < 0x03090000: # We don't use .format() and print_function here just in case someone # still has < 2.6 installed. version_str = '.'.join(map(str, sys.version_info[:3])) - text = ("At least Python 3.8 is required to run qutebrowser, but " + + text = ("At least Python 3.9 is required to run qutebrowser, but " + "it's running with " + version_str + ".\n") show_errors = '--no-err-windows' not in sys.argv diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 06de668b0..18ebd8e28 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -4,7 +4,7 @@ """Things which need to be done really early (e.g. before importing Qt). -At this point we can be sure we have all python 3.8 features available. +At this point we can be sure we have all python 3.9 features available. """ try: diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index ccf4534dd..db6fd2eec 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -335,7 +335,7 @@ def test_launching_with_old_python(python): except FileNotFoundError: pytest.skip(f"{python} not found") assert proc.returncode == 1 - error = "At least Python 3.8 is required to run qutebrowser" + error = "At least Python 3.9 is required to run qutebrowser" assert proc.stderr.decode('ascii').startswith(error) diff --git a/tests/unit/misc/test_checkpyver.py b/tests/unit/misc/test_checkpyver.py index fddf9e9e8..8bcdf9772 100644 --- a/tests/unit/misc/test_checkpyver.py +++ b/tests/unit/misc/test_checkpyver.py @@ -14,7 +14,7 @@ import pytest from qutebrowser.misc import checkpyver -TEXT = (r"At least Python 3.8 is required to run qutebrowser, but it's " +TEXT = (r"At least Python 3.9 is required to run qutebrowser, but it's " r"running with \d+\.\d+\.\d+.") From 71039e0d53a1d80bb66254151678549f8f9b608e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:33:24 +0200 Subject: [PATCH 210/403] Minor Python 3.8 dropping adjustments --- qutebrowser/utils/resources.py | 2 +- qutebrowser/utils/utils.py | 4 ++-- tests/unit/utils/test_utils.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index a97a2e994..66b4a6143 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -46,7 +46,7 @@ def _keyerror_workaround() -> Iterator[None]: WORKAROUND for zipfile.Path resources raising KeyError when a file was notfound: https://bugs.python.org/issue43063 - Only needed for Python 3.8 and 3.9. + Only needed for Python 3.9. """ try: yield diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index cf340e444..12a9c83b1 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -779,9 +779,9 @@ def mimetype_extension(mimetype: str) -> Optional[str]: Most likely, this can be dropped once the minimum Python version is raised to 3.10. """ overrides = { - # Added in 3.10 + # Added in 3.10.0 "application/x-hdf5": ".h5", - # Added around 3.8 + # Added in 3.9.0 "application/manifest+json": ".webmanifest", } if mimetype in overrides: diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 51255aa61..fc4a3e652 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -474,7 +474,7 @@ def test_get_repr(constructor, attrs, expected): assert utils.get_repr(Obj(), constructor, **attrs) == expected -class QualnameObj(): +class QualnameObj: """Test object for test_qualname.""" From 2ad1a579b105bbadb64bd329af63057e3a12292b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 15:33:51 +0200 Subject: [PATCH 211/403] Remove :debug-cache-stats conditionals --- qutebrowser/misc/debugcachestats.py | 13 ++----------- qutebrowser/misc/utilcmds.py | 5 +---- tests/end2end/features/utilcmds.feature | 1 - 3 files changed, 3 insertions(+), 16 deletions(-) diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index d3ac9819b..fb1f7e7c3 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -9,7 +9,6 @@ dependencies as possible to avoid cyclic dependencies. """ import weakref -import sys from typing import Any, Callable, Optional, TypeVar, Mapping from qutebrowser.utils import log @@ -26,16 +25,8 @@ def register(name: Optional[str] = None) -> Callable[[_T], _T]: """Register a lru_cache wrapped function for debug_cache_stats.""" def wrapper(fn: _T) -> _T: fn_name = fn.__name__ if name is None else name - if sys.version_info < (3, 9): - log.misc.vdebug( # type: ignore[attr-defined] - "debugcachestats not supported on python < 3.9, not adding '%s'", - fn_name, - ) - return fn - - else: - _CACHE_FUNCTIONS[fn_name] = fn - return fn + _CACHE_FUNCTIONS[fn_name] = fn + return fn return wrapper diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 6689ad074..548c1e54b 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -8,7 +8,6 @@ import functools import os -import sys import traceback from typing import Optional @@ -114,9 +113,7 @@ def debug_all_objects() -> None: @cmdutils.register(debug=True) def debug_cache_stats() -> None: """Print LRU cache stats.""" - if sys.version_info < (3, 9): - raise cmdutils.CommandError('debugcachestats not supported on python < 3.9') - debugcachestats.debug_cache_stats() # type: ignore[unreachable] + debugcachestats.debug_cache_stats() @cmdutils.register(debug=True) diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index ebacea890..70fb26afc 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -92,7 +92,6 @@ Feature: Miscellaneous utility commands exposed to the user. ## :debug-cache-stats - @python>=3.9.0 Scenario: :debug-cache-stats When I run :debug-cache-stats Then "is_valid_prefix: CacheInfo(*)" should be logged From fe868901aba6efcc28fe903fd20cfe278e4c838d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:51:26 +0200 Subject: [PATCH 212/403] Remove all importlib_resources backport usage --- qutebrowser/misc/earlyinit.py | 4 ---- qutebrowser/misc/pakjoy.py | 2 +- qutebrowser/utils/resources.py | 14 ++++---------- tests/unit/utils/test_resources.py | 4 ++-- tests/unit/utils/test_version.py | 1 - 5 files changed, 7 insertions(+), 18 deletions(-) diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 18ebd8e28..60d2c7c09 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -246,10 +246,6 @@ def check_libraries(): package = f'{machinery.INFO.wrapper}.{subpkg}' modules[package] = _missing_str(package) - if sys.version_info < (3, 9): - # Backport required - modules['importlib_resources'] = _missing_str("importlib_resources") - if sys.platform.startswith('darwin'): from qutebrowser.qt.core import QVersionNumber qt_ver = get_qt_version() diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index b2ebbc4b3..9415113ea 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -175,7 +175,7 @@ def _find_webengine_resources() -> pathlib.Path: qt_data_path = qtutils.library_path(qtutils.LibraryPath.data) if utils.is_mac: # pragma: no cover # I'm not sure how to arrive at this path without hardcoding it - # ourselves. importlib_resources("PyQt6.Qt6") can serve as a + # ourselves. importlib.resources.files("PyQt6.Qt6") can serve as a # replacement for the qtutils bit but it doesn't seem to help find the # actual Resources folder. candidates.append( diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index 66b4a6143..8d1dbfe10 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -9,20 +9,14 @@ import sys import contextlib import posixpath import pathlib +import importlib.resources from typing import Iterator, Iterable, Union, Dict - -# We cannot use the stdlib version on 3.8 because we need the files() API. if sys.version_info >= (3, 11): # pragma: no cover # https://github.com/python/cpython/issues/90276 - import importlib.resources as importlib_resources from importlib.resources.abc import Traversable -elif sys.version_info >= (3, 9): - import importlib.resources as importlib_resources +else: from importlib.abc import Traversable -else: # pragma: no cover - import importlib_resources - from importlib_resources.abc import Traversable import qutebrowser _cache: Dict[str, str] = {} @@ -37,7 +31,7 @@ def _path(filename: str) -> _ResourceType: assert not posixpath.isabs(filename), filename assert os.path.pardir not in filename.split(posixpath.sep), filename - return importlib_resources.files(qutebrowser) / filename + return importlib.resources.files(qutebrowser) / filename @contextlib.contextmanager def _keyerror_workaround() -> Iterator[None]: @@ -71,7 +65,7 @@ def _glob( assert isinstance(glob_path, pathlib.Path) for full_path in glob_path.glob(f'*{ext}'): # . is contained in ext yield full_path.relative_to(resource_path).as_posix() - else: # zipfile.Path or other importlib_resources.abc.Traversable + else: # zipfile.Path or other importlib.resources.abc.Traversable assert glob_path.is_dir(), glob_path for subpath in glob_path.iterdir(): if subpath.name.endswith(ext): diff --git a/tests/unit/utils/test_resources.py b/tests/unit/utils/test_resources.py index 911f072f1..3669e523a 100644 --- a/tests/unit/utils/test_resources.py +++ b/tests/unit/utils/test_resources.py @@ -79,7 +79,7 @@ class TestReadFile: 'html/error.html']) def test_read_cached_file(self, mocker, filename): resources.preload() - m = mocker.patch('qutebrowser.utils.resources.importlib_resources.files') + m = mocker.patch('qutebrowser.utils.resources.importlib.resources.files') resources.read_file(filename) m.assert_not_called() @@ -111,7 +111,7 @@ class TestReadFile: return self if fake_exception is not None: - monkeypatch.setattr(resources.importlib_resources, 'files', + monkeypatch.setattr(resources.importlib.resources, 'files', lambda _pkg: BrokenFileFake(fake_exception)) meth = getattr(resources, name) diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 149b1d855..91d737dd2 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -737,7 +737,6 @@ class TestModuleVersions: ('yaml', True), ('adblock', True), ('dataclasses', False), - ('importlib_resources', False), ('objc', True), ]) def test_existing_attributes(self, name, has_version): From 4d069b8fc3f482fb0308391e453d7d69523de753 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 15:28:44 +0200 Subject: [PATCH 213/403] Use str.removeprefix() and str.removesuffix() https://docs.python.org/3/whatsnew/3.9.html#new-string-methods-to-remove-prefixes-and-suffixes --- qutebrowser/browser/network/pac.py | 2 +- qutebrowser/browser/webengine/darkmode.py | 2 +- qutebrowser/completion/completer.py | 2 +- qutebrowser/config/qtargs.py | 4 ++-- qutebrowser/misc/keyhintwidget.py | 2 +- qutebrowser/utils/error.py | 4 ++-- qutebrowser/utils/urlmatch.py | 2 +- qutebrowser/utils/urlutils.py | 6 +++--- scripts/dev/check_coverage.py | 2 +- .../dev/pylint_checkers/qute_pylint/config.py | 2 +- scripts/dev/recompile_requirements.py | 8 ++++---- scripts/dev/update_3rdparty.py | 3 +-- tests/end2end/features/conftest.py | 20 +++++++++---------- tests/end2end/test_dirbrowser.py | 2 +- tests/end2end/test_invocations.py | 2 +- tests/unit/browser/webengine/test_webview.py | 4 ++-- tests/unit/javascript/test_js_quirks.py | 4 ++-- tests/unit/keyinput/test_keyutils.py | 4 ++-- tests/unit/misc/test_editor.py | 2 +- 19 files changed, 38 insertions(+), 39 deletions(-) diff --git a/qutebrowser/browser/network/pac.py b/qutebrowser/browser/network/pac.py index 656c620db..20516366e 100644 --- a/qutebrowser/browser/network/pac.py +++ b/qutebrowser/browser/network/pac.py @@ -242,7 +242,7 @@ class PACFetcher(QObject): pac_prefix = "pac+" assert url.scheme().startswith(pac_prefix) - url.setScheme(url.scheme()[len(pac_prefix):]) + url.setScheme(url.scheme().removeprefix(pac_prefix)) self._pac_url = url with qtlog.disable_qt_msghandler(): diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index 8f1908547..5aab28051 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -418,7 +418,7 @@ def settings( blink_settings_flag = f'--{_BLINK_SETTINGS}=' for flag in special_flags: if flag.startswith(blink_settings_flag): - for pair in flag[len(blink_settings_flag):].split(','): + for pair in flag.removeprefix(blink_settings_flag).split(','): key, val = pair.split('=', maxsplit=1) result[_BLINK_SETTINGS].append((key, val)) diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 846fa7c22..408660c3a 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -127,7 +127,7 @@ class Completer(QObject): Return: ([parts_before_cursor], 'part_under_cursor', [parts_after_cursor]) """ - text = self._cmd.text()[len(self._cmd.prefix()):] + text = self._cmd.text().removeprefix(self._cmd.prefix()) if not text or not text.strip(): # Only ":", empty part under the cursor with nothing before/after return [], '', [] diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index 3a648524e..eb5e8b58c 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -91,10 +91,10 @@ def _qtwebengine_features( for flag in special_flags: if flag.startswith(_ENABLE_FEATURES): - flag = flag[len(_ENABLE_FEATURES):] + flag = flag.removeprefix(_ENABLE_FEATURES) enabled_features += flag.split(',') elif flag.startswith(_DISABLE_FEATURES): - flag = flag[len(_DISABLE_FEATURES):] + flag = flag.removeprefix(_DISABLE_FEATURES) disabled_features += flag.split(',') elif flag.startswith(_BLINK_SETTINGS): pass diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 32867c17a..5662763b8 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -123,7 +123,7 @@ class KeyHintView(QLabel): ).format( html.escape(prefix), suffix_color, - html.escape(str(seq)[len(prefix):]), + html.escape(str(seq).removeprefix(prefix)), html.escape(cmd) ) text = '{}
'.format(text) diff --git a/qutebrowser/utils/error.py b/qutebrowser/utils/error.py index 010970861..10dad90f7 100644 --- a/qutebrowser/utils/error.py +++ b/qutebrowser/utils/error.py @@ -11,11 +11,11 @@ from qutebrowser.utils import log, utils def _get_name(exc: BaseException) -> str: """Get a suitable exception name as a string.""" - prefixes = ['qutebrowser', 'builtins'] + prefixes = ['qutebrowser.', 'builtins.'] name = utils.qualname(exc.__class__) for prefix in prefixes: if name.startswith(prefix): - name = name[len(prefix) + 1:] + name = name.removeprefix(prefix) break return name diff --git a/qutebrowser/utils/urlmatch.py b/qutebrowser/utils/urlmatch.py index 55b0037dc..0d3f76cb8 100644 --- a/qutebrowser/utils/urlmatch.py +++ b/qutebrowser/utils/urlmatch.py @@ -128,7 +128,7 @@ class UrlPattern: # FIXME This doesn't actually strip the hostname correctly. if (pattern.startswith('file://') and not pattern.startswith('file:///')): - pattern = 'file:///' + pattern[len("file://"):] + pattern = 'file:///' + pattern.removeprefix("file://") return pattern diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 6f0e910cf..785569069 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -553,8 +553,8 @@ def same_domain(url1: QUrl, url2: QUrl) -> bool: if suffix1 != suffix2: return False - domain1 = url1.host()[:-len(suffix1)].split('.')[-1] - domain2 = url2.host()[:-len(suffix2)].split('.')[-1] + domain1 = url1.host().removesuffix(suffix1).split('.')[-1] + domain2 = url2.host().removesuffix(suffix2).split('.')[-1] return domain1 == domain2 @@ -668,7 +668,7 @@ def parse_javascript_url(url: QUrl) -> str: urlstr = url.toString(FormatOption.ENCODED) urlstr = urllib.parse.unquote(urlstr) - code = urlstr[len('javascript:'):] + code = urlstr.removeprefix('javascript:') if not code: raise Error("Resulted in empty JavaScript code") diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index e1d0d8642..6de04703f 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -242,7 +242,7 @@ def _get_filename(filename): os.path.join(os.path.dirname(__file__), '..', '..')) common_path = os.path.commonprefix([basedir, filename]) if common_path: - filename = filename[len(common_path):].lstrip('/') + filename = filename.removeprefix(common_path).lstrip('/') return filename diff --git a/scripts/dev/pylint_checkers/qute_pylint/config.py b/scripts/dev/pylint_checkers/qute_pylint/config.py index be5bae082..6effc8836 100644 --- a/scripts/dev/pylint_checkers/qute_pylint/config.py +++ b/scripts/dev/pylint_checkers/qute_pylint/config.py @@ -50,7 +50,7 @@ class ConfigChecker(checkers.BaseChecker): node_str = node.as_string() prefix = 'config.val.' if node_str.startswith(prefix): - self._check_config(node, node_str[len(prefix):]) + self._check_config(node, node_str.removeprefix(prefix)) def _check_config(self, node, name): """Check that we're accessing proper config options.""" diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 838e75931..5c0622141 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -114,7 +114,7 @@ def get_all_names(): """Get all requirement names based on filenames.""" for filename in glob.glob(os.path.join(REQ_DIR, 'requirements-*.txt-raw')): basename = os.path.basename(filename) - yield basename[len('requirements-'):-len('.txt-raw')] + yield basename.removeprefix('requirements-').removesuffix('.txt-raw') def run_pip(venv_dir, *args, quiet=False, **kwargs): @@ -231,7 +231,7 @@ def extract_requirement_name(path: pathlib.Path) -> str: prefix = "requirements-" assert path.suffix == ".txt", path assert path.stem.startswith(prefix), path - return path.stem[len(prefix):] + return path.stem.removeprefix(prefix) def parse_versioned_line(line): @@ -274,11 +274,11 @@ def _get_changes(diff): continue elif line.startswith('--- '): prefix = '--- a/' - current_path = pathlib.Path(line[len(prefix):]) + current_path = pathlib.Path(line.removeprefix(prefix)) continue elif line.startswith('+++ '): prefix = '+++ b/' - new_path = pathlib.Path(line[len(prefix):]) + new_path = pathlib.Path(line.removeprefix(prefix)) assert current_path == new_path, (current_path, new_path) continue elif not line.strip(): diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 4d8a5e562..1258be106 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -104,8 +104,7 @@ def update_pdfjs(target_version=None, legacy=False, gh_token=None): else: # We need target_version as x.y.z, without the 'v' prefix, though the # user might give it on the command line - if target_version.startswith('v'): - target_version = target_version[1:] + target_version = target_version.removeprefix('v') # version should have the prefix to be consistent with the return value # of get_latest_pdfjs_url() version = 'v' + target_version diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index b7f112182..22fde0001 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -216,22 +216,22 @@ def open_path(quteproc, server, path): while True: if path.endswith(new_tab_suffix): - path = path[:-len(new_tab_suffix)] + path = path.removesuffix(new_tab_suffix) new_tab = True elif path.endswith(new_bg_tab_suffix): - path = path[:-len(new_bg_tab_suffix)] + path = path.removesuffix(new_bg_tab_suffix) new_bg_tab = True elif path.endswith(new_window_suffix): - path = path[:-len(new_window_suffix)] + path = path.removesuffix(new_window_suffix) new_window = True elif path.endswith(private_suffix): - path = path[:-len(private_suffix)] + path = path.removesuffix(private_suffix) private = True elif path.endswith(as_url_suffix): - path = path[:-len(as_url_suffix)] + path = path.removesuffix(as_url_suffix) as_url = True elif path.endswith(do_not_wait_suffix): - path = path[:-len(do_not_wait_suffix)] + path = path.removesuffix(do_not_wait_suffix) wait = False else: break @@ -264,7 +264,7 @@ def run_command(quteproc, server, tmpdir, command): invalid_tag = ' (invalid command)' if command.endswith(invalid_tag): - command = command[:-len(invalid_tag)] + command = command.removesuffix(invalid_tag) invalid = True else: invalid = False @@ -639,11 +639,11 @@ def check_open_tabs(quteproc, request, tabs): while line.endswith(active_suffix) or line.endswith(pinned_suffix): if line.endswith(active_suffix): # active - line = line[:-len(active_suffix)] + line = line.removesuffix(active_suffix) active = True else: # pinned - line = line[:-len(pinned_suffix)] + line = line.removesuffix(pinned_suffix) pinned = True session_tab = session['windows'][0]['tabs'][i] @@ -739,7 +739,7 @@ def set_up_fileselector(quteproc, py_proc, tmpdir, kind, files, output_type): tmp_file = None for i, arg in enumerate(sys.argv): if arg.startswith('--file='): - tmp_file = arg[len('--file='):] + tmp_file = arg.removeprefix('--file=') sys.argv.pop(i) break selected_files = sys.argv[1:] diff --git a/tests/end2end/test_dirbrowser.py b/tests/end2end/test_dirbrowser.py index 58cf66d6a..b2e3a8e29 100644 --- a/tests/end2end/test_dirbrowser.py +++ b/tests/end2end/test_dirbrowser.py @@ -124,7 +124,7 @@ def parse(quteproc): title_prefix = 'Browse directory: ' # Strip off the title prefix to obtain the path of the folder that # we're browsing - path = pathlib.Path(soup.title.string[len(title_prefix):]) + path = pathlib.Path(soup.title.string.removeprefix(title_prefix)) container = soup('div', id='dirbrowserContainer')[0] diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index db6fd2eec..5f65041c7 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -988,7 +988,7 @@ def test_restart(request, quteproc_new): quteproc_new.wait_for_quit() assert line.message.startswith(prefix) - pid = int(line.message[len(prefix):]) + pid = int(line.message.removeprefix(prefix)) os.kill(pid, signal.SIGTERM) try: diff --git a/tests/unit/browser/webengine/test_webview.py b/tests/unit/browser/webengine/test_webview.py index f14a896b6..8ffc81d60 100644 --- a/tests/unit/browser/webengine/test_webview.py +++ b/tests/unit/browser/webengine/test_webview.py @@ -25,10 +25,10 @@ class Naming: def camel_to_snake(naming, name): if naming.prefix: assert name.startswith(naming.prefix) - name = name[len(naming.prefix):] + name = name.removeprefix(naming.prefix) if naming.suffix: assert name.endswith(naming.suffix) - name = name[:-len(naming.suffix)] + name = name.removesuffix(naming.suffix) # https://stackoverflow.com/a/1176023 return re.sub(r'(? Date: Sun, 13 Oct 2024 20:27:34 +0200 Subject: [PATCH 214/403] ci: Avoid Archlinux' pyqt6 6.1.7-3 which lacks QSignalSpy See https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2409077518 --- scripts/dev/ci/docker/Dockerfile.j2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index 257ffecb9..4a720fcf1 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -35,6 +35,10 @@ RUN pacman -Su --noconfirm \ gcc \ libyaml \ xorg-xdpyinfo +{% if unstable and qt6 %} +# WORKAROUND: 6.7.1-3 in [extra-testing] removes QSignalSpy +RUN pacman -U --noconfirm https://mirror.pkgbuild.com/extra/os/x86_64/python-pyqt6-6.7.1-2-x86_64.pkg.tar.zst +{% endif %} RUN useradd user -u 1001 && \ mkdir /home/user && \ From 80ed616230d280f3323aaf193370c2cb3c4883a0 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 14 Oct 2024 04:18:59 +0000 Subject: [PATCH 215/403] Update dependencies --- misc/requirements/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 30a44e2b2..f0aacf4a2 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -16,6 +16,6 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 -types-setuptools==75.1.0.20240917 +types-setuptools==75.1.0.20241014 typing_extensions==4.12.2 zipp==3.20.2 From c598cbbc7165bb0cc51a2ccabcd9de350970b8c6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Oct 2024 00:46:58 +0200 Subject: [PATCH 216/403] Revert "ci: Avoid Archlinux' pyqt6 6.1.7-3 which lacks QSignalSpy" This reverts commit 27e446d26dcdb0653555ab6d72dc12e3d3a64173. Archlinux now uses a PyQt 6.8 development snapshot. --- scripts/dev/ci/docker/Dockerfile.j2 | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/dev/ci/docker/Dockerfile.j2 b/scripts/dev/ci/docker/Dockerfile.j2 index 4a720fcf1..257ffecb9 100644 --- a/scripts/dev/ci/docker/Dockerfile.j2 +++ b/scripts/dev/ci/docker/Dockerfile.j2 @@ -35,10 +35,6 @@ RUN pacman -Su --noconfirm \ gcc \ libyaml \ xorg-xdpyinfo -{% if unstable and qt6 %} -# WORKAROUND: 6.7.1-3 in [extra-testing] removes QSignalSpy -RUN pacman -U --noconfirm https://mirror.pkgbuild.com/extra/os/x86_64/python-pyqt6-6.7.1-2-x86_64.pkg.tar.zst -{% endif %} RUN useradd user -u 1001 && \ mkdir /home/user && \ From 61746e0f58afa36c05e07ff5276aaefa6fd7d1ee Mon Sep 17 00:00:00 2001 From: Willow Barraco Date: Tue, 15 Oct 2024 09:12:53 +0200 Subject: [PATCH 217/403] desktop: qutebrowser as webp mime type viewer This is decent webp viewer, like other web browser. --- misc/org.qutebrowser.qutebrowser.desktop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/org.qutebrowser.qutebrowser.desktop b/misc/org.qutebrowser.qutebrowser.desktop index 741a00371..71097a353 100644 --- a/misc/org.qutebrowser.qutebrowser.desktop +++ b/misc/org.qutebrowser.qutebrowser.desktop @@ -48,7 +48,7 @@ Categories=Network;WebBrowser; Exec=qutebrowser --untrusted-args %u Terminal=false StartupNotify=true -MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute; +MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/webp;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute; Keywords=Browser Actions=new-window;preferences; From 463bde5c8ee87d15b32119df192ac49d67a79a72 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Oct 2024 10:33:45 +0200 Subject: [PATCH 218/403] Update changelog --- doc/changelog.asciidoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 689c08753..caf37a183 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -35,6 +35,12 @@ Added - **Planned:** Full support for Python 3.13, with Windows/macOS binaries using it if possible. +Changed +~~~~~~~ + +- The `.desktop` file now also declares qutebrowser as a valid viewer for + `image/webp`. + [[v3.3.1]] v3.3.1 (2024-10-12) From 26b4ec6cef9d227a2727cdb7a45e71cf41eb4ac3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Oct 2024 11:42:07 +0200 Subject: [PATCH 219/403] Bleeding requirements: Use pytest-bdd release See https://github.com/qutebrowser/qutebrowser/issues/8342 --- misc/requirements/requirements-tests-bleeding.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt index 10369fc30..79d19910a 100644 --- a/misc/requirements/requirements-tests-bleeding.txt +++ b/misc/requirements/requirements-tests-bleeding.txt @@ -7,7 +7,11 @@ git+https://github.com/pallets/flask.git git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python git+https://github.com/pytest-dev/pytest.git -git+https://github.com/pytest-dev/pytest-bdd.git + +# https://github.com/qutebrowser/qutebrowser/issues/8342 +# git+https://github.com/pytest-dev/pytest-bdd.git +pytest-bdd + git+https://github.com/ionelmc/pytest-benchmark.git git+https://github.com/pytest-dev/pytest-instafail.git git+https://github.com/pytest-dev/pytest-mock.git From 8cf9cc9f1b7be8bd07282c041910fe5a9ffd8b13 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 21:49:02 +0200 Subject: [PATCH 220/403] nsis: Check for newer Windows build Follow-up to #8321, so we only claim to support what Qt actually officially does. --- misc/nsis/install.nsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh index 7f11f560d..5c23c4ace 100755 --- a/misc/nsis/install.nsh +++ b/misc/nsis/install.nsh @@ -439,11 +439,11 @@ Function .onInit Goto _os_check_pass ${ElseIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64 ${AndIf} ${AtLeastWin10} - ${AndIf} ${AtLeastBuild} 14393 ; Windows 10 1607 (also in error message below) + ${AndIf} ${AtLeastBuild} 17763 ; Windows 10 1809 (also in error message below) Goto _os_check_pass ${EndIf} MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\ - version of Windows 10 1607 or later." + version of Windows 10 1809 or later." Abort _os_check_pass: From c32b8090ca2d1b6b20466d920651e6a210ea2af0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 14:53:56 +0200 Subject: [PATCH 221/403] Import typing classes from collections.abc See https://peps.python.org/pep-0585/ and https://docs.python.org/3/whatsnew/3.9.html#type-hinting-generics-in-standard-collections Not changing List/Dict/Set/etc. in this commit, as that's a way bigger change. Done via: ruff check --select 'UP035' --fix --config 'target-version = "py39"' Also see #7098. --- qutebrowser/api/cmdutils.py | 3 ++- qutebrowser/app.py | 3 ++- qutebrowser/browser/browsertab.py | 4 ++-- qutebrowser/browser/downloads.py | 3 ++- qutebrowser/browser/downloadview.py | 3 ++- qutebrowser/browser/greasemonkey.py | 3 ++- qutebrowser/browser/hints.py | 4 ++-- qutebrowser/browser/history.py | 3 ++- qutebrowser/browser/qutescheme.py | 3 ++- qutebrowser/browser/shared.py | 3 ++- qutebrowser/browser/urlmarks.py | 2 +- qutebrowser/browser/webelem.py | 3 ++- qutebrowser/browser/webengine/darkmode.py | 4 ++-- qutebrowser/browser/webengine/notification.py | 3 ++- qutebrowser/browser/webengine/webengineelem.py | 3 ++- qutebrowser/browser/webengine/webview.py | 3 ++- qutebrowser/browser/webkit/certificateerror.py | 3 ++- qutebrowser/browser/webkit/cookies.py | 2 +- qutebrowser/browser/webkit/mhtml.py | 3 ++- qutebrowser/browser/webkit/network/networkmanager.py | 3 ++- qutebrowser/browser/webkit/tabhistory.py | 3 ++- qutebrowser/browser/webkit/webkitelem.py | 3 ++- qutebrowser/browser/webkit/webkittab.py | 3 ++- qutebrowser/commands/command.py | 3 ++- qutebrowser/commands/parser.py | 3 ++- qutebrowser/commands/runners.py | 3 ++- qutebrowser/commands/userscripts.py | 3 ++- qutebrowser/completion/models/__init__.py | 3 ++- qutebrowser/completion/models/completionmodel.py | 3 ++- qutebrowser/completion/models/filepathcategory.py | 3 ++- qutebrowser/completion/models/listcategory.py | 3 ++- qutebrowser/completion/models/miscmodels.py | 3 ++- qutebrowser/completion/models/urlmodel.py | 3 ++- qutebrowser/completion/models/util.py | 3 ++- qutebrowser/components/braveadblock.py | 3 ++- qutebrowser/components/misccommands.py | 3 ++- qutebrowser/components/readlinecommands.py | 3 ++- qutebrowser/config/config.py | 4 ++-- qutebrowser/config/configcommands.py | 3 ++- qutebrowser/config/configdata.py | 4 ++-- qutebrowser/config/configexc.py | 3 ++- qutebrowser/config/configfiles.py | 4 ++-- qutebrowser/config/configtypes.py | 6 ++++-- qutebrowser/config/configutils.py | 4 ++-- qutebrowser/config/qtargs.py | 3 ++- qutebrowser/extensions/loader.py | 3 ++- qutebrowser/keyinput/basekeyparser.py | 3 ++- qutebrowser/keyinput/keyutils.py | 3 ++- qutebrowser/keyinput/modeman.py | 3 ++- qutebrowser/keyinput/modeparsers.py | 3 ++- qutebrowser/mainwindow/mainwindow.py | 3 ++- qutebrowser/mainwindow/messageview.py | 3 ++- qutebrowser/mainwindow/prompt.py | 3 ++- qutebrowser/mainwindow/tabbedbrowser.py | 3 ++- qutebrowser/mainwindow/windowundo.py | 3 ++- qutebrowser/misc/backendproblem.py | 3 ++- qutebrowser/misc/cmdhistory.py | 2 +- qutebrowser/misc/consolewidget.py | 3 ++- qutebrowser/misc/crashsignal.py | 3 ++- qutebrowser/misc/debugcachestats.py | 3 ++- qutebrowser/misc/guiprocess.py | 3 ++- qutebrowser/misc/httpclient.py | 2 +- qutebrowser/misc/lineparser.py | 2 +- qutebrowser/misc/pakjoy.py | 3 ++- qutebrowser/misc/quitter.py | 3 ++- qutebrowser/misc/savemanager.py | 2 +- qutebrowser/misc/sessions.py | 3 ++- qutebrowser/misc/sql.py | 3 ++- qutebrowser/misc/throttle.py | 3 ++- qutebrowser/utils/debug.py | 3 ++- qutebrowser/utils/docutils.py | 3 ++- qutebrowser/utils/javascript.py | 3 ++- qutebrowser/utils/jinja.py | 3 ++- qutebrowser/utils/log.py | 3 ++- qutebrowser/utils/message.py | 3 ++- qutebrowser/utils/objreg.py | 5 +++-- qutebrowser/utils/qtlog.py | 3 ++- qutebrowser/utils/qtutils.py | 3 ++- qutebrowser/utils/resources.py | 3 ++- qutebrowser/utils/standarddir.py | 3 ++- qutebrowser/utils/urlutils.py | 3 ++- qutebrowser/utils/usertypes.py | 3 ++- qutebrowser/utils/utils.py | 5 +++-- qutebrowser/utils/version.py | 3 ++- scripts/dev/build_release.py | 3 ++- scripts/dev/misc_checks.py | 3 ++- tests/unit/components/test_braveadblock.py | 3 ++- 87 files changed, 172 insertions(+), 97 deletions(-) diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py index e5466f072..9d825cafd 100644 --- a/qutebrowser/api/cmdutils.py +++ b/qutebrowser/api/cmdutils.py @@ -35,7 +35,8 @@ Possible values: import inspect -from typing import Any, Callable, Iterable, Protocol, Optional, Dict, cast +from typing import Any, Callable, Protocol, Optional, Dict, cast +from collections.abc import Iterable from qutebrowser.utils import qtutils from qutebrowser.commands import command, cmdexc diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 51603a2b9..8223d218f 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -29,7 +29,8 @@ import tempfile import pathlib import datetime import argparse -from typing import Iterable, Optional, List, Tuple +from typing import Optional, List, Tuple +from collections.abc import Iterable from qutebrowser.qt import machinery from qutebrowser.qt.widgets import QApplication, QWidget diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 625046a9c..ff7fc7c4e 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -9,8 +9,8 @@ import pathlib import itertools import functools import dataclasses -from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional, - Sequence, Set, Type, Union, Tuple) +from typing import (cast, TYPE_CHECKING, Any, Callable, List, Optional, Set, Type, Union, Tuple) +from collections.abc import Iterable, Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index fbbf4ff11..d587b2b74 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -13,7 +13,8 @@ import functools import pathlib import tempfile import enum -from typing import Any, Dict, IO, List, MutableSequence, Optional, Union +from typing import Any, Dict, IO, List, Optional, Union +from collections.abc import MutableSequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, QTimer, QAbstractListModel, QUrl) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 4b6a8b2c8..6103c7ab0 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -5,7 +5,8 @@ """The ListView to display downloads in.""" import functools -from typing import Callable, MutableSequence, Tuple, Union +from typing import Callable, Tuple, Union +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSlot, QSize, Qt from qutebrowser.qt.widgets import QListView, QSizePolicy, QMenu, QStyleFactory diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index d41d46361..00c645797 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -12,7 +12,8 @@ import functools import glob import textwrap import dataclasses -from typing import cast, List, Sequence, Tuple, Optional +from typing import cast, List, Tuple, Optional +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSignal, QObject, QUrl diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index dc5c55b05..64fe5072c 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -12,8 +12,8 @@ import html import enum import dataclasses from string import ascii_lowercase -from typing import (TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Mapping, - MutableSequence, Optional, Sequence, Set) +from typing import (TYPE_CHECKING, Callable, Dict, List, Optional, Set) +from collections.abc import Iterable, Iterator, Mapping, MutableSequence, Sequence from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt, QUrl from qutebrowser.qt.widgets import QLabel diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index 45bfeddbf..ebcd26e72 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -8,7 +8,8 @@ import os import time import contextlib import pathlib -from typing import cast, Mapping, MutableSequence, Optional +from typing import cast, Optional +from collections.abc import Mapping, MutableSequence from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSlot, QUrl, QObject, pyqtSignal diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 508d510d7..472e8b637 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -18,7 +18,8 @@ import textwrap import urllib import collections import secrets -from typing import TypeVar, Callable, Dict, List, Optional, Union, Sequence, Tuple +from typing import TypeVar, Callable, Dict, List, Optional, Union, Tuple +from collections.abc import Sequence from qutebrowser.qt.core import QUrlQuery, QUrl diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 358af6d95..acf8ad011 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -10,7 +10,8 @@ import html import enum import netrc import tempfile -from typing import Callable, Mapping, List, Optional, Iterable, Iterator +from typing import Callable, List, Optional +from collections.abc import Mapping, Iterable, Iterator from qutebrowser.qt.core import QUrl, pyqtBoundSignal diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py index 2d2563a1a..f9879274b 100644 --- a/qutebrowser/browser/urlmarks.py +++ b/qutebrowser/browser/urlmarks.py @@ -15,7 +15,7 @@ import os.path import html import functools import collections -from typing import MutableMapping +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, QUrl, QObject diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index 556623ee5..e496b6a12 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -4,7 +4,8 @@ """Generic web element related code.""" -from typing import Iterator, Optional, Set, TYPE_CHECKING, Union, Dict +from typing import Optional, Set, TYPE_CHECKING, Union, Dict +from collections.abc import Iterator import collections.abc from qutebrowser.qt import machinery diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index 5aab28051..8318c52da 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -125,8 +125,8 @@ import copy import enum import dataclasses import collections -from typing import (Any, Iterator, Mapping, MutableMapping, Optional, Set, Tuple, Union, - Sequence, List) +from typing import (Any, Optional, Set, Tuple, Union, List) +from collections.abc import Iterator, Mapping, MutableMapping, Sequence from qutebrowser.config import config from qutebrowser.utils import usertypes, utils, log, version diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index e8b2e27f1..54e7aca7e 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -33,7 +33,8 @@ import dataclasses import itertools import functools import subprocess -from typing import Any, List, Dict, Optional, Iterator, Type, TYPE_CHECKING +from typing import Any, List, Dict, Optional, Type, TYPE_CHECKING +from collections.abc import Iterator from qutebrowser.qt import machinery from qutebrowser.qt.core import (Qt, QObject, QVariant, QMetaType, QByteArray, pyqtSlot, diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index c387ebbcf..7fc7e68af 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -5,7 +5,8 @@ """QtWebEngine specific part of the web element API.""" from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Iterator, Optional, Set, Tuple, Union) + TYPE_CHECKING, Any, Callable, Dict, Optional, Set, Tuple, Union) +from collections.abc import Iterator from qutebrowser.qt.core import QRect, QEventLoop from qutebrowser.qt.widgets import QApplication diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 96c0c97e5..1d8e708a7 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -5,7 +5,8 @@ """The main browser widget for QtWebEngine.""" import mimetypes -from typing import List, Iterable, Optional +from typing import List, Optional +from collections.abc import Iterable from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QUrl diff --git a/qutebrowser/browser/webkit/certificateerror.py b/qutebrowser/browser/webkit/certificateerror.py index 59d9cc897..2c18af62e 100644 --- a/qutebrowser/browser/webkit/certificateerror.py +++ b/qutebrowser/browser/webkit/certificateerror.py @@ -4,7 +4,8 @@ """A wrapper over a list of QSslErrors.""" -from typing import Sequence, Optional +from typing import Optional +from collections.abc import Sequence from qutebrowser.qt.network import QSslError, QNetworkReply diff --git a/qutebrowser/browser/webkit/cookies.py b/qutebrowser/browser/webkit/cookies.py index 9e6ae2f1b..af881175d 100644 --- a/qutebrowser/browser/webkit/cookies.py +++ b/qutebrowser/browser/webkit/cookies.py @@ -4,7 +4,7 @@ """Handling of HTTP cookies.""" -from typing import Sequence +from collections.abc import Sequence from qutebrowser.qt.network import QNetworkCookie, QNetworkCookieJar from qutebrowser.qt.core import pyqtSignal, QDateTime diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 692689b0a..61272aa12 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -19,7 +19,8 @@ import email.mime.multipart import email.message import quopri import dataclasses -from typing import MutableMapping, Set, Tuple, Callable +from typing import Set, Tuple, Callable +from collections.abc import MutableMapping from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 06402a547..9b5ca915c 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -7,7 +7,8 @@ import collections import html import dataclasses -from typing import TYPE_CHECKING, Dict, MutableMapping, Optional, Set +from typing import TYPE_CHECKING, Dict, Optional, Set +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QUrl, QByteArray from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkReply, QSslConfiguration, diff --git a/qutebrowser/browser/webkit/tabhistory.py b/qutebrowser/browser/webkit/tabhistory.py index 80a572385..b21c77402 100644 --- a/qutebrowser/browser/webkit/tabhistory.py +++ b/qutebrowser/browser/webkit/tabhistory.py @@ -4,7 +4,8 @@ """Utilities related to QWebHistory.""" -from typing import Any, List, Mapping +from typing import Any, List +from collections.abc import Mapping from qutebrowser.qt.core import QByteArray, QDataStream, QIODevice, QUrl diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index 0400358af..de1e7758f 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -4,7 +4,8 @@ """QtWebKit specific part of the web element API.""" -from typing import cast, TYPE_CHECKING, Iterator, List, Optional, Set +from typing import cast, TYPE_CHECKING, List, Optional, Set +from collections.abc import Iterator from qutebrowser.qt.core import QRect, Qt # pylint: disable=no-name-in-module diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 1ae976bea..d89295440 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -7,7 +7,8 @@ import re import functools import xml.etree.ElementTree -from typing import cast, Iterable, Optional +from typing import cast, Optional +from collections.abc import Iterable from qutebrowser.qt.core import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize from qutebrowser.qt.gui import QIcon diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index effdcc9b0..350da0357 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -9,8 +9,9 @@ import collections import traceback import typing import dataclasses -from typing import (Any, MutableMapping, MutableSequence, Tuple, Union, List, Optional, +from typing import (Any, Tuple, Union, List, Optional, Callable) +from collections.abc import MutableMapping, MutableSequence from qutebrowser.api import cmdutils from qutebrowser.commands import cmdexc, argparser diff --git a/qutebrowser/commands/parser.py b/qutebrowser/commands/parser.py index d45a18aea..a0f74a2ec 100644 --- a/qutebrowser/commands/parser.py +++ b/qutebrowser/commands/parser.py @@ -5,7 +5,8 @@ """Module for parsing commands entered into the browser.""" import dataclasses -from typing import List, Iterator +from typing import List +from collections.abc import Iterator from qutebrowser.commands import cmdexc, command from qutebrowser.misc import split, objects diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index dec1fb4f5..22ae2ae4e 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -7,7 +7,8 @@ import traceback import re import contextlib -from typing import TYPE_CHECKING, Callable, Dict, Tuple, Iterator, Mapping, MutableMapping +from typing import TYPE_CHECKING, Callable, Dict, Tuple +from collections.abc import Iterator, Mapping, MutableMapping from qutebrowser.qt.core import pyqtSlot, QUrl, QObject diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index 01710a63c..dd4db66dc 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -7,7 +7,8 @@ import os import os.path import tempfile -from typing import cast, Any, MutableMapping, Tuple +from typing import cast, Any, Tuple +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QSocketNotifier diff --git a/qutebrowser/completion/models/__init__.py b/qutebrowser/completion/models/__init__.py index 4fd45e160..a55a91215 100644 --- a/qutebrowser/completion/models/__init__.py +++ b/qutebrowser/completion/models/__init__.py @@ -4,7 +4,8 @@ """Models for the command completion.""" -from typing import Sequence, Optional +from typing import Optional +from collections.abc import Sequence from qutebrowser.completion.models.util import DeleteFuncType from qutebrowser.qt.core import QAbstractItemModel diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py index 5a85f7281..a4eed93d1 100644 --- a/qutebrowser/completion/models/completionmodel.py +++ b/qutebrowser/completion/models/completionmodel.py @@ -4,7 +4,8 @@ """A model that proxies access to one or more completion categories.""" -from typing import MutableSequence, overload, Optional, Any, cast +from typing import overload, Optional, Any, cast +from collections.abc import MutableSequence from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QModelIndex, QAbstractItemModel, QObject diff --git a/qutebrowser/completion/models/filepathcategory.py b/qutebrowser/completion/models/filepathcategory.py index 23ad0d173..43b0725a0 100644 --- a/qutebrowser/completion/models/filepathcategory.py +++ b/qutebrowser/completion/models/filepathcategory.py @@ -14,7 +14,8 @@ is harder to achieve via pathlib. import glob import os import os.path -from typing import List, Optional, Iterable +from typing import List, Optional +from collections.abc import Iterable from qutebrowser.qt.core import QAbstractListModel, QModelIndex, QObject, Qt, QUrl diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index 10639f47d..6dbfd978e 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -5,7 +5,8 @@ """Completion category that uses a list of tuples as a data source.""" import re -from typing import Iterable, Tuple +from typing import Tuple +from collections.abc import Iterable from qutebrowser.qt.core import QSortFilterProxyModel, QRegularExpression from qutebrowser.qt.gui import QStandardItem, QStandardItemModel diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index ea3febe4d..0301339b8 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -6,7 +6,8 @@ import datetime import itertools -from typing import List, Sequence, Tuple +from typing import List, Tuple +from collections.abc import Sequence from qutebrowser.config import config, configdata from qutebrowser.utils import objreg, log, utils diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index 10bee0393..f2a57623e 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -4,7 +4,8 @@ """Function to return the url completion model for the `open` command.""" -from typing import Dict, Sequence +from typing import Dict +from collections.abc import Sequence from qutebrowser.completion.models import (completionmodel, filepathcategory, listcategory, histcategory, diff --git a/qutebrowser/completion/models/util.py b/qutebrowser/completion/models/util.py index 492e1b2e5..d1c646661 100644 --- a/qutebrowser/completion/models/util.py +++ b/qutebrowser/completion/models/util.py @@ -4,7 +4,8 @@ """Utility functions for completion models.""" -from typing import Callable, Sequence +from typing import Callable +from collections.abc import Sequence from qutebrowser.utils import usertypes from qutebrowser.misc import objects diff --git a/qutebrowser/components/braveadblock.py b/qutebrowser/components/braveadblock.py index a827eb546..5be1efbfe 100644 --- a/qutebrowser/components/braveadblock.py +++ b/qutebrowser/components/braveadblock.py @@ -10,7 +10,8 @@ import pathlib import functools import contextlib import subprocess -from typing import Optional, IO, Iterator +from typing import Optional, IO +from collections.abc import Iterator from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py index 0d8fa0b2e..64914bc95 100644 --- a/qutebrowser/components/misccommands.py +++ b/qutebrowser/components/misccommands.py @@ -11,7 +11,8 @@ import os import signal import logging import pathlib -from typing import Optional, Sequence, Callable +from typing import Optional, Callable +from collections.abc import Sequence try: import hunter diff --git a/qutebrowser/components/readlinecommands.py b/qutebrowser/components/readlinecommands.py index a9626637d..ca071448e 100644 --- a/qutebrowser/components/readlinecommands.py +++ b/qutebrowser/components/readlinecommands.py @@ -5,7 +5,8 @@ """Bridge to provide readline-like shortcuts for QLineEdits.""" import os -from typing import Iterable, Optional, MutableMapping, Any, Callable +from typing import Optional, Any, Callable +from collections.abc import Iterable, MutableMapping from qutebrowser.qt.widgets import QApplication, QLineEdit diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index cb7fe77b3..d43b71136 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -7,8 +7,8 @@ import copy import contextlib import functools -from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Mapping, - MutableMapping, MutableSequence, Optional, Tuple, cast) +from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, cast) +from collections.abc import Iterator, Mapping, MutableMapping, MutableSequence from qutebrowser.qt.core import pyqtSignal, QObject, QUrl diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index c4065ceb9..d581989f8 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -6,7 +6,8 @@ import os.path import contextlib -from typing import TYPE_CHECKING, Iterator, List, Optional, Any, Tuple +from typing import TYPE_CHECKING, List, Optional, Any, Tuple +from collections.abc import Iterator from qutebrowser.qt.core import QUrl, QUrlQuery diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 90486f702..a88543fc6 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -9,8 +9,8 @@ Module attributes: DATA: A dict of Option objects after init() has been called. """ -from typing import (Any, Dict, Iterable, List, Mapping, MutableMapping, Optional, - Sequence, Tuple, Union, NoReturn, cast) +from typing import (Any, Dict, List, Optional, Tuple, Union, NoReturn, cast) +from collections.abc import Iterable, Mapping, MutableMapping, Sequence import functools import dataclasses diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 4c8291580..4cce7412f 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -6,7 +6,8 @@ import difflib import dataclasses -from typing import Any, Mapping, Optional, Sequence, Union, List +from typing import Any, Optional, Union, List +from collections.abc import Mapping, Sequence from qutebrowser.utils import usertypes, log diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index 0680cd0e7..e4d852862 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -14,8 +14,8 @@ import traceback import configparser import contextlib import re -from typing import (TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Mapping, - MutableMapping, Optional, Tuple, cast) +from typing import (TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast) +from collections.abc import Iterable, Iterator, Mapping, MutableMapping import yaml from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QSettings, qVersion diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 9fa374712..7d4980097 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -36,8 +36,10 @@ import functools import operator import json import dataclasses -from typing import (Any, Callable, Dict as DictType, Iterable, Iterator, - List as ListType, Optional, Pattern, Sequence, Tuple, Union) +from typing import (Any, Callable, Dict as DictType, + List as ListType, Optional, Tuple, Union) +from re import Pattern +from collections.abc import Iterable, Iterator, Sequence import yaml from qutebrowser.qt.core import QUrl, Qt diff --git a/qutebrowser/config/configutils.py b/qutebrowser/config/configutils.py index fda9552dd..a53fce7ec 100644 --- a/qutebrowser/config/configutils.py +++ b/qutebrowser/config/configutils.py @@ -9,8 +9,8 @@ import collections import itertools import operator from typing import ( - TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Sequence, Set, Union, - MutableMapping) + TYPE_CHECKING, Any, Dict, List, Optional, Set, Union) +from collections.abc import Iterator, Sequence, MutableMapping from qutebrowser.qt.core import QUrl from qutebrowser.qt.gui import QFontDatabase diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index eb5e8b58c..665848232 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -8,7 +8,8 @@ import os import sys import argparse import pathlib -from typing import Any, Dict, Iterator, List, Optional, Sequence, Tuple, Union, Callable +from typing import Any, Dict, List, Optional, Tuple, Union, Callable +from collections.abc import Iterator, Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import QLocale diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index ff9974d9d..291377c93 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -10,7 +10,8 @@ import pathlib import importlib import argparse import dataclasses -from typing import Callable, Iterator, List, Optional, Tuple +from typing import Callable, List, Optional, Tuple +from collections.abc import Iterator from qutebrowser.qt.core import pyqtSlot diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index df6b66f7f..c97570369 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -8,7 +8,8 @@ import string import types import dataclasses import traceback -from typing import Mapping, MutableMapping, Optional, Sequence +from typing import Optional +from collections.abc import Mapping, MutableMapping, Sequence from qutebrowser.qt.core import QObject, pyqtSignal from qutebrowser.qt.gui import QKeySequence, QKeyEvent diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 18730c74b..63a6832e3 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -18,7 +18,8 @@ handle what we actually think we do. import itertools import dataclasses -from typing import Iterator, Iterable, List, Mapping, Optional, Union, overload, cast +from typing import List, Optional, Union, overload, cast +from collections.abc import Iterator, Iterable, Mapping from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt, QEvent diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index f0337ec88..13318f2c9 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -6,7 +6,8 @@ import functools import dataclasses -from typing import Mapping, Callable, MutableMapping, Union, Set, cast +from typing import Callable, Union, Set, cast +from collections.abc import Mapping, MutableMapping from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt, QObject, QEvent diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 05e560111..b9e5951db 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -10,7 +10,8 @@ Module attributes: import traceback import enum -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSlot, Qt, QObject from qutebrowser.qt.gui import QKeySequence, QKeyEvent diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index e39ac4f9a..b5664a8c2 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -8,7 +8,8 @@ import binascii import base64 import itertools import functools -from typing import List, MutableSequence, Optional, Tuple, cast +from typing import List, Optional, Tuple, cast +from collections.abc import MutableSequence from qutebrowser.qt import machinery from qutebrowser.qt.core import (pyqtBoundSignal, pyqtSlot, QRect, QPoint, QTimer, Qt, diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 95bbed724..66d065360 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -4,7 +4,8 @@ """Showing messages above the statusbar.""" -from typing import MutableSequence, Optional +from typing import Optional +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt from qutebrowser.qt.widgets import QWidget, QVBoxLayout, QLabel, QSizePolicy diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index d6ae16ba4..44c37687b 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -9,7 +9,8 @@ import html import collections import functools import dataclasses -from typing import Deque, MutableSequence, Optional, cast +from typing import Deque, Optional, cast +from collections.abc import MutableSequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, QItemSelectionModel, QObject, QEventLoop, QUrl) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 47d8dc680..70fc7d00d 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -10,7 +10,8 @@ import weakref import datetime import dataclasses from typing import ( - Any, Deque, List, Mapping, MutableMapping, MutableSequence, Optional, Tuple) + Any, Deque, List, Optional, Tuple) +from collections.abc import Mapping, MutableMapping, MutableSequence from qutebrowser.qt.widgets import QSizePolicy, QWidget, QApplication from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QTimer, QUrl, QPoint diff --git a/qutebrowser/mainwindow/windowundo.py b/qutebrowser/mainwindow/windowundo.py index 46ff3c8c5..5efb77c32 100644 --- a/qutebrowser/mainwindow/windowundo.py +++ b/qutebrowser/mainwindow/windowundo.py @@ -6,7 +6,8 @@ import collections import dataclasses -from typing import MutableSequence, cast, TYPE_CHECKING +from typing import cast, TYPE_CHECKING +from collections.abc import MutableSequence from qutebrowser.qt.core import QObject, QByteArray diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index 51d3a35c3..244949bd9 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -13,7 +13,8 @@ import shutil import os.path import argparse import dataclasses -from typing import Any, Optional, Sequence, Tuple +from typing import Any, Optional, Tuple +from collections.abc import Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import Qt diff --git a/qutebrowser/misc/cmdhistory.py b/qutebrowser/misc/cmdhistory.py index aa2df63e0..e52dd77dd 100644 --- a/qutebrowser/misc/cmdhistory.py +++ b/qutebrowser/misc/cmdhistory.py @@ -4,7 +4,7 @@ """Command history for the status bar.""" -from typing import MutableSequence +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QObject diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index 08f5dc5ff..d74478b4e 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -6,7 +6,8 @@ import sys import code -from typing import MutableSequence, Optional +from typing import Optional +from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSignal, pyqtSlot, Qt from qutebrowser.qt.widgets import QTextEdit, QWidget, QVBoxLayout, QApplication diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index c33ae1173..4ad6021f5 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -15,7 +15,8 @@ import functools import threading import faulthandler import dataclasses -from typing import TYPE_CHECKING, Optional, MutableMapping, cast, List +from typing import TYPE_CHECKING, Optional, cast, List +from collections.abc import MutableMapping from qutebrowser.qt.core import (pyqtSlot, qInstallMessageHandler, QObject, QSocketNotifier, QTimer, QUrl) diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index fb1f7e7c3..70f088418 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -9,7 +9,8 @@ dependencies as possible to avoid cyclic dependencies. """ import weakref -from typing import Any, Callable, Optional, TypeVar, Mapping +from typing import Any, Callable, Optional, TypeVar +from collections.abc import Mapping from qutebrowser.utils import log diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index d20b4ba0f..ce4b8a32e 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -9,7 +9,8 @@ import locale import shlex import shutil import signal -from typing import Mapping, Sequence, Dict, Optional +from typing import Dict, Optional +from collections.abc import Mapping, Sequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment, QByteArray, QUrl, Qt) diff --git a/qutebrowser/misc/httpclient.py b/qutebrowser/misc/httpclient.py index a6a6025c3..097fdcd43 100644 --- a/qutebrowser/misc/httpclient.py +++ b/qutebrowser/misc/httpclient.py @@ -6,7 +6,7 @@ import functools import urllib.parse -from typing import MutableMapping +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, QObject, QTimer from qutebrowser.qt.network import (QNetworkAccessManager, QNetworkRequest, diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py index c96109e9e..c253c3ef5 100644 --- a/qutebrowser/misc/lineparser.py +++ b/qutebrowser/misc/lineparser.py @@ -7,7 +7,7 @@ import os import os.path import contextlib -from typing import Sequence +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QObject diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index 9415113ea..f0ee7b4f0 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -30,7 +30,8 @@ import shutil import pathlib import dataclasses import contextlib -from typing import ClassVar, IO, Optional, Dict, Tuple, Iterator +from typing import ClassVar, IO, Optional, Dict, Tuple +from collections.abc import Iterator from qutebrowser.config import config from qutebrowser.misc import binparsing, objects diff --git a/qutebrowser/misc/quitter.py b/qutebrowser/misc/quitter.py index 9fe743414..62438001f 100644 --- a/qutebrowser/misc/quitter.py +++ b/qutebrowser/misc/quitter.py @@ -15,7 +15,8 @@ import tokenize import functools import warnings import subprocess -from typing import Iterable, Mapping, MutableSequence, Sequence, cast +from typing import cast +from collections.abc import Iterable, Mapping, MutableSequence, Sequence from qutebrowser.qt.core import QObject, pyqtSignal, QTimer try: diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py index 6017b3d2a..567cba803 100644 --- a/qutebrowser/misc/savemanager.py +++ b/qutebrowser/misc/savemanager.py @@ -6,7 +6,7 @@ import os.path import collections -from typing import MutableMapping +from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSlot, QObject, QTimer diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index dd63904cd..b487fcd2c 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -10,7 +10,8 @@ import itertools import urllib import shutil import pathlib -from typing import Any, Iterable, MutableMapping, MutableSequence, Optional, Union, cast +from typing import Any, Optional, Union, cast +from collections.abc import Iterable, MutableMapping, MutableSequence from qutebrowser.qt.core import Qt, QUrl, QObject, QPoint, QTimer, QDateTime import yaml diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py index b23b862a3..e16ffeb40 100644 --- a/qutebrowser/misc/sql.py +++ b/qutebrowser/misc/sql.py @@ -9,7 +9,8 @@ import collections import contextlib import dataclasses import types -from typing import Any, Dict, Iterator, List, Mapping, MutableSequence, Optional, Type, Union +from typing import Any, Dict, List, Optional, Type, Union +from collections.abc import Iterator, Mapping, MutableSequence from qutebrowser.qt.core import QObject, pyqtSignal from qutebrowser.qt.sql import QSqlDatabase, QSqlError, QSqlQuery diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py index 43325fb08..f4c50c09a 100644 --- a/qutebrowser/misc/throttle.py +++ b/qutebrowser/misc/throttle.py @@ -6,7 +6,8 @@ import dataclasses import time -from typing import Any, Callable, Mapping, Optional, Sequence +from typing import Any, Callable, Optional +from collections.abc import Mapping, Sequence from qutebrowser.qt.core import QObject diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 230c965ef..2ec5e11fe 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -12,7 +12,8 @@ import functools import datetime import types from typing import ( - Any, Callable, List, Mapping, MutableSequence, Optional, Sequence, Type, Union) + Any, Callable, List, Optional, Type, Union) +from collections.abc import Mapping, MutableSequence, Sequence from qutebrowser.qt.core import Qt, QEvent, QMetaMethod, QObject, pyqtBoundSignal diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index 6cd16730c..e60db6167 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -10,7 +10,8 @@ import inspect import os.path import collections import enum -from typing import Any, Callable, MutableMapping, Optional, List, Union +from typing import Any, Callable, Optional, List, Union +from collections.abc import MutableMapping import qutebrowser from qutebrowser.utils import log, utils diff --git a/qutebrowser/utils/javascript.py b/qutebrowser/utils/javascript.py index 9890be446..66470155a 100644 --- a/qutebrowser/utils/javascript.py +++ b/qutebrowser/utils/javascript.py @@ -4,7 +4,8 @@ """Utilities related to javascript interaction.""" -from typing import Sequence, Union +from typing import Union +from collections.abc import Sequence _InnerJsArgType = Union[None, str, bool, int, float] _JsArgType = Union[_InnerJsArgType, Sequence[_InnerJsArgType]] diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index d7c261942..9fcff7cd1 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -10,7 +10,8 @@ import posixpath import functools import contextlib import html -from typing import Any, Callable, FrozenSet, Iterator, List, Set, Tuple +from typing import Any, Callable, FrozenSet, List, Set, Tuple +from collections.abc import Iterator import jinja2 import jinja2.nodes diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index aa3ea4123..55e74cbf4 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -15,8 +15,9 @@ import warnings import json import inspect import argparse -from typing import (TYPE_CHECKING, Any, Iterator, Mapping, MutableSequence, +from typing import (TYPE_CHECKING, Any, Optional, Set, Tuple, Union, TextIO, Literal, cast) +from collections.abc import Iterator, Mapping, MutableSequence # NOTE: This is a Qt-free zone! All imports related to Qt logging should be done in # qutebrowser.utils.qtlog (see https://github.com/qutebrowser/qutebrowser/issues/7769). diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 275ed2f3d..fe1dd16a9 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -10,7 +10,8 @@ import dataclasses import traceback -from typing import Any, Callable, Iterable, List, Union, Optional +from typing import Any, Callable, List, Union, Optional +from collections.abc import Iterable from qutebrowser.qt.core import pyqtSignal, pyqtBoundSignal, QObject diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 8a3489d09..7d3df728a 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -7,8 +7,9 @@ import collections import functools -from typing import (TYPE_CHECKING, Any, Callable, MutableMapping, MutableSequence, - Optional, Sequence, Union) +from typing import (TYPE_CHECKING, Any, Callable, + Optional, Union) +from collections.abc import MutableMapping, MutableSequence, Sequence from qutebrowser.qt.core import QObject, QTimer from qutebrowser.qt.widgets import QApplication diff --git a/qutebrowser/utils/qtlog.py b/qutebrowser/utils/qtlog.py index 78b48ebee..215123f4a 100644 --- a/qutebrowser/utils/qtlog.py +++ b/qutebrowser/utils/qtlog.py @@ -10,7 +10,8 @@ import faulthandler import logging import sys import traceback -from typing import Iterator, Optional +from typing import Optional +from collections.abc import Iterator from qutebrowser.qt import core as qtcore from qutebrowser.utils import log diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index c1f05b78d..6efd8bbe1 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -18,8 +18,9 @@ import enum import pathlib import operator import contextlib -from typing import (Any, TYPE_CHECKING, BinaryIO, IO, Iterator, Literal, +from typing import (Any, TYPE_CHECKING, BinaryIO, IO, Literal, Optional, Union, Tuple, Protocol, cast, overload, TypeVar) +from collections.abc import Iterator from qutebrowser.qt import machinery, sip from qutebrowser.qt.core import (qVersion, QEventLoop, QDataStream, QByteArray, diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index 8d1dbfe10..c9e1c42a7 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -10,7 +10,8 @@ import contextlib import posixpath import pathlib import importlib.resources -from typing import Iterator, Iterable, Union, Dict +from typing import Union, Dict +from collections.abc import Iterator, Iterable if sys.version_info >= (3, 11): # pragma: no cover # https://github.com/python/cpython/issues/90276 diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 1eb296e50..57e81ce19 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -11,7 +11,8 @@ import contextlib import enum import argparse import tempfile -from typing import Iterator, Optional, Dict +from typing import Optional, Dict +from collections.abc import Iterator from qutebrowser.qt.core import QStandardPaths from qutebrowser.qt.widgets import QApplication diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 785569069..0fc2a262e 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -11,7 +11,8 @@ import ipaddress import posixpath import urllib.parse import mimetypes -from typing import Optional, Tuple, Union, Iterable, cast +from typing import Optional, Tuple, Union, cast +from collections.abc import Iterable from qutebrowser.qt import machinery from qutebrowser.qt.core import QUrl, QUrlQuery diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index d61d4aba7..c8e92bf17 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -10,7 +10,8 @@ import enum import time import dataclasses import logging -from typing import Optional, Sequence, TypeVar, Union +from typing import Optional, TypeVar, Union +from collections.abc import Sequence from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QTimer from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 12a9c83b1..126075b5a 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -19,9 +19,10 @@ import contextlib import shlex import sysconfig import mimetypes -from typing import (Any, Callable, IO, Iterator, - Optional, Sequence, Tuple, List, Type, Union, +from typing import (Any, Callable, IO, + Optional, Tuple, List, Type, Union, TypeVar, Protocol) +from collections.abc import Iterator, Sequence from qutebrowser.qt.core import QUrl, QVersionNumber, QRect, QPoint from qutebrowser.qt.gui import QClipboard, QDesktopServices diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 298eba9ca..46d27d537 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -19,8 +19,9 @@ import getpass import functools import dataclasses import importlib.metadata -from typing import (Mapping, Optional, Sequence, Tuple, ClassVar, Dict, Any, +from typing import (Optional, Tuple, ClassVar, Dict, Any, TYPE_CHECKING) +from collections.abc import Mapping, Sequence from qutebrowser.qt import machinery from qutebrowser.qt.core import PYQT_VERSION_STR diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index ed653316b..af44c7f6f 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -20,7 +20,8 @@ import platform import collections import dataclasses import re -from typing import Iterable, List, Optional +from typing import List, Optional +from collections.abc import Iterable try: import winreg diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 4b838b5fe..497c5f1f0 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -15,7 +15,8 @@ import subprocess import tokenize import traceback import pathlib -from typing import List, Iterator, Optional, Tuple +from typing import List, Optional, Tuple +from collections.abc import Iterator REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] sys.path.insert(0, str(REPO_ROOT)) diff --git a/tests/unit/components/test_braveadblock.py b/tests/unit/components/test_braveadblock.py index 54ef85115..197687d1d 100644 --- a/tests/unit/components/test_braveadblock.py +++ b/tests/unit/components/test_braveadblock.py @@ -5,7 +5,8 @@ import pathlib import logging import csv -from typing import Iterable, Tuple +from typing import Tuple +from collections.abc import Iterable from qutebrowser.qt.core import QUrl From 97104b2000933763b5692551e01572183f51b61e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 15:06:01 +0200 Subject: [PATCH 222/403] Use builtin list/dict/set/... types for annotations See https://peps.python.org/pep-0585/ and https://docs.python.org/3/whatsnew/3.9.html#type-hinting-generics-in-standard-collections Done via: ruff check --select 'UP006' --fix --config 'target-version = "py39"' --unsafe-fixes followed by removing unused imports: ruff check --select 'F401' --fix --config 'target-version = "py39"' and a semi-manual review to find imports that are still needed (but ruff doesn't know about yet): git diff | grep '^-' | grep import | grep -v "from typing" Also see #7098. --- qutebrowser/api/cmdutils.py | 4 +- qutebrowser/app.py | 4 +- qutebrowser/browser/browsertab.py | 14 ++-- qutebrowser/browser/commands.py | 4 +- qutebrowser/browser/downloads.py | 6 +- qutebrowser/browser/downloadview.py | 6 +- qutebrowser/browser/greasemonkey.py | 20 +++--- qutebrowser/browser/hints.py | 12 ++-- qutebrowser/browser/navigate.py | 4 +- qutebrowser/browser/network/proxy.py | 4 +- qutebrowser/browser/qtnetworkdownloads.py | 4 +- qutebrowser/browser/qutescheme.py | 12 ++-- qutebrowser/browser/shared.py | 10 +-- qutebrowser/browser/webelem.py | 6 +- qutebrowser/browser/webengine/darkmode.py | 12 ++-- qutebrowser/browser/webengine/notification.py | 16 ++--- .../browser/webengine/webengineelem.py | 8 +-- .../browser/webengine/webenginesettings.py | 6 +- qutebrowser/browser/webengine/webview.py | 4 +- qutebrowser/browser/webkit/httpheaders.py | 3 +- qutebrowser/browser/webkit/mhtml.py | 4 +- .../browser/webkit/network/networkmanager.py | 6 +- qutebrowser/browser/webkit/tabhistory.py | 4 +- qutebrowser/browser/webkit/webkitelem.py | 8 +-- qutebrowser/commands/cmdexc.py | 3 +- qutebrowser/commands/command.py | 8 +-- qutebrowser/commands/parser.py | 9 ++- qutebrowser/commands/runners.py | 6 +- qutebrowser/commands/userscripts.py | 4 +- .../completion/models/filepathcategory.py | 4 +- qutebrowser/completion/models/listcategory.py | 3 +- qutebrowser/completion/models/miscmodels.py | 5 +- qutebrowser/completion/models/urlmodel.py | 3 +- qutebrowser/components/hostblock.py | 10 +-- qutebrowser/components/scrollcommands.py | 4 +- qutebrowser/components/utils/blockutils.py | 6 +- qutebrowser/config/config.py | 12 ++-- qutebrowser/config/configcache.py | 4 +- qutebrowser/config/configcommands.py | 4 +- qutebrowser/config/configdata.py | 8 +-- qutebrowser/config/configexc.py | 4 +- qutebrowser/config/configfiles.py | 14 ++-- qutebrowser/config/configtypes.py | 67 +++++++++---------- qutebrowser/config/configutils.py | 8 +-- qutebrowser/config/qtargs.py | 8 +-- qutebrowser/config/stylesheet.py | 4 +- qutebrowser/config/websettings.py | 10 +-- qutebrowser/extensions/interceptors.py | 4 +- qutebrowser/extensions/loader.py | 8 +-- qutebrowser/keyinput/keyutils.py | 8 +-- qutebrowser/keyinput/macros.py | 8 +-- qutebrowser/keyinput/modeman.py | 4 +- qutebrowser/mainwindow/mainwindow.py | 6 +- qutebrowser/mainwindow/prompt.py | 4 +- qutebrowser/mainwindow/tabbedbrowser.py | 8 +-- qutebrowser/mainwindow/tabwidget.py | 4 +- qutebrowser/misc/backendproblem.py | 4 +- qutebrowser/misc/binparsing.py | 4 +- qutebrowser/misc/crashdialog.py | 3 +- qutebrowser/misc/crashsignal.py | 6 +- qutebrowser/misc/elf.py | 6 +- qutebrowser/misc/guiprocess.py | 4 +- qutebrowser/misc/nativeeventfilter.py | 4 +- qutebrowser/misc/objects.py | 6 +- qutebrowser/misc/pakjoy.py | 6 +- qutebrowser/misc/sql.py | 20 +++--- qutebrowser/qt/machinery.py | 4 +- qutebrowser/utils/debug.py | 24 +++---- qutebrowser/utils/docutils.py | 8 +-- qutebrowser/utils/jinja.py | 12 ++-- qutebrowser/utils/log.py | 10 +-- qutebrowser/utils/message.py | 4 +- qutebrowser/utils/qtutils.py | 5 +- qutebrowser/utils/resources.py | 6 +- qutebrowser/utils/standarddir.py | 4 +- qutebrowser/utils/urlmatch.py | 4 +- qutebrowser/utils/urlutils.py | 6 +- qutebrowser/utils/utils.py | 6 +- qutebrowser/utils/version.py | 16 ++--- scripts/dev/build_release.py | 20 +++--- scripts/dev/misc_checks.py | 8 +-- scripts/mkvenv.py | 12 ++-- tests/end2end/fixtures/notificationserver.py | 5 +- tests/end2end/test_dirbrowser.py | 5 +- tests/helpers/stubs.py | 4 +- tests/unit/browser/test_notification.py | 8 +-- tests/unit/browser/webengine/test_darkmode.py | 5 +- .../browser/webkit/network/test_filescheme.py | 5 +- tests/unit/components/test_braveadblock.py | 3 +- tests/unit/misc/test_ipc.py | 4 +- tests/unit/misc/test_split.py | 5 +- tests/unit/test_qt_machinery.py | 12 ++-- 92 files changed, 348 insertions(+), 363 deletions(-) diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py index 9d825cafd..7fb95a934 100644 --- a/qutebrowser/api/cmdutils.py +++ b/qutebrowser/api/cmdutils.py @@ -35,7 +35,7 @@ Possible values: import inspect -from typing import Any, Callable, Protocol, Optional, Dict, cast +from typing import Any, Callable, Protocol, Optional, cast from collections.abc import Iterable from qutebrowser.utils import qtutils @@ -102,7 +102,7 @@ class _CmdHandlerType(Protocol): Below, we cast the decorated function to _CmdHandlerType to make mypy aware of this. """ - qute_args: Optional[Dict[str, 'command.ArgInfo']] + qute_args: Optional[dict[str, 'command.ArgInfo']] def __call__(self, *args: Any, **kwargs: Any) -> Any: ... diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 8223d218f..66bd485fc 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -29,7 +29,7 @@ import tempfile import pathlib import datetime import argparse -from typing import Optional, List, Tuple +from typing import Optional from collections.abc import Iterable from qutebrowser.qt import machinery @@ -331,7 +331,7 @@ def _open_special_pages(args): tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') - pages: List[Tuple[str, bool, str]] = [ + pages: list[tuple[str, bool, str]] = [ # state, condition, URL ('quickstart-done', True, diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index ff7fc7c4e..f64b413d9 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -9,7 +9,7 @@ import pathlib import itertools import functools import dataclasses -from typing import (cast, TYPE_CHECKING, Any, Callable, List, Optional, Set, Type, Union, Tuple) +from typing import (cast, TYPE_CHECKING, Any, Callable, Optional, Union) from collections.abc import Iterable, Sequence from qutebrowser.qt import machinery @@ -60,7 +60,7 @@ def create(win_id: int, mode_manager = modeman.instance(win_id) if objects.backend == usertypes.Backend.QtWebEngine: from qutebrowser.browser.webengine import webenginetab - tab_class: Type[AbstractTab] = webenginetab.WebEngineTab + tab_class: type[AbstractTab] = webenginetab.WebEngineTab elif objects.backend == usertypes.Backend.QtWebKit: from qutebrowser.browser.webkit import webkittab tab_class = webkittab.WebKitTab @@ -142,7 +142,7 @@ class AbstractAction: """Attribute ``action`` of AbstractTab for Qt WebActions.""" - action_base: Type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] + action_base: type[Union['QWebPage.WebAction', 'QWebEnginePage.WebAction']] def __init__(self, tab: 'AbstractTab') -> None: self._widget = cast(_WidgetType, None) @@ -639,7 +639,7 @@ class AbstractScroller(QObject): def pos_px(self) -> QPoint: raise NotImplementedError - def pos_perc(self) -> Tuple[int, int]: + def pos_perc(self) -> tuple[int, int]: raise NotImplementedError def to_perc(self, x: float = None, y: float = None) -> None: @@ -765,10 +765,10 @@ class AbstractHistory: def _go_to_item(self, item: Any) -> None: raise NotImplementedError - def back_items(self) -> List[Any]: + def back_items(self) -> list[Any]: raise NotImplementedError - def forward_items(self) -> List[Any]: + def forward_items(self) -> list[Any]: raise NotImplementedError @@ -1018,7 +1018,7 @@ class AbstractTab(QWidget): # Note that we remember hosts here, without scheme/port: # QtWebEngine/Chromium also only remembers hostnames, and certificates are # for a given hostname anyways. - _insecure_hosts: Set[str] = set() + _insecure_hosts: set[str] = set() # Sub-APIs initialized by subclasses history: AbstractHistory diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 32533709c..5135f0fa6 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -7,7 +7,7 @@ import os.path import shlex import functools -from typing import cast, Callable, Dict, Union, Optional +from typing import cast, Callable, Union, Optional from qutebrowser.qt.widgets import QApplication, QTabBar from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery @@ -636,7 +636,7 @@ class CommandDispatcher: widget = self._current_widget() url = self._current_url() - handlers: Dict[str, Callable[..., QUrl]] = { + handlers: dict[str, Callable[..., QUrl]] = { 'prev': functools.partial(navigate.prevnext, prev=True), 'next': functools.partial(navigate.prevnext, prev=False), 'up': navigate.path_up, diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index d587b2b74..53cc823f0 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -13,7 +13,7 @@ import functools import pathlib import tempfile import enum -from typing import Any, Dict, IO, List, Optional, Union +from typing import Any, IO, Optional, Union from collections.abc import MutableSequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, @@ -448,7 +448,7 @@ class AbstractDownloadItem(QObject): UnsupportedAttribute, IO[bytes], None ] = UnsupportedAttribute() self.raw_headers: Union[ - UnsupportedAttribute, Dict[bytes, bytes] + UnsupportedAttribute, dict[bytes, bytes] ] = UnsupportedAttribute() self._filename: Optional[str] = None @@ -900,7 +900,7 @@ class AbstractDownloadManager(QObject): def __init__(self, parent=None): super().__init__(parent) - self.downloads: List[AbstractDownloadItem] = [] + self.downloads: list[AbstractDownloadItem] = [] self._update_timer = usertypes.Timer(self, 'download-update') self._update_timer.timeout.connect(self._update_gui) self._update_timer.setInterval(_REFRESH_INTERVAL) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 6103c7ab0..04bcd4cf7 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -5,7 +5,7 @@ """The ListView to display downloads in.""" import functools -from typing import Callable, Tuple, Union +from typing import Callable, Union from collections.abc import MutableSequence from qutebrowser.qt.core import pyqtSlot, QSize, Qt @@ -18,8 +18,8 @@ from qutebrowser.utils import qtutils, utils _ActionListType = MutableSequence[ Union[ - Tuple[None, None], # separator - Tuple[str, Callable[[], None]], + tuple[None, None], # separator + tuple[str, Callable[[], None]], ] ] diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index 00c645797..ab63046db 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -12,7 +12,7 @@ import functools import glob import textwrap import dataclasses -from typing import cast, List, Tuple, Optional +from typing import cast, Optional from collections.abc import Sequence from qutebrowser.qt.core import pyqtSignal, QObject, QUrl @@ -208,9 +208,9 @@ class MatchingScripts: """All userscripts registered to run on a particular url.""" url: QUrl - start: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) - end: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) - idle: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) + start: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) + end: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) + idle: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) @dataclasses.dataclass @@ -218,8 +218,8 @@ class LoadResults: """The results of loading all Greasemonkey scripts.""" - successful: List[GreasemonkeyScript] = dataclasses.field(default_factory=list) - errors: List[Tuple[str, str]] = dataclasses.field(default_factory=list) + successful: list[GreasemonkeyScript] = dataclasses.field(default_factory=list) + errors: list[tuple[str, str]] = dataclasses.field(default_factory=list) def successful_str(self) -> str: """Get a string with all successfully loaded scripts. @@ -295,10 +295,10 @@ class GreasemonkeyManager(QObject): def __init__(self, parent=None): super().__init__(parent) - self._run_start: List[GreasemonkeyScript] = [] - self._run_end: List[GreasemonkeyScript] = [] - self._run_idle: List[GreasemonkeyScript] = [] - self._in_progress_dls: List[downloads.AbstractDownloadItem] = [] + self._run_start: list[GreasemonkeyScript] = [] + self._run_end: list[GreasemonkeyScript] = [] + self._run_idle: list[GreasemonkeyScript] = [] + self._in_progress_dls: list[downloads.AbstractDownloadItem] = [] def load_scripts(self, *, force: bool = False) -> LoadResults: """Re-read Greasemonkey scripts from disk. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 64fe5072c..2cb0cdfb6 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -12,7 +12,7 @@ import html import enum import dataclasses from string import ascii_lowercase -from typing import (TYPE_CHECKING, Callable, Dict, List, Optional, Set) +from typing import (TYPE_CHECKING, Callable, Optional) from collections.abc import Iterable, Iterator, Mapping, MutableSequence, Sequence from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt, QUrl @@ -175,11 +175,11 @@ class HintContext: add_history: bool first: bool baseurl: QUrl - args: List[str] + args: list[str] group: str - all_labels: List[HintLabel] = dataclasses.field(default_factory=list) - labels: Dict[str, HintLabel] = dataclasses.field(default_factory=dict) + all_labels: list[HintLabel] = dataclasses.field(default_factory=list) + labels: dict[str, HintLabel] = dataclasses.field(default_factory=dict) to_follow: Optional[str] = None first_run: bool = True filterstr: Optional[str] = None @@ -1033,7 +1033,7 @@ class WordHinter: def __init__(self) -> None: # will be initialized on first use. - self.words: Set[str] = set() + self.words: set[str] = set() self.dictionary = None def ensure_initialized(self) -> None: @@ -1143,7 +1143,7 @@ class WordHinter: """ self.ensure_initialized() hints = [] - used_hints: Set[str] = set() + used_hints: set[str] = set() words = iter(self.words) for elem in elems: hint = self.new_hint_for(elem, used_hints, words) diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py index e75365bcd..956f222b4 100644 --- a/qutebrowser/browser/navigate.py +++ b/qutebrowser/browser/navigate.py @@ -6,7 +6,7 @@ import re import posixpath -from typing import Optional, Set +from typing import Optional from qutebrowser.qt.core import QUrl @@ -79,7 +79,7 @@ def incdec(url, count, inc_or_dec): inc_or_dec: Either 'increment' or 'decrement'. """ urlutils.ensure_valid(url) - segments: Optional[Set[str]] = ( + segments: Optional[set[str]] = ( set(config.val.url.incdec_segments) ) diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index 5b29c29fc..3e549dfb7 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -4,7 +4,7 @@ """Handling of proxies.""" -from typing import Optional, List +from typing import Optional from qutebrowser.qt.core import QUrl, pyqtSlot from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory, QNetworkProxyQuery @@ -71,7 +71,7 @@ class ProxyFactory(QNetworkProxyFactory): capabilities &= ~lookup_cap proxy.setCapabilities(capabilities) - def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> List[QNetworkProxy]: + def queryProxy(self, query: QNetworkProxyQuery = QNetworkProxyQuery()) -> list[QNetworkProxy]: """Get the QNetworkProxies for a query. Args: diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 63122208f..3d3c0475a 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -9,7 +9,7 @@ import os.path import shutil import functools import dataclasses -from typing import Dict, IO, Optional +from typing import IO, Optional from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QTimer, QUrl from qutebrowser.qt.widgets import QApplication @@ -73,7 +73,7 @@ class DownloadItem(downloads.AbstractDownloadItem): """ super().__init__(manager=manager, parent=manager) self.fileobj: Optional[IO[bytes]] = None - self.raw_headers: Dict[bytes, bytes] = {} + self.raw_headers: dict[bytes, bytes] = {} self._autoclose = True self._retry_info = None diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 472e8b637..98d059502 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -18,7 +18,7 @@ import textwrap import urllib import collections import secrets -from typing import TypeVar, Callable, Dict, List, Optional, Union, Tuple +from typing import TypeVar, Callable, Optional, Union from collections.abc import Sequence from qutebrowser.qt.core import QUrlQuery, QUrl @@ -36,7 +36,7 @@ pyeval_output = ":pyeval was never called" csrf_token: Optional[str] = None -_HANDLERS: Dict[str, "_HandlerCallable"] = {} +_HANDLERS: dict[str, "_HandlerCallable"] = {} class Error(Exception): @@ -78,7 +78,7 @@ class Redirect(Exception): # Return value: (mimetype, data) (encoded as utf-8 if a str is returned) -_HandlerRet = Tuple[str, Union[str, bytes]] +_HandlerRet = tuple[str, Union[str, bytes]] _HandlerCallable = Callable[[QUrl], _HandlerRet] _Handler = TypeVar('_Handler', bound=_HandlerCallable) @@ -106,7 +106,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name return self._function(url) -def data_for_url(url: QUrl) -> Tuple[str, bytes]: +def data_for_url(url: QUrl) -> tuple[str, bytes]: """Get the data to show for the given URL. Args: @@ -181,7 +181,7 @@ def qute_bookmarks(_url: QUrl) -> _HandlerRet: @add_handler('tabs') def qute_tabs(_url: QUrl) -> _HandlerRet: """Handler for qute://tabs. Display information about all open tabs.""" - tabs: Dict[str, List[Tuple[str, str]]] = collections.defaultdict(list) + tabs: dict[str, list[tuple[str, str]]] = collections.defaultdict(list) for win_id, window in objreg.window_registry.items(): if sip.isdeleted(window): continue @@ -202,7 +202,7 @@ def qute_tabs(_url: QUrl) -> _HandlerRet: def history_data( start_time: float, offset: int = None -) -> Sequence[Dict[str, Union[str, int]]]: +) -> Sequence[dict[str, Union[str, int]]]: """Return history data. Arguments: diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index acf8ad011..4d22a4525 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -10,7 +10,7 @@ import html import enum import netrc import tempfile -from typing import Callable, List, Optional +from typing import Callable, Optional from collections.abc import Mapping, Iterable, Iterator from qutebrowser.qt.core import QUrl, pyqtBoundSignal @@ -446,7 +446,7 @@ class FileSelectionMode(enum.Enum): folder = enum.auto() -def choose_file(qb_mode: FileSelectionMode) -> List[str]: +def choose_file(qb_mode: FileSelectionMode) -> list[str]: """Select file(s)/folder for up-/downloading, using an external command. Args: @@ -486,10 +486,10 @@ def choose_file(qb_mode: FileSelectionMode) -> List[str]: def _execute_fileselect_command( - command: List[str], + command: list[str], qb_mode: FileSelectionMode, tmpfilename: Optional[str] = None -) -> List[str]: +) -> list[str]: """Execute external command to choose file. Args: @@ -523,7 +523,7 @@ def _execute_fileselect_command( def _validated_selected_files( qb_mode: FileSelectionMode, - selected_files: List[str], + selected_files: list[str], ) -> Iterator[str]: """Validates selected files if they are. diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py index e496b6a12..82960cc8d 100644 --- a/qutebrowser/browser/webelem.py +++ b/qutebrowser/browser/webelem.py @@ -4,7 +4,7 @@ """Generic web element related code.""" -from typing import Optional, Set, TYPE_CHECKING, Union, Dict +from typing import Optional, TYPE_CHECKING, Union from collections.abc import Iterator import collections.abc @@ -94,7 +94,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a """Get the geometry for this element.""" raise NotImplementedError - def classes(self) -> Set[str]: + def classes(self) -> set[str]: """Get a set of classes assigned to this element.""" raise NotImplementedError @@ -337,7 +337,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a log.webelem.debug("Sending fake click to {!r} at position {} with " "target {}".format(self, pos, click_target)) - target_modifiers: Dict[usertypes.ClickTarget, KeyboardModifierType] = { + target_modifiers: dict[usertypes.ClickTarget, KeyboardModifierType] = { usertypes.ClickTarget.normal: Qt.KeyboardModifier.NoModifier, usertypes.ClickTarget.window: Qt.KeyboardModifier.AltModifier | Qt.KeyboardModifier.ShiftModifier, usertypes.ClickTarget.tab: Qt.KeyboardModifier.ControlModifier, diff --git a/qutebrowser/browser/webengine/darkmode.py b/qutebrowser/browser/webengine/darkmode.py index 8318c52da..88b71a8fe 100644 --- a/qutebrowser/browser/webengine/darkmode.py +++ b/qutebrowser/browser/webengine/darkmode.py @@ -125,7 +125,7 @@ import copy import enum import dataclasses import collections -from typing import (Any, Optional, Set, Tuple, Union, List) +from typing import (Any, Optional, Union) from collections.abc import Iterator, Mapping, MutableMapping, Sequence from qutebrowser.config import config @@ -212,7 +212,7 @@ class _Setting: return str(value) return str(self.mapping[value]) - def chromium_tuple(self, value: Any) -> Optional[Tuple[str, str]]: + def chromium_tuple(self, value: Any) -> Optional[tuple[str, str]]: """Get the Chromium key and value, or None if no value should be set.""" if self.mapping is not None and self.mapping[value] is None: return None @@ -242,7 +242,7 @@ class _Definition: def __init__( self, *args: _Setting, - mandatory: Set[str], + mandatory: set[str], prefix: str, switch_names: Mapping[Optional[str], str] = None, ) -> None: @@ -255,7 +255,7 @@ class _Definition: else: self._switch_names = {None: _BLINK_SETTINGS} - def prefixed_settings(self) -> Iterator[Tuple[str, _Setting]]: + def prefixed_settings(self) -> Iterator[tuple[str, _Setting]]: """Get all "prepared" settings. Yields tuples which contain the Chromium setting key (e.g. 'blink-settings' or @@ -399,7 +399,7 @@ def settings( *, versions: version.WebEngineVersions, special_flags: Sequence[str], -) -> Mapping[str, Sequence[Tuple[str, str]]]: +) -> Mapping[str, Sequence[tuple[str, str]]]: """Get necessary blink settings to configure dark mode for QtWebEngine. Args: @@ -413,7 +413,7 @@ def settings( variant = _variant(versions) log.init.debug(f"Darkmode variant: {variant.name}") - result: Mapping[str, List[Tuple[str, str]]] = collections.defaultdict(list) + result: Mapping[str, list[tuple[str, str]]] = collections.defaultdict(list) blink_settings_flag = f'--{_BLINK_SETTINGS}=' for flag in special_flags: diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 54e7aca7e..d49446065 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -33,7 +33,7 @@ import dataclasses import itertools import functools import subprocess -from typing import Any, List, Dict, Optional, Type, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING from collections.abc import Iterator from qutebrowser.qt import machinery @@ -196,7 +196,7 @@ class NotificationBridgePresenter(QObject): def __init__(self, parent: QObject = None) -> None: super().__init__(parent) - self._active_notifications: Dict[int, 'QWebEngineNotification'] = {} + self._active_notifications: dict[int, 'QWebEngineNotification'] = {} self._adapter: Optional[AbstractNotificationAdapter] = None config.instance.changed.connect(self._init_adapter) @@ -233,8 +233,8 @@ class NotificationBridgePresenter(QObject): def _get_adapter_candidates( self, setting: str, - ) -> List[Type[AbstractNotificationAdapter]]: - candidates: Dict[str, List[Type[AbstractNotificationAdapter]]] = { + ) -> list[type[AbstractNotificationAdapter]]: + candidates: dict[str, list[type[AbstractNotificationAdapter]]] = { "libnotify": [ DBusNotificationAdapter, SystrayNotificationAdapter, @@ -666,7 +666,7 @@ class _ServerCapabilities: kde_origin_name: bool @classmethod - def from_list(cls, capabilities: List[str]) -> "_ServerCapabilities": + def from_list(cls, capabilities: list[str]) -> "_ServerCapabilities": return cls( actions='actions' in capabilities, body_markup='body-markup' in capabilities, @@ -952,10 +952,10 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): qtutils.extract_enum_val(QMetaType.Type.QStringList), ) - def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> Dict[str, Any]: + def _get_hints_arg(self, *, origin_url: QUrl, icon: QImage) -> dict[str, Any]: """Get the hints argument for present().""" origin_url_str = origin_url.toDisplayString() - hints: Dict[str, Any] = { + hints: dict[str, Any] = { # Include the origin in case the user wants to do different things # with different origin's notifications. "x-qutebrowser-origin": origin_url_str, @@ -985,7 +985,7 @@ class DBusNotificationAdapter(AbstractNotificationAdapter): title: str, body: str, actions: QDBusArgument, - hints: Dict[str, Any], + hints: dict[str, Any], timeout: int, ) -> Any: """Wrapper around DBus call to use keyword args.""" diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index 7fc7e68af..b73459d3f 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -5,7 +5,7 @@ """QtWebEngine specific part of the web element API.""" from typing import ( - TYPE_CHECKING, Any, Callable, Dict, Optional, Set, Tuple, Union) + TYPE_CHECKING, Any, Callable, Optional, Union) from collections.abc import Iterator from qutebrowser.qt.core import QRect, QEventLoop @@ -25,11 +25,11 @@ class WebEngineElement(webelem.AbstractWebElement): _tab: "webenginetab.WebEngineTab" - def __init__(self, js_dict: Dict[str, Any], + def __init__(self, js_dict: dict[str, Any], tab: 'webenginetab.WebEngineTab') -> None: super().__init__(tab) # Do some sanity checks on the data we get from JS - js_dict_types: Dict[str, Union[type, Tuple[type, ...]]] = { + js_dict_types: dict[str, Union[type, tuple[type, ...]]] = { 'id': int, 'text': str, 'value': (str, int, float), @@ -106,7 +106,7 @@ class WebEngineElement(webelem.AbstractWebElement): log.stub() return QRect() - def classes(self) -> Set[str]: + def classes(self) -> set[str]: """Get a list of classes assigned to this element.""" return set(self._js_dict['class_name'].split()) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index a9dd3e5ef..b327a7b6b 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -12,7 +12,7 @@ Module attributes: import os import operator import pathlib -from typing import cast, Any, List, Optional, Tuple, Union, TYPE_CHECKING +from typing import cast, Any, Optional, Union, TYPE_CHECKING from qutebrowser.qt import machinery from qutebrowser.qt.gui import QFont @@ -509,7 +509,7 @@ def _init_default_settings(): - Make sure the devtools always get images/JS permissions. - On Qt 6, make sure files in the data path can load external resources. """ - devtools_settings: List[Tuple[str, Any]] = [ + devtools_settings: list[tuple[str, Any]] = [ ('content.javascript.enabled', True), ('content.images', True), ('content.cookies.accept', 'all'), @@ -522,7 +522,7 @@ def _init_default_settings(): hide_userconfig=True) if machinery.IS_QT6: - userscripts_settings: List[Tuple[str, Any]] = [ + userscripts_settings: list[tuple[str, Any]] = [ ("content.local_content_can_access_remote_urls", True), ("content.local_content_can_access_file_urls", False), ] diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 1d8e708a7..0b1d41599 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -5,7 +5,7 @@ """The main browser widget for QtWebEngine.""" import mimetypes -from typing import List, Optional +from typing import Optional from collections.abc import Iterable from qutebrowser.qt import machinery @@ -317,7 +317,7 @@ class WebEnginePage(QWebEnginePage): mode: QWebEnginePage.FileSelectionMode, old_files: Iterable[Optional[str]], accepted_mimetypes: Iterable[Optional[str]], - ) -> List[str]: + ) -> list[str]: """Override chooseFiles to (optionally) invoke custom file uploader.""" accepted_mimetypes_filtered = [m for m in accepted_mimetypes if m is not None] old_files_filtered = [f for f in old_files if f is not None] diff --git a/qutebrowser/browser/webkit/httpheaders.py b/qutebrowser/browser/webkit/httpheaders.py index 95b7b7104..5c22405e0 100644 --- a/qutebrowser/browser/webkit/httpheaders.py +++ b/qutebrowser/browser/webkit/httpheaders.py @@ -8,7 +8,6 @@ import email.headerregistry import email.errors import dataclasses import os.path -from typing import Type from qutebrowser.qt.network import QNetworkRequest @@ -25,7 +24,7 @@ class DefectWrapper: """Wrapper around a email.error for comparison.""" - error_class: Type[email.errors.MessageDefect] + error_class: type[email.errors.MessageDefect] line: str def __eq__(self, other): diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 61272aa12..98a715edc 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -19,7 +19,7 @@ import email.mime.multipart import email.message import quopri import dataclasses -from typing import Set, Tuple, Callable +from typing import Callable from collections.abc import MutableMapping from qutebrowser.qt.core import QUrl @@ -178,7 +178,7 @@ class MHTMLWriter: return msg -_PendingDownloadType = Set[Tuple[QUrl, downloads.AbstractDownloadItem]] +_PendingDownloadType = set[tuple[QUrl, downloads.AbstractDownloadItem]] class _Downloader: diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index 9b5ca915c..a950d4239 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -7,7 +7,7 @@ import collections import html import dataclasses -from typing import TYPE_CHECKING, Dict, Optional, Set +from typing import TYPE_CHECKING, Optional from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSlot, pyqtSignal, QUrl, QByteArray @@ -30,7 +30,7 @@ if TYPE_CHECKING: HOSTBLOCK_ERROR_STRING = '%HOSTBLOCK%' -_proxy_auth_cache: Dict['ProxyId', 'prompt.AuthInfo'] = {} +_proxy_auth_cache: dict['ProxyId', 'prompt.AuthInfo'] = {} @dataclasses.dataclass(frozen=True) @@ -111,7 +111,7 @@ def init(): _SavedErrorsType = MutableMapping[ urlutils.HostTupleType, - Set[certificateerror.CertificateErrorWrapper], + set[certificateerror.CertificateErrorWrapper], ] diff --git a/qutebrowser/browser/webkit/tabhistory.py b/qutebrowser/browser/webkit/tabhistory.py index b21c77402..458f493d1 100644 --- a/qutebrowser/browser/webkit/tabhistory.py +++ b/qutebrowser/browser/webkit/tabhistory.py @@ -4,7 +4,7 @@ """Utilities related to QWebHistory.""" -from typing import Any, List +from typing import Any from collections.abc import Mapping from qutebrowser.qt.core import QByteArray, QDataStream, QIODevice, QUrl @@ -67,7 +67,7 @@ def serialize(items): """ data = QByteArray() stream = QDataStream(data, QIODevice.OpenModeFlag.ReadWrite) - user_data: List[Mapping[str, Any]] = [] + user_data: list[Mapping[str, Any]] = [] current_idx = None diff --git a/qutebrowser/browser/webkit/webkitelem.py b/qutebrowser/browser/webkit/webkitelem.py index de1e7758f..6088a29d3 100644 --- a/qutebrowser/browser/webkit/webkitelem.py +++ b/qutebrowser/browser/webkit/webkitelem.py @@ -4,7 +4,7 @@ """QtWebKit specific part of the web element API.""" -from typing import cast, TYPE_CHECKING, List, Optional, Set +from typing import cast, TYPE_CHECKING, Optional from collections.abc import Iterator from qutebrowser.qt.core import QRect, Qt @@ -91,7 +91,7 @@ class WebKitElement(webelem.AbstractWebElement): self._check_vanished() return self._elem.geometry() - def classes(self) -> Set[str]: + def classes(self) -> set[str]: self._check_vanished() return set(self._elem.classes()) @@ -365,7 +365,7 @@ class WebKitElement(webelem.AbstractWebElement): super()._click_fake_event(click_target) -def get_child_frames(startframe: QWebFrame) -> List[QWebFrame]: +def get_child_frames(startframe: QWebFrame) -> list[QWebFrame]: """Get all children recursively of a given QWebFrame. Loosely based on https://blog.nextgenetics.net/?e=64 @@ -379,7 +379,7 @@ def get_child_frames(startframe: QWebFrame) -> List[QWebFrame]: results = [] frames = [startframe] while frames: - new_frames: List[QWebFrame] = [] + new_frames: list[QWebFrame] = [] for frame in frames: results.append(frame) new_frames += frame.childFrames() diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py index 4335a10e6..9bb5decc3 100644 --- a/qutebrowser/commands/cmdexc.py +++ b/qutebrowser/commands/cmdexc.py @@ -7,7 +7,6 @@ Defined here to avoid circular dependency hell. """ -from typing import List import difflib @@ -21,7 +20,7 @@ class NoSuchCommandError(Error): """Raised when a command isn't found.""" @classmethod - def for_cmd(cls, cmd: str, all_commands: List[str] = None) -> "NoSuchCommandError": + def for_cmd(cls, cmd: str, all_commands: list[str] = None) -> "NoSuchCommandError": """Raise an exception for the given command.""" suffix = '' if all_commands: diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index 350da0357..eb985dca0 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -9,7 +9,7 @@ import collections import traceback import typing import dataclasses -from typing import (Any, Tuple, Union, List, Optional, +from typing import (Any, Union, Optional, Callable) from collections.abc import MutableMapping, MutableSequence @@ -31,7 +31,7 @@ class ArgInfo: metavar: Optional[str] = None flag: Optional[str] = None completion: Optional[Callable[..., completionmodel.CompletionModel]] = None - choices: Optional[List[str]] = None + choices: Optional[list[str]] = None class Command: @@ -106,10 +106,10 @@ class Command: self.parser.add_argument('-h', '--help', action=argparser.HelpAction, default=argparser.SUPPRESS, nargs=0, help=argparser.SUPPRESS) - self.opt_args: MutableMapping[str, Tuple[str, str]] = collections.OrderedDict() + self.opt_args: MutableMapping[str, tuple[str, str]] = collections.OrderedDict() self.namespace = None self._count = None - self.pos_args: MutableSequence[Tuple[str, str]] = [] + self.pos_args: MutableSequence[tuple[str, str]] = [] self.flags_with_args: MutableSequence[str] = [] self._has_vararg = False diff --git a/qutebrowser/commands/parser.py b/qutebrowser/commands/parser.py index a0f74a2ec..00e5c9083 100644 --- a/qutebrowser/commands/parser.py +++ b/qutebrowser/commands/parser.py @@ -5,7 +5,6 @@ """Module for parsing commands entered into the browser.""" import dataclasses -from typing import List from collections.abc import Iterator from qutebrowser.commands import cmdexc, command @@ -19,8 +18,8 @@ class ParseResult: """The result of parsing a commandline.""" cmd: command.Command - args: List[str] - cmdline: List[str] + args: list[str] + cmdline: list[str] class CommandParser: @@ -108,7 +107,7 @@ class CommandParser: for sub in sub_texts: yield self.parse(sub, **kwargs) - def parse_all(self, text: str, **kwargs: bool) -> List[ParseResult]: + def parse_all(self, text: str, **kwargs: bool) -> list[ParseResult]: """Wrapper over _parse_all_gen.""" return list(self._parse_all_gen(text, **kwargs)) @@ -162,7 +161,7 @@ class CommandParser: cmdstr = matches[0] return cmdstr - def _split_args(self, cmd: command.Command, argstr: str, keep: bool) -> List[str]: + def _split_args(self, cmd: command.Command, argstr: str, keep: bool) -> list[str]: """Split the arguments from an arg string. Args: diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 22ae2ae4e..f12f795f2 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -7,7 +7,7 @@ import traceback import re import contextlib -from typing import TYPE_CHECKING, Callable, Dict, Tuple +from typing import TYPE_CHECKING, Callable from collections.abc import Iterator, Mapping, MutableMapping from qutebrowser.qt.core import pyqtSlot, QUrl, QObject @@ -22,7 +22,7 @@ if TYPE_CHECKING: _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str] -last_command: Dict[usertypes.KeyMode, Tuple[str, int]] = {} +last_command: dict[usertypes.KeyMode, tuple[str, int]] = {} def _url(tabbed_browser): @@ -39,7 +39,7 @@ def _url(tabbed_browser): def _init_variable_replacements() -> Mapping[str, _ReplacementFunction]: """Return a dict from variable replacements to fns processing them.""" - replacements: Dict[str, _ReplacementFunction] = { + replacements: dict[str, _ReplacementFunction] = { 'url': lambda tb: _url(tb).toString( QUrl.ComponentFormattingOption.FullyEncoded | QUrl.UrlFormattingOption.RemovePassword), 'url:pretty': lambda tb: _url(tb).toString( diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index dd4db66dc..e2f24d28d 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -7,7 +7,7 @@ import os import os.path import tempfile -from typing import cast, Any, Tuple +from typing import cast, Any from collections.abc import MutableMapping from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, QSocketNotifier @@ -107,7 +107,7 @@ class _BaseUserscriptRunner(QObject): self._env: MutableMapping[str, str] = {} self._text_stored = False self._html_stored = False - self._args: Tuple[Any, ...] = () + self._args: tuple[Any, ...] = () self._kwargs = {} def store_text(self, text): diff --git a/qutebrowser/completion/models/filepathcategory.py b/qutebrowser/completion/models/filepathcategory.py index 43b0725a0..0b2d887b8 100644 --- a/qutebrowser/completion/models/filepathcategory.py +++ b/qutebrowser/completion/models/filepathcategory.py @@ -14,7 +14,7 @@ is harder to achieve via pathlib. import glob import os import os.path -from typing import List, Optional +from typing import Optional from collections.abc import Iterable from qutebrowser.qt.core import QAbstractListModel, QModelIndex, QObject, Qt, QUrl @@ -29,7 +29,7 @@ class FilePathCategory(QAbstractListModel, BaseCategory): def __init__(self, name: str, parent: QObject = None) -> None: super().__init__(parent) - self._paths: List[str] = [] + self._paths: list[str] = [] self.name = name self.columns_to_filter = [0] diff --git a/qutebrowser/completion/models/listcategory.py b/qutebrowser/completion/models/listcategory.py index 6dbfd978e..088f93791 100644 --- a/qutebrowser/completion/models/listcategory.py +++ b/qutebrowser/completion/models/listcategory.py @@ -5,7 +5,6 @@ """Completion category that uses a list of tuples as a data source.""" import re -from typing import Tuple from collections.abc import Iterable from qutebrowser.qt.core import QSortFilterProxyModel, QRegularExpression @@ -22,7 +21,7 @@ class ListCategory(QSortFilterProxyModel, BaseCategory): def __init__(self, name: str, - items: Iterable[Tuple[str, ...]], + items: Iterable[tuple[str, ...]], sort: bool = True, delete_func: util.DeleteFuncType = None, parent: QWidget = None): diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 0301339b8..da7c65094 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -6,7 +6,6 @@ import datetime import itertools -from typing import List, Tuple from collections.abc import Sequence from qutebrowser.config import config, configdata @@ -114,7 +113,7 @@ def _tabs(*, win_id_filter=lambda _win_id: True, add_win_id=True, cur_win_id=Non tabs_are_windows = config.val.tabs.tabs_are_windows # list storing all single-tabbed windows when tabs_are_windows - windows: List[Tuple[str, str, str, str]] = [] + windows: list[tuple[str, str, str, str]] = [] for win_id in objreg.window_registry: if not win_id_filter(win_id): @@ -124,7 +123,7 @@ def _tabs(*, win_id_filter=lambda _win_id: True, add_win_id=True, cur_win_id=Non window=win_id) if tabbed_browser.is_shutting_down: continue - tab_entries: List[Tuple[str, str, str, str]] = [] + tab_entries: list[tuple[str, str, str, str]] = [] for idx in range(tabbed_browser.widget.count()): tab = tabbed_browser.widget.widget(idx) tab_str = ("{}/{}".format(win_id, idx + 1) if add_win_id diff --git a/qutebrowser/completion/models/urlmodel.py b/qutebrowser/completion/models/urlmodel.py index f2a57623e..7532428f1 100644 --- a/qutebrowser/completion/models/urlmodel.py +++ b/qutebrowser/completion/models/urlmodel.py @@ -4,7 +4,6 @@ """Function to return the url completion model for the `open` command.""" -from typing import Dict from collections.abc import Sequence from qutebrowser.completion.models import (completionmodel, filepathcategory, @@ -59,7 +58,7 @@ def url(*, info): in sorted(config.val.url.searchengines.items()) if k != 'DEFAULT'] categories = config.val.completion.open_categories - models: Dict[str, BaseCategory] = {} + models: dict[str, BaseCategory] = {} if searchengines and 'searchengines' in categories: models['searchengines'] = listcategory.ListCategory( diff --git a/qutebrowser/components/hostblock.py b/qutebrowser/components/hostblock.py index 672a530df..7777e1429 100644 --- a/qutebrowser/components/hostblock.py +++ b/qutebrowser/components/hostblock.py @@ -9,7 +9,7 @@ import posixpath import zipfile import logging import pathlib -from typing import cast, IO, Set +from typing import cast, IO from qutebrowser.qt.core import QUrl @@ -92,8 +92,8 @@ class HostBlocker: ) -> None: self.enabled = _should_be_used() self._has_basedir = has_basedir - self._blocked_hosts: Set[str] = set() - self._config_blocked_hosts: Set[str] = set() + self._blocked_hosts: set[str] = set() + self._config_blocked_hosts: set[str] = set() self._local_hosts_file = str(data_dir / "blocked-hosts") self.update_files() @@ -139,7 +139,7 @@ class HostBlocker: ) info.block() - def _read_hosts_line(self, raw_line: bytes) -> Set[str]: + def _read_hosts_line(self, raw_line: bytes) -> set[str]: """Read hosts from the given line. Args: @@ -175,7 +175,7 @@ class HostBlocker: return filtered_hosts - def _read_hosts_file(self, filename: str, target: Set[str]) -> bool: + def _read_hosts_file(self, filename: str, target: set[str]) -> bool: """Read hosts from the given filename. Args: diff --git a/qutebrowser/components/scrollcommands.py b/qutebrowser/components/scrollcommands.py index 3ee525535..219f884a0 100644 --- a/qutebrowser/components/scrollcommands.py +++ b/qutebrowser/components/scrollcommands.py @@ -4,7 +4,7 @@ """Scrolling-related commands.""" -from typing import Dict, Callable +from typing import Callable from qutebrowser.api import cmdutils, apitypes @@ -41,7 +41,7 @@ def scroll(tab: apitypes.Tab, direction: str, count: int = 1) -> None: count: multiplier """ # FIXME:mypy Use a callback protocol to enforce having 'count'? - funcs: Dict[str, Callable[..., None]] = { + funcs: dict[str, Callable[..., None]] = { 'up': tab.scroller.up, 'down': tab.scroller.down, 'left': tab.scroller.left, diff --git a/qutebrowser/components/utils/blockutils.py b/qutebrowser/components/utils/blockutils.py index a65085949..154c04317 100644 --- a/qutebrowser/components/utils/blockutils.py +++ b/qutebrowser/components/utils/blockutils.py @@ -6,7 +6,7 @@ import os import functools -from typing import IO, List, Optional +from typing import IO, Optional from qutebrowser.qt.core import QUrl, QObject, pyqtSignal @@ -47,11 +47,11 @@ class BlocklistDownloads(QObject): single_download_finished = pyqtSignal(object) # arg: the file object all_downloads_finished = pyqtSignal(int) # arg: download count - def __init__(self, urls: List[QUrl], parent: Optional[QObject] = None) -> None: + def __init__(self, urls: list[QUrl], parent: Optional[QObject] = None) -> None: super().__init__(parent) self._urls = urls - self._in_progress: List[downloads.TempDownload] = [] + self._in_progress: list[downloads.TempDownload] = [] self._done_count = 0 self._finished_registering_downloads = False self._started = False diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index d43b71136..b24c9b502 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -7,7 +7,7 @@ import copy import contextlib import functools -from typing import (TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, cast) +from typing import (TYPE_CHECKING, Any, Callable, Optional, cast) from collections.abc import Iterator, Mapping, MutableMapping, MutableSequence from qutebrowser.qt.core import pyqtSignal, QObject, QUrl @@ -29,7 +29,7 @@ key_instance = cast('KeyConfig', None) cache = cast('configcache.ConfigCache', None) # Keeping track of all change filters to validate them later. -change_filters: List["change_filter"] = [] +change_filters: list["change_filter"] = [] # Sentinel UNSET = object() @@ -131,7 +131,7 @@ class KeyConfig: _config: The Config object to be used. """ - _ReverseBindings = Dict[str, MutableSequence[str]] + _ReverseBindings = dict[str, MutableSequence[str]] def __init__(self, config: 'Config') -> None: self._config = config @@ -143,7 +143,7 @@ class KeyConfig: if mode not in configdata.DATA['bindings.default'].default: raise configexc.KeybindingError("Invalid mode {}!".format(mode)) - def get_bindings_for(self, mode: str) -> Dict[keyutils.KeySequence, str]: + def get_bindings_for(self, mode: str) -> dict[keyutils.KeySequence, str]: """Get the combined bindings for the given mode.""" bindings = dict(val.bindings.default[mode]) for key, binding in val.bindings.commands[mode].items(): @@ -291,7 +291,7 @@ class Config(QObject): yaml_config: 'configfiles.YamlConfig', parent: QObject = None) -> None: super().__init__(parent) - self._mutables: MutableMapping[str, Tuple[Any, Any]] = {} + self._mutables: MutableMapping[str, tuple[Any, Any]] = {} self._yaml = yaml_config self._init_values() self.yaml_loaded = False @@ -554,7 +554,7 @@ class Config(QObject): Return: The changed config part as string. """ - lines: List[str] = [] + lines: list[str] = [] for values in sorted(self, key=lambda v: v.opt.name): lines += values.dump(include_hidden=include_hidden) diff --git a/qutebrowser/config/configcache.py b/qutebrowser/config/configcache.py index 9e76466d9..13ddce227 100644 --- a/qutebrowser/config/configcache.py +++ b/qutebrowser/config/configcache.py @@ -4,7 +4,7 @@ """Implementation of a basic config cache.""" -from typing import Any, Dict +from typing import Any from qutebrowser.config import config @@ -22,7 +22,7 @@ class ConfigCache: """ def __init__(self) -> None: - self._cache: Dict[str, Any] = {} + self._cache: dict[str, Any] = {} config.instance.changed.connect(self._on_config_changed) def _on_config_changed(self, attr: str) -> None: diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index d581989f8..9012cc2c4 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -6,7 +6,7 @@ import os.path import contextlib -from typing import TYPE_CHECKING, List, Optional, Any, Tuple +from typing import TYPE_CHECKING, Optional, Any from collections.abc import Iterator from qutebrowser.qt.core import QUrl, QUrlQuery @@ -474,7 +474,7 @@ class ConfigCommands: raise cmdutils.CommandError("{} already exists - use --force to " "overwrite!".format(filename)) - options: List[Tuple[Optional[urlmatch.UrlPattern], configdata.Option, Any]] = [] + options: list[tuple[Optional[urlmatch.UrlPattern], configdata.Option, Any]] = [] if defaults: options = [(None, opt, opt.default) for _name, opt in sorted(configdata.DATA.items())] diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index a88543fc6..d939f7ea6 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -9,7 +9,7 @@ Module attributes: DATA: A dict of Option objects after init() has been called. """ -from typing import (Any, Dict, List, Optional, Tuple, Union, NoReturn, cast) +from typing import (Any, Optional, Union, NoReturn, cast) from collections.abc import Iterable, Mapping, MutableMapping, Sequence import functools import dataclasses @@ -53,8 +53,8 @@ class Migrations: deleted: A list of option names which have been removed. """ - renamed: Dict[str, str] = dataclasses.field(default_factory=dict) - deleted: List[str] = dataclasses.field(default_factory=list) + renamed: dict[str, str] = dataclasses.field(default_factory=dict) + deleted: list[str] = dataclasses.field(default_factory=list) def _raise_invalid_node(name: str, what: str, node: Any) -> NoReturn: @@ -186,7 +186,7 @@ def _parse_yaml_backends( def _read_yaml( yaml_data: str, -) -> Tuple[Mapping[str, Option], Migrations]: +) -> tuple[Mapping[str, Option], Migrations]: """Read config data from a YAML file. Args: diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 4cce7412f..85845f6fc 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -6,7 +6,7 @@ import difflib import dataclasses -from typing import Any, Optional, Union, List +from typing import Any, Optional, Union from collections.abc import Mapping, Sequence from qutebrowser.utils import usertypes, log @@ -78,7 +78,7 @@ class NoOptionError(Error): """Raised when an option was not found.""" def __init__(self, option: str, *, - all_names: List[str] = None, + all_names: list[str] = None, deleted: bool = False, renamed: str = None) -> None: if deleted: diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index e4d852862..4dc5a9373 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -14,7 +14,7 @@ import traceback import configparser import contextlib import re -from typing import (TYPE_CHECKING, Any, Dict, List, Optional, Tuple, cast) +from typing import (TYPE_CHECKING, Any, Optional, cast) from collections.abc import Iterable, Iterator, Mapping, MutableMapping import yaml @@ -34,7 +34,7 @@ if TYPE_CHECKING: state = cast('StateConfig', None) -_SettingsType = Dict[str, Dict[str, Any]] +_SettingsType = dict[str, dict[str, Any]] class VersionChange(enum.Enum): @@ -55,7 +55,7 @@ class VersionChange(enum.Enum): This is intended to use filters like "major" (show major only), "minor" (show major/minor) or "patch" (show all changes). """ - allowed_values: Dict[str, List[VersionChange]] = { + allowed_values: dict[str, list[VersionChange]] = { 'major': [VersionChange.major], 'minor': [VersionChange.major, VersionChange.minor], 'patch': [VersionChange.major, VersionChange.minor, VersionChange.patch], @@ -250,7 +250,7 @@ class YamlConfig(QObject): 'autoconfig.yml') self._dirty = False - self._values: Dict[str, configutils.Values] = {} + self._values: dict[str, configutils.Values] = {} for name, opt in configdata.DATA.items(): self._values[name] = configutils.Values(opt) @@ -702,7 +702,7 @@ class ConfigAPI: ): self._config = conf self._keyconfig = keyconfig - self.errors: List[configexc.ConfigErrorDesc] = [] + self.errors: list[configexc.ConfigErrorDesc] = [] self.configdir = pathlib.Path(standarddir.config()) self.datadir = pathlib.Path(standarddir.data()) self._warn_autoconfig = warn_autoconfig @@ -803,8 +803,8 @@ class ConfigPyWriter: def __init__( self, - options: List[ - Tuple[ + options: list[ + tuple[ Optional[urlmatch.UrlPattern], configdata.Option, Any diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 7d4980097..a74675cd4 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -36,8 +36,7 @@ import functools import operator import json import dataclasses -from typing import (Any, Callable, Dict as DictType, - List as ListType, Optional, Tuple, Union) +from typing import (Any, Callable, Optional, Union) from re import Pattern from collections.abc import Iterable, Iterator, Sequence @@ -67,7 +66,7 @@ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True, '0': False, 'no': False, 'false': False, 'off': False} -_Completions = Optional[Iterable[Tuple[str, str]]] +_Completions = Optional[Iterable[tuple[str, str]]] _StrUnset = Union[str, usertypes.Unset] _UnsetNone = Union[None, usertypes.Unset] _StrUnsetNone = Union[str, _UnsetNone] @@ -104,16 +103,16 @@ class ValidValues: self, *values: Union[ str, - DictType[str, Optional[str]], - Tuple[str, Optional[str]], + dict[str, Optional[str]], + tuple[str, Optional[str]], ], generate_docs: bool = True, others_permitted: bool = False ) -> None: if not values: raise ValueError("ValidValues with no values makes no sense!") - self.descriptions: DictType[str, str] = {} - self.values: ListType[str] = [] + self.descriptions: dict[str, str] = {} + self.values: list[str] = [] self.generate_docs = generate_docs self.others_permitted = others_permitted for value in values: @@ -180,7 +179,7 @@ class BaseType: def _basic_py_validation( self, value: Any, - pytype: Union[type, Tuple[type, ...]]) -> None: + pytype: Union[type, tuple[type, ...]]) -> None: """Do some basic validation for Python values (emptyness, type). Arguments: @@ -346,7 +345,7 @@ class MappingType(BaseType): MAPPING: A mapping from config values to (translated_value, docs) tuples. """ - MAPPING: DictType[str, Tuple[Any, Optional[str]]] = {} + MAPPING: dict[str, tuple[Any, Optional[str]]] = {} def __init__( self, *, @@ -498,7 +497,7 @@ class List(BaseType): def get_valid_values(self) -> Optional[ValidValues]: return self.valtype.get_valid_values() - def from_str(self, value: str) -> Optional[ListType]: + def from_str(self, value: str) -> Optional[list]: self._basic_str_validation(value) if not value: return None @@ -513,15 +512,15 @@ class List(BaseType): self.to_py(yaml_val) return yaml_val - def from_obj(self, value: Optional[ListType]) -> ListType: + def from_obj(self, value: Optional[list]) -> list: if value is None: return [] return [self.valtype.from_obj(v) for v in value] def to_py( self, - value: Union[ListType, usertypes.Unset] - ) -> Union[ListType, usertypes.Unset]: + value: Union[list, usertypes.Unset] + ) -> Union[list, usertypes.Unset]: self._basic_py_validation(value, list) if isinstance(value, usertypes.Unset): return value @@ -536,13 +535,13 @@ class List(BaseType): "be set!".format(self.length)) return [self.valtype.to_py(v) for v in value] - def to_str(self, value: ListType) -> str: + def to_str(self, value: list) -> str: if not value: # An empty list is treated just like None -> empty string return '' return json.dumps(value) - def to_doc(self, value: ListType, indent: int = 0) -> str: + def to_doc(self, value: list, indent: int = 0) -> str: if not value: return 'empty' @@ -587,7 +586,7 @@ class ListOrValue(BaseType): self.listtype = List(valtype=valtype, none_ok=none_ok, **kwargs) self.valtype = valtype - def _val_and_type(self, value: Any) -> Tuple[Any, BaseType]: + def _val_and_type(self, value: Any) -> tuple[Any, BaseType]: """Get the value and type to use for to_str/to_doc/from_str.""" if isinstance(value, list): if len(value) == 1: @@ -668,15 +667,15 @@ class FlagList(List): ) self.valtype.valid_values = valid_values - def _check_duplicates(self, values: ListType) -> None: + def _check_duplicates(self, values: list) -> None: if len(set(values)) != len(values): raise configexc.ValidationError( values, "List contains duplicate values!") def to_py( self, - value: Union[usertypes.Unset, ListType], - ) -> Union[usertypes.Unset, ListType]: + value: Union[usertypes.Unset, list], + ) -> Union[usertypes.Unset, list]: vals = super().to_py(value) if not isinstance(vals, usertypes.Unset): self._check_duplicates(vals) @@ -1112,7 +1111,7 @@ class QtColor(BaseType): kind = value[:openparen] vals = value[openparen+1:-1].split(',') - converters: DictType[str, Callable[..., QColor]] = { + converters: dict[str, Callable[..., QColor]] = { 'rgba': QColor.fromRgb, 'rgb': QColor.fromRgb, 'hsva': QColor.fromHsv, @@ -1202,7 +1201,7 @@ class FontBase(BaseType): (?P.+) # mandatory font family""", re.VERBOSE) @classmethod - def set_defaults(cls, default_family: ListType[str], default_size: str) -> None: + def set_defaults(cls, default_family: list[str], default_size: str) -> None: """Make sure default_family/default_size are available. If the given family value (fonts.default_family in the config) is @@ -1375,7 +1374,7 @@ class Dict(BaseType): self.fixed_keys = fixed_keys self.required_keys = required_keys - def _validate_keys(self, value: DictType) -> None: + def _validate_keys(self, value: dict) -> None: if (self.fixed_keys is not None and not set(value.keys()).issubset(self.fixed_keys)): raise configexc.ValidationError( @@ -1386,7 +1385,7 @@ class Dict(BaseType): raise configexc.ValidationError( value, "Required keys {}".format(self.required_keys)) - def from_str(self, value: str) -> Optional[DictType]: + def from_str(self, value: str) -> Optional[dict]: self._basic_str_validation(value) if not value: return None @@ -1401,14 +1400,14 @@ class Dict(BaseType): self.to_py(yaml_val) return yaml_val - def from_obj(self, value: Optional[DictType]) -> DictType: + def from_obj(self, value: Optional[dict]) -> dict: if value is None: return {} return {self.keytype.from_obj(key): self.valtype.from_obj(val) for key, val in value.items()} - def _fill_fixed_keys(self, value: DictType) -> DictType: + def _fill_fixed_keys(self, value: dict) -> dict: """Fill missing fixed keys with a None-value.""" if self.fixed_keys is None: return value @@ -1419,8 +1418,8 @@ class Dict(BaseType): def to_py( self, - value: Union[DictType, _UnsetNone] - ) -> Union[DictType, usertypes.Unset]: + value: Union[dict, _UnsetNone] + ) -> Union[dict, usertypes.Unset]: self._basic_py_validation(value, dict) if isinstance(value, usertypes.Unset): return value @@ -1436,13 +1435,13 @@ class Dict(BaseType): for key, val in value.items()} return self._fill_fixed_keys(d) - def to_str(self, value: DictType) -> str: + def to_str(self, value: dict) -> str: if not value: # An empty Dict is treated just like None -> empty string return '' return json.dumps(value, sort_keys=True) - def to_doc(self, value: DictType, indent: int = 0) -> str: + def to_doc(self, value: dict, indent: int = 0) -> str: if not value: return 'empty' lines = ['\n'] @@ -1596,8 +1595,8 @@ class ShellCommand(List): def to_py( self, - value: Union[ListType, usertypes.Unset], - ) -> Union[ListType, usertypes.Unset]: + value: Union[list, usertypes.Unset], + ) -> Union[list, usertypes.Unset]: py_value = super().to_py(value) if isinstance(py_value, usertypes.Unset): return py_value @@ -1754,7 +1753,7 @@ class Padding(Dict): def to_py( # type: ignore[override] self, - value: Union[DictType, _UnsetNone], + value: Union[dict, _UnsetNone], ) -> Union[usertypes.Unset, PaddingValues]: d = super().to_py(value) if isinstance(d, usertypes.Unset): @@ -1907,8 +1906,8 @@ class ConfirmQuit(FlagList): def to_py( self, - value: Union[usertypes.Unset, ListType], - ) -> Union[ListType, usertypes.Unset]: + value: Union[usertypes.Unset, list], + ) -> Union[list, usertypes.Unset]: values = super().to_py(value) if isinstance(values, usertypes.Unset): return values diff --git a/qutebrowser/config/configutils.py b/qutebrowser/config/configutils.py index a53fce7ec..2aaef7a97 100644 --- a/qutebrowser/config/configutils.py +++ b/qutebrowser/config/configutils.py @@ -9,7 +9,7 @@ import collections import itertools import operator from typing import ( - TYPE_CHECKING, Any, Dict, List, Optional, Set, Union) + TYPE_CHECKING, Any, Optional, Union) from collections.abc import Iterator, Sequence, MutableMapping from qutebrowser.qt.core import QUrl @@ -78,8 +78,8 @@ class Values: self._vmap: MutableMapping[ Values._VmapKeyType, ScopedValue] = collections.OrderedDict() # A map from domain parts to rules that fall under them. - self._domain_map: Dict[ - Optional[str], Set[ScopedValue]] = collections.defaultdict(set) + self._domain_map: dict[ + Optional[str], set[ScopedValue]] = collections.defaultdict(set) for scoped in values: self._add_scoped(scoped) @@ -203,7 +203,7 @@ class Values: return self._get_fallback(fallback) qtutils.ensure_valid(url) - candidates: List[ScopedValue] = [] + candidates: list[ScopedValue] = [] # Urls trailing with '.' are equivalent to non-trailing types. # urlutils strips them, so in order to match we will need to as well. widened_hosts = urlutils.widened_hostnames(url.host().rstrip('.')) diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index 665848232..994af4619 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -8,7 +8,7 @@ import os import sys import argparse import pathlib -from typing import Any, Dict, List, Optional, Tuple, Union, Callable +from typing import Any, Optional, Union, Callable from collections.abc import Iterator, Sequence from qutebrowser.qt import machinery @@ -24,7 +24,7 @@ _DISABLE_FEATURES = '--disable-features=' _BLINK_SETTINGS = '--blink-settings=' -def qt_args(namespace: argparse.Namespace) -> List[str]: +def qt_args(namespace: argparse.Namespace) -> list[str]: """Get the Qt QApplication arguments based on an argparse namespace. Args: @@ -78,7 +78,7 @@ def qt_args(namespace: argparse.Namespace) -> List[str]: def _qtwebengine_features( versions: version.WebEngineVersions, special_flags: Sequence[str], -) -> Tuple[Sequence[str], Sequence[str]]: +) -> tuple[Sequence[str], Sequence[str]]: """Get a tuple of --enable-features/--disable-features flags for QtWebEngine. Args: @@ -286,7 +286,7 @@ _SettingValueType = Union[ Optional[str], ], ] -_WEBENGINE_SETTINGS: Dict[str, Dict[Any, Optional[_SettingValueType]]] = { +_WEBENGINE_SETTINGS: dict[str, dict[Any, Optional[_SettingValueType]]] = { 'qt.force_software_rendering': { 'software-opengl': None, 'qt-quick': None, diff --git a/qutebrowser/config/stylesheet.py b/qutebrowser/config/stylesheet.py index d9032e2a9..258e26002 100644 --- a/qutebrowser/config/stylesheet.py +++ b/qutebrowser/config/stylesheet.py @@ -5,7 +5,7 @@ """Handling of Qt qss stylesheets.""" import functools -from typing import Optional, FrozenSet +from typing import Optional from qutebrowser.qt.core import pyqtSlot, QObject from qutebrowser.qt.widgets import QWidget @@ -72,7 +72,7 @@ class _StyleSheetObserver(QObject): self._stylesheet = stylesheet if update: - self._options: Optional[FrozenSet[str]] = jinja.template_config_variables( + self._options: Optional[frozenset[str]] = jinja.template_config_variables( self._stylesheet) else: self._options = None diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 7824ae258..f2ce7081d 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -8,7 +8,7 @@ import re import argparse import functools import dataclasses -from typing import Any, Callable, Dict, Optional, Union +from typing import Any, Callable, Optional, Union from qutebrowser.qt.core import QUrl, pyqtSlot, qVersion from qutebrowser.qt.gui import QFont @@ -86,10 +86,10 @@ class AbstractSettings: """Abstract base class for settings set via QWeb(Engine)Settings.""" - _ATTRIBUTES: Dict[str, AttributeInfo] = {} - _FONT_SIZES: Dict[str, Any] = {} - _FONT_FAMILIES: Dict[str, Any] = {} - _FONT_TO_QFONT: Dict[Any, QFont.StyleHint] = {} + _ATTRIBUTES: dict[str, AttributeInfo] = {} + _FONT_SIZES: dict[str, Any] = {} + _FONT_FAMILIES: dict[str, Any] = {} + _FONT_TO_QFONT: dict[Any, QFont.StyleHint] = {} def __init__(self, settings: Any) -> None: self._settings = settings diff --git a/qutebrowser/extensions/interceptors.py b/qutebrowser/extensions/interceptors.py index 8aaa9b28c..1d7ef437e 100644 --- a/qutebrowser/extensions/interceptors.py +++ b/qutebrowser/extensions/interceptors.py @@ -6,7 +6,7 @@ import enum import dataclasses -from typing import Callable, List, Optional +from typing import Callable, Optional from qutebrowser.qt.core import QUrl @@ -89,7 +89,7 @@ class Request: InterceptorType = Callable[[Request], None] -_interceptors: List[InterceptorType] = [] +_interceptors: list[InterceptorType] = [] def register(interceptor: InterceptorType) -> None: diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index 291377c93..d658466f2 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -10,7 +10,7 @@ import pathlib import importlib import argparse import dataclasses -from typing import Callable, List, Optional, Tuple +from typing import Callable, Optional from collections.abc import Iterator from qutebrowser.qt.core import pyqtSlot @@ -22,7 +22,7 @@ from qutebrowser.misc import objects # ModuleInfo objects for all loaded plugins -_module_infos: List["ModuleInfo"] = [] +_module_infos: list["ModuleInfo"] = [] InitHookType = Callable[['InitContext'], None] ConfigChangedHookType = Callable[[], None] @@ -48,8 +48,8 @@ class ModuleInfo: skip_hooks: bool = False init_hook: Optional[InitHookType] = None - config_changed_hooks: List[ - Tuple[ + config_changed_hooks: list[ + tuple[ Optional[str], ConfigChangedHookType, ] diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 63a6832e3..8bb63bbe6 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -18,7 +18,7 @@ handle what we actually think we do. import itertools import dataclasses -from typing import List, Optional, Union, overload, cast +from typing import Optional, Union, overload, cast from collections.abc import Iterator, Iterable, Mapping from qutebrowser.qt import machinery @@ -524,7 +524,7 @@ class KeySequence: _MAX_LEN = 4 def __init__(self, *keys: KeyInfo) -> None: - self._sequences: List[QKeySequence] = [] + self._sequences: list[QKeySequence] = [] for sub in utils.chunk(keys, self._MAX_LEN): try: args = [info.to_qt() for info in sub] @@ -547,7 +547,7 @@ class KeySequence: """Iterate over KeyInfo objects.""" # FIXME:mypy Stubs seem to be unaware that iterating a QKeySequence produces # _KeyInfoType - sequences = cast(List[Iterable[_KeyInfoType]], self._sequences) + sequences = cast(list[Iterable[_KeyInfoType]], self._sequences) for combination in itertools.chain.from_iterable(sequences): yield KeyInfo.from_qt(combination) @@ -720,7 +720,7 @@ class KeySequence: mappings: Mapping['KeySequence', 'KeySequence'] ) -> 'KeySequence': """Get a new KeySequence with the given mappings applied.""" - infos: List[KeyInfo] = [] + infos: list[KeyInfo] = [] for info in self: key_seq = KeySequence(info) if key_seq in mappings: diff --git a/qutebrowser/keyinput/macros.py b/qutebrowser/keyinput/macros.py index 69198ecfb..0eb7244d6 100644 --- a/qutebrowser/keyinput/macros.py +++ b/qutebrowser/keyinput/macros.py @@ -5,7 +5,7 @@ """Keyboard macro system.""" -from typing import cast, Dict, List, Optional, Tuple +from typing import cast, Optional from qutebrowser.commands import runners from qutebrowser.api import cmdutils @@ -13,7 +13,7 @@ from qutebrowser.keyinput import modeman from qutebrowser.utils import message, objreg, usertypes -_CommandType = Tuple[str, int] # command, type +_CommandType = tuple[str, int] # command, type macro_recorder = cast('MacroRecorder', None) @@ -32,9 +32,9 @@ class MacroRecorder: """ def __init__(self) -> None: - self._macros: Dict[str, List[_CommandType]] = {} + self._macros: dict[str, list[_CommandType]] = {} self._recording_macro: Optional[str] = None - self._macro_count: Dict[int, int] = {} + self._macro_count: dict[int, int] = {} self._last_register: Optional[str] = None @cmdutils.register(instance='macro-recorder') diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 13318f2c9..9eb0bc17e 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -6,7 +6,7 @@ import functools import dataclasses -from typing import Callable, Union, Set, cast +from typing import Callable, Union, cast from collections.abc import Mapping, MutableMapping from qutebrowser.qt import machinery @@ -253,7 +253,7 @@ class ModeManager(QObject): self.parsers: ParserDictType = {} self._prev_mode = usertypes.KeyMode.normal self.mode = usertypes.KeyMode.normal - self._releaseevents_to_pass: Set[KeyEvent] = set() + self._releaseevents_to_pass: set[KeyEvent] = set() # Set after __init__ self.hintmanager = cast(hints.HintManager, None) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index b5664a8c2..6e6821612 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -8,7 +8,7 @@ import binascii import base64 import itertools import functools -from typing import List, Optional, Tuple, cast +from typing import Optional, cast from collections.abc import MutableSequence from qutebrowser.qt import machinery @@ -101,7 +101,7 @@ def get_target_window(): return None -_OverlayInfoType = Tuple[QWidget, pyqtBoundSignal, bool, str] +_OverlayInfoType = tuple[QWidget, pyqtBoundSignal, bool, str] class MainWindow(QWidget): @@ -415,7 +415,7 @@ class MainWindow(QWidget): self._vbox.removeWidget(self.tabbed_browser.widget) self._vbox.removeWidget(self._downloadview) self._vbox.removeWidget(self.status) - widgets: List[QWidget] = [self.tabbed_browser.widget] + widgets: list[QWidget] = [self.tabbed_browser.widget] downloads_position = config.val.downloads.position if downloads_position == 'top': diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 44c37687b..c63b02f70 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -9,7 +9,7 @@ import html import collections import functools import dataclasses -from typing import Deque, Optional, cast +from typing import Optional, cast from collections.abc import MutableSequence from qutebrowser.qt.core import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, @@ -90,7 +90,7 @@ class PromptQueue(QObject): self._question = None self._shutting_down = False self._loops: MutableSequence[qtutils.EventLoop] = [] - self._queue: Deque[usertypes.Question] = collections.deque() + self._queue: collections.deque[usertypes.Question] = collections.deque() message.global_bridge.mode_left.connect(self._on_mode_left) def __repr__(self): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 70fc7d00d..42efe0bbf 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -10,7 +10,7 @@ import weakref import datetime import dataclasses from typing import ( - Any, Deque, List, Optional, Tuple) + Any, Optional) from collections.abc import Mapping, MutableMapping, MutableSequence from qutebrowser.qt.widgets import QSizePolicy, QWidget, QApplication @@ -59,10 +59,10 @@ class TabDeque: size = config.val.tabs.focus_stack_size if size < 0: size = None - self._stack: Deque[weakref.ReferenceType[browsertab.AbstractTab]] = ( + self._stack: collections.deque[weakref.ReferenceType[browsertab.AbstractTab]] = ( collections.deque(maxlen=size)) # Items that have been removed from the primary stack. - self._stack_deleted: List[weakref.ReferenceType[browsertab.AbstractTab]] = [] + self._stack_deleted: list[weakref.ReferenceType[browsertab.AbstractTab]] = [] self._ignore_next = False self._keep_deleted_next = False @@ -236,7 +236,7 @@ class TabbedBrowser(QWidget): self.search_text = None self.search_options: Mapping[str, Any] = {} self._local_marks: MutableMapping[QUrl, MutableMapping[str, QPoint]] = {} - self._global_marks: MutableMapping[str, Tuple[QPoint, QUrl]] = {} + self._global_marks: MutableMapping[str, tuple[QPoint, QUrl]] = {} self.default_window_icon = self._window().windowIcon() self.is_private = private self.tab_deque = TabDeque() diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py index 6cc466c93..8d50ac45d 100644 --- a/qutebrowser/mainwindow/tabwidget.py +++ b/qutebrowser/mainwindow/tabwidget.py @@ -7,7 +7,7 @@ import functools import contextlib import dataclasses -from typing import Optional, Dict, Any +from typing import Optional, Any from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer, QUrl) @@ -169,7 +169,7 @@ class TabWidget(QTabWidget): page_title = self.page_title(idx) - fields: Dict[str, Any] = {} + fields: dict[str, Any] = {} fields['id'] = tab.tab_id fields['current_title'] = page_title fields['title_sep'] = ' - ' if page_title else '' diff --git a/qutebrowser/misc/backendproblem.py b/qutebrowser/misc/backendproblem.py index 244949bd9..9d9aef35c 100644 --- a/qutebrowser/misc/backendproblem.py +++ b/qutebrowser/misc/backendproblem.py @@ -13,7 +13,7 @@ import shutil import os.path import argparse import dataclasses -from typing import Any, Optional, Tuple +from typing import Any, Optional from collections.abc import Sequence from qutebrowser.qt import machinery @@ -49,7 +49,7 @@ class _Button: default: bool = False -def _other_backend(backend: usertypes.Backend) -> Tuple[usertypes.Backend, str]: +def _other_backend(backend: usertypes.Backend) -> tuple[usertypes.Backend, str]: """Get the other backend enum/setting for a given backend.""" other_backend = { usertypes.Backend.QtWebKit: usertypes.Backend.QtWebEngine, diff --git a/qutebrowser/misc/binparsing.py b/qutebrowser/misc/binparsing.py index 81e2e6dbb..acb4cc5f8 100644 --- a/qutebrowser/misc/binparsing.py +++ b/qutebrowser/misc/binparsing.py @@ -8,7 +8,7 @@ Used by elf.py as well as pakjoy.py. """ import struct -from typing import Any, IO, Tuple +from typing import Any, IO class ParseError(Exception): @@ -16,7 +16,7 @@ class ParseError(Exception): """Raised when the file can't be parsed.""" -def unpack(fmt: str, fobj: IO[bytes]) -> Tuple[Any, ...]: +def unpack(fmt: str, fobj: IO[bytes]) -> tuple[Any, ...]: """Unpack the given struct format from the given file.""" size = struct.calcsize(fmt) data = safe_read(fobj, size) diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index ad9ce83a7..5b940a8a3 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -13,7 +13,6 @@ import fnmatch import traceback import datetime import enum -from typing import List, Tuple from qutebrowser.qt.core import pyqtSlot, Qt, QSize from qutebrowser.qt.widgets import (QDialog, QLabel, QTextEdit, QPushButton, @@ -103,7 +102,7 @@ class _CrashDialog(QDialog): super().__init__(parent) # We don't set WA_DeleteOnClose here as on an exception, we'll get # closed anyways, and it only could have unintended side-effects. - self._crash_info: List[Tuple[str, str]] = [] + self._crash_info: list[tuple[str, str]] = [] self._btn_box = None self._paste_text = None self.setWindowTitle("Whoops!") diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 4ad6021f5..7bde12fe5 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -15,7 +15,7 @@ import functools import threading import faulthandler import dataclasses -from typing import TYPE_CHECKING, Optional, cast, List +from typing import TYPE_CHECKING, Optional, cast from collections.abc import MutableMapping from qutebrowser.qt.core import (pyqtSlot, qInstallMessageHandler, QObject, @@ -36,8 +36,8 @@ class ExceptionInfo: """Information stored when there was an exception.""" - pages: List[List[str]] - cmd_history: List[str] + pages: list[list[str]] + cmd_history: list[str] objects: str diff --git a/qutebrowser/misc/elf.py b/qutebrowser/misc/elf.py index e44d8b573..a012f4c69 100644 --- a/qutebrowser/misc/elf.py +++ b/qutebrowser/misc/elf.py @@ -49,7 +49,7 @@ import re import dataclasses import mmap import pathlib -from typing import IO, ClassVar, Dict, Optional, cast +from typing import IO, ClassVar, Optional, cast from qutebrowser.qt import machinery from qutebrowser.utils import log, version, qtutils @@ -131,7 +131,7 @@ class Header: shnum: int shstrndx: int - _FORMATS: ClassVar[Dict[Bitness, str]] = { + _FORMATS: ClassVar[dict[Bitness, str]] = { Bitness.x64: ' Tuple[bool, _PointerRetType]: + ) -> tuple[bool, _PointerRetType]: """Handle XCB events.""" # We're only installed when the platform plugin is xcb assert evtype == b"xcb_generic_event_t", evtype diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py index 1b91c6fdd..4a997ffd2 100644 --- a/qutebrowser/misc/objects.py +++ b/qutebrowser/misc/objects.py @@ -8,7 +8,7 @@ # earlyinit. import argparse -from typing import TYPE_CHECKING, Any, Dict, Set, Union, cast +from typing import TYPE_CHECKING, Any, Union, cast if TYPE_CHECKING: from qutebrowser import app @@ -29,7 +29,7 @@ class NoBackend: backend: Union['usertypes.Backend', NoBackend] = NoBackend() -commands: Dict[str, 'command.Command'] = {} -debug_flags: Set[str] = set() +commands: dict[str, 'command.Command'] = {} +debug_flags: set[str] = set() args = cast(argparse.Namespace, None) qapp = cast('app.Application', None) diff --git a/qutebrowser/misc/pakjoy.py b/qutebrowser/misc/pakjoy.py index f0ee7b4f0..dcf8616f4 100644 --- a/qutebrowser/misc/pakjoy.py +++ b/qutebrowser/misc/pakjoy.py @@ -30,7 +30,7 @@ import shutil import pathlib import dataclasses import contextlib -from typing import ClassVar, IO, Optional, Dict, Tuple +from typing import ClassVar, IO, Optional from collections.abc import Iterator from qutebrowser.config import config @@ -129,7 +129,7 @@ class PakParser: return data - def _read_header(self) -> Dict[int, PakEntry]: + def _read_header(self) -> dict[int, PakEntry]: """Read the header and entry index from the .pak file.""" entries = [] @@ -148,7 +148,7 @@ class PakParser: return {entry.resource_id: entry for entry in entries} - def _find_manifest(self, entries: Dict[int, PakEntry]) -> Tuple[PakEntry, bytes]: + def _find_manifest(self, entries: dict[int, PakEntry]) -> tuple[PakEntry, bytes]: to_check = list(entries.values()) for hangouts_id in HANGOUTS_IDS: if hangouts_id in entries: diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py index e16ffeb40..e2140c242 100644 --- a/qutebrowser/misc/sql.py +++ b/qutebrowser/misc/sql.py @@ -9,7 +9,7 @@ import collections import contextlib import dataclasses import types -from typing import Any, Dict, List, Optional, Type, Union +from typing import Any, Optional, Union from collections.abc import Iterator, Mapping, MutableSequence from qutebrowser.qt.core import QObject, pyqtSignal @@ -227,8 +227,8 @@ class Database: """Return a Query instance linked to this Database.""" return Query(self, querystr, forward_only) - def table(self, name: str, fields: List[str], - constraints: Optional[Dict[str, str]] = None, + def table(self, name: str, fields: list[str], + constraints: Optional[dict[str, str]] = None, parent: Optional[QObject] = None) -> 'SqlTable': """Return a SqlTable instance linked to this Database.""" return SqlTable(self, name, fields, constraints, parent) @@ -277,7 +277,7 @@ class Transaction(contextlib.AbstractContextManager): # type: ignore[type-arg] raise_sqlite_error(msg, error) def __exit__(self, - _exc_type: Optional[Type[BaseException]], + _exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], _exc_tb: Optional[types.TracebackType]) -> None: db = self._database.qt_database() @@ -314,7 +314,7 @@ class Query: ok = self.query.prepare(querystr) self._check_ok('prepare', ok) self.query.setForwardOnly(forward_only) - self._placeholders: List[str] = [] + self._placeholders: list[str] = [] def __iter__(self) -> Iterator[Any]: if not self.query.isActive(): @@ -349,7 +349,7 @@ class Query: if None in values: raise BugError("Missing bound values!") - def _bind_values(self, values: Mapping[str, Any]) -> Dict[str, Any]: + def _bind_values(self, values: Mapping[str, Any]) -> dict[str, Any]: self._placeholders = list(values) for key, val in values.items(): self.query.bindValue(f':{key}', val) @@ -405,7 +405,7 @@ class Query: assert rows != -1 return rows - def bound_values(self) -> Dict[str, Any]: + def bound_values(self) -> dict[str, Any]: return { f":{key}": self.query.boundValue(f":{key}") for key in self._placeholders @@ -427,8 +427,8 @@ class SqlTable(QObject): changed = pyqtSignal() database: Database - def __init__(self, database: Database, name: str, fields: List[str], - constraints: Optional[Dict[str, str]] = None, + def __init__(self, database: Database, name: str, fields: list[str], + constraints: Optional[dict[str, str]] = None, parent: Optional[QObject] = None) -> None: """Wrapper over a table in the SQL database. @@ -443,7 +443,7 @@ class SqlTable(QObject): self.database = database self._create_table(fields, constraints) - def _create_table(self, fields: List[str], constraints: Optional[Dict[str, str]], + def _create_table(self, fields: list[str], constraints: Optional[dict[str, str]], *, force: bool = False) -> None: """Create the table if the database is uninitialized. diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index dcb3b3243..f39fb7d7f 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -30,7 +30,7 @@ import argparse import warnings import importlib import dataclasses -from typing import Optional, Dict +from typing import Optional from qutebrowser.utils import log @@ -106,7 +106,7 @@ class SelectionInfo: """Information about outcomes of importing Qt wrappers.""" wrapper: Optional[str] = None - outcomes: Dict[str, str] = dataclasses.field(default_factory=dict) + outcomes: dict[str, str] = dataclasses.field(default_factory=dict) reason: SelectionReason = SelectionReason.unknown def set_module_error(self, name: str, error: Exception) -> None: diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index 2ec5e11fe..b5cddbd2d 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -12,7 +12,7 @@ import functools import datetime import types from typing import ( - Any, Callable, List, Optional, Type, Union) + Any, Callable, Optional, Union) from collections.abc import Mapping, MutableSequence, Sequence from qutebrowser.qt.core import Qt, QEvent, QMetaMethod, QObject, pyqtBoundSignal @@ -22,7 +22,7 @@ from qutebrowser.misc import objects from qutebrowser.qt import sip, machinery -def log_events(klass: Type[QObject]) -> Type[QObject]: +def log_events(klass: type[QObject]) -> type[QObject]: """Class decorator to log Qt events.""" old_event = klass.event @@ -39,7 +39,7 @@ def log_events(klass: Type[QObject]) -> Type[QObject]: return klass -def log_signals(obj: Union[QObject, Type[QObject]]) -> Union[QObject, Type[QObject]]: +def log_signals(obj: Union[QObject, type[QObject]]) -> Union[QObject, type[QObject]]: """Log all signals of an object or class. Can be used as class decorator. @@ -95,7 +95,7 @@ else: def _qenum_key_python( value: _EnumValueType, - klass: Type[_EnumValueType], + klass: type[_EnumValueType], ) -> Optional[str]: """New-style PyQt6: Try getting value from Python enum.""" if isinstance(value, enum.Enum) and value.name: @@ -115,9 +115,9 @@ def _qenum_key_python( def _qenum_key_qt( - base: Type[sip.simplewrapper], + base: type[sip.simplewrapper], value: _EnumValueType, - klass: Type[_EnumValueType], + klass: type[_EnumValueType], ) -> Optional[str]: # On PyQt5, or PyQt6 with int passed: Try to ask Qt's introspection. # However, not every Qt enum value has a staticMetaObject @@ -140,9 +140,9 @@ def _qenum_key_qt( def qenum_key( - base: Type[sip.simplewrapper], + base: type[sip.simplewrapper], value: _EnumValueType, - klass: Type[_EnumValueType] = None, + klass: type[_EnumValueType] = None, ) -> str: """Convert a Qt Enum value to its key as a string. @@ -174,9 +174,9 @@ def qenum_key( return '0x{:04x}'.format(int(value)) # type: ignore[arg-type] -def qflags_key(base: Type[sip.simplewrapper], +def qflags_key(base: type[sip.simplewrapper], value: _EnumValueType, - klass: Type[_EnumValueType] = None) -> str: + klass: type[_EnumValueType] = None) -> str: """Convert a Qt QFlags value to its keys as string. Note: Passing a combined value (such as Qt.AlignmentFlag.AlignCenter) will get the names @@ -326,7 +326,7 @@ class log_time: # noqa: N801,N806 pylint: disable=invalid-name self._started = datetime.datetime.now() def __exit__(self, - _exc_type: Optional[Type[BaseException]], + _exc_type: Optional[type[BaseException]], _exc_val: Optional[BaseException], _exc_tb: Optional[types.TracebackType]) -> None: assert self._started is not None @@ -373,7 +373,7 @@ def get_all_objects(start_obj: QObject = None) -> str: if start_obj is None: start_obj = objects.qapp - pyqt_lines: List[str] = [] + pyqt_lines: list[str] = [] _get_pyqt_objects(pyqt_lines, start_obj) pyqt_lines = [' ' + e for e in pyqt_lines] pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(len(pyqt_lines))) diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index e60db6167..0aa860726 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -10,7 +10,7 @@ import inspect import os.path import collections import enum -from typing import Any, Callable, Optional, List, Union +from typing import Any, Callable, Optional, Union from collections.abc import MutableMapping import qutebrowser @@ -82,10 +82,10 @@ class DocstringParser: """ self._state = self.State.short self._cur_arg_name: Optional[str] = None - self._short_desc_parts: List[str] = [] - self._long_desc_parts: List[str] = [] + self._short_desc_parts: list[str] = [] + self._long_desc_parts: list[str] = [] self.arg_descs: MutableMapping[ - str, Union[str, List[str]]] = collections.OrderedDict() + str, Union[str, list[str]]] = collections.OrderedDict() doc = inspect.getdoc(func) handlers = { self.State.short: self._parse_short, diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 9fcff7cd1..ede9dbb40 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -10,7 +10,7 @@ import posixpath import functools import contextlib import html -from typing import Any, Callable, FrozenSet, List, Set, Tuple +from typing import Any, Callable from collections.abc import Iterator import jinja2 @@ -55,7 +55,7 @@ class Loader(jinja2.BaseLoader): self, _env: jinja2.Environment, template: str - ) -> Tuple[str, str, Callable[[], bool]]: + ) -> tuple[str, str, Callable[[], bool]]: path = os.path.join(self._subdir, template) try: source = resources.read_file(path) @@ -129,10 +129,10 @@ js_environment = jinja2.Environment(loader=Loader('javascript')) @debugcachestats.register() @functools.lru_cache -def template_config_variables(template: str) -> FrozenSet[str]: +def template_config_variables(template: str) -> frozenset[str]: """Return the config variables used in the template.""" - unvisted_nodes: List[jinja2.nodes.Node] = [environment.parse(template)] - result: Set[str] = set() + unvisted_nodes: list[jinja2.nodes.Node] = [environment.parse(template)] + result: set[str] = set() while unvisted_nodes: node = unvisted_nodes.pop() if not isinstance(node, jinja2.nodes.Getattr): @@ -141,7 +141,7 @@ def template_config_variables(template: str) -> FrozenSet[str]: # List of attribute names in reverse order. # For example it's ['ab', 'c', 'd'] for 'conf.d.c.ab'. - attrlist: List[str] = [] + attrlist: list[str] = [] while isinstance(node, jinja2.nodes.Getattr): attrlist.append(node.attr) node = node.node diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 55e74cbf4..01701b3b5 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -16,7 +16,7 @@ import json import inspect import argparse from typing import (TYPE_CHECKING, Any, - Optional, Set, Tuple, Union, TextIO, Literal, cast) + Optional, Union, TextIO, Literal, cast) from collections.abc import Iterator, Mapping, MutableSequence # NOTE: This is a Qt-free zone! All imports related to Qt logging should be done in @@ -241,7 +241,7 @@ def _init_handlers( force_color: bool, json_logging: bool, ram_capacity: int -) -> Tuple[Optional["logging.StreamHandler[TextIO]"], Optional['RAMHandler']]: +) -> tuple[Optional["logging.StreamHandler[TextIO]"], Optional['RAMHandler']]: """Init log handlers. Args: @@ -295,7 +295,7 @@ def _init_formatters( color: bool, force_color: bool, json_logging: bool, -) -> Tuple[ +) -> tuple[ Union['JSONFormatter', 'ColoredFormatter', None], 'ColoredFormatter', 'HTMLFormatter', @@ -397,7 +397,7 @@ class InvalidLogFilterError(Exception): """Raised when an invalid filter string is passed to LogFilter.parse().""" - def __init__(self, names: Set[str]): + def __init__(self, names: set[str]): invalid = names - set(LOGGER_NAMES) super().__init__("Invalid log category {} - valid categories: {}" .format(', '.join(sorted(invalid)), @@ -418,7 +418,7 @@ class LogFilter(logging.Filter): than debug. """ - def __init__(self, names: Set[str], *, negated: bool = False, + def __init__(self, names: set[str], *, negated: bool = False, only_debug: bool = True) -> None: super().__init__() self.names = names diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index fe1dd16a9..e122fbc8b 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -10,7 +10,7 @@ import dataclasses import traceback -from typing import Any, Callable, List, Union, Optional +from typing import Any, Callable, Union, Optional from collections.abc import Iterable from qutebrowser.qt.core import pyqtSignal, pyqtBoundSignal, QObject @@ -240,7 +240,7 @@ class GlobalMessageBridge(QObject): def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self._connected = False - self._cache: List[MessageInfo] = [] + self._cache: list[MessageInfo] = [] def ask(self, question: usertypes.Question, blocking: bool, *, diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 6efd8bbe1..765eddd05 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -19,7 +19,7 @@ import pathlib import operator import contextlib from typing import (Any, TYPE_CHECKING, BinaryIO, IO, Literal, - Optional, Union, Tuple, Protocol, cast, overload, TypeVar) + Optional, Union, Protocol, cast, overload, TypeVar) from collections.abc import Iterator from qutebrowser.qt import machinery, sip @@ -34,7 +34,6 @@ except ImportError: # pragma: no cover if TYPE_CHECKING: from qutebrowser.qt.webkit import QWebHistory from qutebrowser.qt.webenginecore import QWebEngineHistory - from typing_extensions import TypeGuard # added in Python 3.10 from qutebrowser.misc import objects from qutebrowser.utils import usertypes, utils @@ -532,7 +531,7 @@ class EventLoop(QEventLoop): def _get_color_percentage(x1: int, y1: int, z1: int, a1: int, x2: int, y2: int, z2: int, a2: int, - percent: int) -> Tuple[int, int, int, int]: + percent: int) -> tuple[int, int, int, int]: """Get a color which is percent% interpolated between start and end. Args: diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index c9e1c42a7..35fd62f75 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -10,7 +10,7 @@ import contextlib import posixpath import pathlib import importlib.resources -from typing import Union, Dict +from typing import Union from collections.abc import Iterator, Iterable if sys.version_info >= (3, 11): # pragma: no cover @@ -20,8 +20,8 @@ else: from importlib.abc import Traversable import qutebrowser -_cache: Dict[str, str] = {} -_bin_cache: Dict[str, bytes] = {} +_cache: dict[str, str] = {} +_bin_cache: dict[str, bytes] = {} _ResourceType = Union[Traversable, pathlib.Path] diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 57e81ce19..b82845a96 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -11,7 +11,7 @@ import contextlib import enum import argparse import tempfile -from typing import Optional, Dict +from typing import Optional from collections.abc import Iterator from qutebrowser.qt.core import QStandardPaths @@ -20,7 +20,7 @@ from qutebrowser.qt.widgets import QApplication from qutebrowser.utils import log, debug, utils, version, qtutils # The cached locations -_locations: Dict["_Location", str] = {} +_locations: dict["_Location", str] = {} class _Location(enum.Enum): diff --git a/qutebrowser/utils/urlmatch.py b/qutebrowser/utils/urlmatch.py index 0d3f76cb8..1a558f307 100644 --- a/qutebrowser/utils/urlmatch.py +++ b/qutebrowser/utils/urlmatch.py @@ -17,7 +17,7 @@ https://chromium.googlesource.com/chromium/src/+/6f4a6681eae01c2036336c18b06303e import ipaddress import fnmatch import urllib.parse -from typing import Any, Optional, Tuple +from typing import Any, Optional from qutebrowser.qt.core import QUrl @@ -89,7 +89,7 @@ class UrlPattern: self._init_path(parsed) self._init_port(parsed) - def _to_tuple(self) -> Tuple[ + def _to_tuple(self) -> tuple[ bool, # _match_all bool, # _match_subdomains Optional[str], # _scheme diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py index 0fc2a262e..839fdbe84 100644 --- a/qutebrowser/utils/urlutils.py +++ b/qutebrowser/utils/urlutils.py @@ -11,7 +11,7 @@ import ipaddress import posixpath import urllib.parse import mimetypes -from typing import Optional, Tuple, Union, cast +from typing import Optional, Union, cast from collections.abc import Iterable from qutebrowser.qt import machinery @@ -112,7 +112,7 @@ class InvalidUrlError(Error): super().__init__(self.msg) -def _parse_search_term(s: str) -> Tuple[Optional[str], Optional[str]]: +def _parse_search_term(s: str) -> tuple[Optional[str], Optional[str]]: """Get a search engine name and search term from a string. Args: @@ -465,7 +465,7 @@ def filename_from_url(url: QUrl, fallback: str = None) -> Optional[str]: return fallback -HostTupleType = Tuple[str, str, int] +HostTupleType = tuple[str, str, int] def host_tuple(url: QUrl) -> HostTupleType: diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 126075b5a..178e4a306 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -20,7 +20,7 @@ import shlex import sysconfig import mimetypes from typing import (Any, Callable, IO, - Optional, Tuple, List, Type, Union, + Optional, Union, TypeVar, Protocol) from collections.abc import Iterator, Sequence @@ -408,7 +408,7 @@ def qualname(obj: Any) -> str: return repr(obj) -_ExceptionType = Union[Type[BaseException], Tuple[Type[BaseException]]] +_ExceptionType = Union[type[BaseException], tuple[type[BaseException]]] def raises(exc: _ExceptionType, func: Callable[..., Any], *args: Any) -> bool: @@ -847,7 +847,7 @@ def parse_point(s: str) -> QPoint: raise ValueError(e) -def match_globs(patterns: List[str], value: str) -> Optional[str]: +def match_globs(patterns: list[str], value: str) -> Optional[str]: """Match a list of glob-like patterns against a value. Return: diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 46d27d537..baee2f2ce 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -19,7 +19,7 @@ import getpass import functools import dataclasses import importlib.metadata -from typing import (Optional, Tuple, ClassVar, Dict, Any, +from typing import (Optional, ClassVar, Any, TYPE_CHECKING) from collections.abc import Mapping, Sequence @@ -106,7 +106,7 @@ class Distribution(enum.Enum): solus = enum.auto() -def _parse_os_release() -> Optional[Dict[str, str]]: +def _parse_os_release() -> Optional[dict[str, str]]: """Parse an /etc/os-release file.""" filename = os.environ.get('QUTE_FAKE_OS_RELEASE', '/etc/os-release') info = {} @@ -251,7 +251,7 @@ def _git_str_subprocess(gitpath: str) -> Optional[str]: return None -def _release_info() -> Sequence[Tuple[str, str]]: +def _release_info() -> Sequence[tuple[str, str]]: """Try to gather distribution release information. Return: @@ -380,7 +380,7 @@ class ModuleInfo: return text -def _create_module_info() -> Dict[str, ModuleInfo]: +def _create_module_info() -> dict[str, ModuleInfo]: packages = [ ('colorama', ['VERSION', '__version__']), ('jinja2', ['__version__']), @@ -539,7 +539,7 @@ class WebEngineVersions: chromium_security: Optional[str] = None chromium_major: Optional[int] = dataclasses.field(init=False) - _BASES: ClassVar[Dict[int, str]] = { + _BASES: ClassVar[dict[int, str]] = { 83: '83.0.4103.122', # ~2020-06-24 87: '87.0.4280.144', # ~2020-12-02 90: '90.0.4430.228', # 2021-06-22 @@ -552,7 +552,7 @@ class WebEngineVersions: 122: '122.0.6261.171', # ~2024-??-?? } - _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, Tuple[str, Optional[str]]]] = { + _CHROMIUM_VERSIONS: ClassVar[dict[utils.VersionNumber, tuple[str, Optional[str]]]] = { # ====== UNSUPPORTED ===== # Qt 5.12: Chromium 69 @@ -708,7 +708,7 @@ class WebEngineVersions: def _infer_chromium_version( cls, pyqt_webengine_version: utils.VersionNumber, - ) -> Tuple[Optional[str], Optional[str]]: + ) -> tuple[Optional[str], Optional[str]]: """Infer the Chromium version based on the PyQtWebEngine version. Returns: @@ -1018,7 +1018,7 @@ class OpenGLInfo: version_str: Optional[str] = None # The parsed version as a (major, minor) tuple of ints - version: Optional[Tuple[int, ...]] = None + version: Optional[tuple[int, ...]] = None # The vendor specific information following the version number vendor_specific: Optional[str] = None diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index af44c7f6f..3454939ef 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -20,7 +20,7 @@ import platform import collections import dataclasses import re -from typing import List, Optional +from typing import Optional from collections.abc import Iterable try: @@ -271,7 +271,7 @@ def build_mac( qt5: bool, skip_packaging: bool, debug: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build macOS .dmg/.app.""" utils.print_title("Cleaning up...") for f in ['wc.dmg', 'template.dmg']: @@ -359,7 +359,7 @@ def _build_windows_single( qt5: bool, skip_packaging: bool, debug: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build on Windows for a single build type.""" utils.print_title("Running pyinstaller") dist_path = pathlib.Path("dist") @@ -398,7 +398,7 @@ def build_windows( skip_packaging: bool, qt5: bool, debug: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build windows executables/setups.""" utils.print_title("Updating 3rdparty content") update_3rdparty.run(nsis=True, ace=False, pdfjs=True, legacy_pdfjs=qt5, @@ -423,7 +423,7 @@ def _package_windows_single( out_path: pathlib.Path, debug: bool, qt5: bool, -) -> List[Artifact]: +) -> list[Artifact]: """Build the given installer/zip for windows.""" artifacts = [] @@ -476,7 +476,7 @@ def _package_windows_single( return artifacts -def build_sdist() -> List[Artifact]: +def build_sdist() -> list[Artifact]: """Build an sdist and list the contents.""" utils.print_title("Building sdist") @@ -566,7 +566,7 @@ def read_github_token( def github_upload( - artifacts: List[Artifact], + artifacts: list[Artifact], tag: str, gh_token: str, experimental: bool, @@ -644,7 +644,7 @@ def github_upload( break -def pypi_upload(artifacts: List[Artifact], experimental: bool) -> None: +def pypi_upload(artifacts: list[Artifact], experimental: bool) -> None: """Upload the given artifacts to PyPI using twine.""" # https://blog.pypi.org/posts/2023-05-23-removing-pgp/ artifacts = [a for a in artifacts if a.mimetype != 'application/pgp-signature'] @@ -656,13 +656,13 @@ def pypi_upload(artifacts: List[Artifact], experimental: bool) -> None: run_twine('upload', artifacts) -def twine_check(artifacts: List[Artifact]) -> None: +def twine_check(artifacts: list[Artifact]) -> None: """Check packages using 'twine check'.""" utils.print_title("Running twine check...") run_twine('check', artifacts, '--strict') -def run_twine(command: str, artifacts: List[Artifact], *args: str) -> None: +def run_twine(command: str, artifacts: list[Artifact], *args: str) -> None: paths = [a.path for a in artifacts] subprocess.run([sys.executable, '-m', 'twine', command, *args, *paths], check=True) diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py index 497c5f1f0..5ffeb6019 100644 --- a/scripts/dev/misc_checks.py +++ b/scripts/dev/misc_checks.py @@ -15,7 +15,7 @@ import subprocess import tokenize import traceback import pathlib -from typing import List, Optional, Tuple +from typing import Optional from collections.abc import Iterator REPO_ROOT = pathlib.Path(__file__).resolve().parents[2] @@ -31,7 +31,7 @@ BINARY_EXTS = {'.png', '.icns', '.ico', '.bmp', '.gz', '.bin', '.pdf', def _get_files( *, verbose: bool, - ignored: List[pathlib.Path] = None + ignored: list[pathlib.Path] = None ) -> Iterator[pathlib.Path]: """Iterate over all files and yield filenames.""" filenames = subprocess.run( @@ -143,8 +143,8 @@ def _check_spelling_file(path, fobj, patterns): def _check_spelling_all( args: argparse.Namespace, - ignored: List[pathlib.Path], - patterns: List[Tuple[re.Pattern, str]], + ignored: list[pathlib.Path], + patterns: list[tuple[re.Pattern, str]], ) -> Optional[bool]: try: ok = True diff --git a/scripts/mkvenv.py b/scripts/mkvenv.py index 4ab5d8c10..301780ac8 100755 --- a/scripts/mkvenv.py +++ b/scripts/mkvenv.py @@ -17,7 +17,7 @@ import shutil import venv as pyvenv import subprocess import platform -from typing import List, Tuple, Dict, Union +from typing import Union sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) from scripts import utils, link_pyqt @@ -49,7 +49,7 @@ def print_command(*cmd: Union[str, pathlib.Path], venv: bool) -> None: utils.print_col(prefix + ' '.join([str(e) for e in cmd]), 'blue') -def parse_args(argv: List[str] = None) -> argparse.Namespace: +def parse_args(argv: list[str] = None) -> argparse.Namespace: """Parse commandline arguments.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--update', @@ -105,7 +105,7 @@ def _version_key(v): return (999,) -def pyqt_versions() -> List[str]: +def pyqt_versions() -> list[str]: """Get a list of all available PyQt versions. The list is based on the filenames of misc/requirements/ files. @@ -276,7 +276,7 @@ def install_pyqt_wheels(venv_dir: pathlib.Path, pip_install(venv_dir, *wheels) -def install_pyqt_snapshot(venv_dir: pathlib.Path, packages: List[str]) -> None: +def install_pyqt_snapshot(venv_dir: pathlib.Path, packages: list[str]) -> None: """Install PyQt packages from the snapshot server.""" utils.print_title("Installing PyQt snapshots") pip_install(venv_dir, '-U', *packages, '--no-deps', '--pre', @@ -348,9 +348,9 @@ def apply_xcb_util_workaround( link_path.symlink_to(libxcb_util_path) -def _find_libs() -> Dict[Tuple[str, str], List[str]]: +def _find_libs() -> dict[tuple[str, str], list[str]]: """Find all system-wide .so libraries.""" - all_libs: Dict[Tuple[str, str], List[str]] = {} + all_libs: dict[tuple[str, str], list[str]] = {} if pathlib.Path("/sbin/ldconfig").exists(): # /sbin might not be in PATH on e.g. Debian diff --git a/tests/end2end/fixtures/notificationserver.py b/tests/end2end/fixtures/notificationserver.py index 61cff7767..21cbb0e8d 100644 --- a/tests/end2end/fixtures/notificationserver.py +++ b/tests/end2end/fixtures/notificationserver.py @@ -4,7 +4,6 @@ import dataclasses import itertools -from typing import Dict, List from qutebrowser.qt.core import QObject, QByteArray, QUrl, pyqtSlot from qutebrowser.qt.gui import QImage @@ -43,7 +42,7 @@ class TestNotificationServer(QObject): self._bus = QDBusConnection.sessionBus() self._message_id_gen = itertools.count(1) # A dict mapping notification IDs to currently-displayed notifications. - self.messages: Dict[int, NotificationProperties] = {} + self.messages: dict[int, NotificationProperties] = {} self.supports_body_markup = True self.last_id = None @@ -195,7 +194,7 @@ class TestNotificationServer(QObject): return message_id @pyqtSlot(QDBusMessage, result="QStringList") - def GetCapabilities(self, message: QDBusMessage) -> List[str]: + def GetCapabilities(self, message: QDBusMessage) -> list[str]: assert not message.signature() assert not message.arguments() assert message.type() == QDBusMessage.MessageType.MethodCallMessage diff --git a/tests/end2end/test_dirbrowser.py b/tests/end2end/test_dirbrowser.py index b2e3a8e29..c1762b183 100644 --- a/tests/end2end/test_dirbrowser.py +++ b/tests/end2end/test_dirbrowser.py @@ -7,7 +7,6 @@ import pathlib import dataclasses -from typing import List import pytest import bs4 @@ -97,8 +96,8 @@ class Parsed: path: str parent: str - folders: List[str] - files: List[str] + folders: list[str] + files: list[str] @dataclasses.dataclass diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 658b027cb..0aa4f1732 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -6,7 +6,7 @@ """Fake objects/stubs.""" -from typing import Any, Callable, Tuple +from typing import Any, Callable from unittest import mock import contextlib import shutil @@ -327,7 +327,7 @@ class FakeCommand: completion: Any = None maxsplit: int = None takes_count: Callable[[], bool] = lambda: False - modes: Tuple[usertypes.KeyMode] = (usertypes.KeyMode.normal, ) + modes: tuple[usertypes.KeyMode] = (usertypes.KeyMode.normal, ) class FakeTimer(QObject): diff --git a/tests/unit/browser/test_notification.py b/tests/unit/browser/test_notification.py index 3d6c16f87..6c888f084 100644 --- a/tests/unit/browser/test_notification.py +++ b/tests/unit/browser/test_notification.py @@ -7,7 +7,7 @@ import logging import itertools import inspect -from typing import List, Dict, Any, Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING import pytest from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QUrl, QObject @@ -36,7 +36,7 @@ class FakeDBusMessage: self._type = typ self._error_name = error_name - def arguments(self) -> List[Any]: + def arguments(self) -> list[Any]: return self._arguments def signature(self) -> str: @@ -107,8 +107,8 @@ class FakeDBusInterface: icon: str, title: str, body: str, - actions: List[str], - hints: Dict[str, Any], + actions: list[str], + hints: dict[str, Any], timeout: int, ) -> FakeDBusMessage: assert self.notify_reply is not None diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py index 2f7021e95..8b8959a15 100644 --- a/tests/unit/browser/webengine/test_darkmode.py +++ b/tests/unit/browser/webengine/test_darkmode.py @@ -4,7 +4,6 @@ import logging -from typing import List, Tuple import pytest QWebEngineSettings = pytest.importorskip("qutebrowser.qt.webenginecore").QWebEngineSettings @@ -70,7 +69,7 @@ class TestDefinition: ) -> darkmode._Definition: return darkmode._Definition(setting1, setting2, mandatory=set(), prefix="") - def _get_settings(self, definition: darkmode._Definition) -> List[darkmode._Setting]: + def _get_settings(self, definition: darkmode._Definition) -> list[darkmode._Setting]: return [setting for _key, setting in definition.prefixed_settings()] @pytest.mark.parametrize("prefix", ["", "prefix"]) @@ -365,7 +364,7 @@ def test_customization(config_stub, setting, value, exp_key, exp_val): ('6.5.3', 'policy.images', 'smart', [('ImagePolicy', '2')]), ('6.5.3', 'policy.images', 'smart-simple', [('ImagePolicy', '2')]), ]) -def test_image_policy(config_stub, qtwe_version: str, setting: str, value: str, expected: List[Tuple[str, str]]): +def test_image_policy(config_stub, qtwe_version: str, setting: str, value: str, expected: list[tuple[str, str]]): config_stub.val.colors.webpage.darkmode.enabled = True config_stub.set_obj('colors.webpage.darkmode.' + setting, value) diff --git a/tests/unit/browser/webkit/network/test_filescheme.py b/tests/unit/browser/webkit/network/test_filescheme.py index 3ed04ec01..6eee5da52 100644 --- a/tests/unit/browser/webkit/network/test_filescheme.py +++ b/tests/unit/browser/webkit/network/test_filescheme.py @@ -5,7 +5,6 @@ import os import dataclasses -from typing import List import pytest import bs4 @@ -101,8 +100,8 @@ class TestDirbrowserHtml: class Parsed: parent: str - folders: List[str] - files: List[str] + folders: list[str] + files: list[str] @dataclasses.dataclass class Item: diff --git a/tests/unit/components/test_braveadblock.py b/tests/unit/components/test_braveadblock.py index 197687d1d..dab842139 100644 --- a/tests/unit/components/test_braveadblock.py +++ b/tests/unit/components/test_braveadblock.py @@ -5,7 +5,6 @@ import pathlib import logging import csv -from typing import Tuple from collections.abc import Iterable from qutebrowser.qt.core import QUrl @@ -166,7 +165,7 @@ def assert_only_one_success_message(messages): def assert_urls( ad_blocker: braveadblock.BraveAdBlocker, - urls: Iterable[Tuple[str, str, ResourceType]], + urls: Iterable[tuple[str, str, ResourceType]], should_be_blocked: bool, ) -> None: for (str_url, source_str_url, request_type) in urls: diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py index 79c2c7b7d..f611428af 100644 --- a/tests/unit/misc/test_ipc.py +++ b/tests/unit/misc/test_ipc.py @@ -12,7 +12,7 @@ import json import hashlib import dataclasses from unittest import mock -from typing import Optional, List +from typing import Optional import pytest from qutebrowser.qt.core import pyqtSignal, QObject @@ -631,7 +631,7 @@ class TestSendOrListen: no_err_windows: bool basedir: str - command: List[str] + command: list[str] target: Optional[str] @pytest.fixture diff --git a/tests/unit/misc/test_split.py b/tests/unit/misc/test_split.py index f8b700982..2e991dc51 100644 --- a/tests/unit/misc/test_split.py +++ b/tests/unit/misc/test_split.py @@ -5,7 +5,6 @@ """Tests for qutebrowser.misc.split.""" import dataclasses -from typing import List import pytest @@ -100,8 +99,8 @@ def _parse_split_test_data_str(): class TestCase: inp: str - keep: List[str] - no_keep: List[str] + keep: list[str] + no_keep: list[str] for line in test_data_str.splitlines(): if not line: diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py index cf7990393..677494ee5 100644 --- a/tests/unit/test_qt_machinery.py +++ b/tests/unit/test_qt_machinery.py @@ -9,7 +9,7 @@ import sys import html import argparse import typing -from typing import Any, Optional, List, Dict, Union, Type +from typing import Any, Optional, Union import dataclasses import pytest @@ -51,7 +51,7 @@ def undo_init(monkeypatch: pytest.MonkeyPatch) -> None: (machinery.NoWrapperAvailableError(machinery.SelectionInfo()), ImportError), ], ) -def test_importerror_exceptions(exception: Exception, base: Type[Exception]): +def test_importerror_exceptions(exception: Exception, base: type[Exception]): with pytest.raises(base): raise exception @@ -118,7 +118,7 @@ def test_selectioninfo_str(info: machinery.SelectionInfo, expected: str): @pytest.mark.parametrize("order", [["PyQt5", "PyQt6"], ["PyQt6", "PyQt5"]]) -def test_selectioninfo_str_wrapper_precedence(order: List[str]): +def test_selectioninfo_str_wrapper_precedence(order: list[str]): """The order of the wrappers should be the same as in machinery.WRAPPERS.""" info = machinery.SelectionInfo( wrapper="PyQt6", @@ -210,7 +210,7 @@ def modules(): ) def test_autoselect( stubs: Any, - available: Dict[str, Union[bool, Exception]], + available: dict[str, Union[bool, Exception]], expected: machinery.SelectionInfo, monkeypatch: pytest.MonkeyPatch, ): @@ -417,7 +417,7 @@ class TestInit: def test_none_available_implicit( self, stubs: Any, - modules: Dict[str, bool], + modules: dict[str, bool], monkeypatch: pytest.MonkeyPatch, qt_auto_env: None, ): @@ -441,7 +441,7 @@ class TestInit: def test_none_available_explicit( self, stubs: Any, - modules: Dict[str, bool], + modules: dict[str, bool], monkeypatch: pytest.MonkeyPatch, empty_args: argparse.Namespace, qt_auto_env: None, From eb8121ffd576d05c18d14747ed12dc8453ca9fa2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 16:13:08 +0200 Subject: [PATCH 223/403] Use Callable from collections.abc as well Did run with ruff pretending to use Python 3.10, because otherwise it won't reformat those: ruff check --select 'UP035' --fix --config 'target-version = "py310"' --unsafe-fixes This is because collections.abc.Callable inside Optional[...] and Union[...] is broken with Python 3.9.0 and 3.9.1: https://github.com/asottile/pyupgrade/issues/677 https://github.com/astral-sh/ruff/issues/2690 https://github.com/python/cpython/issues/87131 However, pylint can detect problematic usages (of which we only have one), so we might as well use the new thing everywhere possible for consistency. Also see #7098 --- qutebrowser/api/cmdutils.py | 4 ++-- qutebrowser/api/hook.py | 3 ++- qutebrowser/browser/browsertab.py | 4 ++-- qutebrowser/browser/commands.py | 3 ++- qutebrowser/browser/downloadview.py | 4 ++-- qutebrowser/browser/hints.py | 11 +++++++++-- qutebrowser/browser/qutescheme.py | 4 ++-- qutebrowser/browser/shared.py | 4 ++-- qutebrowser/browser/webengine/webengineelem.py | 4 ++-- qutebrowser/browser/webkit/mhtml.py | 3 +-- qutebrowser/commands/command.py | 5 ++--- qutebrowser/commands/runners.py | 4 ++-- qutebrowser/completion/models/util.py | 3 +-- qutebrowser/components/misccommands.py | 4 ++-- qutebrowser/components/readlinecommands.py | 4 ++-- qutebrowser/components/scrollcommands.py | 2 +- qutebrowser/config/config.py | 4 ++-- qutebrowser/config/configtypes.py | 4 ++-- qutebrowser/config/qtargs.py | 3 +++ qutebrowser/config/websettings.py | 3 ++- qutebrowser/extensions/interceptors.py | 3 ++- qutebrowser/extensions/loader.py | 4 ++-- qutebrowser/keyinput/modeman.py | 4 ++-- qutebrowser/misc/debugcachestats.py | 4 ++-- qutebrowser/misc/throttle.py | 4 ++-- qutebrowser/qt/_core_pyqtproperty.py | 2 +- qutebrowser/utils/debug.py | 4 ++-- qutebrowser/utils/docutils.py | 4 ++-- qutebrowser/utils/jinja.py | 4 ++-- qutebrowser/utils/message.py | 4 ++-- qutebrowser/utils/objreg.py | 4 ++-- qutebrowser/utils/utils.py | 4 ++-- tests/helpers/stubs.py | 3 ++- 33 files changed, 70 insertions(+), 58 deletions(-) diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py index 7fb95a934..3939dbb0a 100644 --- a/qutebrowser/api/cmdutils.py +++ b/qutebrowser/api/cmdutils.py @@ -35,8 +35,8 @@ Possible values: import inspect -from typing import Any, Callable, Protocol, Optional, cast -from collections.abc import Iterable +from typing import Any, Protocol, Optional, cast +from collections.abc import Iterable, Callable from qutebrowser.utils import qtutils from qutebrowser.commands import command, cmdexc diff --git a/qutebrowser/api/hook.py b/qutebrowser/api/hook.py index 9a1a7bc9c..f62514e6a 100644 --- a/qutebrowser/api/hook.py +++ b/qutebrowser/api/hook.py @@ -7,7 +7,8 @@ """Hooks for extensions.""" import importlib -from typing import Callable, Any +from typing import Any +from collections.abc import Callable from qutebrowser.extensions import loader diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index f64b413d9..74eacfcd0 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -9,8 +9,8 @@ import pathlib import itertools import functools import dataclasses -from typing import (cast, TYPE_CHECKING, Any, Callable, Optional, Union) -from collections.abc import Iterable, Sequence +from typing import (cast, TYPE_CHECKING, Any, Optional, Union) +from collections.abc import Iterable, Sequence, Callable from qutebrowser.qt import machinery from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 5135f0fa6..a62a00797 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -7,7 +7,8 @@ import os.path import shlex import functools -from typing import cast, Callable, Union, Optional +from typing import cast, Union, Optional +from collections.abc import Callable from qutebrowser.qt.widgets import QApplication, QTabBar from qutebrowser.qt.core import Qt, QUrl, QEvent, QUrlQuery diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 04bcd4cf7..5f67b344d 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -5,8 +5,8 @@ """The ListView to display downloads in.""" import functools -from typing import Callable, Union -from collections.abc import MutableSequence +from typing import Union +from collections.abc import MutableSequence, Callable from qutebrowser.qt.core import pyqtSlot, QSize, Qt from qutebrowser.qt.widgets import QListView, QSizePolicy, QMenu, QStyleFactory diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 2cb0cdfb6..b3f45610d 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -12,8 +12,15 @@ import html import enum import dataclasses from string import ascii_lowercase -from typing import (TYPE_CHECKING, Callable, Optional) -from collections.abc import Iterable, Iterator, Mapping, MutableSequence, Sequence +from typing import (TYPE_CHECKING, Optional) +from collections.abc import ( + Iterable, + Iterator, + Mapping, + MutableSequence, + Sequence, + Callable, +) from qutebrowser.qt.core import pyqtSignal, pyqtSlot, QObject, Qt, QUrl from qutebrowser.qt.widgets import QLabel diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 98d059502..a6a4e8763 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -18,8 +18,8 @@ import textwrap import urllib import collections import secrets -from typing import TypeVar, Callable, Optional, Union -from collections.abc import Sequence +from typing import TypeVar, Optional, Union +from collections.abc import Sequence, Callable from qutebrowser.qt.core import QUrlQuery, QUrl diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 4d22a4525..aab5b9876 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -10,8 +10,8 @@ import html import enum import netrc import tempfile -from typing import Callable, Optional -from collections.abc import Mapping, Iterable, Iterator +from typing import Optional +from collections.abc import Mapping, Iterable, Iterator, Callable from qutebrowser.qt.core import QUrl, pyqtBoundSignal diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py index b73459d3f..f65044998 100644 --- a/qutebrowser/browser/webengine/webengineelem.py +++ b/qutebrowser/browser/webengine/webengineelem.py @@ -5,8 +5,8 @@ """QtWebEngine specific part of the web element API.""" from typing import ( - TYPE_CHECKING, Any, Callable, Optional, Union) -from collections.abc import Iterator + TYPE_CHECKING, Any, Optional, Union) +from collections.abc import Iterator, Callable from qutebrowser.qt.core import QRect, QEventLoop from qutebrowser.qt.widgets import QApplication diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py index 98a715edc..11e381929 100644 --- a/qutebrowser/browser/webkit/mhtml.py +++ b/qutebrowser/browser/webkit/mhtml.py @@ -19,8 +19,7 @@ import email.mime.multipart import email.message import quopri import dataclasses -from typing import Callable -from collections.abc import MutableMapping +from collections.abc import MutableMapping, Callable from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index eb985dca0..620f6a4ae 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -9,9 +9,8 @@ import collections import traceback import typing import dataclasses -from typing import (Any, Union, Optional, - Callable) -from collections.abc import MutableMapping, MutableSequence +from typing import (Any, Union, Optional) +from collections.abc import MutableMapping, MutableSequence, Callable from qutebrowser.api import cmdutils from qutebrowser.commands import cmdexc, argparser diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index f12f795f2..636f1bf6b 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -7,8 +7,8 @@ import traceback import re import contextlib -from typing import TYPE_CHECKING, Callable -from collections.abc import Iterator, Mapping, MutableMapping +from typing import TYPE_CHECKING +from collections.abc import Iterator, Mapping, MutableMapping, Callable from qutebrowser.qt.core import pyqtSlot, QUrl, QObject diff --git a/qutebrowser/completion/models/util.py b/qutebrowser/completion/models/util.py index d1c646661..fb48017e8 100644 --- a/qutebrowser/completion/models/util.py +++ b/qutebrowser/completion/models/util.py @@ -4,8 +4,7 @@ """Utility functions for completion models.""" -from typing import Callable -from collections.abc import Sequence +from collections.abc import Sequence, Callable from qutebrowser.utils import usertypes from qutebrowser.misc import objects diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py index 64914bc95..b4eaa55d1 100644 --- a/qutebrowser/components/misccommands.py +++ b/qutebrowser/components/misccommands.py @@ -11,8 +11,8 @@ import os import signal import logging import pathlib -from typing import Optional, Callable -from collections.abc import Sequence +from typing import Optional +from collections.abc import Sequence, Callable try: import hunter diff --git a/qutebrowser/components/readlinecommands.py b/qutebrowser/components/readlinecommands.py index ca071448e..a26f7ea4c 100644 --- a/qutebrowser/components/readlinecommands.py +++ b/qutebrowser/components/readlinecommands.py @@ -5,8 +5,8 @@ """Bridge to provide readline-like shortcuts for QLineEdits.""" import os -from typing import Optional, Any, Callable -from collections.abc import Iterable, MutableMapping +from typing import Optional, Any +from collections.abc import Iterable, MutableMapping, Callable from qutebrowser.qt.widgets import QApplication, QLineEdit diff --git a/qutebrowser/components/scrollcommands.py b/qutebrowser/components/scrollcommands.py index 219f884a0..da4544bd7 100644 --- a/qutebrowser/components/scrollcommands.py +++ b/qutebrowser/components/scrollcommands.py @@ -4,7 +4,7 @@ """Scrolling-related commands.""" -from typing import Callable +from collections.abc import Callable from qutebrowser.api import cmdutils, apitypes diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index b24c9b502..d286bf733 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -7,8 +7,8 @@ import copy import contextlib import functools -from typing import (TYPE_CHECKING, Any, Callable, Optional, cast) -from collections.abc import Iterator, Mapping, MutableMapping, MutableSequence +from typing import (TYPE_CHECKING, Any, Optional, cast) +from collections.abc import Iterator, Mapping, MutableMapping, MutableSequence, Callable from qutebrowser.qt.core import pyqtSignal, QObject, QUrl diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index a74675cd4..d6782322f 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -36,9 +36,9 @@ import functools import operator import json import dataclasses -from typing import (Any, Callable, Optional, Union) +from typing import Any, Optional, Union from re import Pattern -from collections.abc import Iterable, Iterator, Sequence +from collections.abc import Iterable, Iterator, Sequence, Callable import yaml from qutebrowser.qt.core import QUrl, Qt diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py index 994af4619..cafc6ff38 100644 --- a/qutebrowser/config/qtargs.py +++ b/qutebrowser/config/qtargs.py @@ -8,6 +8,9 @@ import os import sys import argparse import pathlib +# Using deprecated typing.Callable as a WORKAROUND because +# collections.abc.Callable inside Optional[...]/Union[...] +# is broken on Python 3.9.0 and 3.9.1 from typing import Any, Optional, Union, Callable from collections.abc import Iterator, Sequence diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index f2ce7081d..2b71216bd 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -8,7 +8,8 @@ import re import argparse import functools import dataclasses -from typing import Any, Callable, Optional, Union +from typing import Any, Optional, Union +from collections.abc import Callable from qutebrowser.qt.core import QUrl, pyqtSlot, qVersion from qutebrowser.qt.gui import QFont diff --git a/qutebrowser/extensions/interceptors.py b/qutebrowser/extensions/interceptors.py index 1d7ef437e..1032fc6d0 100644 --- a/qutebrowser/extensions/interceptors.py +++ b/qutebrowser/extensions/interceptors.py @@ -6,7 +6,8 @@ import enum import dataclasses -from typing import Callable, Optional +from typing import Optional +from collections.abc import Callable from qutebrowser.qt.core import QUrl diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index d658466f2..a6917be35 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -10,8 +10,8 @@ import pathlib import importlib import argparse import dataclasses -from typing import Callable, Optional -from collections.abc import Iterator +from typing import Optional +from collections.abc import Iterator, Callable from qutebrowser.qt.core import pyqtSlot diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index 9eb0bc17e..681deeff6 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -6,8 +6,8 @@ import functools import dataclasses -from typing import Callable, Union, cast -from collections.abc import Mapping, MutableMapping +from typing import Union, cast +from collections.abc import Mapping, MutableMapping, Callable from qutebrowser.qt import machinery from qutebrowser.qt.core import pyqtSlot, pyqtSignal, Qt, QObject, QEvent diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index 70f088418..7deb8bdcd 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -9,8 +9,8 @@ dependencies as possible to avoid cyclic dependencies. """ import weakref -from typing import Any, Callable, Optional, TypeVar -from collections.abc import Mapping +from typing import Any, Optional, TypeVar +from collections.abc import Mapping, Callable from qutebrowser.utils import log diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py index f4c50c09a..78bc7f29b 100644 --- a/qutebrowser/misc/throttle.py +++ b/qutebrowser/misc/throttle.py @@ -6,8 +6,8 @@ import dataclasses import time -from typing import Any, Callable, Optional -from collections.abc import Mapping, Sequence +from typing import Any, Optional +from collections.abc import Mapping, Sequence, Callable from qutebrowser.qt.core import QObject diff --git a/qutebrowser/qt/_core_pyqtproperty.py b/qutebrowser/qt/_core_pyqtproperty.py index 02c1cab97..50752de41 100644 --- a/qutebrowser/qt/_core_pyqtproperty.py +++ b/qutebrowser/qt/_core_pyqtproperty.py @@ -9,7 +9,7 @@ https://github.com/python-qt-tools/PyQt5-stubs/blob/5.15.6.0/PyQt5-stubs/QtCore. """ # flake8: noqa -# pylint: disable=invalid-name,missing-class-docstring,too-many-arguments,redefined-builtin,unused-argument +# pylint: disable=invalid-name,missing-class-docstring,too-many-arguments,redefined-builtin,unused-argument,deprecated-typing-alias import typing from PyQt6.QtCore import QObject, pyqtSignal diff --git a/qutebrowser/utils/debug.py b/qutebrowser/utils/debug.py index b5cddbd2d..de7f87f1e 100644 --- a/qutebrowser/utils/debug.py +++ b/qutebrowser/utils/debug.py @@ -12,8 +12,8 @@ import functools import datetime import types from typing import ( - Any, Callable, Optional, Union) -from collections.abc import Mapping, MutableSequence, Sequence + Any, Optional, Union) +from collections.abc import Mapping, MutableSequence, Sequence, Callable from qutebrowser.qt.core import Qt, QEvent, QMetaMethod, QObject, pyqtBoundSignal diff --git a/qutebrowser/utils/docutils.py b/qutebrowser/utils/docutils.py index 0aa860726..c357a2cd4 100644 --- a/qutebrowser/utils/docutils.py +++ b/qutebrowser/utils/docutils.py @@ -10,8 +10,8 @@ import inspect import os.path import collections import enum -from typing import Any, Callable, Optional, Union -from collections.abc import MutableMapping +from typing import Any, Optional, Union +from collections.abc import MutableMapping, Callable import qutebrowser from qutebrowser.utils import log, utils diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index ede9dbb40..c12bac5aa 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -10,8 +10,8 @@ import posixpath import functools import contextlib import html -from typing import Any, Callable -from collections.abc import Iterator +from typing import Any +from collections.abc import Iterator, Callable import jinja2 import jinja2.nodes diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index e122fbc8b..8fc8f6fbe 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -10,8 +10,8 @@ import dataclasses import traceback -from typing import Any, Callable, Union, Optional -from collections.abc import Iterable +from typing import Any, Union, Optional +from collections.abc import Iterable, Callable from qutebrowser.qt.core import pyqtSignal, pyqtBoundSignal, QObject diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 7d3df728a..aa5bfc7f9 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -7,9 +7,9 @@ import collections import functools -from typing import (TYPE_CHECKING, Any, Callable, +from typing import (TYPE_CHECKING, Any, Optional, Union) -from collections.abc import MutableMapping, MutableSequence, Sequence +from collections.abc import MutableMapping, MutableSequence, Sequence, Callable from qutebrowser.qt.core import QObject, QTimer from qutebrowser.qt.widgets import QApplication diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 178e4a306..3f127e37f 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -19,10 +19,10 @@ import contextlib import shlex import sysconfig import mimetypes -from typing import (Any, Callable, IO, +from typing import (Any, IO, Optional, Union, TypeVar, Protocol) -from collections.abc import Iterator, Sequence +from collections.abc import Iterator, Sequence, Callable from qutebrowser.qt.core import QUrl, QVersionNumber, QRect, QPoint from qutebrowser.qt.gui import QClipboard, QDesktopServices diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py index 0aa4f1732..50528ee8b 100644 --- a/tests/helpers/stubs.py +++ b/tests/helpers/stubs.py @@ -6,7 +6,8 @@ """Fake objects/stubs.""" -from typing import Any, Callable +from typing import Any +from collections.abc import Callable from unittest import mock import contextlib import shutil From 088b5973eb20fe666c57408243303b57332e7874 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 16:44:59 +0200 Subject: [PATCH 224/403] Update mimetype overrides See https://github.com/python/cpython/commits/main/Lib/mimetypes.py --- doc/changelog.asciidoc | 6 ++++++ qutebrowser/utils/utils.py | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 49688dac1..4919fccc5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -38,6 +38,12 @@ Added - **Planned:** Full support for Python 3.13, with Windows/macOS binaries using it if possible. +Changed +~~~~~~~ + +- Updated mimetype information for getting a suitable extension when downloading + a `data:` URL. + [[v3.3.1]] v3.3.1 (2024-10-12) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index 3f127e37f..9c506471d 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -777,14 +777,38 @@ def mimetype_extension(mimetype: str) -> Optional[str]: This mostly delegates to Python's mimetypes.guess_extension(), but backports some changes (via a simple override dict) which are missing from earlier Python versions. - Most likely, this can be dropped once the minimum Python version is raised to 3.10. """ - overrides = { - # Added in 3.10.0 - "application/x-hdf5": ".h5", - # Added in 3.9.0 - "application/manifest+json": ".webmanifest", - } + overrides = {} + if sys.version_info[:2] < (3, 13): + overrides.update({ + "text/rtf": ".rtf", + "text/markdown": ".md", + "text/x-rst": ".rst", + }) + if sys.version_info[:2] < (3, 12): + overrides.update({ + "text/javascript": ".js", + }) + if sys.version_info[:2] < (3, 11): + overrides.update({ + "application/n-quads": ".nq", + "application/n-triples": ".nt", + "application/trig": ".trig", + "image/avif": ".avif", + "image/webp": ".webp", + "text/n3": ".n3", + "text/vtt": ".vtt", + }) + if sys.version_info[:2] < (3, 10): + overrides.update({ + "application/x-hdf5": ".h5", + "audio/3gpp": ".3gp", + "audio/3gpp2": ".3g2", + "audio/aac": ".aac", + "audio/opus": ".opus", + "image/heic": ".heic", + "image/heif": ".heif", + }) if mimetype in overrides: return overrides[mimetype] return mimetypes.guess_extension(mimetype, strict=False) From 0fd6fc19f20392653c832107deabab3e1f6d7fb9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 17:33:38 +0200 Subject: [PATCH 225/403] recompile_requirements: Fix with diff.mnemonicPrefix set --- scripts/dev/recompile_requirements.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py index 5c0622141..f844e82a8 100644 --- a/scripts/dev/recompile_requirements.py +++ b/scripts/dev/recompile_requirements.py @@ -161,8 +161,11 @@ def parse_args(): def git_diff(*args): """Run a git diff command.""" - command = (['git', '--no-pager', 'diff'] + list(args) + [ - '--', 'requirements.txt', 'misc/requirements/requirements-*.txt']) + command = ( + ["git", "--no-pager", "-c", "diff.mnemonicPrefix=false", "diff"] + + list(args) + + ["--", "requirements.txt", "misc/requirements/requirements-*.txt"] + ) proc = subprocess.run(command, stdout=subprocess.PIPE, encoding='utf-8', From cc18a624b5e9499baa79eb4585c7360351c20b78 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 17:48:41 +0200 Subject: [PATCH 226/403] Remove importlib_resources from requirements --- misc/requirements/requirements-dev.txt-raw | 1 + misc/requirements/requirements-mypy.txt-raw | 3 --- misc/requirements/requirements-pylint.txt-raw | 1 - misc/requirements/requirements-qutebrowser.txt-raw | 5 ----- misc/requirements/requirements-tests.txt-raw | 1 - 5 files changed, 1 insertion(+), 10 deletions(-) diff --git a/misc/requirements/requirements-dev.txt-raw b/misc/requirements/requirements-dev.txt-raw index fc991474c..8bf0d9772 100644 --- a/misc/requirements/requirements-dev.txt-raw +++ b/misc/requirements/requirements-dev.txt-raw @@ -9,6 +9,7 @@ twine # Included to override setuptools' vendored version that is being included in # the lock file by pip freeze. +importlib_resources platformdirs # Already included via test requirements diff --git a/misc/requirements/requirements-mypy.txt-raw b/misc/requirements/requirements-mypy.txt-raw index 027f4fef6..683e8bec7 100644 --- a/misc/requirements/requirements-mypy.txt-raw +++ b/misc/requirements/requirements-mypy.txt-raw @@ -6,6 +6,3 @@ PyQt5-stubs types-PyYAML types-colorama types-Pygments - -# So stubs are available even on newer Python versions -importlib_resources diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw index 99a2cf02f..78da3a1a3 100644 --- a/misc/requirements/requirements-pylint.txt-raw +++ b/misc/requirements/requirements-pylint.txt-raw @@ -7,7 +7,6 @@ pefile # fix qute-pylint location #@ replace: qute[_-]pylint.* ./scripts/dev/pylint_checkers -#@ markers: typed-ast python_version<"3.8" # Already included via test requirements #@ ignore: urllib3 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index ca4081d1d..586049b82 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -12,12 +12,7 @@ PyYAML #@ add: pyobjc-core ; sys_platform=="darwin" #@ add: pyobjc-framework-Cocoa ; sys_platform=="darwin" -## stdlib backports -importlib_resources - ## Optional dependencies Pygments # For :view-source --pygments or on QtWebKit colorama # Colored log output on Windows adblock # Improved adblocking - -#@ markers: importlib_resources python_version=="3.8.*" diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 58984f164..1df954e53 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -39,7 +39,6 @@ tldextract # Include them here even though we don't need them to make sure we at least # get an up to date version. importlib_resources -#@ markers: importlib_resources python_version=="3.8.*" jaraco.context platformdirs From 7083fee6550cb220b11e95c77d321fb1835309db Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 18:10:49 +0200 Subject: [PATCH 227/403] Recompile requirements --- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-mypy.txt | 4 +--- misc/requirements/requirements-pylint.txt | 4 ++-- misc/requirements/requirements-pyroma.txt | 2 +- misc/requirements/requirements-sphinx.txt | 20 ++++++++++---------- misc/requirements/requirements-tests.txt | 8 ++++---- requirements.txt | 4 +--- 7 files changed, 21 insertions(+), 25 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 2d9404147..955fb8380 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -8,7 +8,7 @@ certifi==2024.8.30 cffi==1.17.1 charset-normalizer==3.4.0 cryptography==43.0.1 -docutils==0.20.1 +docutils==0.21.2 github3.py==4.0.1 hunter==3.7.0 idna==3.10 @@ -37,7 +37,7 @@ Pympler==1.1 pyproject_hooks==1.2.0 PyQt-builder==1.16.4 python-dateutil==2.9.0.post0 -readme_renderer==43.0 +readme_renderer==44.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 30a44e2b2..ba04eaa33 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -2,10 +2,9 @@ chardet==5.2.0 diff_cover==9.2.0 -importlib_resources==6.4.5 Jinja2==3.1.4 lxml==5.3.0 -MarkupSafe==2.1.5 +MarkupSafe==3.0.1 mypy==1.11.2 mypy-extensions==1.0.0 pluggy==1.5.0 @@ -18,4 +17,3 @@ types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 types-setuptools==75.1.0.20240917 typing_extensions==4.12.2 -zipp==3.20.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index d2c903ce6..d5e1c9031 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,6 +1,6 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==3.2.4 +astroid==3.3.5 certifi==2024.8.30 cffi==1.17.1 charset-normalizer==3.4.0 @@ -14,7 +14,7 @@ pefile==2024.8.26 platformdirs==4.3.6 pycparser==2.22 PyJWT==2.9.0 -pylint==3.2.7 +pylint==3.3.1 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 0df8ecead..821878286 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -3,7 +3,7 @@ build==1.2.2.post1 certifi==2024.8.30 charset-normalizer==3.4.0 -docutils==0.20.1 +docutils==0.21.2 idna==3.10 importlib_metadata==8.5.0 packaging==24.1 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 8bc582b4f..a46869097 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -1,26 +1,26 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -alabaster==0.7.13 +alabaster==0.7.16 babel==2.16.0 certifi==2024.8.30 charset-normalizer==3.4.0 -docutils==0.20.1 +docutils==0.21.2 idna==3.10 imagesize==1.4.1 importlib_metadata==8.5.0 Jinja2==3.1.4 -MarkupSafe==2.1.5 +MarkupSafe==3.0.1 packaging==24.1 Pygments==2.18.0 -pytz==2024.2 requests==2.32.3 snowballstemmer==2.2.0 -Sphinx==7.1.2 -sphinxcontrib-applehelp==1.0.4 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==2.0.1 +Sphinx==7.4.7 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 +tomli==2.0.2 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c04bb74d6..c933bfb09 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,16 +9,16 @@ certifi==2024.8.30 charset-normalizer==3.4.0 cheroot==10.0.1 click==8.1.7 -coverage==7.6.1 +coverage==7.6.2 exceptiongroup==1.2.2 execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.113.0 +hypothesis==6.115.0 idna==3.10 importlib_metadata==8.5.0 -importlib_resources==6.4.5 ; python_version=="3.8.*" +importlib_resources==6.4.5 inflect==7.3.1 iniconfig==2.0.0 itsdangerous==2.2.0 @@ -29,7 +29,7 @@ jaraco.text==3.12.1 # Jinja2==3.1.4 Mako==1.3.5 manhole==1.8.1 -# MarkupSafe==2.1.5 +# MarkupSafe==3.0.1 more-itertools==10.5.0 packaging==24.1 parse==1.20.2 diff --git a/requirements.txt b/requirements.txt index 4102f1af6..7c3a0abd4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,10 @@ adblock==0.6.0 colorama==0.4.6 -importlib_resources==6.4.5 ; python_version=="3.8.*" Jinja2==3.1.4 -MarkupSafe==2.1.5 +MarkupSafe==3.0.1 Pygments==2.18.0 PyYAML==6.0.2 -zipp==3.20.2 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" From 2ab963cef88bdc9b42bdffc74a0751c7b6a85d68 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 18:11:12 +0200 Subject: [PATCH 228/403] Remove pytz changelog URL --- scripts/dev/changelog_urls.json | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index b614a91d8..215696bf9 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -71,7 +71,6 @@ "babel": "https://github.com/python-babel/babel/blob/master/CHANGES.rst", "alabaster": "https://alabaster.readthedocs.io/en/latest/changelog.html", "imagesize": "https://github.com/shibukawa/imagesize_py/commits/master", - "pytz": "https://mm.icann.org/pipermail/tz-announce/", "sphinxcontrib-applehelp": "https://www.sphinx-doc.org/en/master/changes.html", "sphinxcontrib-devhelp": "https://www.sphinx-doc.org/en/master/changes.html", "sphinxcontrib-htmlhelp": "https://www.sphinx-doc.org/en/master/changes.html", From ab7d04a951adcd59b2b5f93e5180c9239a636f34 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 18:12:23 +0200 Subject: [PATCH 229/403] Fix wrong type annotation This was wrong ever since 0a835ecd926cc334f5f0347e6d0b809dcfd86d0f, but due to the version conditional usage, mypy did not check it. --- qutebrowser/misc/debugcachestats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutebrowser/misc/debugcachestats.py b/qutebrowser/misc/debugcachestats.py index 7deb8bdcd..b12995c5c 100644 --- a/qutebrowser/misc/debugcachestats.py +++ b/qutebrowser/misc/debugcachestats.py @@ -10,13 +10,13 @@ dependencies as possible to avoid cyclic dependencies. import weakref from typing import Any, Optional, TypeVar -from collections.abc import Mapping, Callable +from collections.abc import MutableMapping, Callable from qutebrowser.utils import log # The callable should be a lru_cache wrapped function -_CACHE_FUNCTIONS: Mapping[str, Any] = weakref.WeakValueDictionary() +_CACHE_FUNCTIONS: MutableMapping[str, Any] = weakref.WeakValueDictionary() _T = TypeVar('_T', bound=Callable[..., Any]) From f175f611f8f8a9613c7ce5ae296ae9483e0dca49 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 18:18:00 +0200 Subject: [PATCH 230/403] pylint: Disable too-many-positional-arguments Added in 3.3.0: https://pylint.pycqa.org/en/latest/whatsnew/3/3.3/index.html Some of those arguments could probably indeed be keyword-only, but for some of the functions shown by pylint, those are qutebrowser command handlers where a positional argument has different semantics. --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index f304cf1ac..3830f8d44 100644 --- a/.pylintrc +++ b/.pylintrc @@ -45,6 +45,7 @@ disable=locally-disabled, too-many-locals, too-many-branches, too-many-statements, + too-many-positional-arguments, too-few-public-methods, import-outside-toplevel, consider-using-f-string, From ff5d4d35641863aaec740c5b5923ae5d90072a35 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 13 Oct 2024 19:08:42 +0200 Subject: [PATCH 231/403] Avoid passing a parent to QProcess With the upgrade to MarkupSafe 3.0, something funny happened when trying to pass the GUIProcess object to jinja after launching a userscript: [...] File "[...]/qutebrowser/browser/qutescheme.py", line 291, in qute_process src = jinja.render('process.html', title=f'Process {pid}', proc=proc) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "[...]/qutebrowser/utils/jinja.py", line 123, in render return environment.get_template(template).render(**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [...] File "html/process.html", line 11, in block 'content' File "[...]/lib/python3.11/site-packages/markupsafe/__init__.py", line 42, in escape if hasattr(s, "__html__"): ^^^^^^^^^^^^^^^^^^^^^^ RuntimeError: wrapped C/C++ object of type GUIProcess has been deleted This can be reproduced with: qutebrowser --temp-basedir ':cmd-later 0 spawn -u -o /bin/echo test' We pass the `GUIProcess` to the Jinja template as `proc`, which then formats it as `{{ proc }}`` (to stringify it). For some reason, with the newest MarkupSafe/Jinja versions, this now triggers the `if hasattr(s, "__html__")` check in MarkupSafe (which has been around for a while). That then presumably causes PyQt to try and access the underlying C++ object for `GUIProcess``, but that has already been deleted. But why is it deleted in the first place, if we keep track of even completed processes data ever since we added `:process` in a3adba81c? It looks like the Qt parent-child relationship is the culprit here: When we pass a parent to the `GUIProcess`` from the userscript runner, it will get deleted as soon as said runner is cleaned up (which happens after the userscript has finished). We probably never noticed this before because we only accessed data from the Python wrapper and not from the C++ side, but it still seems like a good idea to avoid passing a parent for a long-lived object (with time-based cleanup) in the first place. --- doc/changelog.asciidoc | 6 ++++++ qutebrowser/browser/commands.py | 3 +-- qutebrowser/commands/userscripts.py | 2 +- qutebrowser/misc/editor.py | 2 +- qutebrowser/misc/guiprocess.py | 5 +++-- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 4919fccc5..c1b8f1a2f 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -44,6 +44,12 @@ Changed - Updated mimetype information for getting a suitable extension when downloading a `data:` URL. +Fixed +~~~~~ + +- Crash with recent Jinja/Markupsafe versions when viewing a finished userscript + (or potentially editor) process via `:process`. + [[v3.3.1]] v3.3.1 (2024-10-12) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index a62a00797..f686f522c 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1137,8 +1137,7 @@ class CommandDispatcher: else: cmd = os.path.expanduser(cmd) proc = guiprocess.GUIProcess(what='command', verbose=verbose, - output_messages=output_messages, - parent=self._tabbed_browser) + output_messages=output_messages) if detach: ok = proc.start_detached(cmd, args) if not ok: diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py index e2f24d28d..e929de3d2 100644 --- a/qutebrowser/commands/userscripts.py +++ b/qutebrowser/commands/userscripts.py @@ -156,7 +156,7 @@ class _BaseUserscriptRunner(QObject): self.proc = guiprocess.GUIProcess( 'userscript', additional_env=self._env, - output_messages=output_messages, verbose=verbose, parent=self) + output_messages=output_messages, verbose=verbose) self.proc.finished.connect(self.on_proc_finished) self.proc.error.connect(self.on_proc_error) self.proc.start(cmd, args) diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 948b4ab9e..9f77fa75e 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -180,7 +180,7 @@ class ExternalEditor(QObject): line: the line number to pass to the editor column: the column number to pass to the editor """ - self._proc = guiprocess.GUIProcess(what='editor', parent=self) + self._proc = guiprocess.GUIProcess(what='editor') self._proc.finished.connect(self._on_proc_closed) self._proc.error.connect(self._on_proc_error) editor = config.val.editor.command diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 6b1dcbf6b..2e4f33748 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -177,9 +177,10 @@ class GUIProcess(QObject): verbose: bool = False, additional_env: Mapping[str, str] = None, output_messages: bool = False, - parent: QObject = None, ): - super().__init__(parent) + # We do not accept a parent, as GUIProcesses keep track of themselves + # (see all_processes and _post_start() / _on_cleanup_timer()) + super().__init__() self.what = what self.verbose = verbose self._output_messages = output_messages From cc3c1e2050ad37fb60f5c57ae9c05d7a6ec6e010 Mon Sep 17 00:00:00 2001 From: toofar Date: Tue, 15 Oct 2024 18:28:35 +1300 Subject: [PATCH 232/403] Enable pylint Too many positional arguments warning This re-enables the pylint too-many-positional-arguments for the main application code. It's still disabled for tests because that's how you pull in pytlint fixtures, and I don't think we want to push people into being creative with fixtures just to get around that. When functions are called with many positional arguments the reader has to do a bit of heavy lifting to figure out in what position a value is being passed, and it's easier to make mistakes. So I would like to encourage using keyword arguments for long argument lists. I've set the `max-positional-arguments` to a completely arbitrary 7, from a completely arbitrary 5, because there were many more violations under 7. If like 99% of our functions fit under 7 it's probably fine. Regarding the exceptions: * objreg.register: I grepped it and it looks like everything is only passing the first two args as positional already, lucky! * `_get_color_percentage`: only one usage of it, but I was in "add directive comment" mode * update_3rdparty.py: only one usage, already using kwargs * pyqtProperty: idk * commands.py: "its complicated". Many methods in this file map to commands used in qutebrowser's command mode. In that case it's usual for them to be called as flags, rather than positional. But it could be complicated to wade into that, and having one file excluded isn't so bad. --- .pylintrc | 4 ++-- qutebrowser/browser/commands.py | 2 ++ qutebrowser/qt/_core_pyqtproperty.py | 2 +- qutebrowser/utils/objreg.py | 1 + qutebrowser/utils/qtutils.py | 8 +++++--- scripts/dev/run_pylint_on_tests.py | 1 + scripts/dev/update_3rdparty.py | 2 +- 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.pylintrc b/.pylintrc index 3830f8d44..1cb4e0f1b 100644 --- a/.pylintrc +++ b/.pylintrc @@ -45,7 +45,6 @@ disable=locally-disabled, too-many-locals, too-many-branches, too-many-statements, - too-many-positional-arguments, too-few-public-methods, import-outside-toplevel, consider-using-f-string, @@ -72,7 +71,8 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$ variable-rgx=[a-z_][a-z0-9_]{0,30}$ docstring-min-length=3 no-docstring-rgx=(^_|^main$) -class-const-naming-style = snake_case +class-const-naming-style=snake_case +max-positional-arguments=7 [FORMAT] # FIXME:v4 (lint) down to 88 again once we use black diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index f686f522c..65d6635a3 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +# pylint: disable=too-many-positional-arguments + """Command dispatcher for TabbedBrowser.""" import os.path diff --git a/qutebrowser/qt/_core_pyqtproperty.py b/qutebrowser/qt/_core_pyqtproperty.py index 50752de41..c2b034df3 100644 --- a/qutebrowser/qt/_core_pyqtproperty.py +++ b/qutebrowser/qt/_core_pyqtproperty.py @@ -33,7 +33,7 @@ if typing.TYPE_CHECKING: ) class pyqtProperty: - def __init__( + def __init__( # pylint: disable=too-many-positional-arguments self, type: typing.Union[type, str], fget: typing.Optional[typing.Callable[[QObjectT], TPropertyTypeVal]] = None, diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index aa5bfc7f9..c027b3cf6 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -241,6 +241,7 @@ def get(name: str, def register(name: str, obj: Any, + *, update: bool = False, scope: str = None, registry: ObjectRegistry = None, diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 765eddd05..a027db74a 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -529,9 +529,11 @@ class EventLoop(QEventLoop): return status -def _get_color_percentage(x1: int, y1: int, z1: int, a1: int, - x2: int, y2: int, z2: int, a2: int, - percent: int) -> tuple[int, int, int, int]: +def _get_color_percentage( # pylint: disable=too-many-positional-arguments + x1: int, y1: int, z1: int, a1: int, + x2: int, y2: int, z2: int, a2: int, + percent: int +) -> tuple[int, int, int, int]: """Get a color which is percent% interpolated between start and end. Args: diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index 580ef988f..d55caaf36 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -40,6 +40,7 @@ def main(): 'redefined-outer-name', 'unused-argument', 'too-many-arguments', + 'too-many-positional-arguments', # things which are okay in tests 'missing-docstring', 'protected-access', diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py index 1258be106..7ad27c915 100755 --- a/scripts/dev/update_3rdparty.py +++ b/scripts/dev/update_3rdparty.py @@ -171,7 +171,7 @@ def test_dicts(): print('ERROR: {}'.format(response.status)) -def run(nsis=False, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, +def run(*, nsis=False, ace=False, pdfjs=True, legacy_pdfjs=False, fancy_dmg=False, pdfjs_version=None, dicts=False, gh_token=None): """Update components based on the given arguments.""" if nsis: From af835c26ad5a3f4a3a4249bc92e3dc95131de423 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 15 Oct 2024 12:00:25 +0200 Subject: [PATCH 233/403] Update changelog --- doc/changelog.asciidoc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 3eb58df94..78082a8ac 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -29,6 +29,9 @@ Removed ~~~~~~~ - Support for Python 3.8 is dropped, and Python 3.9 is now required. +- When using the installer on Windows 10, build 1809 or newer is now required + (previous versions required 1607 or newer, but that's not officialy supported by + Qt upstream). Added ~~~~~ From 2aacfe1a8c7cf6230e2e287d5aefc0cc586c1a1a Mon Sep 17 00:00:00 2001 From: Marcel Schilling Date: Tue, 15 Oct 2024 15:17:45 +0200 Subject: [PATCH 234/403] Rewrite `.feature` files to use the `"""` syntax fixes #8342 --- tests/end2end/features/backforward.feature | 12 ++ tests/end2end/features/caret.feature | 4 + tests/end2end/features/completion.feature | 2 + tests/end2end/features/downloads.feature | 6 + tests/end2end/features/editor.feature | 10 + tests/end2end/features/hints.feature | 6 + tests/end2end/features/history.feature | 6 + tests/end2end/features/invoke.feature | 14 ++ tests/end2end/features/misc.feature | 12 ++ tests/end2end/features/notifications.feature | 2 + tests/end2end/features/open.feature | 14 ++ tests/end2end/features/private.feature | 12 ++ tests/end2end/features/prompts.feature | 8 + tests/end2end/features/qutescheme.feature | 4 + tests/end2end/features/search.feature | 6 + tests/end2end/features/sessions.feature | 34 ++++ tests/end2end/features/spawn.feature | 4 + tests/end2end/features/tabs.feature | 196 +++++++++++++++++++ tests/end2end/features/urlmarks.feature | 12 ++ tests/end2end/features/utilcmds.feature | 2 + tests/end2end/features/yankpaste.feature | 26 +++ 21 files changed, 392 insertions(+) diff --git a/tests/end2end/features/backforward.feature b/tests/end2end/features/backforward.feature index d60fde645..c85ede539 100644 --- a/tests/end2end/features/backforward.feature +++ b/tests/end2end/features/backforward.feature @@ -13,12 +13,14 @@ Feature: Going back and forward. And I wait until data/backforward/2.txt is loaded And I reload Then the session should look like: + """ windows: - tabs: - history: - url: http://localhost:*/data/backforward/1.txt - active: true url: http://localhost:*/data/backforward/2.txt + """ # https://travis-ci.org/qutebrowser/qutebrowser/jobs/157941720 @qtwebengine_flaky @@ -29,6 +31,7 @@ Feature: Going back and forward. And I run :back -t And I wait until data/backforward/1.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -40,6 +43,7 @@ Feature: Going back and forward. - active: true url: http://localhost:*/data/backforward/1.txt - url: http://localhost:*/data/backforward/2.txt + """ Scenario: Going back in a new tab without history Given I open data/backforward/1.txt @@ -47,12 +51,14 @@ Feature: Going back and forward. And I run :back -t Then the error "At beginning of history." should be shown Then the session should look like: + """ windows: - tabs: - active: true history: - active: true url: http://localhost:*/data/backforward/1.txt + """ Scenario: Going back in a new background tab Given I open data/backforward/1.txt @@ -61,6 +67,7 @@ Feature: Going back and forward. And I run :back -b And I wait until data/backforward/1.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -72,6 +79,7 @@ Feature: Going back and forward. - active: true url: http://localhost:*/data/backforward/1.txt - url: http://localhost:*/data/backforward/2.txt + """ @flaky Scenario: Going back with count. @@ -83,6 +91,7 @@ Feature: Going back and forward. And I wait until data/backforward/1.txt is loaded And I reload Then the session should look like: + """ windows: - tabs: - history: @@ -90,6 +99,7 @@ Feature: Going back and forward. url: http://localhost:*/data/backforward/1.txt - url: http://localhost:*/data/backforward/2.txt - url: http://localhost:*/data/backforward/3.txt + """ Scenario: Going back too much with count. Given I open data/backforward/1.txt @@ -114,6 +124,7 @@ Feature: Going back and forward. And I run :back -w And I wait until data/backforward/1.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -129,6 +140,7 @@ Feature: Going back and forward. - active: true url: http://localhost:*/data/backforward/1.txt - url: http://localhost:*/data/backforward/2.txt + """ Scenario: Going back without history Given I open data/backforward/1.txt diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature index d6e65440c..04f88e743 100644 --- a/tests/end2end/features/caret.feature +++ b/tests/end2end/features/caret.feature @@ -53,8 +53,10 @@ Feature: Caret mode And I run :selection-follow --tab Then data/hello.txt should be loaded And the following tabs should be open: + """ - data/caret.html - data/hello.txt (active) + """ Scenario: :selection-follow with --tab (without JS) When I set content.javascript.enabled to false @@ -65,8 +67,10 @@ Feature: Caret mode And I run :selection-follow --tab Then data/hello.txt should be loaded And the following tabs should be open: + """ - data/caret.html - data/hello.txt + """ @flaky Scenario: :selection-follow with link tabbing (without JS) diff --git a/tests/end2end/features/completion.feature b/tests/end2end/features/completion.feature index 800858c4c..81df5bb1d 100644 --- a/tests/end2end/features/completion.feature +++ b/tests/end2end/features/completion.feature @@ -91,8 +91,10 @@ Feature: Using completion And I run :tab-move 1 And I run :tab-select hello2.txt Then the following tabs should be open: + """ - data/hello2.txt (active) - data/hello.txt + """ Scenario: Space updates completion model after selecting full command When I run :cmd-set-text :set diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature index 58a1e498a..6fe38841e 100644 --- a/tests/end2end/features/downloads.feature +++ b/tests/end2end/features/downloads.feature @@ -186,8 +186,10 @@ Feature: Downloading things from a website. And I run :download-retry And I wait for the error "Download error: * - server replied: NOT FOUND" Then the requests should be: + """ does-not-exist does-not-exist + """ @flaky Scenario: Retrying with count @@ -197,9 +199,11 @@ Feature: Downloading things from a website. And I run :download-retry with count 2 And I wait for the error "Download error: * - server replied: NOT FOUND" Then the requests should be: + """ data/downloads/download.bin does-not-exist does-not-exist + """ Scenario: Retrying with two failed downloads When I run :download http://localhost:(port)/does-not-exist @@ -209,9 +213,11 @@ Feature: Downloading things from a website. And I run :download-retry And I wait for the error "Download error: * - server replied: NOT FOUND" Then the requests should be: + """ does-not-exist does-not-exist-2 does-not-exist + """ Scenario: Retrying a download which does not exist When I run :download-retry with count 42 diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature index 018d65b9f..8b813667a 100644 --- a/tests/end2end/features/editor.feature +++ b/tests/end2end/features/editor.feature @@ -15,8 +15,10 @@ Feature: Opening external editors And I run :edit-url -t Then data/numbers/2.txt should be loaded And the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) + """ Scenario: Editing a URL with -rt When I set tabs.new_position.related to prev @@ -26,8 +28,10 @@ Feature: Opening external editors And I run :edit-url -rt Then data/numbers/2.txt should be loaded And the following tabs should be open: + """ - data/numbers/2.txt (active) - data/numbers/1.txt + """ Scenario: Editing a URL with -b When I run :tab-only @@ -36,8 +40,10 @@ Feature: Opening external editors And I run :edit-url -b Then data/numbers/2.txt should be loaded And the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt + """ Scenario: Editing a URL with -w When I run :window-only @@ -47,6 +53,7 @@ Feature: Opening external editors And I run :edit-url -w Then data/numbers/2.txt should be loaded And the session should look like: + """ windows: - tabs: - active: true @@ -58,6 +65,7 @@ Feature: Opening external editors history: - active: true url: http://localhost:*/data/numbers/2.txt + """ Scenario: Editing a URL with -p When I open data/numbers/1.txt in a new tab @@ -67,6 +75,7 @@ Feature: Opening external editors And I run :edit-url -p Then data/numbers/2.txt should be loaded And the session should look like: + """ windows: - tabs: - active: true @@ -79,6 +88,7 @@ Feature: Opening external editors - active: true url: http://localhost:*/data/numbers/2.txt private: true + """ Scenario: Editing a URL with -t and -b When I run :edit-url -t -b diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index b2a549fb5..710b90f04 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -41,8 +41,10 @@ Feature: Using hints And I hint with args "links normal" and follow a And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hints/link_blank.html - data/hello.txt + """ # https://github.com/qutebrowser/qutebrowser/issues/7842 @qtwebkit_skip @@ -160,6 +162,7 @@ Feature: Using hints # We should check what the active tab is, but for some reason that makes # the test flaky Then the session should look like: + """ windows: - tabs: - history: @@ -168,6 +171,7 @@ Feature: Using hints - url: http://localhost:*/data/hello.txt - history: - url: http://localhost:*/data/hello2.txt + """ Scenario: Using hint --rapid to hit multiple buttons When I open data/hints/buttons.html @@ -295,8 +299,10 @@ Feature: Using hints And I hint with args "links tab" and follow s And I wait until data/hello2.txt is loaded Then the following tabs should be open: + """ - data/hints/iframe_target.html (active) - data/hello2.txt + """ Scenario: Clicking on iframe with :hint all current When I open data/hints/iframe.html diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index caec8017b..603f0c3e0 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -9,8 +9,10 @@ Feature: Page history When I open data/numbers/1.txt And I open data/numbers/2.txt Then the history should contain: + """ http://localhost:(port)/data/numbers/1.txt http://localhost:(port)/data/numbers/2.txt + """ Scenario: History item with title When I open data/title.html @@ -21,8 +23,10 @@ Feature: Page history When I open redirect-to?url=data/title.html without waiting And I wait until data/title.html is loaded Then the history should contain: + """ r http://localhost:(port)/redirect-to?url=data/title.html Test title http://localhost:(port)/data/title.html Test title + """ Scenario: History item with spaces in URL When I open data/title with spaces.html @@ -72,8 +76,10 @@ Feature: Page history When I open data/hints/html/simple.html And I hint with args "--add-history links yank" and follow a Then the history should contain: + """ http://localhost:(port)/data/hints/html/simple.html Simple link http://localhost:(port)/data/hello.txt + """ @flaky Scenario: Listing history diff --git a/tests/end2end/features/invoke.feature b/tests/end2end/features/invoke.feature index d9c472ec4..14ce9c4bf 100644 --- a/tests/end2end/features/invoke.feature +++ b/tests/end2end/features/invoke.feature @@ -17,14 +17,17 @@ Feature: Invoking a new process And I open data/title.html And I open data/search.html as a URL Then the following tabs should be open: + """ - data/title.html (active) - data/search.html + """ Scenario: Using new_instance_open_target = window When I set new_instance_open_target to window And I open data/title.html And I open data/search.html as a URL Then the session should look like: + """ windows: - tabs: - history: @@ -33,12 +36,14 @@ Feature: Invoking a new process - tabs: - history: - url: http://localhost:*/data/search.html + """ Scenario: Using new_instance_open_target = private-window When I set new_instance_open_target to private-window And I open data/title.html And I open data/search.html as a URL Then the session should look like: + """ windows: - tabs: - history: @@ -48,6 +53,7 @@ Feature: Invoking a new process tabs: - history: - url: http://localhost:*/data/search.html + """ Scenario: Using new_instance_open_target_window = last-opened When I set new_instance_open_target to tab @@ -56,6 +62,7 @@ Feature: Invoking a new process And I open data/search.html in a new window And I open data/hello.txt as a URL Then the session should look like: + """ windows: - tabs: - history: @@ -66,6 +73,7 @@ Feature: Invoking a new process - url: http://localhost:*/data/search.html - history: - url: http://localhost:*/data/hello.txt + """ Scenario: Using new_instance_open_target_window = first-opened When I set new_instance_open_target to tab @@ -74,6 +82,7 @@ Feature: Invoking a new process And I open data/search.html in a new window And I open data/hello.txt as a URL Then the session should look like: + """ windows: - tabs: - history: @@ -84,6 +93,7 @@ Feature: Invoking a new process - tabs: - history: - url: http://localhost:*/data/search.html + """ # issue #1060 @@ -96,6 +106,7 @@ Feature: Invoking a new process And I wait until data/search.html is loaded And I open data/hello.txt as a URL Then the session should look like: + """ windows: - tabs: - history: @@ -106,6 +117,7 @@ Feature: Invoking a new process - tabs: - history: - url: http://localhost:*/data/search.html + """ Scenario: Opening a new qutebrowser instance with no parameters When I set new_instance_open_target to tab @@ -114,6 +126,7 @@ Feature: Invoking a new process And I spawn a new window And I wait until data/hello.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -122,3 +135,4 @@ Feature: Invoking a new process - tabs: - history: - url: http://localhost:*/data/hello.txt + """ diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index c66d89d40..d3da8cbb9 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -162,12 +162,14 @@ Feature: Various utility commands. And I run :debug-webaction Back And I wait until data/backforward/1.txt is loaded Then the session should look like: + """ windows: - tabs: - history: - active: true url: http://localhost:*/data/backforward/1.txt - url: http://localhost:*/data/backforward/2.txt + """ Scenario: :debug-webaction with invalid value When I open data/hello.txt @@ -212,8 +214,10 @@ Feature: Various utility commands. And I open redirect-later-continue in a new tab And I wait 1s Then the unordered requests should be: + """ redirect-later-continue redirect-later?delay=-1 + """ # no request on / because we stopped the redirect Scenario: :stop with wrong count @@ -227,8 +231,10 @@ Feature: Various utility commands. And I run :reload And I wait until data/reload.txt is loaded Then the requests should be: + """ data/reload.txt data/reload.txt + """ Scenario: :reload with force When I open headers @@ -251,6 +257,7 @@ Feature: Various utility commands. When I run :tab-only And I run :view-source Then the session should look like: + """ windows: - tabs: - history: @@ -258,6 +265,7 @@ Feature: Various utility commands. url: http://localhost:*/data/hello.txt - active: true history: [] + """ And the page should contain the html "/* Literal.Number.Integer */" # Flaky due to :view-source being async? @@ -438,11 +446,13 @@ Feature: Various utility commands. And I wait for "Closing window *" in the log And I wait for "removed: main-window" in the log Then the session should look like: + """ windows: - tabs: - active: true history: - url: http://localhost:*/data/hello3.txt + """ ## :click-element @@ -467,8 +477,10 @@ Feature: Various utility commands. And I run :click-element id link --target=tab Then data/hello.txt should be loaded And the following tabs should be open: + """ - data/click_element.html - data/hello.txt (active) + """ Scenario: Clicking an element by CSS selector When I open data/click_element.html diff --git a/tests/end2end/features/notifications.feature b/tests/end2end/features/notifications.feature index 8f12afd6a..498f2d72c 100644 --- a/tests/end2end/features/notifications.feature +++ b/tests/end2end/features/notifications.feature @@ -123,9 +123,11 @@ Feature: Notifications And I click the notification Then the javascript message "notification clicked" should be logged And the following tabs should be open: + """ - about:blank - data/javascript/notifications.html (active) - about:blank + """ @pyqtwebengine<5.15.0 Scenario: User clicks presented notification (old Qt) diff --git a/tests/end2end/features/open.feature b/tests/end2end/features/open.feature index 62c12aade..da915ca49 100644 --- a/tests/end2end/features/open.feature +++ b/tests/end2end/features/open.feature @@ -6,6 +6,7 @@ Feature: Opening pages And I wait until data/numbers/1.txt is loaded And I run :tab-only Then the session should look like: + """ windows: - tabs: - active: true @@ -13,6 +14,7 @@ Feature: Opening pages - url: about:blank - active: true url: http://localhost:*/data/numbers/1.txt + """ Scenario: :open without URL When I set url.default_page to http://localhost:(port)/data/numbers/11.txt @@ -46,8 +48,10 @@ Feature: Opening pages And I run :open -t http://localhost:(port)/data/numbers/4.txt And I wait until data/numbers/4.txt is loaded Then the following tabs should be open: + """ - about:blank - data/numbers/4.txt (active) + """ Scenario: Opening in a new background tab Given I open about:blank @@ -55,8 +59,10 @@ Feature: Opening pages And I run :open -b http://localhost:(port)/data/numbers/5.txt And I wait until data/numbers/5.txt is loaded Then the following tabs should be open: + """ - about:blank (active) - data/numbers/5.txt + """ Scenario: :open with count Given I open about:blank @@ -65,6 +71,7 @@ Feature: Opening pages And I run :open http://localhost:(port)/data/numbers/6.txt with count 2 And I wait until data/numbers/6.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -74,6 +81,7 @@ Feature: Opening pages - url: about:blank - active: true url: http://localhost:*/data/numbers/6.txt + """ Scenario: Opening in a new tab (unrelated) Given I open about:blank @@ -83,8 +91,10 @@ Feature: Opening pages And I run :open -t http://localhost:(port)/data/numbers/7.txt And I wait until data/numbers/7.txt is loaded Then the following tabs should be open: + """ - about:blank - data/numbers/7.txt (active) + """ Scenario: Opening in a new tab (related) Given I open about:blank @@ -94,8 +104,10 @@ Feature: Opening pages And I run :open -t --related http://localhost:(port)/data/numbers/8.txt And I wait until data/numbers/8.txt is loaded Then the following tabs should be open: + """ - data/numbers/8.txt (active) - about:blank + """ Scenario: Opening in a new window Given I open about:blank @@ -103,6 +115,7 @@ Feature: Opening pages And I run :open -w http://localhost:(port)/data/numbers/9.txt And I wait until data/numbers/9.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -114,6 +127,7 @@ Feature: Opening pages history: - active: true url: http://localhost:*/data/numbers/9.txt + """ Scenario: Opening a quickmark When I run :quickmark-add http://localhost:(port)/data/numbers/10.txt quickmarktest diff --git a/tests/end2end/features/private.feature b/tests/end2end/features/private.feature index 14e9cbef7..35473dd62 100644 --- a/tests/end2end/features/private.feature +++ b/tests/end2end/features/private.feature @@ -73,6 +73,7 @@ Feature: Using private browsing And I run :navigate -w increment And I wait until data/numbers/2.txt is loaded Then the session should look like: + """ windows: - private: True tabs: @@ -82,6 +83,7 @@ Feature: Using private browsing tabs: - history: - url: http://localhost:*/data/numbers/2.txt + """ Scenario: Opening private window with :navigate next # Private window handled in navigate.py @@ -90,6 +92,7 @@ Feature: Using private browsing And I run :navigate -w next And I wait until data/navigate/next.html is loaded Then the session should look like: + """ windows: - private: True tabs: @@ -99,6 +102,7 @@ Feature: Using private browsing tabs: - history: - url: http://localhost:*/data/navigate/next.html + """ Scenario: Opening private window with :tab-clone When I open data/hello.txt in a private window @@ -106,6 +110,7 @@ Feature: Using private browsing And I run :tab-clone -w And I wait until data/hello.txt is loaded Then the session should look like: + """ windows: - private: True tabs: @@ -115,6 +120,7 @@ Feature: Using private browsing tabs: - history: - url: http://localhost:*/data/hello.txt + """ Scenario: Opening private window via :click-element When I open data/click_element.html in a private window @@ -122,6 +128,7 @@ Feature: Using private browsing And I run :click-element --target window id link And I wait until data/hello.txt is loaded Then the session should look like: + """ windows: - private: True tabs: @@ -131,6 +138,7 @@ Feature: Using private browsing tabs: - history: - url: http://localhost:*/data/hello.txt + """ Scenario: Skipping private window when saving session When I open data/hello.txt in a private window @@ -163,12 +171,14 @@ Feature: Using private browsing And I run :quickmark-load two And I wait until data/numbers/2.txt is loaded Then the session should look like: + """ windows: - private: True tabs: - history: - url: http://localhost:*/data/numbers/1.txt - url: http://localhost:*/data/numbers/2.txt + """ @skip # Too flaky Scenario: Saving a private session with only-active-window @@ -188,6 +198,7 @@ Feature: Using private browsing And I run :session-load -c window_session_name And I wait until data/numbers/5.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -197,6 +208,7 @@ Feature: Using private browsing - history: - active: true url: http://localhost:*/data/numbers/5.txt + """ # https://github.com/qutebrowser/qutebrowser/issues/5810 diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 9e2062d13..4b36371af 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -372,10 +372,12 @@ Feature: Prompts And I run :prompt-accept And I wait until basic-auth/user1/password1 is loaded Then the json on the page should be: + """ { "authenticated": true, "user": "user1" } + """ Scenario: Authentication with :prompt-accept value When I open about:blank in a new tab @@ -384,10 +386,12 @@ Feature: Prompts And I run :prompt-accept user2:password2 And I wait until basic-auth/user2/password2 is loaded Then the json on the page should be: + """ { "authenticated": true, "user": "user2" } + """ Scenario: Authentication with invalid :prompt-accept value When I open about:blank in a new tab @@ -410,10 +414,12 @@ Feature: Prompts And I run :prompt-accept And I wait until basic-auth/user4/password4 is loaded Then the json on the page should be: + """ { "authenticated": true, "user": "user4" } + """ @qtwebengine_skip Scenario: Cancelling webpage authentication with QtWebKit @@ -517,10 +523,12 @@ Feature: Prompts And I wait until basic-auth/user5/password5 is loaded # We're on the second page Then the json on the page should be: + """ { "authenticated": true, "user": "user6" } + """ # https://github.com/qutebrowser/qutebrowser/issues/1249#issuecomment-175205531 # https://github.com/qutebrowser/qutebrowser/pull/2054#issuecomment-258285544 diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature index f05e58eb8..f5b2aba2c 100644 --- a/tests/end2end/features/qutescheme.feature +++ b/tests/end2end/features/qutescheme.feature @@ -43,8 +43,10 @@ Feature: Special qute:// pages And I run :help -t And I wait until qute://help/index.html is loaded Then the following tabs should be open: + """ - about:blank - qute://help/index.html (active) + """ # https://github.com/qutebrowser/qutebrowser/issues/2513 Scenario: Opening link with qute:help @@ -103,8 +105,10 @@ Feature: Special qute:// pages And I run :history -t And I wait until qute://history/ is loaded Then the following tabs should be open: + """ - about:blank - qute://history/ (active) + """ # qute://settings diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature index 38c474db2..0ef3da173 100644 --- a/tests/end2end/features/search.feature +++ b/tests/end2end/features/search.feature @@ -344,8 +344,10 @@ Feature: Searching on a page And I run :selection-follow -t And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/search.html - data/hello.txt (active) + """ Scenario: Don't follow searched text When I run :window-only @@ -374,8 +376,10 @@ Feature: Searching on a page And I run :selection-follow -t And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/search.html - data/hello.txt (active) + """ @qtwebkit_skip: Not supported in qtwebkit @skip Scenario: Follow a searched link in an iframe @@ -397,8 +401,10 @@ Feature: Searching on a page And I run :selection-follow -t And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/iframe_search.html - data/hello.txt (active) + """ Scenario: Closing a tab during a search When I run :open -b about:blank diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature index 9a61baf61..1d352fbf8 100644 --- a/tests/end2end/features/sessions.feature +++ b/tests/end2end/features/sessions.feature @@ -7,6 +7,7 @@ Feature: Saving and loading sessions When I open data/hello.txt And I open data/title.html in a new tab Then the session should look like: + """ windows: - active: true tabs: @@ -19,12 +20,14 @@ Feature: Saving and loading sessions - active: true url: http://localhost:*/data/title.html title: Test title + """ @qtwebengine_skip Scenario: Zooming (qtwebkit) When I open data/hello.txt And I run :zoom 50 Then the session should look like: + """ windows: - tabs: - history: @@ -32,6 +35,7 @@ Feature: Saving and loading sessions zoom: 1.0 - url: http://localhost:*/data/hello.txt zoom: 0.5 + """ # The zoom level is only stored for the newest element for QtWebEngine. @qtwebkit_skip @@ -39,18 +43,21 @@ Feature: Saving and loading sessions When I open data/hello.txt And I run :zoom 50 Then the session should look like: + """ windows: - tabs: - history: - url: about:blank - url: http://localhost:*/data/hello.txt zoom: 0.5 + """ @qtwebengine_skip Scenario: Scrolling (qtwebkit) When I open data/scroll/simple.html And I run :scroll-px 10 20 Then the session should look like: + """ windows: - tabs: - history: @@ -62,6 +69,7 @@ Feature: Saving and loading sessions scroll-pos: x: 10 y: 20 + """ # The scroll position is only stored for the newest element for QtWebEngine. @qtwebkit_skip @@ -70,6 +78,7 @@ Feature: Saving and loading sessions And I run :scroll-px 10 20 And I wait until the scroll position changed to 10/20 Then the session should look like: + """ windows: - tabs: - history: @@ -78,10 +87,12 @@ Feature: Saving and loading sessions scroll-pos: x: 10 y: 20 + """ Scenario: Redirect When I open redirect-to?url=data/title.html without waiting And I wait until data/title.html is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -90,16 +101,19 @@ Feature: Saving and loading sessions url: http://localhost:*/data/title.html original-url: http://localhost:*/redirect-to?url=data/title.html title: Test title + """ Scenario: Valid UTF-8 data When I open data/sessions/snowman.html Then the session should look like: + """ windows: - tabs: - history: - url: about:blank - url: http://localhost:*/data/sessions/snowman.html title: snow☃man + """ @qtwebengine_skip Scenario: Long output comparison (qtwebkit) @@ -109,6 +123,7 @@ Feature: Saving and loading sessions And I open data/numbers/3.txt in a new window # Full output apart from "geometry:" and the active window (needs qutewm) Then the session should look like: + """ windows: - tabs: - history: @@ -150,6 +165,7 @@ Feature: Saving and loading sessions title: '' url: http://localhost:*/data/numbers/3.txt zoom: 1.0 + """ # FIXME:qtwebengine what's up with the titles there? @qtwebkit_skip @@ -160,6 +176,7 @@ Feature: Saving and loading sessions And I open data/numbers/3.txt in a new window # Full output apart from "geometry:" and the active window (needs qutewm) Then the session should look like: + """ windows: - tabs: - history: @@ -193,26 +210,31 @@ Feature: Saving and loading sessions title: localhost:*/data/numbers/3.txt url: http://localhost:*/data/numbers/3.txt zoom: 1.0 + """ Scenario: Saving with --no-history When I open data/numbers/1.txt And I open data/numbers/2.txt And I open data/numbers/3.txt Then the session saved with --no-history should look like: + """ windows: - tabs: - history: - url: http://localhost:*/data/numbers/3.txt + """ Scenario: Saving with --no-history and --only-active-window When I open data/numbers/1.txt And I open data/numbers/2.txt And I open data/numbers/3.txt Then the session saved with --no-history --only-active-window should look like: + """ windows: - tabs: - history: - url: http://localhost:*/data/numbers/3.txt + """ # https://github.com/qutebrowser/qutebrowser/issues/879 @@ -220,6 +242,7 @@ Feature: Saving and loading sessions When I open data/sessions/history_replace_state.html without waiting Then the javascript message "Called history.replaceState" should be logged And the session should look like: + """ windows: - tabs: - history: @@ -227,6 +250,7 @@ Feature: Saving and loading sessions - active: true url: http://localhost:*/data/sessions/history_replace_state.html?state=2 title: Test title + """ @qtwebengine_skip Scenario: Saving a session with a page using history.replaceState() and navigating away (qtwebkit) @@ -234,6 +258,7 @@ Feature: Saving and loading sessions And I open data/hello.txt Then the javascript message "Called history.replaceState" should be logged And the session should look like: + """ windows: - tabs: - history: @@ -244,6 +269,7 @@ Feature: Saving and loading sessions title: http://localhost:*/data/sessions/history_replace_state.html?state=2 - active: true url: http://localhost:*/data/hello.txt + """ # Seems like that bug is fixed upstream in QtWebEngine @skip # Too flaky @@ -252,6 +278,7 @@ Feature: Saving and loading sessions And I wait for "* Called history.replaceState" in the log And I open data/hello.txt Then the session should look like: + """ windows: - tabs: - history: @@ -260,6 +287,7 @@ Feature: Saving and loading sessions title: Test title - active: true url: http://localhost:*/data/hello.txt + """ # :session-save @@ -314,6 +342,7 @@ Feature: Saving and loading sessions And I wait until data/numbers/4.txt is loaded And I wait until data/numbers/5.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -327,6 +356,7 @@ Feature: Saving and loading sessions - history: - active: true url: http://localhost:*/data/numbers/5.txt + """ # https://github.com/qutebrowser/qutebrowser/issues/7696 @qtwebkit_skip @@ -339,6 +369,7 @@ Feature: Saving and loading sessions And I run :session-load --clear current And I wait until data/downloads/downloads.html is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -347,6 +378,7 @@ Feature: Saving and loading sessions url: http://localhost:*/data/downloads/downloads.html - active: true history: [] + """ # :session-delete @@ -436,7 +468,9 @@ Feature: Saving and loading sessions And I open data/numbers/4.txt Then the message "Tab is pinned! Opening in new tab." should be shown And the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) (pinned) - data/numbers/4.txt - data/numbers/3.txt + """ diff --git a/tests/end2end/features/spawn.feature b/tests/end2end/features/spawn.feature index 563259fc8..bac6ff8c9 100644 --- a/tests/end2end/features/spawn.feature +++ b/tests/end2end/features/spawn.feature @@ -54,8 +54,10 @@ Feature: :spawn And I run :spawn -u (testdata)/userscripts/open_current_url And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hello.txt - data/hello.txt (active) + """ @posix Scenario: Running :spawn with userscript and count @@ -75,8 +77,10 @@ Feature: :spawn And I run :spawn -u (testdata)/userscripts/open_current_url.bat And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hello.txt - data/hello.txt (active) + """ @posix Scenario: Running :spawn with userscript that expects the stdin getting closed diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 942ee2028..40cd7a2a5 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -14,8 +14,10 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-close Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) + """ Scenario: :tab-close with count When I open data/numbers/1.txt @@ -23,8 +25,10 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-close with count 1 Then the following tabs should be open: + """ - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-close with invalid count When I open data/numbers/1.txt @@ -32,9 +36,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-close with count 23 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-close with tabs.select_on_remove = next When I set tabs.select_on_remove to next @@ -44,8 +50,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) + """ Scenario: :tab-close with tabs.select_on_remove = prev When I set tabs.select_on_remove to prev @@ -55,8 +63,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/3.txt + """ Scenario: :tab-close with tabs.select_on_remove = last-used When I set tabs.select_on_remove to last-used @@ -67,9 +77,11 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt - data/numbers/4.txt (active) + """ Scenario: :tab-close with tabs.select_on_remove = prev and --next When I set tabs.select_on_remove to prev @@ -79,8 +91,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close --next Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) + """ Scenario: :tab-close with tabs.select_on_remove = next and --prev When I set tabs.select_on_remove to next @@ -90,8 +104,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close --prev Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/3.txt + """ Scenario: :tab-close with tabs.select_on_remove = prev and --opposite When I set tabs.select_on_remove to prev @@ -101,8 +117,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close --opposite Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) + """ Scenario: :tab-close with tabs.select_on_remove = next and --opposite When I set tabs.select_on_remove to next @@ -112,8 +130,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close --opposite Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/3.txt + """ Scenario: :tab-close with tabs.select_on_remove = last-used and --opposite When I set tabs.select_on_remove to last-used @@ -131,8 +151,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-close Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/4.txt (active) + """ # :tab-only @@ -151,8 +173,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-only --prev Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) + """ Scenario: :tab-only with --next When I open data/numbers/1.txt @@ -161,8 +185,10 @@ Feature: Tab management And I run :tab-focus 2 And I run :tab-only --next Then the following tabs should be open: + """ - data/numbers/2.txt (active) - data/numbers/3.txt + """ Scenario: :tab-only with --prev and --next When I run :tab-only --prev --next @@ -180,9 +206,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-focus 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) - data/numbers/3.txt + """ Scenario: :tab-focus without index/count When I open data/numbers/1.txt @@ -192,9 +220,11 @@ Feature: Tab management And I run :tab-focus Then the warning "Using :tab-focus without count is deprecated, use :tab-next instead." should be shown And the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-focus with invalid index When I run :tab-focus 23 @@ -210,9 +240,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-focus with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) - data/numbers/3.txt + """ Scenario: :tab-focus with count and index When I open data/numbers/1.txt @@ -220,9 +252,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-focus 4 with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) - data/numbers/3.txt + """ Scenario: :tab-focus last When I open data/numbers/1.txt @@ -232,9 +266,11 @@ Feature: Tab management And I run :tab-focus 3 And I run :tab-focus last Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: :tab-focus with current tab number When I open data/numbers/1.txt @@ -244,9 +280,11 @@ Feature: Tab management And I run :tab-focus 3 And I run :tab-focus 3 Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: :tab-focus with current tab number and --no-last When I open data/numbers/1.txt @@ -256,9 +294,11 @@ Feature: Tab management And I run :tab-focus 3 And I run :tab-focus --no-last 3 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-focus with -1 When I open data/numbers/1.txt @@ -267,9 +307,11 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-focus -1 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-focus negative index When I open data/numbers/1.txt @@ -277,9 +319,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-focus -2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) - data/numbers/3.txt + """ Scenario: :tab-focus with invalid negative index When I open data/numbers/1.txt @@ -305,11 +349,13 @@ Feature: Tab management And I run :tab-focus 3 And I run :cmd-repeat 2 tab-focus stack-prev Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) - data/numbers/3.txt - data/numbers/4.txt - data/numbers/5.txt + """ Scenario: :tab-focus next stacking When I open data/numbers/1.txt @@ -325,11 +371,13 @@ Feature: Tab management And I run :cmd-repeat 3 tab-focus stack-prev And I run :cmd-repeat 2 tab-focus stack-next Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt - data/numbers/4.txt (active) - data/numbers/5.txt + """ Scenario: :tab-focus stacking limit When I set tabs.focus_stack_size to 1 @@ -349,11 +397,13 @@ Feature: Tab management And I run :cmd-repeat 4 tab-focus stack-prev Then the error "Could not find requested tab!" should be shown And the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt - data/numbers/4.txt - data/numbers/5.txt + """ Scenario: :tab-focus stacking and last When I open data/numbers/1.txt @@ -369,11 +419,13 @@ Feature: Tab management And I run :cmd-repeat 2 tab-focus stack-prev And I run :cmd-repeat 3 tab-focus last Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt - data/numbers/4.txt (active) - data/numbers/5.txt + """ Scenario: :tab-focus last after moving current tab @@ -383,9 +435,11 @@ Feature: Tab management And I run :tab-move 2 And I run :tab-focus last Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt - data/numbers/2.txt (active) + """ Scenario: :tab-focus last after closing a lower number tab When I open data/numbers/1.txt @@ -394,8 +448,10 @@ Feature: Tab management And I run :tab-close with count 1 And I run :tab-focus last Then the following tabs should be open: + """ - data/numbers/2.txt (active) - data/numbers/3.txt + """ # tab-prev/tab-next @@ -404,8 +460,10 @@ Feature: Tab management And I open data/numbers/2.txt in a new tab And I run :tab-prev Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt + """ Scenario: :tab-next When I open data/numbers/1.txt @@ -413,8 +471,10 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-next Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) + """ Scenario: :tab-prev with count When I open data/numbers/1.txt @@ -422,9 +482,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-prev with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: :tab-next with count When I open data/numbers/1.txt @@ -433,9 +495,11 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-next with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-prev on first tab without wrap When I set tabs.wrap to false @@ -457,9 +521,11 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-prev Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-next with last tab with wrap When I set tabs.wrap to true @@ -468,9 +534,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-next Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: :tab-next with last tab, wrap and count When I set tabs.wrap to true @@ -479,9 +547,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-next with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (active) - data/numbers/3.txt + """ # :tab-move @@ -491,9 +561,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move Then the following tabs should be open: + """ - data/numbers/3.txt (active) - data/numbers/1.txt - data/numbers/2.txt + """ Scenario: :tab-move with absolute position and count. When I open data/numbers/1.txt @@ -501,9 +573,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) - data/numbers/2.txt + """ Scenario: :tab-move with absolute position and invalid count. When I open data/numbers/1.txt @@ -512,9 +586,11 @@ Feature: Tab management And I run :tab-move with count 23 Then the error "Can't move tab to position 23!" should be shown And the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-move with index. When I open data/numbers/1.txt @@ -522,9 +598,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) - data/numbers/2.txt + """ Scenario: :tab-move with negative index. When I open data/numbers/1.txt @@ -532,9 +610,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move -3 Then the following tabs should be open: + """ - data/numbers/3.txt (active) - data/numbers/1.txt - data/numbers/2.txt + """ Scenario: :tab-move with invalid index. When I open data/numbers/1.txt @@ -543,9 +623,11 @@ Feature: Tab management And I run :tab-move -5 Then the error "Can't move tab to position -1!" should be shown And the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-move with index and count. When I open data/numbers/1.txt @@ -553,9 +635,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move 1 with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) - data/numbers/2.txt + """ Scenario: :tab-move with index and invalid count. When I open data/numbers/1.txt @@ -564,9 +648,11 @@ Feature: Tab management And I run :tab-move -2 with count 4 Then the error "Can't move tab to position 4!" should be shown And the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-move with relative position (negative). When I open data/numbers/1.txt @@ -574,9 +660,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move - Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) - data/numbers/2.txt + """ Scenario: :tab-move with relative position (positive). When I open data/numbers/1.txt @@ -585,9 +673,11 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-move + Then the following tabs should be open: + """ - data/numbers/2.txt - data/numbers/1.txt (active) - data/numbers/3.txt + """ Scenario: :tab-move with relative position (negative) and count. When I open data/numbers/1.txt @@ -595,9 +685,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move - with count 2 Then the following tabs should be open: + """ - data/numbers/3.txt (active) - data/numbers/1.txt - data/numbers/2.txt + """ Scenario: :tab-move with relative position and too big count. When I set tabs.wrap to false @@ -615,9 +707,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move + Then the following tabs should be open: + """ - data/numbers/3.txt (active) - data/numbers/1.txt - data/numbers/2.txt + """ Scenario: :tab-move with relative position (negative), wrap and count When I set tabs.wrap to true @@ -627,9 +721,11 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-move - with count 8 Then the following tabs should be open: + """ - data/numbers/2.txt - data/numbers/1.txt (active) - data/numbers/3.txt + """ Scenario: :tab-move with absolute position When I open data/numbers/1.txt @@ -638,9 +734,11 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-move end Then the following tabs should be open: + """ - data/numbers/2.txt - data/numbers/3.txt - data/numbers/1.txt (active) + """ Scenario: :tab-move with absolute position When I open data/numbers/1.txt @@ -648,9 +746,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-move start Then the following tabs should be open: + """ - data/numbers/3.txt (active) - data/numbers/1.txt - data/numbers/2.txt + """ Scenario: Make sure :tab-move retains metadata When I open data/title.html @@ -658,6 +758,7 @@ Feature: Tab management And I run :tab-focus 1 And I run :tab-move + Then the session should look like: + """ windows: - tabs: - history: @@ -667,6 +768,7 @@ Feature: Tab management - url: about:blank - url: http://localhost:*/data/title.html title: Test title + """ # :tab-clone @@ -679,6 +781,7 @@ Feature: Tab management And I run :tab-clone And I wait until data/title.html is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -690,6 +793,7 @@ Feature: Tab management - url: about:blank - url: http://localhost:*/data/title.html title: Test title + """ Scenario: Cloning zoom value When I open data/hello.txt @@ -697,6 +801,7 @@ Feature: Tab management And I run :tab-clone And I wait until data/hello.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -708,20 +813,24 @@ Feature: Tab management - url: about:blank - url: http://localhost:*/data/hello.txt zoom: 1.2 + """ Scenario: Cloning to background tab When I open data/hello2.txt And I run :tab-clone -b And I wait until data/hello2.txt is loaded Then the following tabs should be open: + """ - data/hello2.txt (active) - data/hello2.txt + """ Scenario: Cloning to new window When I open data/title.html And I run :tab-clone -w And I wait until data/title.html is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -735,6 +844,7 @@ Feature: Tab management - url: about:blank - url: http://localhost:*/data/title.html title: Test title + """ Scenario: Cloning with tabs_are_windows = true When I open data/title.html @@ -742,6 +852,7 @@ Feature: Tab management And I run :tab-clone And I wait until data/title.html is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -755,12 +866,14 @@ Feature: Tab management - url: about:blank - url: http://localhost:*/data/title.html title: Test title + """ Scenario: Cloning to private window When I open data/title.html And I run :tab-clone -p And I wait until data/title.html is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -775,6 +888,7 @@ Feature: Tab management - url: about:blank - url: http://localhost:*/data/title.html title: Test title + """ # https://github.com/qutebrowser/qutebrowser/issues/2289 @@ -801,6 +915,7 @@ Feature: Tab management And I run :undo And I wait until data/numbers/3.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -810,6 +925,7 @@ Feature: Tab management history: - url: http://localhost:*/data/numbers/2.txt - url: http://localhost:*/data/numbers/3.txt + """ @qtwebengine_flaky Scenario: Undo with auto-created last tab @@ -878,9 +994,11 @@ Feature: Tab management And I run :tab-close with count 1 And I run :undo Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: Undo a tab closed after switching tabs When I open data/numbers/1.txt @@ -890,9 +1008,11 @@ Feature: Tab management And I run :tab-focus 2 And I run :undo Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: Undo a tab closed after rearranging tabs When I open data/numbers/1.txt @@ -902,9 +1022,11 @@ Feature: Tab management And I run :tab-move with count 1 And I run :undo Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/3.txt - data/numbers/2.txt + """ @flaky Scenario: Undo a tab closed after new tab opened @@ -915,9 +1037,11 @@ Feature: Tab management And I run :undo And I wait until data/numbers/1.txt is loaded Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ Scenario: Undo the closing of tabs using :tab-only When I open data/numbers/1.txt @@ -927,9 +1051,11 @@ Feature: Tab management And I run :tab-only And I run :undo Then the following tabs should be open: + """ - data/numbers/1.txt (active) - data/numbers/2.txt - data/numbers/3.txt + """ # :undo --window @@ -942,6 +1068,7 @@ Feature: Tab management And I run :undo -w And I wait for "Focus object changed: *" in the log Then the session should look like: + """ windows: - tabs: - active: true @@ -953,6 +1080,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/numbers/2.txt + """ Scenario: Undo the closing of a window with multiple tabs Given I clear the log @@ -964,6 +1092,7 @@ Feature: Tab management And I run :undo -w And I wait for "Focus object changed: *" in the log Then the session should look like: + """ windows: - tabs: - active: true @@ -977,6 +1106,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/numbers/3.txt + """ Scenario: Undo the closing of a window with multiple tabs with undo stack Given I clear the log @@ -990,6 +1120,7 @@ Feature: Tab management And I run :undo And I wait for "Focus object changed: *" in the log Then the session should look like: + """ windows: - tabs: - active: true @@ -1003,6 +1134,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/numbers/3.txt + """ Scenario: Undo the closing of a window with tabs are windows Given I clear the log @@ -1015,6 +1147,7 @@ Feature: Tab management And I run :undo -w And I wait for "Focus object changed: *" in the log Then the session should look like: + """ windows: - tabs: - active: true @@ -1025,6 +1158,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/numbers/2.txt + """ # :undo with count @@ -1036,8 +1170,10 @@ Feature: Tab management And I run :tab-close And I run :undo with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/3.txt (active) + """ Scenario: Undo with a too-high count When I open data/numbers/1.txt @@ -1076,8 +1212,10 @@ Feature: Tab management And I wait until data/numbers/7.txt is loaded And I wait until data/numbers/8.txt is loaded Then the following tabs should be open: + """ - data/numbers/7.txt - data/numbers/8.txt (active) + """ Scenario: tabs.last_close = default-page When I set url.default_page to http://localhost:(port)/data/numbers/9.txt @@ -1104,8 +1242,10 @@ Feature: Tab management And I hint with args "all tab" and follow a And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hints/html/simple.html (active) - data/hello.txt + """ Scenario: opening tab with tabs.new_position.related prev When I set tabs.new_position.related to prev @@ -1115,9 +1255,11 @@ Feature: Tab management And I run :click-element id link --target=tab And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - about:blank - data/hello.txt (active) - data/hints/html/simple.html + """ Scenario: opening tab with tabs.new_position.related next When I set tabs.new_position.related to next @@ -1127,9 +1269,11 @@ Feature: Tab management And I run :click-element id link --target=tab And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - about:blank - data/hints/html/simple.html - data/hello.txt (active) + """ Scenario: opening tab with tabs.new_position.related first When I set tabs.new_position.related to first @@ -1139,9 +1283,11 @@ Feature: Tab management And I run :click-element id link --target=tab And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hello.txt (active) - about:blank - data/hints/html/simple.html + """ Scenario: opening tab with tabs.new_position.related last When I set tabs.new_position.related to last @@ -1152,9 +1298,11 @@ Feature: Tab management And I run :click-element id link --target=tab And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hints/html/simple.html - about:blank - data/hello.txt (active) + """ # stacking tabs Scenario: stacking tabs opening tab with tabs.new_position.related next @@ -1168,10 +1316,12 @@ Feature: Tab management And I wait until data/navigate/prev.html is loaded And I wait until data/navigate/next.html is loaded Then the following tabs should be open: + """ - about:blank - data/navigate/index.html (active) - data/navigate/prev.html - data/navigate/next.html + """ Scenario: stacking tabs opening tab with tabs.new_position.related prev When I set tabs.new_position.related to prev @@ -1184,10 +1334,12 @@ Feature: Tab management And I wait until data/navigate/prev.html is loaded And I wait until data/navigate/next.html is loaded Then the following tabs should be open: + """ - about:blank - data/navigate/next.html - data/navigate/prev.html - data/navigate/index.html (active) + """ Scenario: no stacking tabs opening tab with tabs.new_position.related next When I set tabs.new_position.related to next @@ -1200,10 +1352,12 @@ Feature: Tab management And I wait until data/navigate/prev.html is loaded And I wait until data/navigate/next.html is loaded Then the following tabs should be open: + """ - about:blank - data/navigate/index.html (active) - data/navigate/next.html - data/navigate/prev.html + """ Scenario: no stacking tabs opening tab with tabs.new_position.related prev When I set tabs.new_position.related to prev @@ -1216,10 +1370,12 @@ Feature: Tab management And I wait until data/navigate/prev.html is loaded And I wait until data/navigate/next.html is loaded Then the following tabs should be open: + """ - about:blank - data/navigate/prev.html - data/navigate/next.html - data/navigate/index.html (active) + """ # :tab-select @@ -1234,9 +1390,11 @@ Feature: Tab management And I run :tab-select Searching text And I wait for "Current tab changed, focusing " in the log Then the following tabs should be open: + """ - data/title.html - data/search.html (active) - data/scroll/simple.html + """ Scenario: :tab-select with no matching title When I run :tab-select invalid title @@ -1252,6 +1410,7 @@ Feature: Tab management And I run :tab-select Scrolling And I wait for "Focus object changed: *" in the log Then the session should look like: + """ windows: - active: true tabs: @@ -1269,6 +1428,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/paste_primary.html + """ Scenario: :tab-select with no matching index When I open data/title.html @@ -1292,6 +1452,7 @@ Feature: Tab management And I run :tab-select 0/2 And I wait for "Focus object changed: *" in the log Then the session should look like: + """ windows: - active: true tabs: @@ -1309,6 +1470,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/paste_primary.html + """ Scenario: :tab-select with wrong argument (-1) When I open data/title.html @@ -1346,6 +1508,7 @@ Feature: Tab management And I open data/numbers/2.txt in a new window And I run :tab-take 0/1 Then the session should look like: + """ windows: - tabs: - history: @@ -1355,6 +1518,7 @@ Feature: Tab management - url: http://localhost:*/data/numbers/2.txt - history: - url: http://localhost:*/data/numbers/1.txt + """ Scenario: Take a tab from the same window Given I have a fresh instance @@ -1379,6 +1543,7 @@ Feature: Tab management And I run :tab-take 1/1 And I wait until data/numbers/2.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -1387,6 +1552,7 @@ Feature: Tab management - active: true history: - url: http://localhost:*/data/numbers/2.txt + """ # :tab-give @@ -1397,6 +1563,7 @@ Feature: Tab management And I open data/numbers/2.txt in a new window And I run :tab-give 0 Then the session should look like: + """ windows: - tabs: - history: @@ -1406,6 +1573,7 @@ Feature: Tab management - tabs: - history: - url: about:blank + """ Scenario: Give a tab to the same window Given I have a fresh instance @@ -1419,6 +1587,7 @@ Feature: Tab management And I run :tab-give And I wait until data/numbers/2.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -1427,6 +1596,7 @@ Feature: Tab management - tabs: - history: - url: http://localhost:*/data/numbers/2.txt + """ Scenario: Give a tab from window with only one tab When I open data/hello.txt @@ -1455,6 +1625,7 @@ Feature: Tab management And I run :tab-give 1 And I wait until data/numbers/1.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -1462,6 +1633,7 @@ Feature: Tab management - url: http://localhost:*/data/numbers/2.txt - history: - url: http://localhost:*/data/numbers/1.txt + """ # Other @@ -1485,6 +1657,7 @@ Feature: Tab management And I hint with args "all tab-fg" and follow a And I wait until data/hello.txt is loaded Then the session should look like: + """ windows: - tabs: - history: @@ -1493,6 +1666,7 @@ Feature: Tab management - tabs: - history: - url: http://localhost:*/data/hello.txt + """ Scenario: Closing tab with tabs_are_windows When I set tabs.tabs_are_windows to true @@ -1502,12 +1676,14 @@ Feature: Tab management And I run :tab-close And I wait for "removed: tabbed-browser" in the log Then the session should look like: + """ windows: - tabs: - active: true history: - url: about:blank - url: http://localhost:*/data/numbers/1.txt + """ # :tab-pin @@ -1517,9 +1693,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-pin Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) (pinned) + """ Scenario: :tab-pin unpin When I open data/numbers/1.txt @@ -1529,9 +1707,11 @@ Feature: Tab management And I run :tab-pin And I run :tab-pin Then the following tabs should be open: + """ - data/numbers/1.txt (pinned) - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: :tab-pin to index 2 When I open data/numbers/1.txt @@ -1539,9 +1719,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-pin with count 2 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (pinned) - data/numbers/3.txt (active) + """ Scenario: :tab-pin with an invalid count When I open data/numbers/1.txt @@ -1549,9 +1731,11 @@ Feature: Tab management And I open data/numbers/3.txt in a new tab And I run :tab-pin with count 23 Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt - data/numbers/3.txt (active) + """ Scenario: Pinned :tab-close prompt yes When I open data/numbers/1.txt @@ -1573,8 +1757,10 @@ Feature: Tab management And I wait for "*want to close a pinned tab*" in the log And I run :prompt-accept no Then the following tabs should be open: + """ - data/numbers/1.txt (pinned) - data/numbers/2.txt (active) (pinned) + """ Scenario: Pinned :tab-only prompt yes When I open data/numbers/1.txt @@ -1598,8 +1784,10 @@ Feature: Tab management And I wait for "*want to close pinned tabs*" in the log And I run :prompt-accept no Then the following tabs should be open: + """ - data/numbers/1.txt (active) (pinned) - data/numbers/2.txt (pinned) + """ Scenario: Pinned :tab-only close all but pinned tab When I open data/numbers/1.txt @@ -1627,8 +1815,10 @@ Feature: Tab management And I run :tab-next And I run :tab-only --pinned keep Then the following tabs should be open: + """ - data/numbers/1.txt (active) (pinned) - data/numbers/2.txt (pinned) + """ Scenario: Pinned :tab-only --pinned prompt When I open data/numbers/1.txt @@ -1645,8 +1835,10 @@ Feature: Tab management And I open data/numbers/2.txt Then the message "Tab is pinned! Opening in new tab." should be shown And the following tabs should be open: + """ - data/numbers/1.txt (active) (pinned) - data/numbers/2.txt + """ Scenario: :tab-pin open url with tabs.pinned.frozen = false When I set tabs.pinned.frozen to false @@ -1680,8 +1872,10 @@ Feature: Tab management And I run :tab-clone And I wait until data/numbers/1.txt is loaded Then the following tabs should be open: + """ - data/numbers/1.txt (pinned) - data/numbers/1.txt (pinned) (active) + """ Scenario: Undo a pinned tab When I open data/numbers/1.txt @@ -1691,8 +1885,10 @@ Feature: Tab management And I run :undo And I wait until data/numbers/2.txt is loaded Then the following tabs should be open: + """ - data/numbers/1.txt - data/numbers/2.txt (pinned) (active) + """ Scenario: Focused webview after clicking link in bg diff --git a/tests/end2end/features/urlmarks.feature b/tests/end2end/features/urlmarks.feature index 70962d2d1..ec839fb53 100644 --- a/tests/end2end/features/urlmarks.feature +++ b/tests/end2end/features/urlmarks.feature @@ -42,8 +42,10 @@ Feature: quickmarks and bookmarks And I run :bookmark-load -t http://localhost:(port)/data/numbers/2.txt Then data/numbers/2.txt should be loaded And the following tabs should be open: + """ - about:blank - data/numbers/2.txt (active) + """ Scenario: Loading a bookmark in a background tab Given I open about:blank @@ -51,8 +53,10 @@ Feature: quickmarks and bookmarks And I run :bookmark-load -b http://localhost:(port)/data/numbers/3.txt Then data/numbers/3.txt should be loaded And the following tabs should be open: + """ - about:blank (active) - data/numbers/3.txt + """ Scenario: Loading a bookmark in a new window Given I open about:blank @@ -60,6 +64,7 @@ Feature: quickmarks and bookmarks And I run :bookmark-load -w http://localhost:(port)/data/numbers/4.txt And I wait until data/numbers/4.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -71,6 +76,7 @@ Feature: quickmarks and bookmarks history: - active: true url: http://localhost:*/data/numbers/4.txt + """ Scenario: Loading a bookmark with -t and -b When I run :bookmark-load -t -b about:blank @@ -177,8 +183,10 @@ Feature: quickmarks and bookmarks And I run :quickmark-load -t fourteen Then data/numbers/14.txt should be loaded And the following tabs should be open: + """ - about:blank - data/numbers/14.txt (active) + """ Scenario: Loading a quickmark in a background tab Given I open about:blank @@ -187,8 +195,10 @@ Feature: quickmarks and bookmarks And I run :quickmark-load -b fifteen Then data/numbers/15.txt should be loaded And the following tabs should be open: + """ - about:blank (active) - data/numbers/15.txt + """ Scenario: Loading a quickmark in a new window Given I open about:blank @@ -197,6 +207,7 @@ Feature: quickmarks and bookmarks And I run :quickmark-load -w sixteen And I wait until data/numbers/16.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -208,6 +219,7 @@ Feature: quickmarks and bookmarks history: - active: true url: http://localhost:*/data/numbers/16.txt + """ Scenario: Loading a quickmark which does not exist When I run :quickmark-load -b doesnotexist diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 70fb26afc..0dfe0b9df 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -143,8 +143,10 @@ Feature: Miscellaneous utility commands exposed to the user. And I run :hint-follow a And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - data/hints/link_blank.html - data/hello.txt (active) + """ ## :debug-log-capacity diff --git a/tests/end2end/features/yankpaste.feature b/tests/end2end/features/yankpaste.feature index 5dc43cfda..718c72c68 100644 --- a/tests/end2end/features/yankpaste.feature +++ b/tests/end2end/features/yankpaste.feature @@ -125,22 +125,27 @@ Feature: Yanking and pasting. And I run :open -t {clipboard} And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - about:blank - data/hello.txt (active) + """ Scenario: Pasting in a background tab When I put "http://localhost:(port)/data/hello.txt" into the clipboard And I run :open -b {clipboard} And I wait until data/hello.txt is loaded Then the following tabs should be open: + """ - about:blank (active) - data/hello.txt + """ Scenario: Pasting in a new window When I put "http://localhost:(port)/data/hello.txt" into the clipboard And I run :open -w {clipboard} And I wait until data/hello.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -152,6 +157,7 @@ Feature: Yanking and pasting. history: - active: true url: http://localhost:*/data/hello.txt + """ Scenario: Pasting an invalid URL When I set url.auto_search to never @@ -163,72 +169,91 @@ Feature: Yanking and pasting. @qtwebengine_flaky Scenario: Pasting multiple urls in a new tab When I put the following lines into the clipboard: + """ http://localhost:(port)/data/hello.txt http://localhost:(port)/data/hello2.txt http://localhost:(port)/data/hello3.txt + """ And I run :open -t {clipboard} And I wait until data/hello.txt is loaded And I wait until data/hello2.txt is loaded And I wait until data/hello3.txt is loaded Then the following tabs should be open: + """ - about:blank - data/hello.txt (active) - data/hello2.txt - data/hello3.txt + """ Scenario: Pasting multiline text When I set url.auto_search to naive And I set url.searchengines to {"DEFAULT": "http://localhost:(port)/data/hello.txt?q={}"} And I put the following lines into the clipboard: + """ this url: http://qutebrowser.org should not open + """ And I run :open -t {clipboard} And I wait until data/hello.txt?q=this%20url%3A%0Ahttp%3A//qutebrowser.org%0Ashould%20not%20open is loaded Then the following tabs should be open: + """ - about:blank - data/hello.txt?q=this%20url%3A%0Ahttp%3A//qutebrowser.org%0Ashould%20not%20open (active) + """ Scenario: Pasting multiline whose first line looks like a URI When I set url.auto_search to naive And I set url.searchengines to {"DEFAULT": "http://localhost:(port)/data/hello.txt?q={}"} And I put the following lines into the clipboard: + """ text: should open as search + """ And I run :open -t {clipboard} And I wait until data/hello.txt?q=text%3A%0Ashould%20open%0Aas%20search is loaded Then the following tabs should be open: + """ - about:blank - data/hello.txt?q=text%3A%0Ashould%20open%0Aas%20search (active) + """ # https://travis-ci.org/qutebrowser/qutebrowser/jobs/157941726 @qtwebengine_flaky Scenario: Pasting multiple urls in a background tab When I put the following lines into the clipboard: + """ http://localhost:(port)/data/hello.txt http://localhost:(port)/data/hello2.txt http://localhost:(port)/data/hello3.txt + """ And I run :open -b {clipboard} And I wait until data/hello.txt is loaded And I wait until data/hello2.txt is loaded And I wait until data/hello3.txt is loaded Then the following tabs should be open: + """ - about:blank (active) - data/hello.txt - data/hello2.txt - data/hello3.txt + """ Scenario: Pasting multiple urls in new windows When I put the following lines into the clipboard: + """ http://localhost:(port)/data/hello.txt http://localhost:(port)/data/hello2.txt http://localhost:(port)/data/hello3.txt + """ And I run :open -w {clipboard} And I wait until data/hello.txt is loaded And I wait until data/hello2.txt is loaded And I wait until data/hello3.txt is loaded Then the session should look like: + """ windows: - tabs: - active: true @@ -250,6 +275,7 @@ Feature: Yanking and pasting. history: - active: true url: http://localhost:*/data/hello3.txt + """ Scenario: Pasting multiple urls with an empty one And I put "http://localhost:(port)/data/hello.txt\n\nhttp://localhost:(port)/data/hello2.txt" into the clipboard From dc5662b141b5ca82f8ec93316b623060f38b6f83 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 21 Oct 2024 10:28:32 +0200 Subject: [PATCH 235/403] Update chromium release dates --- qutebrowser/utils/version.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index baee2f2ce..ec167377c 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -539,19 +539,21 @@ class WebEngineVersions: chromium_security: Optional[str] = None chromium_major: Optional[int] = dataclasses.field(init=False) + # Dates based on https://chromium.googlesource.com/chromium/src/+refs _BASES: ClassVar[dict[int, str]] = { - 83: '83.0.4103.122', # ~2020-06-24 - 87: '87.0.4280.144', # ~2020-12-02 - 90: '90.0.4430.228', # 2021-06-22 - 94: '94.0.4606.126', # 2021-11-17 - 102: '102.0.5005.177', # ~2022-05-24 + 83: '83.0.4103.122', # 2020-06-27, Qt 5.15.2 + 87: '87.0.4280.144', # 2021-01-08, Qt 5.15 + 90: '90.0.4430.228', # 2021-06-22, Qt 6.2 + 94: '94.0.4606.126', # 2021-11-17, Qt 6.3 + 102: '102.0.5005.177', # 2022-09-01, Qt 6.4 # (.220 claimed by code, .181 claimed by CHROMIUM_VERSION) - 108: '108.0.5359.220', # ~2022-12-23 - 112: '112.0.5615.213', # ~2023-04-18 - 118: '118.0.5993.220', # ~2023-10-24 - 122: '122.0.6261.171', # ~2024-??-?? + 108: '108.0.5359.220', # 2023-01-27, Qt 6.5 + 112: '112.0.5615.213', # 2023-05-24, Qt 6.6 + 118: '118.0.5993.220', # 2024-01-25, Qt 6.7 + 122: '122.0.6261.171', # 2024-04-15, Qt 6.8 } + # Dates based on https://chromereleases.googleblog.com/ _CHROMIUM_VERSIONS: ClassVar[dict[utils.VersionNumber, tuple[str, Optional[str]]]] = { # ====== UNSUPPORTED ===== From ad29c973f80bd51ec5e135d3add9e8cba853c1a2 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 21 Oct 2024 04:20:44 +0000 Subject: [PATCH 236/403] Update dependencies --- misc/requirements/requirements-dev.txt | 2 +- misc/requirements/requirements-mypy.txt | 6 +++--- misc/requirements/requirements-pyinstaller.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 2 +- misc/requirements/requirements-pyroma.txt | 2 +- misc/requirements/requirements-sphinx.txt | 2 +- misc/requirements/requirements-tests.txt | 8 ++++---- misc/requirements/requirements-tox.txt | 6 +++--- requirements.txt | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 955fb8380..ae3a154f7 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -7,7 +7,7 @@ bump2version==1.0.1 certifi==2024.8.30 cffi==1.17.1 charset-normalizer==3.4.0 -cryptography==43.0.1 +cryptography==43.0.3 docutils==0.21.2 github3.py==4.0.1 hunter==3.7.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 68920c7a8..abfdc7986 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -4,8 +4,8 @@ chardet==5.2.0 diff_cover==9.2.0 Jinja2==3.1.4 lxml==5.3.0 -MarkupSafe==3.0.1 -mypy==1.11.2 +MarkupSafe==3.0.2 +mypy==1.12.1 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 @@ -15,5 +15,5 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 -types-setuptools==75.1.0.20241014 +types-setuptools==75.2.0.20241019 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index d7e92fc61..20e298a71 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -3,6 +3,6 @@ altgraph==0.17.4 importlib_metadata==8.5.0 packaging==24.1 -pyinstaller==6.10.0 -pyinstaller-hooks-contrib==2024.8 +pyinstaller==6.11.0 +pyinstaller-hooks-contrib==2024.9 zipp==3.20.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index d5e1c9031..c1fffcd9c 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -4,7 +4,7 @@ astroid==3.3.5 certifi==2024.8.30 cffi==1.17.1 charset-normalizer==3.4.0 -cryptography==43.0.1 +cryptography==43.0.3 dill==0.3.9 github3.py==4.0.1 idna==3.10 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 821878286..f67c21e82 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -12,6 +12,6 @@ pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 tomli==2.0.2 -trove-classifiers==2024.10.13 +trove-classifiers==2024.10.16 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index a46869097..7e0cd41b4 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -9,7 +9,7 @@ idna==3.10 imagesize==1.4.1 importlib_metadata==8.5.0 Jinja2==3.1.4 -MarkupSafe==3.0.1 +MarkupSafe==3.0.2 packaging==24.1 Pygments==2.18.0 requests==2.32.3 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c933bfb09..6a360e7bd 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,13 +9,13 @@ certifi==2024.8.30 charset-normalizer==3.4.0 cheroot==10.0.1 click==8.1.7 -coverage==7.6.2 +coverage==7.6.4 exceptiongroup==1.2.2 execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.115.0 +hypothesis==6.115.3 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 @@ -29,12 +29,12 @@ jaraco.text==3.12.1 # Jinja2==3.1.4 Mako==1.3.5 manhole==1.8.1 -# MarkupSafe==3.0.1 +# MarkupSafe==3.0.2 more-itertools==10.5.0 packaging==24.1 parse==1.20.2 parse_type==0.6.4 -pillow==10.4.0 +pillow==11.0.0 platformdirs==4.3.6 pluggy==1.5.0 py-cpuinfo==9.0.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 3eef4d652..d76fa3fc0 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -10,9 +10,9 @@ pip==24.2 platformdirs==4.3.6 pluggy==1.5.0 pyproject-api==1.8.0 -setuptools==75.1.0 +setuptools==75.2.0 tomli==2.0.2 -tox==4.21.2 +tox==4.23.0 typing_extensions==4.12.2 -virtualenv==20.26.6 +virtualenv==20.27.0 wheel==0.44.0 diff --git a/requirements.txt b/requirements.txt index 7c3a0abd4..4afe7fb9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ adblock==0.6.0 colorama==0.4.6 Jinja2==3.1.4 -MarkupSafe==3.0.1 +MarkupSafe==3.0.2 Pygments==2.18.0 PyYAML==6.0.2 # Unpinned due to recompile_requirements.py limitations From d49675eeed1b53542973b3446ef6dac30c55b76d Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Oct 2024 18:10:19 +0200 Subject: [PATCH 237/403] Remove outdated version check --- tests/conftest.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 89f32fed1..add8cd036 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -350,12 +350,8 @@ def apply_fake_os(monkeypatch, request): @pytest.fixture(scope='session', autouse=True) def check_yaml_c_exts(): - """Make sure PyYAML C extensions are available on CI. - - Not available yet with a nightly Python, see: - https://github.com/yaml/pyyaml/issues/630 - """ - if testutils.ON_CI and sys.version_info[:2] != (3, 11): + """Make sure PyYAML C extensions are available on CI.""" + if testutils.ON_CI: from yaml import CLoader # pylint: disable=unused-import From a138ab89789f3624da78a62629de84d5c42cbdac Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 22 Oct 2024 18:17:08 +0200 Subject: [PATCH 238/403] Fix some broken links --- doc/help/configuring.asciidoc | 2 +- doc/help/settings.asciidoc | 2 +- qutebrowser/config/configdata.yml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc index 54f4f34f9..b166477c1 100644 --- a/doc/help/configuring.asciidoc +++ b/doc/help/configuring.asciidoc @@ -452,7 +452,7 @@ Various emacs/conkeror-like keybinding configs exist: - https://gitlab.com/Kaligule/qutebrowser-emacs-config/blob/master/config.py[Kaligule] - https://web.archive.org/web/20210512185023/https://me0w.net/pit/1540882719[nm0i] - https://www.reddit.com/r/qutebrowser/comments/eh10i7/config_share_qute_with_emacs_keybindings/[jasonsun0310] -- https://git.sr.ht/~willvaughn/dots/tree/mjolnir/item/.config/qutebrowser/qutemacs.py[willvaughn] +- https://git.sr.ht/~willvaughn/dots/tree/main/item/.config/qutebrowser/qutemacs.py[willvaughn] It's also mostly possible to get rid of modal keybindings by setting `input.insert_mode.auto_enter` to `false`, and `input.forward_unbound_keys` to diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 629efa96c..ce6320e90 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -3891,7 +3891,7 @@ Chromium has various sandboxing layers, which should be enabled for normal brows Open `chrome://sandbox` to see the current sandbox status. Changing this setting is only recommended if you know what you're doing, as it **disables one of Chromium's security layers**. To avoid sandboxing being accidentally disabled persistently, this setting can only be set via `config.py`, not via `:set`. See the Chromium documentation for more details: -- https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] +- https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/linux/README.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/mac/README.md[Mac] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] This setting requires a restart. diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 58bd92934..a3e5dbed4 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -322,8 +322,9 @@ qt.chromium.sandboxing: See the Chromium documentation for more details: - - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/linux/sandboxing.md[Linux] + - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/linux/README.md[Linux] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox.md[Windows] + - https://chromium.googlesource.com/chromium/src/\+/HEAD/sandbox/mac/README.md[Mac] - https://chromium.googlesource.com/chromium/src/\+/HEAD/docs/design/sandbox_faq.md[FAQ (Windows-centric)] # yamllint enable rule:line-length From 8064e3ea6f4e172775f6c73d83568d5c19084ec5 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 13 Oct 2024 20:55:28 +1300 Subject: [PATCH 239/403] Disable QtWebEngine's permissions persistence feature QtWebEngine has a new feature where it will remember what permissions you have granted or denied. It has three options regarding permissions storage: AskEveryTime -- don't store StoreInMemory -- store in memory only StoreOnDisk -- store in memory and on disk By default it does the StoreOnDisk behavior. Having webengine remember whether you granted or denied a permission would make the qutebrowser UX around that area inconsistent. For example the default y/n actions for a permission prompt in qutebrowser will only accept that single permission request, and you'll be re-prompted if you reload the page. Users may be used to this and if webengine started remembering permission grants the users may be surprised to find that the page was accessing features without prompting them for permission anymore. Additionally we already have our own permission storage machinery in autoconfig.yml. This commit will set the webengine feature to AskEveryTime, which disables any storing of permission grants. Also adjusts the skip marker of the affected tests so they'll be enabled again on Qt versions with the appropriate permissions API. --- pytest.ini | 2 +- qutebrowser/browser/webengine/webenginesettings.py | 11 ++++++++++- tests/end2end/conftest.py | 4 ++-- tests/end2end/features/prompts.feature | 6 +++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pytest.ini b/pytest.ini index adff9ae7f..9dc54548f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -41,7 +41,7 @@ markers = qt6_only: Tests which should only run with Qt 6 qt5_xfail: Tests which fail with Qt 5 qt6_xfail: Tests which fail with Qt 6 - qt68_beta4_skip: Fails on Qt 6.8 beta 4 + qt68_no_permission_api: Fails on Qt 6.8 with PyQt<6.8 qt_log_level_fail = WARNING qt_log_ignore = # GitHub Actions diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index b4f6f8a1b..63f740a2e 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -26,7 +26,7 @@ from qutebrowser.config import config, websettings from qutebrowser.config.websettings import AttributeInfo as Attr from qutebrowser.misc import pakjoy from qutebrowser.utils import (standarddir, qtutils, message, log, - urlmatch, usertypes, objreg, version) + urlmatch, usertypes, objreg, version, utils) if TYPE_CHECKING: from qutebrowser.browser.webengine import interceptor @@ -281,6 +281,7 @@ class ProfileSetter: self._set_hardcoded_settings() self.set_persistent_cookie_policy() self.set_dictionary_language() + self.disable_persistent_permissions_policy() def _set_hardcoded_settings(self): """Set up settings with a fixed value.""" @@ -345,6 +346,14 @@ class ProfileSetter: self._profile.setSpellCheckLanguages(filenames) self._profile.setSpellCheckEnabled(bool(filenames)) + def disable_persistent_permissions_policy(self): + """Disable webengine's permission persistence.""" + pyqt_version = utils.VersionNumber.parse(version.PYQT_WEBENGINE_VERSION_STR) + if pyqt_version >= utils.VersionNumber(6, 8): + self._profile.setPersistentPermissionsPolicy( + QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime + ) + def _update_settings(option): """Update global settings when qwebsettings changed.""" diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 17703d1e1..83c66380d 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -192,8 +192,8 @@ def pytest_collection_modifyitems(config, items): 'Skipped on Windows', pytest.mark.skipif, utils.is_windows), - ('qt68_beta4_skip', # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2184542226 - "Fails on Qt 6.8 beta 4", + ('qt68_no_permission_api', # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2184542226 + "Fails on Qt 6.8 with PyQt<6.8", pytest.mark.xfail, machinery.IS_QT6 and version.qtwebengine_versions( avoid_init=True diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 9e2062d13..2b65bcc56 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -251,7 +251,7 @@ Feature: Prompts And I run :click-element id button Then the javascript message "geolocation permission denied" should be logged - @qt68_beta4_skip + @qt68_no_permission_api Scenario: geolocation with ask -> false When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -260,7 +260,7 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "geolocation permission denied" should be logged - @qt68_beta4_skip + @qt68_no_permission_api Scenario: geolocation with ask -> false and save When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab @@ -270,7 +270,7 @@ Feature: Prompts Then the javascript message "geolocation permission denied" should be logged And the per-domain option content.geolocation should be set to false for http://localhost:(port) - @qt68_beta4_skip + @qt68_no_permission_api Scenario: geolocation with ask -> abort When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab From 5ad68024b2085902b24be5bec09d66e1c1b4f4c3 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 20 Oct 2024 08:53:18 +1300 Subject: [PATCH 240/403] mypy: ignore pyqt attribute from the future Once pyqt 6.8 is released mypy will probably start complaining about an unnecessary ignore statement and we can remove it. --- qutebrowser/browser/webengine/webenginesettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 63f740a2e..30a43ecaf 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -351,7 +351,7 @@ class ProfileSetter: pyqt_version = utils.VersionNumber.parse(version.PYQT_WEBENGINE_VERSION_STR) if pyqt_version >= utils.VersionNumber(6, 8): self._profile.setPersistentPermissionsPolicy( - QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime + QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime # type: ignore[attr-defined] ) From 4fab7b43573b0e21c4a06febff5c5e018d65ef2a Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 20 Oct 2024 10:27:26 +1300 Subject: [PATCH 241/403] Add clipboard permission tests There are some changes in this area in Qt6.8, so it would be good to have some test coverage. The "access permission - copy" one is broken in 6.8. Still need to raise that upstream. --- tests/end2end/features/prompts.feature | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 2b65bcc56..9e1ff8443 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -159,6 +159,49 @@ Feature: Prompts And I run :click-element id button Then the javascript message "Prompt reply: null" should be logged + # Clipboard permissions + + Scenario: Clipboard - no permission - copy + Given I have a fresh instance + When I set content.javascript.clipboard to none + And I open data/prompt/clipboard.html + And I run :click-element id copy + Then the javascript message "Failed to copy text." should be logged + + Scenario: Clipboard - no permission - paste + When I set content.javascript.clipboard to none + And I open data/prompt/clipboard.html + And I run :click-element id paste + Then the javascript message "Failed to read from clipboard." should be logged + + # access permission no longer allows copy permission on 6.8 because it + # falls back to a permission prompt that we don't support + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-130599 + @qt<6.8 + Scenario: Clipboard - access permission - copy + When I set content.javascript.clipboard to access + And I open data/prompt/clipboard.html + And I run :click-element id copy + Then the javascript message "Text copied: default text" should be logged + + Scenario: Clipboard - access permission - paste + When I set content.javascript.clipboard to access + And I open data/prompt/clipboard.html + And I run :click-element id paste + Then the javascript message "Failed to read from clipboard." should be logged + + Scenario: Clipboard - full permission - copy + When I set content.javascript.clipboard to access-paste + And I open data/prompt/clipboard.html + And I run :click-element id copy + Then the javascript message "Text copied: default text" should be logged + + Scenario: Clipboard - full permission - paste + When I set content.javascript.clipboard to access-paste + And I open data/prompt/clipboard.html + And I run :click-element id paste + Then the javascript message "Text pasted: default text" should be logged + # SSL Scenario: SSL error with content.tls.certificate_errors = load-insecurely From 0192b58a6adbb5952094d48c22d1bec5b4536eed Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 20 Oct 2024 12:10:33 +1300 Subject: [PATCH 242/403] Support 'ask' for clipboard permissions WebEngine now supports prompting for permission when a web page tries to access the clipboard. Previously we only supported fixed permissions that applied globally. This commit 1. adds support for permission requests with the new ClipboardReadWrite permission 2. tweaks the generic JS prompt handler in browser/shared.py to handle seeing a setting which isn't of type BoolAsk 3. adds end2end tests around clipboard permissions, both the existing global ones and the new per-URL ones 1. the ClipboardReadWrite permission I added this as an int because the relevant PyQt isn't out yet and users with 6.8 running with PyQt6.7 are already seeing this. I added an "ask" value to the existing String type `content.javascript.clipboard` setting and set the default/global/fallback permissions to False if ask is set globally. Hmm, maybe we should change the default actually... I'll have to check what the other prompt supporting settings default to. 2. tweaked prompt handler This was treating the string values that weren't "ask" (like "none" and "access") as truthy and allowing the action. I've changed the bool checks to be exact checks to add a warning if this happens again. Then I added an exception to the warning logging for known cases like this. I did try looking at adding a new setting type. Something that was descended from String but had an `__eq__` method that understood bools and would treat `access-paste` as True and everything else as False. But that didn't work out because it looks like config values are stored as python values and the config classes are just static and don't actually hold values. Oh well, maybe a better pattern will emerge with time. 3. tests Apparently there were no tests around the clipboard settings. Perhaps not a coincidence given how confusing they are (what does access-paste mean?). I copied a test file from some random test site, tweaked it a little bit and got to work. For the paste test it's a bit awkward because I don't know if we have a way to fill the clipboard in a nice way for the tests. So I'm just matching on "Text pasted: *", which is usually an empty string. The prompt tests require running against Qt6.8 to get the new prompt behaviour (don't need pyqt68 though). --- qutebrowser/browser/shared.py | 12 +- .../browser/webengine/webenginesettings.py | 4 + qutebrowser/browser/webengine/webenginetab.py | 3 + qutebrowser/config/configdata.yml | 1 + tests/end2end/data/prompt/clipboard.html | 133 ++++++++++++++++++ tests/end2end/features/prompts.feature | 70 ++++++++- 6 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 tests/end2end/data/prompt/clipboard.html diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 358af6d95..48bbab222 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -328,10 +328,20 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on, cancel_action=no_action, abort_on=abort_on, title='Permission request', text=text, url=urlstr, option=option) - elif config_val: + elif config_val is True: yes_action() return None + elif config_val is False: + no_action() + return None else: + if option not in { + "content.javascript.clipboard" # String type option with 'ask' value + }: + log.misc.warning( + f"Unsupported value for permission prompt setting ({option}), expected boolean or " + f"'ask', got: {config_val} ({type(config_val)})" + ) no_action() return None diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 30a43ecaf..83c54a3b3 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -216,6 +216,10 @@ class WebEngineSettings(websettings.AbstractSettings): QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: True, QWebEngineSettings.WebAttribute.JavascriptCanPaste: True, }, + 'ask': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: False, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: False, + }, } def set_unknown_url_scheme_policy( diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 04ed7c409..8142c071e 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -886,6 +886,8 @@ class _WebEnginePermissions(QObject): QWebEnginePage.Feature.MouseLock: 'content.mouse_lock', QWebEnginePage.Feature.DesktopVideoCapture: 'content.desktop_capture', QWebEnginePage.Feature.DesktopAudioVideoCapture: 'content.desktop_capture', + # 8 == ClipboardReadWrite, new in 6.8 + QWebEnginePage.Feature(8): 'content.javascript.clipboard', } _messages = { @@ -897,6 +899,7 @@ class _WebEnginePermissions(QObject): QWebEnginePage.Feature.MouseLock: 'hide your mouse pointer', QWebEnginePage.Feature.DesktopVideoCapture: 'capture your desktop', QWebEnginePage.Feature.DesktopAudioVideoCapture: 'capture your desktop and audio', + QWebEnginePage.Feature(8): 'read and write your clipboard', } def __init__(self, tab, parent=None): diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 58bd92934..c14367e9d 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -930,6 +930,7 @@ content.javascript.clipboard: - none: Disable access to clipboard. - access: Allow reading from and writing to the clipboard. - access-paste: Allow accessing the clipboard and pasting clipboard content. + - ask: Prompt when requested - grants 'access-paste' permission supports_pattern: true desc: >- Allow JavaScript to read from or write to the clipboard. diff --git a/tests/end2end/data/prompt/clipboard.html b/tests/end2end/data/prompt/clipboard.html new file mode 100644 index 000000000..dd247eceb --- /dev/null +++ b/tests/end2end/data/prompt/clipboard.html @@ -0,0 +1,133 @@ + + + + + + + +
+
+ + + +

Permissions:

+ + +
+
+
+        
+
+ + + + diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 9e1ff8443..9a10599c7 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -159,10 +159,9 @@ Feature: Prompts And I run :click-element id button Then the javascript message "Prompt reply: null" should be logged - # Clipboard permissions + # Clipboard permissions - static Scenario: Clipboard - no permission - copy - Given I have a fresh instance When I set content.javascript.clipboard to none And I open data/prompt/clipboard.html And I run :click-element id copy @@ -200,7 +199,72 @@ Feature: Prompts When I set content.javascript.clipboard to access-paste And I open data/prompt/clipboard.html And I run :click-element id paste - Then the javascript message "Text pasted: default text" should be logged + Then the javascript message "Text pasted: *" should be logged + + # Clipboard permissions - prompt + # A fresh instance is only required for these tests on Qt<6.8 + + @qt>=6.8 + Scenario: Clipboard - ask allow - copy + Given I have a fresh instance + When I set content.javascript.clipboard to ask + And I open data/prompt/clipboard.html + And I run :click-element id copy + And I wait for a prompt + And I run :prompt-accept yes + Then the javascript message "Text copied: default text" should be logged + + @qt>=6.8 + Scenario: Clipboard - ask allow - paste + Given I have a fresh instance + When I set content.javascript.clipboard to ask + And I open data/prompt/clipboard.html + And I run :click-element id paste + And I wait for a prompt + And I run :prompt-accept yes + Then the javascript message "Text pasted: *" should be logged + + @qt>=6.8 + Scenario: Clipboard - ask deny - copy + Given I have a fresh instance + When I set content.javascript.clipboard to ask + And I open data/prompt/clipboard.html + And I run :click-element id copy + And I wait for a prompt + And I run :prompt-accept no + Then the javascript message "Failed to copy text." should be logged + + @qt>=6.8 + Scenario: Clipboard - ask deny - paste + Given I have a fresh instance + When I set content.javascript.clipboard to ask + And I open data/prompt/clipboard.html + And I run :click-element id paste + And I wait for a prompt + And I run :prompt-accept no + Then the javascript message "Failed to read from clipboard." should be logged + + @qt>=6.8 + Scenario: Clipboard - ask per url - paste + Given I may need a fresh instance + When I set content.javascript.clipboard to none + And I run :set -u localhost:* content.javascript.clipboard ask + And I open data/prompt/clipboard.html + And I run :click-element id paste + And I wait for a prompt + And I run :prompt-accept yes + Then the javascript message "Text pasted: *" should be logged + And I run :config-unset -u localhost:* content.javascript.clipboard + + @qt>=6.8 + Scenario: Clipboard - deny per url - paste + Given I may need a fresh instance + When I set content.javascript.clipboard to access-paste + And I run :set -u localhost:* content.javascript.clipboard none + And I open data/prompt/clipboard.html + And I run :click-element id paste + Then the javascript message "Failed to read from clipboard." should be logged + And I run :config-unset -u localhost:* content.javascript.clipboard # SSL From f551bbbb7687142f8195b1bd5d81b8da6592f87e Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 26 Oct 2024 12:30:53 +1300 Subject: [PATCH 243/403] Move new permission API check from pytest markers to BDD step For tests that do the same prompt in the same session, they are failing on Qt6.8 and PyQWebEnginet6.7 because we can't disable the new WebEngine behaviour. We can work around this by getting a fresh instance for each test, but I don't want to make more tests slower if I don't have to. So move that logic into a custom "fresh instance" prompt that will only create one when needed. --- pytest.ini | 1 - tests/end2end/conftest.py | 9 +-------- tests/end2end/features/prompts.feature | 14 +++++++------- tests/end2end/features/test_prompts_bdd.py | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/pytest.ini b/pytest.ini index 9dc54548f..619656fb0 100644 --- a/pytest.ini +++ b/pytest.ini @@ -41,7 +41,6 @@ markers = qt6_only: Tests which should only run with Qt 6 qt5_xfail: Tests which fail with Qt 5 qt6_xfail: Tests which fail with Qt 6 - qt68_no_permission_api: Fails on Qt 6.8 with PyQt<6.8 qt_log_level_fail = WARNING qt_log_ignore = # GitHub Actions diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 83c66380d..eda930cfc 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -12,7 +12,6 @@ import pstats import operator import pytest -from qutebrowser.qt import machinery from qutebrowser.qt.core import PYQT_VERSION, QCoreApplication pytest.register_assert_rewrite('end2end.fixtures') @@ -29,7 +28,7 @@ from end2end.fixtures.quteprocess import ( ) from end2end.fixtures.testprocess import pytest_runtest_makereport # pylint: enable=unused-import -from qutebrowser.utils import qtutils, utils, version +from qutebrowser.utils import qtutils, utils def pytest_configure(config): @@ -192,12 +191,6 @@ def pytest_collection_modifyitems(config, items): 'Skipped on Windows', pytest.mark.skipif, utils.is_windows), - ('qt68_no_permission_api', # WORKAROUND: https://github.com/qutebrowser/qutebrowser/issues/8242#issuecomment-2184542226 - "Fails on Qt 6.8 with PyQt<6.8", - pytest.mark.xfail, - machinery.IS_QT6 and version.qtwebengine_versions( - avoid_init=True - ).webengine == utils.VersionNumber(6, 8) and version.PYQT_WEBENGINE_VERSION_STR < '6.8.0'), ] for item in items: diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 9a10599c7..74e2b80bf 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -206,7 +206,7 @@ Feature: Prompts @qt>=6.8 Scenario: Clipboard - ask allow - copy - Given I have a fresh instance + Given I may need a fresh instance When I set content.javascript.clipboard to ask And I open data/prompt/clipboard.html And I run :click-element id copy @@ -216,7 +216,7 @@ Feature: Prompts @qt>=6.8 Scenario: Clipboard - ask allow - paste - Given I have a fresh instance + Given I may need a fresh instance When I set content.javascript.clipboard to ask And I open data/prompt/clipboard.html And I run :click-element id paste @@ -226,7 +226,7 @@ Feature: Prompts @qt>=6.8 Scenario: Clipboard - ask deny - copy - Given I have a fresh instance + Given I may need a fresh instance When I set content.javascript.clipboard to ask And I open data/prompt/clipboard.html And I run :click-element id copy @@ -236,7 +236,7 @@ Feature: Prompts @qt>=6.8 Scenario: Clipboard - ask deny - paste - Given I have a fresh instance + Given I may need a fresh instance When I set content.javascript.clipboard to ask And I open data/prompt/clipboard.html And I run :click-element id paste @@ -358,8 +358,8 @@ Feature: Prompts And I run :click-element id button Then the javascript message "geolocation permission denied" should be logged - @qt68_no_permission_api Scenario: geolocation with ask -> false + Given I may need a fresh instance When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab And I run :click-element id button @@ -367,8 +367,8 @@ Feature: Prompts And I run :prompt-accept no Then the javascript message "geolocation permission denied" should be logged - @qt68_no_permission_api Scenario: geolocation with ask -> false and save + Given I may need a fresh instance When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab And I run :click-element id button @@ -377,8 +377,8 @@ Feature: Prompts Then the javascript message "geolocation permission denied" should be logged And the per-domain option content.geolocation should be set to false for http://localhost:(port) - @qt68_no_permission_api Scenario: geolocation with ask -> abort + Given I may need a fresh instance When I set content.geolocation to ask And I open data/prompt/geolocation.html in a new tab And I run :click-element id button diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index c7d676f4d..2e0cf06dc 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -7,6 +7,8 @@ import logging import pytest_bdd as bdd bdd.scenarios('prompts.feature') +from qutebrowser.utils import qtutils, version + @bdd.when("I load an SSL page") def load_ssl_page(quteproc, ssl_server): @@ -37,6 +39,20 @@ def wait_for_prompt(quteproc): quteproc.wait_for(message='Asking question *') +@bdd.given("I may need a fresh instance") +def fresh_instance(quteproc): + """Restart qutebrowser to bypass webengine's permission persistance.""" + # Qt6.8 by default will remember feature grants or denies. When we are + # on PyQt6.8 we disable that with the new API, otherwise restart the + # browser to make it forget previous prompts. + if ( + qtutils.version_check("6.8", compiled=False) + and version.PYQT_WEBENGINE_VERSION_STR < "6.8.0" + ): + quteproc.terminate() + quteproc.start() + + @bdd.then("no prompt should be shown") def no_prompt_shown(quteproc): quteproc.ensure_not_logged(message='Entering mode KeyMode.* (reason: ' From 28890f6fbbd2c0787cad2f1b3b53b700702ffab7 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 27 Oct 2024 12:04:28 +1300 Subject: [PATCH 244/403] Change `content.javascript.clipboard` default to `ask` Now that we support prompting for clipboard access, change the default for this setting to match all the other settings that have an "ask" value. --- doc/changelog.asciidoc | 5 +++++ qutebrowser/config/configdata.yml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 689c08753..61bf9d187 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -35,6 +35,11 @@ Added - **Planned:** Full support for Python 3.13, with Windows/macOS binaries using it if possible. +Changed +~~~~~~~ + +- The `content.javascript.clipboard` setting now defaults to "ask". + [[v3.3.1]] v3.3.1 (2024-10-12) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index c14367e9d..cb15adf30 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -923,7 +923,7 @@ content.javascript.alert: desc: Show javascript alerts. content.javascript.clipboard: - default: none + default: ask type: name: String valid_values: From f891dd31372b63c42fa0e0c74dc621f1c8d9e80e Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 28 Oct 2024 11:54:43 +1300 Subject: [PATCH 245/403] Wait for quteproc shutdown in across-restarts prompt test This is failing with: ERROR tests/end2end/test_invocations.py::test_permission_prompt_across_restart - PermissionError: [WinError 5] Access is denied: 'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\tmpytep6gj0\\cache\\webengine\\Cache\\Cache_Data\\data_0' In pytest teardown, while trying to clean up a temp dir, probably the basedir. The test above waits for shutdown at the end of the test, maybe that's what's needed here. Otherwise maybe just `@pytest.mark.skipif(utils.is_windows)` :shrug: --- tests/end2end/test_invocations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index a5a03ecf6..75df387fb 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -662,6 +662,9 @@ def test_permission_prompt_across_restart(quteproc_new, request, short_tmpdir): # We should be re-prompted in the new instance notification_prompt('no') + quteproc_new.send_cmd(':quit') + quteproc_new.wait_for_quit() + # The 'colors' dictionaries in the parametrize decorator below have (QtWebEngine # version, CPU architecture) as keys. Either of those (or both) can be None to From 4c3337f553ef8d9a712c18ebb85520a427c08663 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 28 Oct 2024 12:29:33 +1300 Subject: [PATCH 246/403] Skip clipboard end2end tests on webkit The test page is using a JS API that is too new for qtwebkit: 23:07:54.898 DEBUG js shared:javascript_log_message:190 [http://localhost:37635/data/prompt/clipboard.html:97] TypeError: undefined is not an object (evaluating 'navigator.clipboard.readText') --- tests/end2end/features/prompts.feature | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 74e2b80bf..889cbd05d 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -161,12 +161,14 @@ Feature: Prompts # Clipboard permissions - static + @qtwebkit_skip Scenario: Clipboard - no permission - copy When I set content.javascript.clipboard to none And I open data/prompt/clipboard.html And I run :click-element id copy Then the javascript message "Failed to copy text." should be logged + @qtwebkit_skip Scenario: Clipboard - no permission - paste When I set content.javascript.clipboard to none And I open data/prompt/clipboard.html @@ -176,25 +178,28 @@ Feature: Prompts # access permission no longer allows copy permission on 6.8 because it # falls back to a permission prompt that we don't support # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-130599 - @qt<6.8 + @qt<6.8 @qtwebkit_skip Scenario: Clipboard - access permission - copy When I set content.javascript.clipboard to access And I open data/prompt/clipboard.html And I run :click-element id copy Then the javascript message "Text copied: default text" should be logged + @qtwebkit_skip Scenario: Clipboard - access permission - paste When I set content.javascript.clipboard to access And I open data/prompt/clipboard.html And I run :click-element id paste Then the javascript message "Failed to read from clipboard." should be logged + @qtwebkit_skip Scenario: Clipboard - full permission - copy When I set content.javascript.clipboard to access-paste And I open data/prompt/clipboard.html And I run :click-element id copy Then the javascript message "Text copied: default text" should be logged + @qtwebkit_skip Scenario: Clipboard - full permission - paste When I set content.javascript.clipboard to access-paste And I open data/prompt/clipboard.html From 643bdf73052dd43c237f762ec2e2da3477a1b873 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 28 Oct 2024 04:21:24 +0000 Subject: [PATCH 247/403] Update dependencies --- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-flake8.txt | 4 ++-- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pyroma.txt | 2 +- misc/requirements/requirements-tests.txt | 6 +++--- misc/requirements/requirements-tox.txt | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index ae3a154f7..209f3d87e 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -21,7 +21,7 @@ jaraco.context==6.0.1 jaraco.functools==4.1.0 jaraco.text==3.12.1 jeepney==0.8.0 -keyring==25.4.1 +keyring==25.5.0 manhole==1.8.1 markdown-it-py==3.0.0 mdurl==0.1.2 @@ -41,7 +41,7 @@ readme_renderer==44.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.9.2 +rich==13.9.3 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 456c3be34..8c6134f7b 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -4,7 +4,7 @@ attrs==24.2.0 flake8==7.1.1 flake8-bugbear==24.8.19 flake8-builtins==2.5.0 -flake8-comprehensions==3.15.0 +flake8-comprehensions==3.16.0 flake8-debugger==4.1.2 flake8-deprecated==2.2.1 flake8-docstrings==1.7.0 @@ -12,7 +12,7 @@ flake8-future-import==0.4.7 flake8-plugin-utils==1.3.3 flake8-pytest-style==2.0.0 flake8-string-format==0.3.0 -flake8-tidy-imports==4.10.0 +flake8-tidy-imports==4.11.0 flake8-tuple==0.4.1 mccabe==0.7.0 pep8-naming==0.14.1 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index abfdc7986..3797f9a6f 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -5,7 +5,7 @@ diff_cover==9.2.0 Jinja2==3.1.4 lxml==5.3.0 MarkupSafe==3.0.2 -mypy==1.12.1 +mypy==1.13.0 mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 @@ -15,5 +15,5 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 -types-setuptools==75.2.0.20241019 +types-setuptools==75.2.0.20241025 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index f67c21e82..32b3d95cc 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -12,6 +12,6 @@ pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 tomli==2.0.2 -trove-classifiers==2024.10.16 +trove-classifiers==2024.10.21.16 urllib3==2.2.3 zipp==3.20.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 6a360e7bd..c6afb010a 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -15,7 +15,7 @@ execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.115.3 +hypothesis==6.115.5 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 @@ -27,7 +27,7 @@ jaraco.context==6.0.1 jaraco.functools==4.1.0 jaraco.text==3.12.1 # Jinja2==3.1.4 -Mako==1.3.5 +Mako==1.3.6 manhole==1.8.1 # MarkupSafe==3.0.2 more-itertools==10.5.0 @@ -62,5 +62,5 @@ typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 vulture==2.13 -Werkzeug==3.0.4 +Werkzeug==3.0.6 zipp==3.20.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index d76fa3fc0..b0541b7f7 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -6,13 +6,13 @@ colorama==0.4.6 distlib==0.3.9 filelock==3.16.1 packaging==24.1 -pip==24.2 +pip==24.3.1 platformdirs==4.3.6 pluggy==1.5.0 pyproject-api==1.8.0 setuptools==75.2.0 tomli==2.0.2 -tox==4.23.0 +tox==4.23.2 typing_extensions==4.12.2 virtualenv==20.27.0 wheel==0.44.0 From fbd148f98399090704d2cb055c928ca14f18cba2 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 28 Oct 2024 14:58:09 +1300 Subject: [PATCH 248/403] Support persisting clipboard prompt choices All the promptable feature permissions so far have been of type BoolAsk. The prompt uses a "yesno" mode prompt and only results in a bool. The persistence logic only supports bools. Previously I made the shared prompt support the String type clipboard permission setting by treating non "ask" values as False (you only get prompted if the global setting is "none" anyway), but saving the prompt results with `:prompt-accept --save` didn't work because the persistence code only supported bools. What we want to do when saving is convert `False` to "none" and `True` to "access-paste". This mirrors the new webengine logic. It does mean we can't let users choose to persist either none/access/access-paste, but webengine doesn't prompt us if the page is already allowed "access" but is trying to paste anyway. If it did we would have to use a non-yesno prompt for this (perhaps it could be driven directly by the ConfigType). For now I've added a new concept to the ConfigTypes to allow them to be casted to and from bools, so that we can plumb this String type from the boolean yesno prompt. TODO: * try to make it an interface so we don't have to use `hasattr` (even if it means multiple inheritance) * add test coverage to test_configtypes.py --- qutebrowser/browser/shared.py | 18 ++++++++++-------- qutebrowser/config/configdata.yml | 2 +- qutebrowser/config/configtypes.py | 17 +++++++++++++++++ qutebrowser/mainwindow/prompt.py | 15 +++++++++++++-- tests/end2end/features/prompts.feature | 13 +++++++++++++ 5 files changed, 54 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 48bbab222..4dddafcf2 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -303,6 +303,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on, None otherwise. """ config_val = config.instance.get(option, url=url) + opt = config.instance.get_opt(option) if config_val == 'ask': if url.isValid(): urlstr = url.toString(QUrl.UrlFormattingOption.RemovePassword | QUrl.ComponentFormattingOption.FullyEncoded) @@ -328,20 +329,21 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on, cancel_action=no_action, abort_on=abort_on, title='Permission request', text=text, url=urlstr, option=option) - elif config_val is True: + + if hasattr(opt.typ, "to_bool"): + config_val = opt.typ.to_bool(config_val) + + if config_val is True: yes_action() return None elif config_val is False: no_action() return None else: - if option not in { - "content.javascript.clipboard" # String type option with 'ask' value - }: - log.misc.warning( - f"Unsupported value for permission prompt setting ({option}), expected boolean or " - f"'ask', got: {config_val} ({type(config_val)})" - ) + log.misc.warning( + f"Unsupported value for permission prompt setting ({option}), expected boolean or " + f"'ask', got: {config_val} ({type(config_val)})" + ) no_action() return None diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index cb15adf30..0bbc95960 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -925,7 +925,7 @@ content.javascript.alert: content.javascript.clipboard: default: ask type: - name: String + name: JSClipboardPermission valid_values: - none: Disable access to clipboard. - access: Allow reading from and writing to the clipboard. diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 9fa374712..a7a6474b9 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -2016,3 +2016,20 @@ class StatusbarWidget(String): if value.startswith("text:") or value.startswith("clock:"): return super()._validate_valid_values(value) + + +class JSClipboardPermission(String): + + """Permission for page JS to access the system clipboard. + + String + BoolAsk + """ + + def to_bool(self, value: str) -> bool: + return value == "access-paste" + + def from_bool(self, value: bool) -> str: + if value is True: + return "access-paste" + else: + return "none" diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index d6ae16ba4..4716d37e8 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -976,12 +976,23 @@ class YesNoPrompt(_BasePrompt): raise Error("Invalid value {} - expected yes/no!".format(value)) if save: + value = self.question.answer opt = config.instance.get_opt(self.question.option) - assert isinstance(opt.typ, configtypes.Bool) + if isinstance(opt.typ, configtypes.Bool): + pass + elif hasattr(opt.typ, "from_bool"): + value = opt.typ.from_bool(value) + else: + raise AssertionError( + "Cannot save prompt answer. Expected 'Bool' option or " + "option with 'from_bool()'. " + f"option={opt.name} type={type(opt.typ)}" + ) + pattern = urlmatch.UrlPattern(self.question.url) try: - config.instance.set_obj(opt.name, self.question.answer, + config.instance.set_obj(opt.name, value, pattern=pattern, save_yaml=True) except configexc.Error as e: raise Error(str(e)) diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature index 889cbd05d..78a3f57ed 100644 --- a/tests/end2end/features/prompts.feature +++ b/tests/end2end/features/prompts.feature @@ -271,6 +271,19 @@ Feature: Prompts Then the javascript message "Failed to read from clipboard." should be logged And I run :config-unset -u localhost:* content.javascript.clipboard + @qt>=6.8 + Scenario: Clipboard - ask allow persistent - paste + Given I may need a fresh instance + When I set content.javascript.clipboard to ask + And I open data/prompt/clipboard.html + And I run :click-element id paste + And I wait for a prompt + And I run :prompt-accept --save yes + And I wait for "*Text pasted: *" in the log + And I reload + And I run :click-element id paste + Then the javascript message "Text pasted: *" should be logged + # SSL Scenario: SSL error with content.tls.certificate_errors = load-insecurely From e55624703bb2be91bfb89ebbca40ebe1580443e8 Mon Sep 17 00:00:00 2001 From: toofar Date: Mon, 28 Oct 2024 15:12:46 +1300 Subject: [PATCH 249/403] Move config-type-to-bool thing to an abstract class Relying on `hasattr()` made me feel a bit guilty. So I've moved these conditionals to be backed by a class. The only class based alternative I can think of is putting it on the base type and leaving all the other config variables to raise errors. But that doesn't tell the type system anything. --- qutebrowser/browser/shared.py | 4 ++-- qutebrowser/config/configtypes.py | 18 +++++++++++++----- qutebrowser/mainwindow/prompt.py | 7 +++---- tests/unit/config/test_configtypes.py | 26 ++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 4dddafcf2..3fa52c996 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -14,7 +14,7 @@ from typing import Callable, Mapping, List, Optional, Iterable, Iterator from qutebrowser.qt.core import QUrl, pyqtBoundSignal -from qutebrowser.config import config +from qutebrowser.config import config, configtypes from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils, qtutils, version, urlutils) from qutebrowser.mainwindow import mainwindow @@ -330,7 +330,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on, title='Permission request', text=text, url=urlstr, option=option) - if hasattr(opt.typ, "to_bool"): + if isinstance(opt.typ, configtypes.AsBool): config_val = opt.typ.to_bool(config_val) if config_val is True: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index a7a6474b9..56bd7b13a 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -146,6 +146,17 @@ class ValidValues: self.descriptions == other.descriptions) +class AsBool: + + """A non-Bool type that can be converted to bool.""" + + def to_bool(self, value: Any) -> bool: + raise NotImplementedError + + def from_bool(self, value: bool) -> Any: + raise NotImplementedError + + class BaseType: """A type used for a setting value. @@ -2018,12 +2029,9 @@ class StatusbarWidget(String): super()._validate_valid_values(value) -class JSClipboardPermission(String): +class JSClipboardPermission(String, AsBool): - """Permission for page JS to access the system clipboard. - - String + BoolAsk - """ + """Permission for page JS to access the system clipboard.""" def to_bool(self, value: str) -> bool: return value == "access-paste" diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 4716d37e8..5d00b429d 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -980,13 +980,12 @@ class YesNoPrompt(_BasePrompt): opt = config.instance.get_opt(self.question.option) if isinstance(opt.typ, configtypes.Bool): pass - elif hasattr(opt.typ, "from_bool"): + elif isinstance(opt.typ, configtypes.AsBool): value = opt.typ.from_bool(value) else: raise AssertionError( - "Cannot save prompt answer. Expected 'Bool' option or " - "option with 'from_bool()'. " - f"option={opt.name} type={type(opt.typ)}" + f"Cannot save prompt answer ({opt.name}). Expected 'Bool' or 'AsBool' " + f"type option, got: value={value} type={type(opt.typ)}" ) pattern = urlmatch.UrlPattern(self.question.url) diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 09fc4fa75..89dc58912 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -2145,3 +2145,29 @@ def test_regex_eq(first, second, equal): else: assert first != second assert second != first + + +class TestJSClipboardPermission: + + @pytest.fixture + def typ(self): + return configtypes.JSClipboardPermission() + + @pytest.mark.parametrize('value, expected', [ + ("access-paste", True), + ("none", False), + ("asdf", False), + ("access", False), + ("paste", False), + (None, False), + ]) + def test_to_bool(self, typ, value, expected): + assert typ.to_bool(value) == expected + + @pytest.mark.parametrize('value, expected', [ + (True, "access-paste"), + (False, "none"), + (None, "none"), + ]) + def test_from_bool(self, typ, value, expected): + assert typ.from_bool(value) == expected From b495e78b8a671784def51ca27ed5e85484d32aa3 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 4 Nov 2024 04:20:40 +0000 Subject: [PATCH 250/403] Update dependencies --- misc/requirements/requirements-dev.txt | 2 +- misc/requirements/requirements-flake8.txt | 2 +- misc/requirements/requirements-tests.txt | 8 ++++---- misc/requirements/requirements-tox.txt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 209f3d87e..f4b243aa2 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -41,7 +41,7 @@ readme_renderer==44.0 requests==2.32.3 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.9.3 +rich==13.9.4 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 8c6134f7b..8e84107ae 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,7 +2,7 @@ attrs==24.2.0 flake8==7.1.1 -flake8-bugbear==24.8.19 +flake8-bugbear==24.10.31 flake8-builtins==2.5.0 flake8-comprehensions==3.16.0 flake8-debugger==4.1.2 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index c6afb010a..56b32c8fd 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -15,7 +15,7 @@ execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.115.5 +hypothesis==6.116.0 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 @@ -41,8 +41,8 @@ py-cpuinfo==9.0.0 Pygments==2.18.0 pytest==8.3.3 pytest-bdd==7.3.0 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 +pytest-benchmark==5.1.0 +pytest-cov==6.0.0 pytest-instafail==0.5.0 pytest-mock==3.14.0 pytest-qt==4.4.0 @@ -62,5 +62,5 @@ typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 vulture==2.13 -Werkzeug==3.0.6 +Werkzeug==3.1.1 zipp==3.20.2 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index b0541b7f7..c49a7c5bf 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -10,9 +10,9 @@ pip==24.3.1 platformdirs==4.3.6 pluggy==1.5.0 pyproject-api==1.8.0 -setuptools==75.2.0 +setuptools==75.3.0 tomli==2.0.2 tox==4.23.2 typing_extensions==4.12.2 -virtualenv==20.27.0 +virtualenv==20.27.1 wheel==0.44.0 From 49e67c4dc92c8ea5859059b2a5bc86fd1d6565e9 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 7 Nov 2024 14:58:34 +0100 Subject: [PATCH 251/403] Fix up changelog See #7625 --- doc/changelog.asciidoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 78082a8ac..afcd7be32 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -74,16 +74,16 @@ Added - Added the `qt.workarounds.disable_hangouts_extension` setting, for disabling the Google Hangouts extension built into Chromium/QtWebEngine. +- Failed end2end tests will now save screenshots of the browser window when + run under xvfb (the default on linux). Screenshots will be under + `$TEMP/pytest-current/pytest-screenshots/` or attached to the GitHub actions + run as an artifact. (#7625) Removed ~~~~~~~ - Support for macOS 11 Big Sur is dropped. Binaries are now built on macOS 12 Monterey and are unlikely to still run on older macOS versions. -- Failed end2end tests will now save screenshots of the browser window when - run under xvfb (the default on linux). Screenshots will be under - `$TEMP/pytest-current/pytest-screenshots/` or attached to the GitHub actions - run as an artifact. (#7625) Changed ~~~~~~~ From 57465a6768c85ced746da9fdc58eb83b008af340 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 10 Nov 2024 14:38:47 +1300 Subject: [PATCH 252/403] Assert on unexpected config value when prompting This branch gets entered if the value of a setting linked to a feature is anything other than True, False or "ask". I think this could only happen due to a programming error, for example when you add an entry to `_WebEnginePermissions` that was linked to a String type setting. So I think putting an assert here instead of a warning should be fine and more explicit. (Or should be be `utils.Unreachable`? Or `ValueError`?) --- qutebrowser/browser/shared.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 061b820b9..425f4d489 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -341,12 +341,10 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on, no_action() return None else: - log.misc.warning( + raise AssertionError( f"Unsupported value for permission prompt setting ({option}), expected boolean or " f"'ask', got: {config_val} ({type(config_val)})" ) - no_action() - return None def get_tab(win_id, target): From f382a1a18fba94c3a368129ab7fda8182f3d78ce Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 10 Nov 2024 19:36:51 +0100 Subject: [PATCH 253/403] Remove pytest-benchmark filterwarning --- pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/pytest.ini b/pytest.ini index adff9ae7f..87cd362af 100644 --- a/pytest.ini +++ b/pytest.ini @@ -83,7 +83,5 @@ xfail_strict = true filterwarnings = error default:Test process .* failed to terminate!:UserWarning - # Python 3.12: https://github.com/ionelmc/pytest-benchmark/issues/240 (fixed but not released) - ignore:(datetime\.)?datetime\.utcnow\(\) is deprecated and scheduled for removal in a future version\. Use timezone-aware objects to represent datetimes in UTC. (datetime\.)?datetime\.now\(datetime\.UTC\)\.:DeprecationWarning:pytest_benchmark\.utils faulthandler_timeout = 90 xvfb_colordepth = 24 From a04126b22b13b82b32c0ff98c5653a4b18b925a0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 10 Nov 2024 19:40:54 +0100 Subject: [PATCH 254/403] hypothesis: Inherit our CI settings from hypothesis CI profile See https://github.com/HypothesisWorks/hypothesis/commit/13c3785854da056387aeac789300537e501a3c14 deadline=None and suppressing the too_slow health check is already part of that. Also fixes a hypotheses -> hypothesis typo. --- tests/conftest.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index add8cd036..9e93dd55a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,9 +34,9 @@ _qute_scheme_handler = None # Set hypothesis settings -hypotheses_optional_kwargs = {} +hypothesis_optional_kwargs = {} if "HYPOTHESIS_EXAMPLES_DIR" in os.environ: - hypotheses_optional_kwargs[ + hypothesis_optional_kwargs[ "database" ] = hypothesis.database.DirectoryBasedExampleDatabase( os.environ["HYPOTHESIS_EXAMPLES_DIR"] @@ -46,17 +46,16 @@ hypothesis.settings.register_profile( 'default', hypothesis.settings( deadline=600, suppress_health_check=[hypothesis.HealthCheck.function_scoped_fixture], - **hypotheses_optional_kwargs, + **hypothesis_optional_kwargs, ) ) hypothesis.settings.register_profile( 'ci', hypothesis.settings( - deadline=None, + hypothesis.settings.get_profile('ci'), suppress_health_check=[ hypothesis.HealthCheck.function_scoped_fixture, - hypothesis.HealthCheck.too_slow ], - **hypotheses_optional_kwargs, + **hypothesis_optional_kwargs, ) ) hypothesis.settings.load_profile('ci' if testutils.ON_CI else 'default') From af884a02c86d2669fb28e3ab9bf05643222ca7fe Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 10 Nov 2024 20:01:44 +0100 Subject: [PATCH 255/403] ci: Upgrade codecov action --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9039bc9a3..15dc4f507 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -251,7 +251,7 @@ jobs: if: "failure()" - name: Upload coverage if: "endsWith(matrix.testenv, '-cov')" - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: "${{ matrix.testenv }}" - name: Gather info From c03d3dd6acf7e1d5d3a6b5474a55d12701f0b094 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 11 Nov 2024 04:20:25 +0000 Subject: [PATCH 256/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 4 ++-- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-mypy.txt | 2 +- misc/requirements/requirements-pyinstaller.txt | 8 ++++---- misc/requirements/requirements-pyroma.txt | 4 ++-- misc/requirements/requirements-sphinx.txt | 4 ++-- misc/requirements/requirements-tests.txt | 12 ++++++------ misc/requirements/requirements-tox.txt | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 349e4399b..cdd6dc757 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -3,7 +3,7 @@ build==1.2.2.post1 check-manifest==0.50 importlib_metadata==8.5.0 -packaging==24.1 +packaging==24.2 pyproject_hooks==1.2.0 tomli==2.0.2 -zipp==3.20.2 +zipp==3.21.0 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index f4b243aa2..ffdd3bfb7 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -27,7 +27,7 @@ markdown-it-py==3.0.0 mdurl==0.1.2 more-itertools==10.5.0 nh3==0.2.18 -packaging==24.1 +packaging==24.2 pkginfo==1.10.0 platformdirs==4.3.6 pycparser==2.22 @@ -51,4 +51,4 @@ typeguard==4.3.0 typing_extensions==4.12.2 uritemplate==4.1.1 # urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 3797f9a6f..211487a8b 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -15,5 +15,5 @@ types-colorama==0.4.15.20240311 types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 -types-setuptools==75.2.0.20241025 +types-setuptools==75.3.0.20241107 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 20e298a71..92c4e9b28 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -2,7 +2,7 @@ altgraph==0.17.4 importlib_metadata==8.5.0 -packaging==24.1 -pyinstaller==6.11.0 -pyinstaller-hooks-contrib==2024.9 -zipp==3.20.2 +packaging==24.2 +pyinstaller==6.11.1 +pyinstaller-hooks-contrib==2024.10 +zipp==3.21.0 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 32b3d95cc..44a1240e2 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -6,7 +6,7 @@ charset-normalizer==3.4.0 docutils==0.21.2 idna==3.10 importlib_metadata==8.5.0 -packaging==24.1 +packaging==24.2 Pygments==2.18.0 pyproject_hooks==1.2.0 pyroma==4.2 @@ -14,4 +14,4 @@ requests==2.32.3 tomli==2.0.2 trove-classifiers==2024.10.21.16 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 7e0cd41b4..f9c12c5aa 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -10,7 +10,7 @@ imagesize==1.4.1 importlib_metadata==8.5.0 Jinja2==3.1.4 MarkupSafe==3.0.2 -packaging==24.1 +packaging==24.2 Pygments==2.18.0 requests==2.32.3 snowballstemmer==2.2.0 @@ -23,4 +23,4 @@ sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 tomli==2.0.2 urllib3==2.2.3 -zipp==3.20.2 +zipp==3.21.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 56b32c8fd..9dc72aabd 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -4,7 +4,7 @@ attrs==24.2.0 autocommand==2.2.2 backports.tarfile==1.2.0 beautifulsoup4==4.12.3 -blinker==1.8.2 +blinker==1.9.0 certifi==2024.8.30 charset-normalizer==3.4.0 cheroot==10.0.1 @@ -15,7 +15,7 @@ execnet==2.1.1 filelock==3.16.1 Flask==3.0.3 hunter==3.7.0 -hypothesis==6.116.0 +hypothesis==6.118.7 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 @@ -31,7 +31,7 @@ Mako==1.3.6 manhole==1.8.1 # MarkupSafe==3.0.2 more-itertools==10.5.0 -packaging==24.1 +packaging==24.2 parse==1.20.2 parse_type==0.6.4 pillow==11.0.0 @@ -56,11 +56,11 @@ requests-file==2.1.0 six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.6 -tldextract==5.1.2 +tldextract==5.1.3 tomli==2.0.2 typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 vulture==2.13 -Werkzeug==3.1.1 -zipp==3.20.2 +Werkzeug==3.1.3 +zipp==3.21.0 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index c49a7c5bf..dbaeb8821 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -5,7 +5,7 @@ chardet==5.2.0 colorama==0.4.6 distlib==0.3.9 filelock==3.16.1 -packaging==24.1 +packaging==24.2 pip==24.3.1 platformdirs==4.3.6 pluggy==1.5.0 @@ -15,4 +15,4 @@ tomli==2.0.2 tox==4.23.2 typing_extensions==4.12.2 virtualenv==20.27.1 -wheel==0.44.0 +wheel==0.45.0 From 07bd9a691a53a5883e5aafd9635347633e15f26b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 11 Nov 2024 12:53:15 +0100 Subject: [PATCH 257/403] tests: Ignore irrelevant Chromium warning --- tests/end2end/fixtures/quteprocess.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 956efa82c..435fd05a6 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -231,6 +231,12 @@ def is_ignored_chromium_message(line): # Some MojoDiscardableSharedMemoryManagerImpls are still alive. They # will be leaked. "Some MojoDiscardableSharedMemoryManagerImpls are still alive. They will be leaked.", + + # Qt 6.7 on GitHub Actions + # [3456:5752:1111/103609.929:ERROR:block_files.cc(443)] Failed to open + # C:\Users\RUNNER~1\AppData\Local\Temp\qutebrowser-basedir-ruvn1lys\data\webengine\DawnCache\data_0 + "Failed to open *webengine*DawnCache*data_*", + ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) From 4dd36aca04552e81abab83d8c0440d8199ae1752 Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 10 Nov 2024 15:17:25 +1300 Subject: [PATCH 258/403] Change check for new API to try/catch Previously it would have crashed with an AttributeError if a user had a PyQt of 6.8 but Qt of 6.7. Switch to catching any AttributeError which will hopefully cover either or both of Qt and PyQt not being a new enough version, even if it's a bit less of an explicit check. --- qutebrowser/browser/webengine/webenginesettings.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index c7b07f6e3..db9fe4960 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -26,7 +26,7 @@ from qutebrowser.config import config, websettings from qutebrowser.config.websettings import AttributeInfo as Attr from qutebrowser.misc import pakjoy from qutebrowser.utils import (standarddir, qtutils, message, log, - urlmatch, usertypes, objreg, version, utils) + urlmatch, usertypes, objreg, version) if TYPE_CHECKING: from qutebrowser.browser.webengine import interceptor @@ -352,11 +352,13 @@ class ProfileSetter: def disable_persistent_permissions_policy(self): """Disable webengine's permission persistence.""" - pyqt_version = utils.VersionNumber.parse(version.PYQT_WEBENGINE_VERSION_STR) - if pyqt_version >= utils.VersionNumber(6, 8): + try: + # New in WebEngine 6.8.0 self._profile.setPersistentPermissionsPolicy( QWebEngineProfile.PersistentPermissionsPolicy.AskEveryTime # type: ignore[attr-defined] ) + except AttributeError: + pass def _update_settings(option): From a085e3caa083829865083db1726822c4b9e3bdab Mon Sep 17 00:00:00 2001 From: toofar Date: Sun, 10 Nov 2024 15:47:39 +1300 Subject: [PATCH 259/403] use `unlink(missing_ok=True)` Combine the `if exists` and `unlink` in one step and avoid any race conditions with the file disappearing in between them. Co-Authored-By: Florian Bruhin --- qutebrowser/browser/webengine/webenginesettings.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index db9fe4960..39bd2d661 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -418,13 +418,12 @@ def _clear_webengine_permissions_json(): because Qt will load the file during that. """ permissions_file = pathlib.Path(standarddir.data()) / "webengine" / "permissions.json" - if permissions_file.exists(): - try: - permissions_file.unlink() - except OSError as err: - log.init.warning( - f"Error while cleaning up webengine permissions file: {err}" - ) + try: + permissions_file.unlink(missing_ok=True) + except OSError as err: + log.init.warning( + f"Error while cleaning up webengine permissions file: {err}" + ) def _init_default_profile(): From 6d9563035e90ec01f68399244af73fbf029f10dd Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 16 Nov 2024 14:42:46 +1300 Subject: [PATCH 260/403] Add test checking `Feature(8)` == ClipboardReadWrite Co-Authored-By: Florian Bruhin --- tests/unit/browser/webengine/test_webenginetab.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/unit/browser/webengine/test_webenginetab.py b/tests/unit/browser/webengine/test_webenginetab.py index d71a4bcd8..173254919 100644 --- a/tests/unit/browser/webengine/test_webenginetab.py +++ b/tests/unit/browser/webengine/test_webenginetab.py @@ -230,3 +230,17 @@ class TestFindFlags: backward=backward, ) assert str(flags) == expected + + +class TestWebEnginePermissions: + + def test_clipboard_value(self): + # Ensure the ClipboardReadWrite permission is in the permission map, + # despite us specifying it by number. + permissions_cls = webenginetab._WebEnginePermissions + try: + clipboard = QWebEnginePage.Feature.ClipboardReadWrite + except AttributeError: + pytest.skip("enum member not available") + assert clipboard in permissions_cls._options + assert clipboard in permissions_cls._messages From 664c554bf6d048fc6d3a63a74977d761a1268f45 Mon Sep 17 00:00:00 2001 From: qutebrowser bot Date: Mon, 18 Nov 2024 04:22:39 +0000 Subject: [PATCH 261/403] Update dependencies --- misc/requirements/requirements-check-manifest.txt | 2 +- misc/requirements/requirements-dev.txt | 4 ++-- misc/requirements/requirements-mypy.txt | 4 ++-- misc/requirements/requirements-pylint.txt | 4 ++-- misc/requirements/requirements-pyroma.txt | 2 +- misc/requirements/requirements-sphinx.txt | 2 +- misc/requirements/requirements-tests.txt | 11 ++++++----- misc/requirements/requirements-tox.txt | 4 ++-- misc/requirements/requirements-vulture.txt | 2 +- 9 files changed, 18 insertions(+), 17 deletions(-) diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index cdd6dc757..d8ecd8be4 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -5,5 +5,5 @@ check-manifest==0.50 importlib_metadata==8.5.0 packaging==24.2 pyproject_hooks==1.2.0 -tomli==2.0.2 +tomli==2.1.0 zipp==3.21.0 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index ffdd3bfb7..64de1264b 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -32,7 +32,7 @@ pkginfo==1.10.0 platformdirs==4.3.6 pycparser==2.22 Pygments==2.18.0 -PyJWT==2.9.0 +PyJWT==2.10.0 Pympler==1.1 pyproject_hooks==1.2.0 PyQt-builder==1.16.4 @@ -45,7 +45,7 @@ rich==13.9.4 SecretStorage==3.3.3 sip==6.8.6 six==1.16.0 -tomli==2.0.2 +tomli==2.1.0 twine==5.1.1 typeguard==4.3.0 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 211487a8b..5448c92af 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -10,10 +10,10 @@ mypy-extensions==1.0.0 pluggy==1.5.0 Pygments==2.18.0 PyQt5-stubs==5.15.6.0 -tomli==2.0.2 +tomli==2.1.0 types-colorama==0.4.15.20240311 types-docutils==0.21.0.20241005 types-Pygments==2.18.0.20240506 types-PyYAML==6.0.12.20240917 -types-setuptools==75.3.0.20241107 +types-setuptools==75.5.0.20241116 typing_extensions==4.12.2 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index c1fffcd9c..5e3fd77bb 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -13,13 +13,13 @@ mccabe==0.7.0 pefile==2024.8.26 platformdirs==4.3.6 pycparser==2.22 -PyJWT==2.9.0 +PyJWT==2.10.0 pylint==3.3.1 python-dateutil==2.9.0.post0 ./scripts/dev/pylint_checkers requests==2.32.3 six==1.16.0 -tomli==2.0.2 +tomli==2.1.0 tomlkit==0.13.2 typing_extensions==4.12.2 uritemplate==4.1.1 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index 44a1240e2..9635e676d 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -11,7 +11,7 @@ Pygments==2.18.0 pyproject_hooks==1.2.0 pyroma==4.2 requests==2.32.3 -tomli==2.0.2 +tomli==2.1.0 trove-classifiers==2024.10.21.16 urllib3==2.2.3 zipp==3.21.0 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index f9c12c5aa..0cd943477 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -21,6 +21,6 @@ sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 -tomli==2.0.2 +tomli==2.1.0 urllib3==2.2.3 zipp==3.21.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 9dc72aabd..70875edfe 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -9,13 +9,14 @@ certifi==2024.8.30 charset-normalizer==3.4.0 cheroot==10.0.1 click==8.1.7 -coverage==7.6.4 +coverage==7.6.7 exceptiongroup==1.2.2 execnet==2.1.1 filelock==3.16.1 -Flask==3.0.3 +Flask==3.1.0 +gherkin-official==29.0.0 hunter==3.7.0 -hypothesis==6.118.7 +hypothesis==6.119.3 idna==3.10 importlib_metadata==8.5.0 importlib_resources==6.4.5 @@ -40,7 +41,7 @@ pluggy==1.5.0 py-cpuinfo==9.0.0 Pygments==2.18.0 pytest==8.3.3 -pytest-bdd==7.3.0 +pytest-bdd==8.0.0 pytest-benchmark==5.1.0 pytest-cov==6.0.0 pytest-instafail==0.5.0 @@ -57,7 +58,7 @@ six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.6 tldextract==5.1.3 -tomli==2.0.2 +tomli==2.1.0 typeguard==4.3.0 typing_extensions==4.12.2 urllib3==2.2.3 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index dbaeb8821..43f3e4463 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -10,8 +10,8 @@ pip==24.3.1 platformdirs==4.3.6 pluggy==1.5.0 pyproject-api==1.8.0 -setuptools==75.3.0 -tomli==2.0.2 +setuptools==75.5.0 +tomli==2.1.0 tox==4.23.2 typing_extensions==4.12.2 virtualenv==20.27.1 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index bdedceb1d..5aa5e7a68 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -tomli==2.0.2 +tomli==2.1.0 vulture==2.13 From 590a602e5753affcb853c810ea224c25c273f4cc Mon Sep 17 00:00:00 2001 From: Aryan Dev Shourie Date: Fri, 22 Nov 2024 22:29:03 +0530 Subject: [PATCH 262/403] capitalized github text to maintain consistency --- README.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.asciidoc b/README.asciidoc index 9567473ee..ccb4ca72a 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -44,7 +44,7 @@ image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"] Downloads --------- -See the https://github.com/qutebrowser/qutebrowser/releases[github releases +See the https://github.com/qutebrowser/qutebrowser/releases[GitHub releases page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for detailed instructions on how to get qutebrowser running on various platforms. From 1888944a690f9840f193c887ddb201471467f727 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 16 Nov 2024 15:54:27 +1300 Subject: [PATCH 263/403] minimize conditional expression review feedback I've mixed opinions on this. I'm not convinced that ternary expressions are more readable than an if/else block. Also if someone passes a string into this function it'll return "access-paste" now. --- qutebrowser/config/configtypes.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index 69288e0f4..a64600652 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -2038,7 +2038,4 @@ class JSClipboardPermission(String, AsBool): return value == "access-paste" def from_bool(self, value: bool) -> str: - if value is True: - return "access-paste" - else: - return "none" + return "access-paste" if value else "none" From 427fb47e0ccf9f6fb21e622d60ace6ffa083f324 Mon Sep 17 00:00:00 2001 From: toofar Date: Sat, 16 Nov 2024 16:03:54 +1300 Subject: [PATCH 264/403] add copyright header to test file review feedback Hopefully this is an okay header format for checkers to pick up, it's the same as in qutebrowser/html/version.html except with the doctype declaration above. --- tests/end2end/data/prompt/clipboard.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/end2end/data/prompt/clipboard.html b/tests/end2end/data/prompt/clipboard.html index dd247eceb..fff148343 100644 --- a/tests/end2end/data/prompt/clipboard.html +++ b/tests/end2end/data/prompt/clipboard.html @@ -1,5 +1,8 @@ - +