diff options
25 files changed, 137 insertions, 67 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 098c4e929..b6385b015 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -24,6 +24,10 @@ 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 + security fixes have been backported to the base QtWebEngine version from. + (#7187) Changed ~~~~~~~ @@ -41,6 +45,8 @@ Fixed - `input.insert_mode.auto_load` sometimes not triggering due to a race condition. +- Worked around qutebrowser quitting when closing a KDE file dialog due to a Qt + bug. [[v3.1.1]] v3.1.1 (unreleased) diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 810c6da1b..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.0.0 +backports.tarfile==1.1.1 build==1.2.1 bump2version==1.0.1 certifi==2024.2.2 @@ -10,14 +10,14 @@ cryptography==42.0.5 docutils==0.20.1 github3.py==4.0.1 hunter==3.6.1 -idna==3.6 +idna==3.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 +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.0 +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 3f2d52014..a560af530 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,8 +2,8 @@ attrs==23.2.0 flake8==7.0.0 -flake8-bugbear==24.2.6 -flake8-builtins==2.4.0 +flake8-bugbear==24.4.26 +flake8-builtins==2.5.0 flake8-comprehensions==3.14.0 flake8-debugger==4.1.2 flake8-deprecated==2.2.1 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 8f6f90f6c..fcea79aca 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==8.0.3 +diff_cover==9.0.0 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.4.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.2.0.20240317 +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 d8dc96e1c..8435786a5 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.5.0 -pyinstaller-hooks-contrib==2024.3 +pyinstaller==6.6.0 +pyinstaller-hooks-contrib==2024.5 zipp==3.18.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index ab2cfafc6..192ba4c97 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -7,11 +7,11 @@ charset-normalizer==3.3.2 cryptography==42.0.5 dill==0.3.8 github3.py==4.0.1 -idna==3.6 +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-pyroma.txt b/misc/requirements/requirements-pyroma.txt index f3926d842..141faf1cb 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -4,7 +4,7 @@ build==1.2.1 certifi==2024.2.2 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.6 +idna==3.7 importlib_metadata==7.1.0 packaging==24.0 Pygments==2.17.2 @@ -12,6 +12,6 @@ pyproject_hooks==1.0.0 pyroma==4.2 requests==2.31.0 tomli==2.0.1 -trove-classifiers==2024.3.25 +trove-classifiers==2024.4.10 urllib3==2.2.1 zipp==3.18.1 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index ac0d905ae..7bb66c6a0 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -5,7 +5,7 @@ Babel==2.14.0 certifi==2024.2.2 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.6 +idna==3.7 imagesize==1.4.1 importlib_metadata==7.1.0 Jinja2==3.1.3 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 5179a467d..2a86c3202 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,35 +2,35 @@ 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.0 +cheroot==10.0.1 click==8.1.7 -coverage==7.4.4 -exceptiongroup==1.2.0 -execnet==2.1.0 -filelock==3.13.3 +coverage==7.5.0 +exceptiongroup==1.2.1 +execnet==2.1.1 +filelock==3.13.4 Flask==3.0.3 hunter==3.6.1 -hypothesis==6.100.0 -idna==3.6 +hypothesis==6.100.2 +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.2 +Mako==1.3.3 manhole==1.8.0 # MarkupSafe==2.1.5 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 +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 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 9acc07370..975774969 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.13.3 +filelock==3.13.4 packaging==24.0 pip==24.0 -platformdirs==4.2.0 -pluggy==1.4.0 +platformdirs==4.2.1 +pluggy==1.5.0 pyproject-api==1.6.1 -setuptools==69.2.0 +setuptools==69.5.1 tomli==2.0.1 -tox==4.14.2 -virtualenv==20.25.1 +tox==4.15.0 +virtualenv==20.26.0 wheel==0.43.0 diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 015715eef..51603a2b9 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -132,6 +132,9 @@ def init(*, args: argparse.Namespace) -> None: crashsignal.crash_handler.init_faulthandler() objects.qapp.setQuitOnLastWindowClosed(False) + # WORKAROUND for KDE file dialogs / QEventLoopLocker quitting: + # https://bugreports.qt.io/browse/QTBUG-124386 + objects.qapp.setQuitLockEnabled(False) quitter.instance.shutting_down.connect(QApplication.closeAllWindows) _init_icon() 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.""" 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/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/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index a4bb893f6..32d5357db 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -535,6 +535,7 @@ class WebEngineVersions: webengine: utils.VersionNumber chromium: Optional[str] source: str + chromium_security: Optional[str] = None chromium_major: Optional[int] = dataclasses.field(init=False) _CHROMIUM_VERSIONS: ClassVar[Dict[utils.VersionNumber, str]] = { @@ -638,6 +639,8 @@ class WebEngineVersions: s = f'QtWebEngine {self.webengine}' if self.chromium is not None: s += 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 @@ -695,7 +698,12 @@ class WebEngineVersions: return cls._CHROMIUM_VERSIONS.get(minor_version) @classmethod - def from_api(cls, qtwe_version: str, chromium_version: Optional[str]) -> 'WebEngineVersions': + def from_api( + cls, + qtwe_version: str, + chromium_version: Optional[str], + chromium_security: Optional[str] = None, + ) -> 'WebEngineVersions': """Get the versions based on the exact versions. This is called if we have proper APIs to get the versions easily @@ -705,6 +713,7 @@ class WebEngineVersions: return cls( webengine=parsed, chromium=chromium_version, + chromium_security=chromium_security, source='api', ) @@ -805,11 +814,20 @@ def qtwebengine_versions(*, avoid_init: bool = False) -> WebEngineVersions: except ImportError: pass # Needs QtWebEngine 6.2+ with PyQtWebEngine 6.3.1+ else: + try: + from qutebrowser.qt.webenginecore import ( + qWebEngineChromiumSecurityPatchVersion, + ) + chromium_security = qWebEngineChromiumSecurityPatchVersion() + except ImportError: + chromium_security = None # Needs QtWebEngine 6.3+ + qtwe_version = qWebEngineVersion() assert qtwe_version is not None return WebEngineVersions.from_api( qtwe_version=qtwe_version, chromium_version=qWebEngineChromiumVersion(), + chromium_security=chromium_security, ) from qutebrowser.browser.webengine import webenginesettings 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 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 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 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 <Escape> 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 x<greater>y<less>" " Then the javascript message "key press: 88" should be logged And the javascript message "key release: 88" should be logged 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, 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 diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 38134b40e..f24bf2a7a 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -915,6 +915,17 @@ class TestWebEngineVersions: source='faked'), "QtWebEngine 5.15.2, based on Chromium 87.0.4280.144 (from faked)", ), + ( + version.WebEngineVersions( + webengine=utils.VersionNumber(5, 15, 2), + chromium='87.0.4280.144', + 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)" + ), + ), ]) def test_str(self, version, expected): assert str(version) == expected @@ -1024,6 +1035,20 @@ class TestWebEngineVersions: assert inferred == real + def test_real_chromium_security_version(self, qapp): + """Check the API for reading the chromium security patch version.""" + try: + from qutebrowser.qt.webenginecore import ( + qWebEngineChromiumVersion, + qWebEngineChromiumSecurityPatchVersion, + ) + except ImportError: + pytest.skip("Requires QtWebEngine 6.3+") + + base = utils.VersionNumber.parse(qWebEngineChromiumVersion()) + security = utils.VersionNumber.parse(qWebEngineChromiumSecurityPatchVersion()) + assert security >= base + class FakeQSslSocket: |