diff options
author | toofar <toofar@spalge.com> | 2024-04-21 17:31:39 +1200 |
---|---|---|
committer | toofar <toofar@spalge.com> | 2024-04-21 17:31:39 +1200 |
commit | 9bf2bae60f6daf631729cb901c845e6c48da4778 (patch) | |
tree | 8dfbd390b2ad17bd303c45609a124f7be75fe56b | |
parent | 7ab4346e501b2011a4be1c5d584008833c6aad6f (diff) | |
parent | 80931acab0cc63760dfc484820b783aaced439b6 (diff) | |
download | qutebrowser-tree-tabs-integration.tar.gz qutebrowser-tree-tabs-integration.zip |
Merge remote-tracking branch 'upstream/main' into tree-tabs-integrationtree-tabs-integration
83 files changed, 601 insertions, 269 deletions
@@ -70,6 +70,4 @@ per-file-ignores = copyright-check = True copyright-regexp = # Copyright [\d-]+ .* copyright-min-file-size = 110 -pytest-fixture-no-parentheses = True -pytest-mark-no-parentheses = True pytest-parametrize-names-type = csv diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe1bbefe7..599ba3b1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | .mypy_cache @@ -56,7 +56,7 @@ jobs: run: "python scripts/dev/ci/problemmatchers.py ${{ matrix.testenv }} ${{ runner.temp }}" - name: Install dependencies run: | - [[ ${{ matrix.testenv }} == eslint ]] && npm install -g eslint + [[ ${{ matrix.testenv }} == eslint ]] && npm install -g 'eslint@<9.0.0' [[ ${{ matrix.testenv }} == docs ]] && sudo apt-get update && sudo apt-get install --no-install-recommends asciidoc libegl1-mesa [[ ${{ matrix.testenv }} == vulture || ${{ matrix.testenv }} == pylint ]] && sudo apt-get update && sudo apt-get install --no-install-recommends libegl1-mesa if [[ ${{ matrix.testenv }} == shellcheck ]]; then @@ -184,7 +184,7 @@ jobs: - uses: actions/checkout@v4 with: persist-credentials: false - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: | .mypy_cache diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3762e6f48..9dc925e29 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -10,6 +10,7 @@ jobs: if: "github.repository == 'qutebrowser/qutebrowser'" runs-on: ubuntu-20.04 strategy: + fail-fast: false matrix: image: - archlinux-webkit diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 764bcae31..b326c2ad6 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -59,7 +59,9 @@ jobs: - name: Patch qutebrowser for debugging if: "contains(matrix.args, '--debug')" run: | - sed -i '' '/.-d., .--debug.,/s/$/ default=True,/' qutebrowser/qutebrowser.py + sed '/.-d., .--debug.,/s/$/ default=True,/' qutebrowser/qutebrowser.py > qutebrowser/qutebrowser.py.tmp + cp qutebrowser/qutebrowser.py.tmp qutebrowser/qutebrowser.py + rm qutebrowser/qutebrowser.py.tmp - name: Run tox run: "tox -e ${{ matrix.toxenv }} -- --gh-token ${{ secrets.GITHUB_TOKEN }} ${{ matrix.args }}" - name: Gather info diff --git a/.github/workflows/recompile-requirements.yml b/.github/workflows/recompile-requirements.yml index 1e5637a10..6d42c3137 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@v5 + uses: peter-evans/create-pull-request@v6 with: committer: qutebrowser bot <bot@qutebrowser.org> author: qutebrowser bot <bot@qutebrowser.org> diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a751591f..aa8b3b2ef 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -117,7 +117,7 @@ jobs: git push --set-upstream origin v${{ steps.bump.outputs.version_x }} - name: Create GitHub draft release id: create-release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.bump.outputs.version }} draft: true @@ -22,13 +22,15 @@ disallow_any_unimported = True enable_error_code = ignore-without-code ### Output -show_error_codes = True show_error_context = True pretty = True ### FIXME:v4 get rid of this no_implicit_optional = False +### Future default behavior +local_partial_types = True + [mypy-hunter] # https://github.com/ionelmc/python-hunter/issues/43 ignore_missing_imports = True diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 91b02b0da..b6385b015 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -19,12 +19,34 @@ breaking changes (such as renamed commands) can happen in minor releases. v3.2.0 (unreleased) ------------------- +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 ~~~~~~~ - 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) +- Elements with an ARIA `role="switch"` now get hints (toggle switches like + e.g. on cookie banners). +- The `tor_identity` userscript now validates that the -c|--control-port + argument value is an int. (#8162) + +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/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 3ff98a9bf..73646e541 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -115,7 +115,7 @@ |<<colors.tooltip.bg,colors.tooltip.bg>>|Background color of tooltips. |<<colors.tooltip.fg,colors.tooltip.fg>>|Foreground color of tooltips. |<<colors.webpage.bg,colors.webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color). -|<<colors.webpage.darkmode.algorithm,colors.webpage.darkmode.algorithm>>|Which algorithm to use for modifying how colors are rendered with darkmode. +|<<colors.webpage.darkmode.algorithm,colors.webpage.darkmode.algorithm>>|Which algorithm to use for modifying how colors are rendered with dark mode. |<<colors.webpage.darkmode.contrast,colors.webpage.darkmode.contrast>>|Contrast for dark mode. |<<colors.webpage.darkmode.enabled,colors.webpage.darkmode.enabled>>|Render all web contents using a dark theme. |<<colors.webpage.darkmode.policy.images,colors.webpage.darkmode.policy.images>>|Which images to apply dark mode to. @@ -1662,7 +1662,7 @@ Default: +pass:[white]+ [[colors.webpage.darkmode.algorithm]] === colors.webpage.darkmode.algorithm -Which algorithm to use for modifying how colors are rendered with darkmode. +Which algorithm to use for modifying how colors are rendered with dark mode. The `lightness-cielab` value was added with QtWebEngine 5.14 and is treated like `lightness-hsl` with older QtWebEngine versions. This setting requires a restart. @@ -3493,6 +3493,7 @@ Default: * +pass:[[role="button"\]]+ * +pass:[[role="tab"\]]+ * +pass:[[role="checkbox"\]]+ +* +pass:[[role="switch"\]]+ * +pass:[[role="menuitem"\]]+ * +pass:[[role="menuitemcheckbox"\]]+ * +pass:[[role="menuitemradio"\]]+ diff --git a/doc/install.asciidoc b/doc/install.asciidoc index ec53e2fb5..98cc6fb05 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -280,14 +280,14 @@ track down issues. NOTE: Due to GitHub limitations, you need to be signed in with a GitHub account to download the files. -https://chocolatey.org/packages/qutebrowser[Chocolatey package] -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Package managers +~~~~~~~~~~~~~~~~ * PackageManagement PowerShell module ---- PS C:\> Install-Package qutebrowser ---- -* Chocolatey's client +* https://chocolatey.org/packages/qutebrowser[Chocolatey package] with `choco`: ---- C:\> choco install qutebrowser ---- diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 0d250cf3c..b0993ea58 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.0.3 +build==1.2.1 check-manifest==0.49 -importlib-metadata==7.0.1 -packaging==23.2 +importlib_metadata==7.1.0 +packaging==24.0 pyproject_hooks==1.0.0 tomli==2.0.1 -zipp==3.17.0 +zipp==3.18.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 5e7f67489..59a1f8a03 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,45 +1,48 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==1.0.3 +backports.tarfile==1.0.0 +build==1.2.1 bump2version==1.0.1 -certifi==2023.11.17 +certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==41.0.7 +cryptography==42.0.5 docutils==0.20.1 github3.py==4.0.1 hunter==3.6.1 -idna==3.6 -importlib-metadata==7.0.1 -importlib-resources==6.1.1 -jaraco.classes==3.3.0 +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 jeepney==0.8.0 -keyring==24.3.0 +keyring==25.1.0 manhole==1.8.0 markdown-it-py==3.0.0 mdurl==0.1.2 -more-itertools==10.1.0 -nh3==0.2.15 -packaging==23.2 -pkginfo==1.9.6 -pycparser==2.21 +more-itertools==10.2.0 +nh3==0.2.17 +packaging==24.0 +pkginfo==1.10.0 +pycparser==2.22 Pygments==2.17.2 PyJWT==2.8.0 Pympler==1.0.1 pyproject_hooks==1.0.0 -PyQt-builder==1.15.4 -python-dateutil==2.8.2 -readme-renderer==42.0 +PyQt-builder==1.16.0 +python-dateutil==2.9.0.post0 +readme_renderer==43.0 requests==2.31.0 requests-toolbelt==1.0.0 rfc3986==2.0.0 -rich==13.7.0 +rich==13.7.1 SecretStorage==3.3.3 -sip==6.8.1 +sip==6.8.3 six==1.16.0 tomli==2.0.1 -twine==4.0.2 -typing_extensions==4.9.0 +twine==5.0.0 +typing_extensions==4.11.0 uritemplate==4.1.1 -# urllib3==2.1.0 -zipp==3.17.0 +# urllib3==2.2.1 +zipp==3.18.1 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 41b9e6c3e..fa541e4a8 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -2,15 +2,15 @@ attrs==23.2.0 flake8==7.0.0 -flake8-bugbear==23.12.2 -flake8-builtins==2.2.0 +flake8-bugbear==24.2.6 +flake8-builtins==2.5.0 flake8-comprehensions==3.14.0 flake8-debugger==4.1.2 flake8-deprecated==2.2.1 flake8-docstrings==1.7.0 flake8-future-import==0.4.7 flake8-plugin-utils==1.3.3 -flake8-pytest-style==1.7.2 +flake8-pytest-style==2.0.0 flake8-string-format==0.3.0 flake8-tidy-imports==4.10.0 flake8-tuple==0.4.1 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index b31bb7722..9bb872d13 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.2 -importlib-resources==6.1.1 -Jinja2==3.1.2 -lxml==5.0.1 -MarkupSafe==2.1.3 -mypy==1.8.0 +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-extensions==1.0.0 -pluggy==1.3.0 +pluggy==1.4.0 Pygments==2.17.2 PyQt5-stubs==5.15.6.0 tomli==2.0.1 -types-colorama==0.4.15.20240106 -types-docutils==0.20.0.20240106 -types-Pygments==2.17.0.20240106 -types-PyYAML==6.0.12.12 -types-setuptools==69.0.0.20240106 -typing_extensions==4.9.0 -zipp==3.17.0 +types-colorama==0.4.15.20240311 +types-docutils==0.20.0.20240406 +types-Pygments==2.17.0.20240310 +types-PyYAML==6.0.12.20240311 +types-setuptools==69.5.0.20240415 +typing_extensions==4.11.0 +zipp==3.18.1 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index af7a329fd..6c4b1d43b 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==7.0.1 -packaging==23.2 -pyinstaller==6.3.0 -pyinstaller-hooks-contrib==2023.12 -zipp==3.17.0 +importlib_metadata==7.1.0 +packaging==24.0 +pyinstaller==6.6.0 +pyinstaller-hooks-contrib==2024.4 +zipp==3.18.1 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index b3a5198b4..94aac82a0 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.0.2 -certifi==2023.11.17 +astroid==3.1.0 +certifi==2024.2.2 cffi==1.16.0 charset-normalizer==3.3.2 -cryptography==41.0.7 -dill==0.3.7 +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.1.0 -pycparser==2.21 +platformdirs==4.2.0 +pycparser==2.22 PyJWT==2.8.0 -pylint==3.0.3 -python-dateutil==2.8.2 +pylint==3.1.0 +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.3 -typing_extensions==4.9.0 +tomlkit==0.12.4 +typing_extensions==4.11.0 uritemplate==4.1.1 -# urllib3==2.1.0 +# urllib3==2.2.1 diff --git a/misc/requirements/requirements-pyqt-6.6.txt b/misc/requirements/requirements-pyqt-6.6.txt index 914422a38..02f1a325f 100644 --- a/misc/requirements/requirements-pyqt-6.6.txt +++ b/misc/requirements/requirements-pyqt-6.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.1 +PyQt6-Qt6==6.6.3 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.6.0 -PyQt6-WebEngine-Qt6==6.6.1 +PyQt6-WebEngine-Qt6==6.6.3 diff --git a/misc/requirements/requirements-pyqt-6.txt b/misc/requirements/requirements-pyqt-6.txt index 914422a38..02f1a325f 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.1 +PyQt6-Qt6==6.6.3 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.6.0 -PyQt6-WebEngine-Qt6==6.6.1 +PyQt6-WebEngine-Qt6==6.6.3 diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 914422a38..02f1a325f 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.1 +PyQt6-Qt6==6.6.3 PyQt6-sip==13.6.0 PyQt6-WebEngine==6.6.0 -PyQt6-WebEngine-Qt6==6.6.1 +PyQt6-WebEngine-Qt6==6.6.3 diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index b93fb2be5..141faf1cb 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.0.3 -certifi==2023.11.17 +build==1.2.1 +certifi==2024.2.2 charset-normalizer==3.3.2 docutils==0.20.1 -idna==3.6 -importlib-metadata==7.0.1 -packaging==23.2 +idna==3.7 +importlib_metadata==7.1.0 +packaging==24.0 Pygments==2.17.2 pyproject_hooks==1.0.0 pyroma==4.2 requests==2.31.0 tomli==2.0.1 -trove-classifiers==2023.11.29 -urllib3==2.1.0 -zipp==3.17.0 +trove-classifiers==2024.4.10 +urllib3==2.2.1 +zipp==3.18.1 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index bd33e26e4..ca4081d1d 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -13,11 +13,11 @@ PyYAML #@ add: pyobjc-framework-Cocoa ; sys_platform=="darwin" ## stdlib backports -importlib-resources +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.*" +#@ markers: importlib_resources python_version=="3.8.*" diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index 8d7cc145c..7bb66c6a0 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -2,17 +2,17 @@ alabaster==0.7.13 Babel==2.14.0 -certifi==2023.11.17 +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.0.1 -Jinja2==3.1.2 -MarkupSafe==2.1.3 -packaging==23.2 +importlib_metadata==7.1.0 +Jinja2==3.1.3 +MarkupSafe==2.1.5 +packaging==24.0 Pygments==2.17.2 -pytz==2023.3.post1 +pytz==2024.1 requests==2.31.0 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.1.0 -zipp==3.17.0 +urllib3==2.2.1 +zipp==3.18.1 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 744abbcfc..0d8aa9bc4 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -1,57 +1,56 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py attrs==23.2.0 -beautifulsoup4==4.12.2 +beautifulsoup4==4.12.3 blinker==1.7.0 -certifi==2023.11.17 +certifi==2024.2.2 charset-normalizer==3.3.2 cheroot==10.0.0 click==8.1.7 -coverage==7.4.0 +coverage==7.4.4 exceptiongroup==1.2.0 -execnet==2.0.2 -filelock==3.13.1 -Flask==3.0.0 +execnet==2.1.1 +filelock==3.13.4 +Flask==3.0.3 hunter==3.6.1 -hypothesis==6.92.3 -idna==3.6 -importlib-metadata==7.0.1 +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 -# Jinja2==3.1.2 -Mako==1.3.0 +# Jinja2==3.1.3 +Mako==1.3.3 manhole==1.8.0 -# MarkupSafe==2.1.3 -more-itertools==10.1.0 -packaging==23.2 -parse==1.20.0 +# MarkupSafe==2.1.5 +more-itertools==10.2.0 +packaging==24.0 +parse==1.20.1 parse-type==0.6.2 -pluggy==1.3.0 +pluggy==1.4.0 py-cpuinfo==9.0.0 Pygments==2.17.2 -pytest==7.4.4 -pytest-bdd==7.0.1 +pytest==8.1.1 +pytest-bdd==7.1.2 pytest-benchmark==4.0.0 -pytest-cov==4.1.0 +pytest-cov==5.0.0 pytest-instafail==0.5.0 -pytest-mock==3.12.0 -pytest-qt==4.3.1 +pytest-mock==3.14.0 +pytest-qt==4.4.0 pytest-repeat==0.9.3 -pytest-rerunfailures==13.0 +pytest-rerunfailures==14.0 pytest-xdist==3.5.0 pytest-xvfb==3.0.0 PyVirtualDisplay==3.0 requests==2.31.0 -requests-file==1.5.1 +requests-file==2.0.0 six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.5 -tldextract==5.1.1 -toml==0.10.2 +tldextract==5.1.2 tomli==2.0.1 -typing_extensions==4.9.0 -urllib3==2.1.0 -vulture==2.10 -Werkzeug==3.0.1 -zipp==3.17.0 +typing_extensions==4.11.0 +urllib3==2.2.1 +vulture==2.11 +Werkzeug==3.0.2 +zipp==3.18.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index 79f770342..afea097d0 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.2 +cachetools==5.3.3 chardet==5.2.0 colorama==0.4.6 distlib==0.3.8 -filelock==3.13.1 -packaging==23.2 -pip==23.3.2 -platformdirs==4.1.0 -pluggy==1.3.0 +filelock==3.13.4 +packaging==24.0 +pip==24.0 +platformdirs==4.2.0 +pluggy==1.4.0 pyproject-api==1.6.1 -setuptools==69.0.3 +setuptools==69.5.1 tomli==2.0.1 -tox==4.11.4 -virtualenv==20.25.0 -wheel==0.42.0 +tox==4.14.2 +virtualenv==20.25.1 +wheel==0.43.0 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 9bceeb7b1..a7d37e73a 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 -toml==0.10.2 -vulture==2.10 +tomli==2.0.1 +vulture==2.11 diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt index 8ecdcd508..4fb649ec4 100644 --- a/misc/requirements/requirements-yamllint.txt +++ b/misc/requirements/requirements-yamllint.txt @@ -2,4 +2,4 @@ pathspec==0.12.1 PyYAML==6.0.1 -yamllint==1.33.0 +yamllint==1.35.1 diff --git a/misc/userscripts/tor_identity b/misc/userscripts/tor_identity index 6b3828ed4..a6d7c9250 100755 --- a/misc/userscripts/tor_identity +++ b/misc/userscripts/tor_identity @@ -32,7 +32,7 @@ except ImportError: if __name__ == '__main__': parser = ArgumentParser(prog='tor_identity') - parser.add_argument('-c', '--control-port', default=9051, + parser.add_argument('-c', '--control-port', type=int, default=9051, help='Tor control port (default 9051).') parser.add_argument('-p', '--password', type=str, default=None, help='Tor control port password.') diff --git a/pytest.ini b/pytest.ini index f2f746284..2de880eae 100644 --- a/pytest.ini +++ b/pytest.ini @@ -61,6 +61,15 @@ qt_log_ignore = ^QBackingStore::endPaint\(\) called with active painter; did you forget to destroy it or call QPainter::end\(\) on it\?$ # Qt 6.5 after system update, from qt-qt.accessibility.atspi Error in contacting registry: "org\.freedesktop\.DBus\.Error\.Disconnected" "Not connected to D-Bus server" + # Seen in Qt 6.6.2 on CI, https://github.com/qutebrowser/qutebrowser/pull/8106#issuecomment-1952320663 + ^QDBusConnection: couldn't handle call to Notify, no slot matched + ^QDBusConnection: couldn't handle call to CloseNotification, no slot matched + # Qt 6.7 + ^Path override failed for key base::DIR_APP_DICTIONARIES and path '.*/qtwebengine_dictionaries' + # Sometime the above message gets printed twice at the same time and the messages get interleaved. + # 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'$ xfail_strict = true filterwarnings = error 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/browsertab.py b/qutebrowser/browser/browsertab.py index 9419770ea..e7e56a061 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -14,7 +14,7 @@ from typing import (cast, TYPE_CHECKING, Any, Callable, Iterable, List, Optional from qutebrowser.qt import machinery from qutebrowser.qt.core import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt, - QEvent, QPoint, QRect) + QEvent, QPoint, QRect, QTimer) from qutebrowser.qt.gui import QKeyEvent, QIcon, QPixmap from qutebrowser.qt.widgets import QApplication, QWidget from qutebrowser.qt.printsupport import QPrintDialog, QPrinter @@ -904,7 +904,13 @@ class AbstractTabPrivate: modeman.enter(self._tab.win_id, usertypes.KeyMode.insert, 'load finished', only_if_normal=True) - self._tab.elements.find_focused(_auto_insert_mode_cb) + # There seems to be a race between loadFinished being called, + # and the autoload attribute on websites actually focusing anything. + # Thus, we delay this by a bit. Locally, a delay of 13ms caused no races + # with 5000 test reruns (even with simultaneous CPU stress testing), + # so 65ms should be a safe bet and still not be too noticeable. + QTimer.singleShot( + 65, lambda: self._tab.elements.find_focused(_auto_insert_mode_cb)) def clear_ssl_errors(self) -> None: raise NotImplementedError diff --git a/qutebrowser/browser/network/proxy.py b/qutebrowser/browser/network/proxy.py index f0c511853..62872d68e 100644 --- a/qutebrowser/browser/network/proxy.py +++ b/qutebrowser/browser/network/proxy.py @@ -4,6 +4,8 @@ """Handling of proxies.""" +from typing import Optional + from qutebrowser.qt.core import QUrl, pyqtSlot from qutebrowser.qt.network import QNetworkProxy, QNetworkProxyFactory @@ -13,7 +15,7 @@ from qutebrowser.misc import objects from qutebrowser.browser.network import pac -application_factory = None +application_factory: Optional["ProxyFactory"] = None def init(): diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py index 9dd507ab5..0360eed66 100644 --- a/qutebrowser/browser/qtnetworkdownloads.py +++ b/qutebrowser/browser/qtnetworkdownloads.py @@ -19,7 +19,7 @@ from qutebrowser.config import config, websettings from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug, objreg, qtlog from qutebrowser.misc import quitter from qutebrowser.browser import downloads -from qutebrowser.browser.webkit import http +from qutebrowser.browser.webkit import httpheaders from qutebrowser.browser.webkit.network import networkmanager @@ -533,7 +533,7 @@ class DownloadManager(downloads.AbstractDownloadManager): try: suggested_filename = target.suggested_filename() except downloads.NoFilenameError: - _, suggested_filename = http.parse_content_disposition(reply) + _, suggested_filename = httpheaders.parse_content_disposition(reply) log.downloads.debug("fetch: {} -> {}".format(reply.url(), suggested_filename)) download = DownloadItem(reply, manager=self) diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index f1c6711fd..e7bb27d8d 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -32,10 +32,10 @@ from qutebrowser.qt import sip pyeval_output = ":pyeval was never called" -csrf_token = None +csrf_token: Optional[str] = None -_HANDLERS = {} +_HANDLERS: Dict[str, "_HandlerCallable"] = {} class Error(Exception): diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 2b375a7b0..78a4946ad 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -37,7 +37,7 @@ private_profile: Optional[QWebEngineProfile] = None # The global WebEngineSettings object _global_settings = cast('WebEngineSettings', None) -parsed_user_agent = None +parsed_user_agent: Optional[websettings.UserAgent] = None _qute_scheme_handler = cast(webenginequtescheme.QuteSchemeHandler, None) _req_interceptor = cast('interceptor.RequestInterceptor', None) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 1c712db5e..02d912a50 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -292,6 +292,8 @@ class WebEngineCaret(browsertab.AbstractCaret): flags = set() if utils.is_windows: flags.add('windows') + if 'caret' in objects.debug_flags: + flags.add('debug') return list(flags) @pyqtSlot(usertypes.KeyMode) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index a6f2ae113..96c0c97e5 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -238,9 +238,7 @@ class WebEnginePage(QWebEnginePage): self._set_bg_color() config.instance.changed.connect(self._set_bg_color) if machinery.IS_QT6: - self.certificateError.connect( # pylint: disable=no-member - self._handle_certificate_error - ) + self.certificateError.connect(self._handle_certificate_error) # Qt 5: Overridden method instead of signal @config.change_filter('colors.webpage.bg') diff --git a/qutebrowser/browser/webkit/http.py b/qutebrowser/browser/webkit/httpheaders.py index 95b7b7104..95b7b7104 100644 --- a/qutebrowser/browser/webkit/http.py +++ b/qutebrowser/browser/webkit/httpheaders.py diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index ea19174ec..595432dc9 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -18,7 +18,7 @@ from qutebrowser.qt.webkitwidgets import QWebPage, QWebFrame from qutebrowser.config import websettings, config from qutebrowser.browser import pdfjs, shared, downloads, greasemonkey -from qutebrowser.browser.webkit import http +from qutebrowser.browser.webkit import httpheaders from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.utils import message, usertypes, log, jinja, objreg from qutebrowser.qt import sip @@ -263,14 +263,14 @@ class BrowserPage(QWebPage): At some point we might want to implement the MIME Sniffing standard here: https://mimesniff.spec.whatwg.org/ """ - inline, suggested_filename = http.parse_content_disposition(reply) + inline, suggested_filename = httpheaders.parse_content_disposition(reply) download_manager = objreg.get('qtnetwork-download-manager') if not inline: # Content-Disposition: attachment -> force download download_manager.fetch(reply, suggested_filename=suggested_filename) return - mimetype, _rest = http.parse_content_type(reply) + mimetype, _rest = httpheaders.parse_content_type(reply) if mimetype == 'image/jpg': # Some servers (e.g. the LinkedIn CDN) send a non-standard # image/jpg (instead of image/jpeg, defined in RFC 1341 section diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 1ad563c5d..0d63d0021 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, Iterator, Mapping, MutableMapping +from typing import TYPE_CHECKING, Callable, Dict, Tuple, Iterator, Mapping, MutableMapping from qutebrowser.qt.core import pyqtSlot, QUrl, QObject @@ -21,7 +21,7 @@ if TYPE_CHECKING: _ReplacementFunction = Callable[['tabbedbrowser.TabbedBrowser'], str] -last_command = {} +last_command: Dict[usertypes.KeyMode, Tuple[str, int]] = {} def _url(tabbed_browser): diff --git a/qutebrowser/completion/completionwidget.py b/qutebrowser/completion/completionwidget.py index f042be0a1..0f5dc0de9 100644 --- a/qutebrowser/completion/completionwidget.py +++ b/qutebrowser/completion/completionwidget.py @@ -437,8 +437,7 @@ class CompletionView(QTreeView): contents_height = ( self.viewportSizeHint().height() + bar.sizeHint().height()) - if contents_height <= height: - height = contents_height + height = min(height, contents_height) # The width isn't really relevant as we're expanding anyways. return QSize(-1, height) diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 573f25fa1..cb7fe77b3 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -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 = [] +change_filters: List["change_filter"] = [] # Sentinel UNSET = object() diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 6d391e596..8abc0b7d2 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -1784,6 +1784,7 @@ hints.selectors: - '[role="button"]' - '[role="tab"]' - '[role="checkbox"]' + - '[role="switch"]' - '[role="menuitem"]' - '[role="menuitemcheckbox"]' - '[role="menuitemradio"]' @@ -3330,7 +3331,7 @@ colors.webpage.darkmode.enabled: colors.webpage.darkmode.algorithm: default: lightness-cielab desc: >- - Which algorithm to use for modifying how colors are rendered with darkmode. + Which algorithm to use for modifying how colors are rendered with dark mode. The `lightness-cielab` value was added with QtWebEngine 5.14 and is treated like `lightness-hsl` with older QtWebEngine versions. diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 5d000c3ec..a08ddb619 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -7,6 +7,7 @@ import argparse import os.path import sys +from typing import Optional from qutebrowser.qt.widgets import QMessageBox @@ -19,7 +20,7 @@ from qutebrowser.misc import msgbox, objects, savemanager # Error which happened during init, so we can show a message box. -_init_errors = None +_init_errors: Optional[configexc.ConfigFileErrors] = None def early_init(args: argparse.Namespace) -> None: diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 9f5bf92d0..7824ae258 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -246,7 +246,7 @@ def clear_private_data() -> None: elif objects.backend == usertypes.Backend.QtWebKit: from qutebrowser.browser.webkit import cookies assert cookies.ram_cookie_jar is not None - cookies.ram_cookie_jar.setAllCookies([]) # type: ignore[unreachable] + cookies.ram_cookie_jar.setAllCookies([]) else: raise utils.Unreachable(objects.backend) diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py index b5b232c5a..ff9974d9d 100644 --- a/qutebrowser/extensions/loader.py +++ b/qutebrowser/extensions/loader.py @@ -21,7 +21,7 @@ from qutebrowser.misc import objects # ModuleInfo objects for all loaded plugins -_module_infos = [] +_module_infos: List["ModuleInfo"] = [] InitHookType = Callable[['InitContext'], None] ConfigChangedHookType = Callable[[], None] diff --git a/qutebrowser/javascript/caret.js b/qutebrowser/javascript/caret.js index b16a15348..4aeefcdb9 100644 --- a/qutebrowser/javascript/caret.js +++ b/qutebrowser/javascript/caret.js @@ -740,6 +740,12 @@ window._qutebrowser.caret = (function() { CaretBrowsing.isWindows = null; /** + * Whether we should log debug outputs. + * @type {boolean} + */ + CaretBrowsing.isDebug = null; + + /** * The id returned by window.setInterval for our stopAnimation function, so * we can cancel it when we call stopAnimation again. * @type {number?} @@ -1150,6 +1156,8 @@ window._qutebrowser.caret = (function() { action = "extend"; } + CaretBrowsing.debug(`(move) ${action} ${count} ${granularity} ${direction}, selection ${CaretBrowsing.selectionState}`); + for (let i = 0; i < count; i++) { if (CaretBrowsing.selectionState === CaretBrowsing.SelectionState.LINE) { CaretBrowsing.updateLineSelection(direction, granularity); @@ -1180,6 +1188,8 @@ window._qutebrowser.caret = (function() { if (CaretBrowsing.selectionState !== CaretBrowsing.SelectionState.NONE) { action = "extend"; } + CaretBrowsing.debug(`(moveToBlock) ${action} paragraph ${paragraph}, boundary ${boundary}, count ${count}, selection ${CaretBrowsing.selectionState}`); + for (let i = 0; i < count; i++) { window. getSelection(). @@ -1196,6 +1206,7 @@ window._qutebrowser.caret = (function() { }; CaretBrowsing.toggle = function(value) { + CaretBrowsing.debug(`(toggle) enabled ${CaretBrowsing.isEnabled}, force ${CaretBrowsing.forceEnabled}`); if (CaretBrowsing.forceEnabled) { CaretBrowsing.recreateCaretElement(); return; @@ -1231,6 +1242,7 @@ window._qutebrowser.caret = (function() { * is enabled and whether this window / iframe has focus. */ CaretBrowsing.updateIsCaretVisible = function() { + CaretBrowsing.debug(`(updateIsCaretVisible) isEnabled ${CaretBrowsing.isEnabled}, isWindowFocused ${CaretBrowsing.isWindowFocused}, isCaretVisible ${CaretBrowsing.isCaretVisible}, caretElement ${CaretBrowsing.caretElement}`); CaretBrowsing.isCaretVisible = (CaretBrowsing.isEnabled && CaretBrowsing.isWindowFocused); if (CaretBrowsing.isCaretVisible && !CaretBrowsing.caretElement) { @@ -1274,6 +1286,12 @@ window._qutebrowser.caret = (function() { } }; + CaretBrowsing.debug = (text) => { + if (CaretBrowsing.isDebug) { + console.debug(`caret: ${text}`); + } + } + CaretBrowsing.init = function() { CaretBrowsing.isWindowFocused = document.hasFocus(); @@ -1313,6 +1331,7 @@ window._qutebrowser.caret = (function() { funcs.setFlags = (flags) => { CaretBrowsing.isWindows = flags.includes("windows"); + CaretBrowsing.isDebug = flags.includes("debug"); }; funcs.disableCaret = () => { diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index af19bb61c..54b6e88b1 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -26,7 +26,7 @@ from qutebrowser.qt.gui import QKeySequence, QKeyEvent if machinery.IS_QT6: from qutebrowser.qt.core import QKeyCombination else: - QKeyCombination = None # QKeyCombination was added in Qt 6 + QKeyCombination: None = None # QKeyCombination was added in Qt 6 from qutebrowser.utils import utils, qtutils, debug diff --git a/qutebrowser/misc/consolewidget.py b/qutebrowser/misc/consolewidget.py index b489aaf36..08f5dc5ff 100644 --- a/qutebrowser/misc/consolewidget.py +++ b/qutebrowser/misc/consolewidget.py @@ -6,7 +6,7 @@ import sys import code -from typing import MutableSequence +from typing import MutableSequence, Optional from qutebrowser.qt.core import pyqtSignal, pyqtSlot, Qt from qutebrowser.qt.widgets import QTextEdit, QWidget, QVBoxLayout, QApplication @@ -17,7 +17,7 @@ from qutebrowser.misc import cmdhistory, miscwidgets from qutebrowser.utils import utils, objreg -console_widget = None +console_widget: Optional["ConsoleWidget"] = None class ConsoleLineEdit(miscwidgets.CommandLineEdit): diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index c69dcbe29..05e5806df 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -22,8 +22,9 @@ from qutebrowser.qt.core import (pyqtSlot, qInstallMessageHandler, QObject, from qutebrowser.qt.widgets import QApplication from qutebrowser.api import cmdutils +from qutebrowser.config import configfiles, configexc from qutebrowser.misc import earlyinit, crashdialog, ipc, objects -from qutebrowser.utils import usertypes, standarddir, log, objreg, debug, utils +from qutebrowser.utils import usertypes, standarddir, log, objreg, debug, utils, message from qutebrowser.qt import sip if TYPE_CHECKING: from qutebrowser.misc import quitter @@ -322,6 +323,17 @@ class SignalHandler(QObject): self._activated = False self._orig_wakeup_fd: Optional[int] = None + self._handlers = { + signal.SIGINT: self.interrupt, + signal.SIGTERM: self.interrupt, + } + platform_dependant_handlers = { + "SIGHUP": self.reload_config, + } + for sig_str, handler in platform_dependant_handlers.items(): + if hasattr(signal.Signals, sig_str): + self._handlers[signal.Signals[sig_str]] = handler + def activate(self): """Set up signal handlers. @@ -331,10 +343,8 @@ class SignalHandler(QObject): On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get notified. """ - self._orig_handlers[signal.SIGINT] = signal.signal( - signal.SIGINT, self.interrupt) - self._orig_handlers[signal.SIGTERM] = signal.signal( - signal.SIGTERM, self.interrupt) + for sig, handler in self._handlers.items(): + self._orig_handlers[sig] = signal.signal(sig, handler) if utils.is_posix and hasattr(signal, 'set_wakeup_fd'): # pylint: disable=import-error,no-member,useless-suppression @@ -430,6 +440,15 @@ class SignalHandler(QObject): print("WHY ARE YOU DOING THIS TO ME? :(") sys.exit(128 + signum) + def reload_config(self, _signum, _frame): + """Reload the config.""" + log.signals.info("SIGHUP received, reloading config.") + filename = standarddir.config_py() + try: + configfiles.read_config_py(filename) + except configexc.ConfigFileErrors as e: + message.error(str(e)) + def init(q_app: QApplication, args: argparse.Namespace, diff --git a/qutebrowser/misc/ipc.py b/qutebrowser/misc/ipc.py index 2dc34c886..21a3352d6 100644 --- a/qutebrowser/misc/ipc.py +++ b/qutebrowser/misc/ipc.py @@ -28,7 +28,7 @@ PROTOCOL_VERSION = 1 # The ipc server instance -server = None +server: Optional["IPCServer"] = None def _get_socketname_windows(basedir): diff --git a/qutebrowser/misc/nativeeventfilter.py b/qutebrowser/misc/nativeeventfilter.py index 9b1bbb97c..06533bd42 100644 --- a/qutebrowser/misc/nativeeventfilter.py +++ b/qutebrowser/misc/nativeeventfilter.py @@ -20,7 +20,7 @@ from qutebrowser.utils import log # Needs to be saved to avoid garbage collection -_instance = None +_instance: Optional["NativeEventFilter"] = None # Using C-style naming for C structures in this file # pylint: disable=invalid-name diff --git a/qutebrowser/qt/machinery.py b/qutebrowser/qt/machinery.py index 616c7ccfc..9f45dd6ce 100644 --- a/qutebrowser/qt/machinery.py +++ b/qutebrowser/qt/machinery.py @@ -34,7 +34,7 @@ from qutebrowser.utils import log # sed -i 's/_WRAPPER_OVERRIDE = .*/_WRAPPER_OVERRIDE = "PyQt6"/' qutebrowser/qt/machinery.py # # Users: Set the QUTE_QT_WRAPPER environment variable to change the default wrapper. -_WRAPPER_OVERRIDE = None +_WRAPPER_OVERRIDE = None # type: ignore[var-annotated] WRAPPERS = [ "PyQt6", @@ -168,9 +168,9 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: - Otherwise, try the wrappers in WRAPPER in order (PyQt6 -> PyQt5) """ # If any Qt wrapper has been imported before this, something strange might - # be happening. + # be happening. With PyInstaller, it imports the Qt bindings early. for name in WRAPPERS: - if name in sys.modules: + if name in sys.modules and not hasattr(sys, "frozen"): warnings.warn(f"{name} already imported", stacklevel=1) if args is not None and args.qt_wrapper is not None: @@ -190,7 +190,7 @@ def _select_wrapper(args: Optional[argparse.Namespace]) -> SelectionInfo: return SelectionInfo(wrapper=env_wrapper, reason=SelectionReason.env) if _WRAPPER_OVERRIDE is not None: - assert _WRAPPER_OVERRIDE in WRAPPERS # type: ignore[unreachable] + assert _WRAPPER_OVERRIDE in WRAPPERS return SelectionInfo(wrapper=_WRAPPER_OVERRIDE, reason=SelectionReason.override) return _autoselect_wrapper() diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index c3a0a60be..e68156759 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -171,12 +171,13 @@ def debug_flag_error(flag): avoid-chromium-init: Enable `--version` without initializing Chromium. werror: Turn Python warnings into errors. test-notification-service: Use the testing libnotify service. + caret: Enable debug logging for caret.js. """ valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history', 'no-scroll-filtering', 'log-requests', 'log-cookies', 'log-scroll-pos', 'log-sensitive-keys', 'stack', 'chromium', 'wait-renderer-process', 'avoid-chromium-init', 'werror', - 'test-notification-service', 'log-qt-events'] + 'test-notification-service', 'log-qt-events', 'caret'] if flag in valid_flags: return flag diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 3e3b407b0..9695ec5a2 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: from qutebrowser.config import config as configmodule _log_inited = False -_args = None +_args: Optional[argparse.Namespace] = None COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white'] COLOR_ESCAPES = {color: '\033[{}m'.format(i) @@ -146,7 +146,7 @@ LOGGER_NAMES = [ ram_handler: Optional['RAMHandler'] = None console_handler: Optional[logging.Handler] = None -console_filter = None +console_filter: Optional["LogFilter"] = None def stub(suffix: str = '') -> None: diff --git a/qutebrowser/utils/qtlog.py b/qutebrowser/utils/qtlog.py index 1de9181cf..78b48ebee 100644 --- a/qutebrowser/utils/qtlog.py +++ b/qutebrowser/utils/qtlog.py @@ -15,7 +15,7 @@ from typing import Iterator, Optional from qutebrowser.qt import core as qtcore from qutebrowser.utils import log -_args = None +_args: Optional[argparse.Namespace] = None def init(args: argparse.Namespace) -> None: diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py index 89175ca4e..21f3b8478 100644 --- a/qutebrowser/utils/qtutils.py +++ b/qutebrowser/utils/qtutils.py @@ -193,6 +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 stream.status() != QDataStream.Status.Ok: raise OSError(status_to_str[stream.status()]) @@ -736,4 +745,4 @@ else: def add_optional(obj: Optional[_T]) -> Optional[_T]: return obj - QT_NONE = None + QT_NONE: None = None diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py index 60d90fd31..a40f9d2bd 100644 --- a/qutebrowser/utils/resources.py +++ b/qutebrowser/utils/resources.py @@ -9,7 +9,7 @@ import sys import contextlib import posixpath import pathlib -from typing import Iterator, Iterable, Union +from typing import Iterator, Iterable, Union, Dict # We cannot use the stdlib version on 3.8 because we need the files() API. @@ -25,7 +25,7 @@ else: # pragma: no cover from importlib_resources.abc import Traversable import qutebrowser -_cache = {} +_cache: Dict[str, str] = {} _ResourceType = Union[Traversable, pathlib.Path] diff --git a/qutebrowser/utils/standarddir.py b/qutebrowser/utils/standarddir.py index 2d2ae53f9..026376dc2 100644 --- a/qutebrowser/utils/standarddir.py +++ b/qutebrowser/utils/standarddir.py @@ -10,7 +10,7 @@ import sys import contextlib import enum import argparse -from typing import Iterator, Optional +from typing import Iterator, Optional, Dict from qutebrowser.qt.core import QStandardPaths from qutebrowser.qt.widgets import QApplication @@ -18,7 +18,7 @@ from qutebrowser.qt.widgets import QApplication from qutebrowser.utils import log, debug, utils, version, qtutils # The cached locations -_locations = {} +_locations: Dict["_Location", str] = {} class _Location(enum.Enum): diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index f83f838fe..11c160c9e 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -38,7 +38,7 @@ except ImportError: # pragma: no cover from qutebrowser.utils import log -fake_clipboard = None +fake_clipboard: Optional[str] = None log_clipboard = False is_mac = sys.platform.startswith('darwin') @@ -341,7 +341,7 @@ class prevent_exceptions: # noqa: N801,N806 pylint: disable=invalid-name """Call the original function.""" try: return func(*args, **kwargs) - except BaseException: + except BaseException: # noqa: B036 log.misc.exception("Error in {}".format(qualname(func))) return retval diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index 59da5b5f0..32d5357db 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -78,7 +78,7 @@ class DistributionInfo: pretty: str -pastebin_url = None +pastebin_url: Optional[str] = None class Distribution(enum.Enum): @@ -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]] = { @@ -610,12 +611,21 @@ class WebEngineVersions: # 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: Chromium 112 # 112.0.5615.213 (~2023-04-18) - # 6.6.0: Security fixes up to 116.0.5845.110 (?) (2023-08-22) + # 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.6.0: Security fixes up to 122.0.6261.128 (?) (2024-03-12) + utils.VersionNumber(6, 7): '118.0.5993.220', } def __post_init__(self) -> None: @@ -629,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 @@ -686,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 @@ -696,6 +713,7 @@ class WebEngineVersions: return cls( webengine=parsed, chromium=chromium_version, + chromium_security=chromium_security, source='api', ) @@ -796,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/requirements.txt b/requirements.txt index 81e0d0606..229f9e9c8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,12 +2,12 @@ adblock==0.6.0 colorama==0.4.6 -importlib-resources==6.1.1 ; python_version=="3.8.*" -Jinja2==3.1.2 -MarkupSafe==2.1.3 +importlib_resources==6.4.0 ; python_version=="3.8.*" +Jinja2==3.1.3 +MarkupSafe==2.1.5 Pygments==2.17.2 PyYAML==6.0.1 -zipp==3.17.0 +zipp==3.18.1 # Unpinned due to recompile_requirements.py limitations pyobjc-core ; sys_platform=="darwin" pyobjc-framework-Cocoa ; sys_platform=="darwin" diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 65eef720c..40cedc2e8 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -171,6 +171,9 @@ def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None: 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([ diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index 48158d339..645bb6385 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -45,12 +45,12 @@ "flake8": "https://github.com/PyCQA/flake8/tree/main/docs/source/release-notes", "flake8-docstrings": "https://github.com/PyCQA/flake8-docstrings/blob/main/HISTORY.rst", "flake8-debugger": "https://github.com/JBKahn/flake8-debugger/", - "flake8-builtins": "https://github.com/gforcada/flake8-builtins/blob/master/CHANGES.rst", + "flake8-builtins": "https://github.com/gforcada/flake8-builtins/blob/main/CHANGES.rst", "flake8-bugbear": "https://github.com/PyCQA/flake8-bugbear#change-log", "flake8-tidy-imports": "https://github.com/adamchainz/flake8-tidy-imports/blob/main/CHANGELOG.rst", "flake8-tuple": "https://github.com/ar4s/flake8_tuple/blob/master/HISTORY.rst", "flake8-comprehensions": "https://github.com/adamchainz/flake8-comprehensions/blob/main/CHANGELOG.rst", - "flake8-deprecated": "https://github.com/gforcada/flake8-deprecated/blob/master/CHANGES.rst", + "flake8-deprecated": "https://github.com/gforcada/flake8-deprecated/blob/main/CHANGES.rst", "flake8-future-import": "https://github.com/xZise/flake8-future-import#changes", "flake8-string-format": "https://github.com/xZise/flake8-string-format#changes", "flake8-plugin-utils": "https://github.com/afonasev/flake8-plugin-utils#change-log", @@ -78,7 +78,6 @@ "sphinxcontrib-jsmath": "https://www.sphinx-doc.org/en/master/changes.html", "sphinxcontrib-qthelp": "https://www.sphinx-doc.org/en/master/changes.html", "sphinxcontrib-serializinghtml": "https://www.sphinx-doc.org/en/master/changes.html", - "jaraco.functools": "https://jaracofunctools.readthedocs.io/en/latest/history.html", "parse": "https://github.com/r1chardj0n3s/parse#potential-gotchas", "Pympler": "https://github.com/pympler/pympler/blob/master/CHANGELOG.md", "pytest-mock": "https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst", @@ -93,13 +92,12 @@ "urllib3": "https://github.com/urllib3/urllib3/blob/main/CHANGES.rst", "lxml": "https://github.com/lxml/lxml/blob/master/CHANGES.txt", "cryptography": "https://cryptography.io/en/latest/changelog.html", - "toml": "https://github.com/uiri/toml/releases", "tomli": "https://github.com/hukkin/tomli/blob/master/CHANGELOG.md", "PyQt5": "https://www.riverbankcomputing.com/news", "PyQt5-Qt5": "https://www.riverbankcomputing.com/news", "PyQtWebEngine": "https://www.riverbankcomputing.com/news", "PyQtWebEngine-Qt5": "https://www.riverbankcomputing.com/news", - "PyQt-builder": "https://www.riverbankcomputing.com/news", + "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", @@ -120,33 +118,36 @@ "tldextract": "https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md", "typing_extensions": "https://github.com/python/typing_extensions/blob/main/CHANGELOG.md", "diff_cover": "https://github.com/Bachmann1234/diff_cover/blob/main/CHANGELOG", - "beautifulsoup4": "https://bazaar.launchpad.net/~leonardr/beautifulsoup/bs4/view/head:/CHANGELOG", + "beautifulsoup4": "https://git.launchpad.net/beautifulsoup/tree/CHANGELOG", "check-manifest": "https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst", "yamllint": "https://github.com/adrienverge/yamllint/blob/master/CHANGELOG.rst", "pathspec": "https://github.com/cpburnz/python-path-specification/blob/master/CHANGES.rst", "filelock": "https://github.com/tox-dev/py-filelock/releases", "github3.py": "https://github3.readthedocs.io/en/latest/release-notes/index.html", "manhole": "https://github.com/ionelmc/python-manhole/blob/master/CHANGELOG.rst", - "pycparser": "https://github.com/eliben/pycparser/blob/master/CHANGES", + "pycparser": "https://github.com/eliben/pycparser/releases", "python-dateutil": "https://dateutil.readthedocs.io/en/stable/changelog.html", "platformdirs": "https://github.com/platformdirs/platformdirs/releases", "pluggy": "https://github.com/pytest-dev/pluggy/blob/main/CHANGELOG.rst", "mypy-extensions": "https://github.com/python/mypy_extensions/commits/master", "pyroma": "https://github.com/regebro/pyroma/blob/master/CHANGES.txt", "adblock": "https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md", - "importlib-resources": "https://importlib-resources.readthedocs.io/en/latest/history.html", - "importlib-metadata": "https://github.com/python/importlib_metadata/blob/main/NEWS.rst", - "zipp": "https://github.com/jaraco/zipp/blob/main/NEWS.rst", + "importlib_resources": "https://importlib-resources.readthedocs.io/en/latest/history.html", + "importlib_metadata": "https://github.com/python/importlib_metadata/blob/main/NEWS.rst", + "zipp": "https://zipp.readthedocs.io/en/latest/history.html", "pip": "https://pip.pypa.io/en/stable/news/", "wheel": "https://wheel.readthedocs.io/en/stable/news.html", "setuptools": "https://setuptools.readthedocs.io/en/latest/history.html", "pefile": "https://github.com/erocarrera/pefile/commits/master", "SecretStorage": "https://github.com/mitya57/secretstorage/blob/master/changelog", "jeepney": "https://gitlab.com/takluyver/jeepney/-/blob/master/docs/release-notes.rst", - "keyring": "https://github.com/jaraco/keyring/blob/main/NEWS.rst", - "jaraco.classes": "https://github.com/jaraco/jaraco.classes/blob/main/NEWS.rst", + "keyring": "https://keyring.readthedocs.io/en/latest/history.html", + "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", + "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", + "readme_renderer": "https://github.com/pypa/readme_renderer/blob/main/CHANGES.rst", "requests-toolbelt": "https://github.com/requests/toolbelt/blob/master/HISTORY.rst", "rfc3986": "https://rfc3986.readthedocs.io/en/latest/release-notes/index.html", "twine": "https://twine.readthedocs.io/en/stable/changelog.html", diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 38a8f6ca1..e1d0d8642 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -73,8 +73,8 @@ PERFECT_FILES = [ 'qutebrowser/browser/history.py'), ('tests/unit/browser/test_pdfjs.py', 'qutebrowser/browser/pdfjs.py'), - ('tests/unit/browser/webkit/http/test_http.py', - 'qutebrowser/browser/webkit/http.py'), + ('tests/unit/browser/webkit/http/test_httpheaders.py', + 'qutebrowser/browser/webkit/httpheaders.py'), # ('tests/unit/browser/webkit/test_webkitelem.py', # 'qutebrowser/browser/webkit/webkitelem.py'), # ('tests/unit/browser/webkit/test_webkitelem.py', diff --git a/scripts/importer.py b/scripts/importer.py index 1914e6976..cf084d178 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -209,7 +209,7 @@ def import_html_bookmarks(bookmarks_file, bookmark_types, output_format): } bookmarks = [] for typ in bookmark_types: - tags = soup.findAll(bookmark_query[typ]) + tags = soup.find_all(bookmark_query[typ]) for tag in tags: if typ == 'search': tag['href'] = search_escape(tag['href']).replace('%s', '{}') diff --git a/tests/end2end/data/click_element.html b/tests/end2end/data/click_element.html index b2a691e08..7fac2a381 100644 --- a/tests/end2end/data/click_element.html +++ b/tests/end2end/data/click_element.html @@ -7,7 +7,7 @@ <span onclick='console.log("click_element special chars")'>"Don't", he shouted</span> <span>Duplicate</span> <span class='clickable' onclick='console.log("click_element CSS selector")'>Duplicate</span> - <form><input autofocus id='qute-input'></input></form> + <form><input id='qute-input' onfocus='console.log("qute-input focused")'></input></form> <a href="/data/hello.txt" id='link'>link</a> <span id='foo.bar' onclick='console.log("id with dot")'>ID with dot</span> <span style='position: absolute; left: 20px;top: 42px; width:10px; height:10px;' diff --git a/tests/end2end/data/insert_mode_settings/html/autofocus.html b/tests/end2end/data/insert_mode_settings/html/autofocus.html index 366f436f6..ca189b016 100644 --- a/tests/end2end/data/insert_mode_settings/html/autofocus.html +++ b/tests/end2end/data/insert_mode_settings/html/autofocus.html @@ -10,6 +10,9 @@ elem.addEventListener('input', function() { console.log("contents: " + elem.value); }); + elem.addEventListener('focus', function() { + console.log("autofocus element focused"); + }); } </script> </head> diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature index 1302a1e6d..d6e65440c 100644 --- a/tests/end2end/features/caret.feature +++ b/tests/end2end/features/caret.feature @@ -34,11 +34,11 @@ Feature: Caret mode Scenario: :yank selection with --keep When I run :selection-toggle - And I run :move-to-end-of-word + And I run :move-to-next-word And I run :yank selection --keep And I run :move-to-end-of-word And I run :yank selection --keep - Then the message "3 chars yanked to clipboard" should be shown + Then the message "4 chars yanked to clipboard" should be shown And the message "7 chars yanked to clipboard" should be shown And the clipboard should contain "one two" diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index 9b7ffdeb5..ce31125b6 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -730,8 +730,15 @@ def should_quit(qtbot, quteproc): def _get_scroll_values(quteproc): data = quteproc.get_session() - pos = data['windows'][0]['tabs'][0]['history'][-1]['scroll-pos'] - return (pos['x'], pos['y']) + + def get_active(things): + return next(thing for thing in things if thing.get("active")) + + active_window = get_active(data["windows"]) + active_tab = get_active(active_window["tabs"]) + current_entry = get_active(active_tab["history"]) + pos = current_entry["scroll-pos"] + return (pos["x"], pos["y"]) @bdd.then(bdd.parsers.re(r"the page should be scrolled " diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 1872ca5a6..90ce5334a 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -502,14 +502,13 @@ Feature: Various utility commands. Scenario: Clicking on focused element when there is none When I open data/click_element.html - # Need to loose focus on input element - And I run :click-element position 20,42 - And I wait for the javascript message "click_element position" And I run :click-element focused Then the error "No element found with focus!" should be shown Scenario: Clicking on focused element When I open data/click_element.html + And I run :fake-key <Tab> + 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 diff --git a/tests/end2end/features/utilcmds.feature b/tests/end2end/features/utilcmds.feature index 1b5306adb..ebacea890 100644 --- a/tests/end2end/features/utilcmds.feature +++ b/tests/end2end/features/utilcmds.feature @@ -3,6 +3,7 @@ Feature: Miscellaneous utility commands exposed to the user. Background: Given I open data/scroll/simple.html And I run :tab-only + And I run :window-only ## :cmd-later diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 748d1d19e..e77757880 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -75,6 +75,8 @@ def is_ignored_lowlevel_message(message): 'glx: failed to create drisw screen', 'failed to load driver: zink', 'DRI3 not available', + # Webkit on arch with a newer mesa + 'MESA: error: ZINK: failed to load libvulkan.so.1', ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) @@ -213,6 +215,16 @@ def is_ignored_chromium_message(line): # [9895:9983:0904/043039.500565:ERROR:gpu_memory_buffer_support_x11.cc(49)] # dri3 extension not supported. "dri3 extension not supported.", + + # Qt 6.7 debug build + # [44513:44717:0325/173456.146759:WARNING:render_message_filter.cc(144)] + # Could not find tid + "Could not find tid", + + # [127693:127748:0325/230155.835421:WARNING:discardable_shared_memory_manager.cc(438)] + # Some MojoDiscardableSharedMemoryManagerImpls are still alive. They + # will be leaked. + "Some MojoDiscardableSharedMemoryManagerImpls are still alive. They will be leaked.", ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) @@ -395,9 +407,11 @@ class QuteProc(testprocess.Process): backend = 'webengine' if self.request.config.webengine else 'webkit' args = ['--debug', '--no-err-windows', '--temp-basedir', '--json-logging', '--loglevel', 'vdebug', - '--backend', backend, '--debug-flag', 'no-sql-history', - '--debug-flag', 'werror', '--debug-flag', - 'test-notification-service', + '--backend', backend, + '--debug-flag', 'no-sql-history', + '--debug-flag', 'werror', + '--debug-flag', 'test-notification-service', + '--debug-flag', 'caret', '--qt-flag', 'disable-features=PaintHoldingCrossOrigin'] if self.request.config.webengine and testutils.disable_seccomp_bpf_sandbox(): diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py index 5051efa85..924cb520b 100644 --- a/tests/end2end/fixtures/webserver.py +++ b/tests/end2end/fixtures/webserver.py @@ -16,6 +16,7 @@ import pytest from qutebrowser.qt.core import pyqtSignal, QUrl from end2end.fixtures import testprocess +from helpers import testutils class Request(testprocess.Line): @@ -111,6 +112,17 @@ class ExpectedRequest: return NotImplemented +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 " + "alert certificate unknown (_ssl.c:*)')" + ), + value=line, + ) + + class WebserverProcess(testprocess.Process): """Abstraction over a running Flask server process. @@ -151,7 +163,13 @@ class WebserverProcess(testprocess.Process): if started_re.fullmatch(line): self.ready.emit() return None - return Request(line) + + try: + return Request(line) + except testprocess.InvalidLine: + if is_ignored_webserver_message(line): + return None + raise def _executable_args(self): if hasattr(sys, 'frozen'): diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py index abf32dbde..95757591d 100644 --- a/tests/end2end/test_insert_mode.py +++ b/tests/end2end/test_insert_mode.py @@ -43,7 +43,6 @@ def test_insert_mode(file_name, elem_id, source, input_text, zoom, (True, False, True), # enabled and foreground tab (True, True, False), # background tab ]) -@pytest.mark.flaky def test_auto_load(quteproc, auto_load, background, insert_mode): quteproc.set_setting('input.insert_mode.auto_load', str(auto_load)) url_path = 'data/insert_mode_settings/html/autofocus.html' diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py index ba1da4145..d51cc69ff 100644 --- a/tests/unit/browser/test_caret.py +++ b/tests/unit/browser/test_caret.py @@ -9,7 +9,8 @@ import textwrap import pytest from qutebrowser.qt.core import QUrl -from qutebrowser.utils import usertypes +from qutebrowser.qt import machinery +from qutebrowser.utils import utils, usertypes from qutebrowser.browser import browsertab @@ -241,6 +242,13 @@ class TestWord: caret.move_to_end_of_word() selection.check("one") + @pytest.mark.xfail( + machinery.IS_QT6 and utils.is_windows, + reason=( + "move-to-end-of-word is broken with Qt 6 and Windows: " + "https://github.com/qutebrowser/qutebrowser/issues/8146" + ) + ) def test_moving_to_end_and_selecting_a_word(self, caret, selection): caret.move_to_end_of_word() selection.toggle() diff --git a/tests/unit/browser/webkit/http/test_content_disposition.py b/tests/unit/browser/webkit/http/test_content_disposition.py index 7cf80e3fd..4f3ef13c7 100644 --- a/tests/unit/browser/webkit/http/test_content_disposition.py +++ b/tests/unit/browser/webkit/http/test_content_disposition.py @@ -6,7 +6,7 @@ import logging import pytest -from qutebrowser.browser.webkit import http +from qutebrowser.browser.webkit import httpheaders DEFAULT_NAME = 'qutebrowser-download' @@ -30,7 +30,7 @@ class HeaderChecker: """Check if the passed header has the given filename.""" reply = self.stubs.FakeNetworkReply( headers={'Content-Disposition': header}) - cd_inline, cd_filename = http.parse_content_disposition(reply) + cd_inline, cd_filename = httpheaders.parse_content_disposition(reply) assert cd_filename is not None assert cd_filename == filename assert cd_inline == expected_inline @@ -40,7 +40,7 @@ class HeaderChecker: reply = self.stubs.FakeNetworkReply( headers={'Content-Disposition': header}) with self.caplog.at_level(logging.ERROR, 'network'): - cd_inline, cd_filename = http.parse_content_disposition(reply) + cd_inline, cd_filename = httpheaders.parse_content_disposition(reply) assert cd_filename == DEFAULT_NAME assert cd_inline @@ -48,7 +48,7 @@ class HeaderChecker: """Check if the passed header results in an unnamed attachment.""" reply = self.stubs.FakeNetworkReply( headers={'Content-Disposition': header}) - cd_inline, cd_filename = http.parse_content_disposition(reply) + cd_inline, cd_filename = httpheaders.parse_content_disposition(reply) assert cd_filename == DEFAULT_NAME assert not cd_inline @@ -164,7 +164,7 @@ class TestAttachment: """ reply = stubs.FakeNetworkReply( headers={'Content-Disposition': 'attachment'}) - cd_inline, cd_filename = http.parse_content_disposition(reply) + cd_inline, cd_filename = httpheaders.parse_content_disposition(reply) assert not cd_inline assert cd_filename == DEFAULT_NAME diff --git a/tests/unit/browser/webkit/http/test_http.py b/tests/unit/browser/webkit/http/test_httpheaders.py index 210d79486..7368575e8 100644 --- a/tests/unit/browser/webkit/http/test_http.py +++ b/tests/unit/browser/webkit/http/test_httpheaders.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -"""Tests for qutebrowser.browser.webkit.http.""" +"""Tests for qutebrowser.browser.webkit.httpheaders.""" import logging @@ -11,7 +11,7 @@ import hypothesis from hypothesis import strategies from qutebrowser.qt.core import QUrl -from qutebrowser.browser.webkit import http +from qutebrowser.browser.webkit import httpheaders @pytest.mark.parametrize('url, expected', [ @@ -24,7 +24,7 @@ from qutebrowser.browser.webkit import http ]) def test_no_content_disposition(stubs, url, expected): reply = stubs.FakeNetworkReply(url=QUrl(url)) - inline, filename = http.parse_content_disposition(reply) + inline, filename = httpheaders.parse_content_disposition(reply) assert inline assert filename == expected @@ -40,8 +40,8 @@ def test_no_content_disposition(stubs, url, expected): # dropping QtWebKit. ]) def test_parse_content_disposition_invalid(value): - with pytest.raises(http.ContentDispositionError): - http.ContentDisposition.parse(value) + with pytest.raises(httpheaders.ContentDispositionError): + httpheaders.ContentDisposition.parse(value) @pytest.mark.parametrize('template', [ @@ -58,16 +58,16 @@ def test_parse_content_disposition_hypothesis(caplog, template, stubs, s): header = template.format(s) reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) with caplog.at_level(logging.ERROR, 'network'): - http.parse_content_disposition(reply) + httpheaders.parse_content_disposition(reply) @hypothesis.given(strategies.binary()) def test_content_disposition_directly_hypothesis(s): """Test rfc6266 parsing directly with binary data.""" try: - cd = http.ContentDisposition.parse(s) + cd = httpheaders.ContentDisposition.parse(s) cd.filename() - except http.ContentDispositionError: + except httpheaders.ContentDispositionError: pass @@ -83,7 +83,7 @@ def test_parse_content_type(stubs, content_type, expected_mimetype, reply = stubs.FakeNetworkReply() else: reply = stubs.FakeNetworkReply(headers={'Content-Type': content_type}) - mimetype, rest = http.parse_content_type(reply) + mimetype, rest = httpheaders.parse_content_type(reply) assert mimetype == expected_mimetype assert rest == expected_rest @@ -91,4 +91,4 @@ def test_parse_content_type(stubs, content_type, expected_mimetype, @hypothesis.given(strategies.text()) def test_parse_content_type_hypothesis(stubs, s): reply = stubs.FakeNetworkReply(headers={'Content-Type': s}) - http.parse_content_type(reply) + httpheaders.parse_content_type(reply) diff --git a/tests/unit/browser/webkit/test_certificateerror.py b/tests/unit/browser/webkit/test_certificateerror.py index 2feb7dcf1..7f5cce9c6 100644 --- a/tests/unit/browser/webkit/test_certificateerror.py +++ b/tests/unit/browser/webkit/test_certificateerror.py @@ -18,15 +18,15 @@ class FakeError: return self.msg -@pytest.mark.parametrize('errors, expected', [ +@pytest.mark.parametrize('error_factories, expected', [ ( - [QSslError(QSslError.SslError.UnableToGetIssuerCertificate)], + [lambda: QSslError(QSslError.SslError.UnableToGetIssuerCertificate)], ['<p>The issuer certificate could not be found</p>'], ), ( [ - QSslError(QSslError.SslError.UnableToGetIssuerCertificate), - QSslError(QSslError.SslError.UnableToDecryptCertificateSignature), + lambda: QSslError(QSslError.SslError.UnableToGetIssuerCertificate), + lambda: QSslError(QSslError.SslError.UnableToDecryptCertificateSignature), ], [ '<ul>', @@ -37,13 +37,13 @@ class FakeError: ), ( - [FakeError('Escaping test: <>')], + [lambda: FakeError('Escaping test: <>')], ['<p>Escaping test: <></p>'], ), ( [ - FakeError('Escaping test 1: <>'), - FakeError('Escaping test 2: <>'), + lambda: FakeError('Escaping test 1: <>'), + lambda: FakeError('Escaping test 2: <>'), ], [ '<ul>', @@ -53,8 +53,9 @@ class FakeError: ], ), ]) -def test_html(stubs, errors, expected): +def test_html(stubs, error_factories, expected): reply = stubs.FakeNetworkReply(url=QUrl("https://example.com")) + errors = [factory() for factory in error_factories] wrapper = certificateerror.CertificateErrorWrapper(reply=reply, errors=errors) lines = [line.strip() for line in wrapper.html().splitlines() if line.strip()] assert lines == expected diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index d908e3ac6..bcd257ed7 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1874,24 +1874,24 @@ class TestProxy: def klass(self): return configtypes.Proxy - @pytest.mark.parametrize('val, expected', [ - ('system', configtypes.SYSTEM_PROXY), - ('none', QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)), + @pytest.mark.parametrize('val, expected_factory', [ + ('system', lambda: configtypes.SYSTEM_PROXY), + ('none', lambda: QNetworkProxy(QNetworkProxy.ProxyType.NoProxy)), ('socks://example.com/', - QNetworkProxy(QNetworkProxy.ProxyType.Socks5Proxy, 'example.com')), + lambda: QNetworkProxy(QNetworkProxy.ProxyType.Socks5Proxy, 'example.com')), ('socks5://foo:bar@example.com:2323', - QNetworkProxy(QNetworkProxy.ProxyType.Socks5Proxy, 'example.com', 2323, - 'foo', 'bar')), + lambda: QNetworkProxy( + QNetworkProxy.ProxyType.Socks5Proxy, 'example.com', 2323, 'foo', 'bar')), ('pac+http://example.com/proxy.pac', - pac.PACFetcher(QUrl('pac+http://example.com/proxy.pac'))), + lambda: pac.PACFetcher(QUrl('pac+http://example.com/proxy.pac'))), ('pac+file:///tmp/proxy.pac', - pac.PACFetcher(QUrl('pac+file:///tmp/proxy.pac'))), + lambda: pac.PACFetcher(QUrl('pac+file:///tmp/proxy.pac'))), ]) - def test_to_py_valid(self, klass, val, expected): + def test_to_py_valid(self, klass, val, expected_factory): actual = klass().to_py(val) if isinstance(actual, QNetworkProxy): actual = QNetworkProxy(actual) - assert actual == expected + assert actual == expected_factory() @pytest.mark.parametrize('val', [ 'blah', diff --git a/tests/unit/misc/test_crashsignal.py b/tests/unit/misc/test_crashsignal.py new file mode 100644 index 000000000..7019118e5 --- /dev/null +++ b/tests/unit/misc/test_crashsignal.py @@ -0,0 +1,104 @@ +# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) <mail@qutebrowser.org> +# +# SPDX-License-Identifier: GPL-3.0-or-later + +"""Tests for qutebrowser.misc.crashsignal.""" + +import signal + +import pytest + +from qutebrowser.config import configexc +from qutebrowser.qt.widgets import QApplication +from qutebrowser.misc import crashsignal, quitter + + +@pytest.fixture +def read_config_mock(mocker): + # covers reload_config + mocker.patch.object( + crashsignal.standarddir, + "config_py", + return_value="config.py-unittest", + ) + return mocker.patch.object( + crashsignal.configfiles, + "read_config_py", + autospec=True, + ) + + +@pytest.fixture +def signal_handler(qtbot, mocker, read_config_mock): + """Signal handler instance with all external methods mocked out.""" + # covers init + mocker.patch.object(crashsignal.sys, "exit", autospec=True) + signal_handler = crashsignal.SignalHandler( + app=mocker.Mock(spec=QApplication), + quitter=mocker.Mock(spec=quitter.Quitter), + ) + + return signal_handler + + +def test_handlers_registered(signal_handler): + signal_handler.activate() + + for sig, handler in signal_handler._handlers.items(): + registered = signal.signal(sig, signal.SIG_DFL) + assert registered == handler + + +def test_handlers_deregistered(signal_handler): + known_handler = lambda *_args: None + for sig in signal_handler._handlers: + signal.signal(sig, known_handler) + + signal_handler.activate() + signal_handler.deactivate() + + for sig in signal_handler._handlers: + registered = signal.signal(sig, signal.SIG_DFL) + assert registered == known_handler + + +def test_interrupt_repeatedly(signal_handler): + signal_handler.activate() + test_signal = signal.SIGINT + + expected_handlers = [ + signal_handler.interrupt, + signal_handler.interrupt_forcefully, + signal_handler.interrupt_really_forcefully, + ] + + # Call the SIGINT handler multiple times and make sure it calls the + # expected sequence of functions. + for expected in expected_handlers: + registered = signal.signal(test_signal, signal.SIG_DFL) + assert registered == expected + expected(test_signal, None) + + +@pytest.mark.posix +def test_reload_config_call_on_hup(signal_handler, read_config_mock): + signal_handler._handlers[signal.SIGHUP](None, None) + + read_config_mock.assert_called_once_with("config.py-unittest") + + +@pytest.mark.posix +def test_reload_config_displays_errors(signal_handler, read_config_mock, mocker): + read_config_mock.side_effect = configexc.ConfigFileErrors( + "config.py", + [ + configexc.ConfigErrorDesc("no config.py", ValueError("asdf")) + ] + ) + message_mock = mocker.patch.object(crashsignal.message, "error") + + signal_handler._handlers[signal.SIGHUP](None, None) + + message_mock.assert_called_once_with( + "Errors occurred while reading config.py:\n no config.py: asdf" + ) diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 3aceca668..c7af3162c 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -208,6 +208,18 @@ def test_ensure_valid(obj, raising, exc_reason, exc_str): "The data stream has read corrupt data."), (QDataStream.Status.WriteFailed, True, "The data stream cannot write to the underlying device."), + pytest.param( + getattr(QDataStream.Status, "SizeLimitExceeded", None), + True, + ( + "The data stream cannot read or write the data because its size is larger " + "than supported by the current platform." + ), + marks=pytest.mark.skipif( + not hasattr(QDataStream.Status, "SizeLimitExceeded"), + reason="Added in Qt 6.7" + ) + ), ]) def test_check_qdatastream(status, raising, message): """Test check_qdatastream. @@ -226,10 +238,25 @@ def test_check_qdatastream(status, raising, message): qtutils.check_qdatastream(stream) -def test_qdatastream_status_count(): - """Make sure no new members are added to QDataStream.Status.""" - status_vals = testutils.enum_members(QDataStream, QDataStream.Status) - assert len(status_vals) == 4 +def test_qdatastream_status_members(): + """Make sure no new members are added to QDataStream.Status. + + If this fails, qtutils.check_qdatastream will need to be updated with the + respective error documentation. + """ + status_vals = set(testutils.enum_members(QDataStream, QDataStream.Status).values()) + expected = { + QDataStream.Status.Ok, + QDataStream.Status.ReadPastEnd, + QDataStream.Status.ReadCorruptData, + QDataStream.Status.WriteFailed, + } + try: + expected.add(QDataStream.Status.SizeLimitExceeded) + except AttributeError: + # Added in Qt 6.7 + pass + assert status_vals == expected @pytest.mark.parametrize('color, expected', [ 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: @@ -16,8 +16,6 @@ setenv = pyqt{515,5152}: PYTEST_QT_API=pyqt5 pyqt{515,5152}: QUTE_QT_WRAPPER=PyQt5 cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= - py312: VIRTUALENV_PIP=23.2 - py312: PIP_REQUIRE_VIRTUALENV=0 passenv = PYTHON DISPLAY @@ -71,8 +69,8 @@ setenv = pip_pre = true deps = -r{toxinidir}/misc/requirements/requirements-tests-bleeding.txt commands_pre = - qt5: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt5 PyQtWebEngine PyQt5-Qt5 PyQtWebEngine-Qt5 PyQt5-sip - !qt5: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt6 PyQt6-WebEngine PyQt6-Qt6 PyQt6-WebEngine-Qt6 PyQt6-sip + qt5: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade --only-binary PyQt5,PyQtWebEngine PyQt5 PyQtWebEngine PyQt5-Qt5 PyQtWebEngine-Qt5 PyQt5-sip + !qt5: pip install --extra-index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade --only-binary PyQt6,PyQt6-WebEngine PyQt6 PyQt6-WebEngine PyQt6-Qt6 PyQt6-WebEngine-Qt6 PyQt6-sip commands = {envpython} -bb -m pytest {posargs:tests} # other envs |