Compare commits

...

66 Commits
v3.6.0 ... main

Author SHA1 Message Date
Florian Bruhin 7e3df43463 Disable child focus workaround on unaffected Qt versions
The workaround added for #7820 seems to cause datalist dropdowns to lose focus
on Wayland. Let's just disable the old workaround on Qt versions that are not affected
by the original issue, which seems to be Qt 6.6.3+.

Fixes #8831.
2026-01-04 22:25:49 +01:00
Florian Bruhin 9ae082b29b version: Add QtWebEngine 6.9.3 2026-01-04 22:25:49 +01:00
dependabot[bot] b417f2a23b build(deps): bump peter-evans/create-pull-request from 7 to 8
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7 to 8.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v7...v8)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 12:51:26 +01:00
dependabot[bot] 615cee7309 build(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 12:51:19 +01:00
dependabot[bot] 65c1ca9691 build(deps): bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 12:51:09 +01:00
qutebrowser bot 12bed611c5 Update dependencies 2025-12-15 17:05:29 +01:00
Florian Bruhin bc72687d7d Update changelog URLs 2025-12-08 16:55:52 +01:00
Florian Bruhin c32b7d4b60 Fix dependency update issues 2025-12-08 10:53:13 +01:00
qutebrowser bot 3f9ef123e7 Update dependencies 2025-12-08 04:29:09 +00:00
Florian Bruhin f2547f8a09 scripts: Make smoke test fail output less confusing 2025-11-30 18:46:03 +01:00
qutebrowser bot edd5114492 Release v3.6.3
(cherry picked from commit ee13dac738)
2025-11-30 17:37:53 +00:00
Florian Bruhin 184a242937 ci: Make sure git knows the main branch
Something seems to have changed about how sparse checkouts are done on GHA, so the main branch isn't found.
2025-11-30 18:35:05 +01:00
Florian Bruhin 17c19a09b7 build_release: Try to get more information on crashes
Currently CI crashes on macOS, but without any useful logs.
2025-11-30 18:11:25 +01:00
Florian Bruhin 88aa47c377 Add qt.workarounds.disable_accessibility setting
This disables accessibility with Qt 6.10.1, which
causes frequent segfaults.

Closes #8797
2025-11-30 17:54:33 +01:00
dependabot[bot] 0570545342 build(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-30 00:15:59 +01:00
qutebrowser bot f408f20ad9 Release v3.6.2
(cherry picked from commit b2c5d5fa0d)
2025-11-27 20:57:13 +00:00
Florian Bruhin 500a8df209 Update user agents 2025-11-27 21:55:44 +01:00
Florian Bruhin 13d9904b90 Update changelog for v3.6.2 2025-11-27 21:29:21 +01:00
Florian Bruhin b3e4dba731 qtargs: Remove old workaround with Qt 6.10.1 2025-11-27 21:28:34 +01:00
qutebrowser bot 4164205663 Update dependencies 2025-11-27 17:21:32 +01:00
qutebrowser bot f5e2660890 Update dependencies 2025-11-24 16:37:39 +01:00
Florian Bruhin 69f3882ce3 tests: Skip hangouts extension test on Qt 5 2025-11-23 11:42:49 +01:00
Florian Bruhin 8e42727d31 Update changelog 2025-11-22 10:40:30 +01:00
Florian Bruhin 31a5737c61 Avoid disabling off-the-record profile Hangouts extension with Qt 6.10.1
Otherwise this results in a crash, see #8785
2025-11-22 10:38:46 +01:00
Florian Bruhin 8ae5e3d83b version: Use correct profile for extension list
See #8785
2025-11-21 22:50:52 +01:00
Florian Bruhin 4f40a8b46b tests: Improve test_version output 2025-11-21 18:39:24 +01:00
Florian Bruhin 59a64af67f tests: Adjust permissions storage workaround for Qt 6.10.1 2025-11-21 18:35:03 +01:00
Florian Bruhin 66cbe0d9c9 Add QtWebEngine 6.10.1 security patch version 2025-11-21 18:31:43 +01:00
Florian Bruhin 0ef5053a65 tests: Stabilize flaky session scrolling test
Equivalent of d8079515fa
See #5390
2025-11-18 15:00:20 +01:00
qutebrowser bot 6ddff3ae0d Update dependencies 2025-11-18 14:08:13 +01:00
Florian Bruhin 9316d428ef ci: Drop Archlinux Qt 5 images/jobs
For now, Qt 5 is still tested via the Qt 5.15 PyPI wheels.

See https://github.com/qutebrowser/qutebrowser/issues/8417#issuecomment-3495979318
https://lists.archlinux.org/archives/list/arch-dev-public@lists.archlinux.org/thread/U45C4RAW4IXVLO376XGFNLEGGFFXCULV/
2025-11-11 09:13:30 +01:00
Florian Bruhin 71ed8cdbf5 Update changelog 2025-11-11 09:09:05 +01:00
Florian Bruhin 62fdb15532 Merge commit 'bc191b798' 2025-11-11 09:07:00 +01:00
Florian Bruhin bc191b798d wmname: Remove trivial functions 2025-11-11 09:06:49 +01:00
Florian Bruhin f8fbb0609f Update changelog 2025-11-11 08:46:36 +01:00
Florian Bruhin 55fb26fce1 Revert "fix: change fullscreen state when switching tab"
This reverts commit b89bf07d1e.

This turned out to be annoying when enabling fullscreen manually and then
switching through tabs.
2025-11-11 08:45:55 +01:00
Jan Palus 25dc019886
Unify librarry loading for X11/Wayland wmname
libwayland-client.so is development symlink used during linking and there's no need to
have it installed (usually shipped in -devel/-dev packages) on user's machines. Instead
of hardcoding library file name, use same mechanism as in libX11 which let's Python
figure the details and share common logic between X11 and Wayland.

Fixes #8771
2025-11-11 00:51:05 +01:00
Florian Bruhin 81d7b6a74c tests: Use star-unpacking instead of itertools.chain
pytest will soon deprecate using a non-collection iterable in parametrize:
https://docs.pytest.org/en/latest/deprecations.html#parametrize-iterators
2025-11-10 13:02:26 +01:00
qutebrowser bot 6ec5504ab3 Update dependencies 2025-11-10 07:39:05 +01:00
Florian Bruhin 9b69c889ef doc: Add additional chrome:// pages 2025-11-07 11:45:51 +01:00
Florian Bruhin 4e87ef303f ci: Update macOS runners / versions
https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/

- CI: Switch to macOS 15 Intel runner
  (macOS 14 is still tested with Apple Silicon)
- Nightly: Use macOS 15 Intel runner for nightly releases
  (macOS 14 would be better to align with actual Intel releases, but it is
  a -large runner, thus possibly metered)
- Releases: Use macOS 14 for Intel releases
  This is a -large runner, but releases don't happen often.
2025-11-05 16:51:43 +01:00
gesh 2f8234ee2e doc: Correct Arch Linux links
Arch hasn't been using the [community] repository for 9 months now[1],
correct the links for that.
Also, youtube-dl has been replaced in [extra] by yt-dlp[2][3], unsure
when -- I think this was in 2023?
Finally (and the trigger for this commit), given #8332, correct the
guidance on Arch Linux to point to pdfjs-legacy instead of pdfjs.

[1]: https://archlinux.org/news/cleaning-up-old-repositories/
[2]: https://aur.archlinux.org/packages/youtube-dl
[3]: https://archlinux.org/packages/extra/any/yt-dlp/
2025-11-05 14:26:04 +01:00
Florian Bruhin a8f0b47451 tests: Ignore more bogus Chromium messages 2025-11-03 18:32:17 +01:00
Florian Bruhin 4a5b7bd6e4 Update changelog 2025-11-03 18:10:24 +01:00
OmeletWithoutEgg b89bf07d1e fix: change fullscreen state when switching tab 2025-11-03 18:09:28 +01:00
Rebecca 70bf4689fc Fixed whitespace
Removed excess line of whitespace.
2025-11-03 18:09:01 +01:00
Rebecca 1cbb6fccf0 Fixed minor issue in configuration docs
The docs show an example for adding domain filtering for configuration options. However the example only matches the root of a domain rather than all pages on a domain which is for example, the default case when using the `tsh` shortcut to disable/enable javascript on a page.
2025-11-03 18:09:01 +01:00
Rebecca d3e4245d0f Improved logging for configuration loading
- When loading a configuration file, if the configuration uses domain filtering then the domain filter expression is logged in the debug log.
2025-11-03 18:09:01 +01:00
qutebrowser bot 3160048619 Release v3.6.1
(cherry picked from commit 2e5f805cce)
2025-11-03 15:30:02 +00:00
Florian Bruhin aa93eb1614 Adjust stack trace parsing for newer Python 2025-11-03 16:26:12 +01:00
qutebrowser bot df9cef3a58 Update dependencies 2025-11-03 06:33:33 +01:00
dependabot[bot] 214e2e9ac2 build(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 20:41:53 +01:00
qutebrowser bot 68574b88cc Update dependencies 2025-10-27 08:21:55 +01:00
Florian Bruhin 6e8e24050d Fix changelog 2025-10-26 23:02:14 +01:00
dependabot[bot] 5fe9bf97e3 build(deps): bump actions/setup-node from 5 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-25 22:36:25 +02:00
Florian Bruhin b646d606d7 Fix releasing focus when leaving command mode
The fix for #8223 in 6f21accfae
was misguided: We don't really care about the statusbar being hidden,
controlled release of keyboard focus needs to happen in any case where
we're hiding the command widget (as that's when we lose keyboard focus).

Fixes #8750.
2025-10-25 16:43:06 +02:00
Florian Bruhin 1e4ddc2c6b ci: Fix finding existing draft release 2025-10-24 17:02:41 +02:00
Florian Bruhin 3fce0518bd scripts: Avoid showing entire file tree diff in CI log 2025-10-24 17:00:19 +02:00
Florian Bruhin 3808ebfdb3 ci: Find existing draft release for reuploads 2025-10-24 16:23:03 +02:00
Florian Bruhin 0421aacd64 ci: Check out release branch for reuploads 2025-10-24 15:45:15 +02:00
Florian Bruhin 0c2a673e27 scripts: Clean up build_release 2025-10-24 15:06:20 +02:00
Florian Bruhin afa456f396 ci: Skip existing artifacts when reuploading
After a release failed and we want to retry,
some assets might already have been uploaded.

Skip those instead of attempting to reupload.
2025-10-24 14:53:40 +02:00
Florian Bruhin 461077b6cf ci: Fix reupload in script 2025-10-24 14:34:20 +02:00
Florian Bruhin e208f5e121 ci: Add ability to reupload after borked release 2025-10-24 14:33:24 +02:00
Florian Bruhin 0f320051e0 scripts: Ignore another line during smoke tests 2025-10-24 14:17:29 +02:00
Florian Bruhin ee40f13a30 Update release checklist
minor releases happen far more often
2025-10-24 14:17:29 +02:00
55 changed files with 643 additions and 302 deletions

View File

@ -1,5 +1,5 @@
[tool.bumpversion]
current_version = "3.6.0"
current_version = "3.6.3"
commit = true
message = "Release v{new_version}"
tag = true

View File

@ -42,6 +42,7 @@ exclude = .*,__pycache__,resources.py
# W503: like break before binary operator
# W504: line break after binary operator
# FI18: __future__ import "annotations" missing
# FI58: __future__ import "annotations" present
# PT004: fixture '{name}' does not return anything, add leading underscore
# PT011: pytest.raises(ValueError) is too broad, set the match parameter or use a more specific exception
# PT012: pytest.raises() block should contain a single simple statement
@ -54,7 +55,7 @@ ignore =
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413,
A003,
W503, W504,
FI18,
FI18,FI58,
PT004,
PT011,
PT012

View File

@ -17,8 +17,6 @@ jobs:
matrix:
include:
- testenv: bleeding
image: "archlinux-webengine-unstable-qt6"
- testenv: bleeding-qt5
image: "archlinux-webengine-unstable"
container:
image: "qutebrowser/ci:${{ matrix.image }}"
@ -33,14 +31,13 @@ jobs:
- /home/runner/work/_temp/:/home/runner/work/_temp/
options: --privileged --tty
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up problem matchers
run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}"
- name: Upgrade 3rd party assets
run: "tox exec -e ${{ matrix.testenv }} -- python scripts/dev/update_3rdparty.py --gh-token ${{ secrets.GITHUB_TOKEN }} --modern-pdfjs"
if: "endsWith(matrix.image, '-qt6')"
- name: Run tox
run: dbus-run-session tox -e ${{ matrix.testenv }}
- name: Gather info
@ -51,7 +48,7 @@ jobs:
shell: bash
if: failure()
- name: Upload screenshots
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
path: |

View File

@ -34,10 +34,10 @@ jobs:
- testenv: actionlint
- testenv: package
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
.mypy_cache
@ -47,7 +47,7 @@ jobs:
- uses: actions/setup-python@v6
with:
python-version: '3.10'
- uses: actions/setup-node@v5
- uses: actions/setup-node@v6
with:
node-version: '22.x'
if: "matrix.testenv == 'eslint'"
@ -90,14 +90,10 @@ jobs:
fail-fast: false
matrix:
include:
- testenv: py-qt5
- testenv: py
image: archlinux-webengine
- testenv: py-qt5
- testenv: py
image: archlinux-webengine-unstable
- testenv: py
image: archlinux-webengine-qt6
- testenv: py
image: archlinux-webengine-unstable-qt6
container:
image: "qutebrowser/ci:${{ matrix.image }}"
env:
@ -110,7 +106,7 @@ jobs:
- /home/runner/work/_temp/:/home/runner/work/_temp/
options: --privileged --tty
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up problem matchers
@ -125,7 +121,7 @@ jobs:
shell: bash
if: failure()
- name: Upload screenshots
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.image }}"
path: |
@ -202,16 +198,16 @@ jobs:
- testenv: py314-pyqt610
os: ubuntu-24.04
python: "3.14"
### macOS Ventura
- testenv: py314-pyqt610
os: macos-13
python: "3.14"
args: "tests/unit" # Only run unit tests on macOS
### macOS Sonoma (M1 runner)
- testenv: py314-pyqt610
os: macos-14
python: "3.14"
args: "tests/unit" # Only run unit tests on macOS
### macOS Sequoia (Intel runner)
- testenv: py314-pyqt610
os: macos-15-intel
python: "3.14"
args: "tests/unit" # Only run unit tests on macOS
### Windows
- testenv: py314-pyqt610
os: windows-2022
@ -221,10 +217,10 @@ jobs:
python: "3.14"
runs-on: "${{ matrix.os }}"
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/cache@v4
- uses: actions/cache@v5
with:
path: |
.mypy_cache
@ -273,7 +269,7 @@ jobs:
shell: bash
if: failure()
- name: Upload screenshots
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: "end2end-screenshots-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.testenv }}-${{ matrix.os }}"
path: |
@ -289,7 +285,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Initialize CodeQL

View File

@ -15,10 +15,8 @@ jobs:
image:
- archlinux-webengine
- archlinux-webengine-unstable
- archlinux-webengine-unstable-qt6
- archlinux-webengine-qt6
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: '3.x'

View File

@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-13
- os: macos-15-intel
toxenv: build-release
name: macos-intel
- os: macos-14
@ -23,7 +23,7 @@ jobs:
- os: windows-latest
toxenv: build-release
name: windows
- os: macos-13
- os: macos-15-intel
args: --debug
toxenv: build-release
name: macos-debug-intel
@ -37,7 +37,7 @@ jobs:
runs-on: "${{ matrix.os }}"
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python
@ -72,7 +72,7 @@ jobs:
echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
shell: bash
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: "qutebrowser-nightly-${{ steps.info.outputs.date }}-${{ steps.info.outputs.sha_short }}-${{ matrix.name }}"
path: |

View File

@ -18,7 +18,7 @@ jobs:
timeout-minutes: 20
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Set up Python 3.9
@ -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@v7
uses: peter-evans/create-pull-request@v8
with:
committer: qutebrowser bot <bot@qutebrowser.org>
author: qutebrowser bot <bot@qutebrowser.org>

View File

@ -12,6 +12,7 @@ on:
- 'patch'
- 'minor'
- 'major'
- 'reupload' # reupload last release
# FIXME do we want a possibility to do prereleases here?
python_version:
description: 'Python version'
@ -31,7 +32,8 @@ jobs:
timeout-minutes: 5
outputs:
version: ${{ steps.bump.outputs.version }}
release_id: ${{ steps.create-release.outputs.id }}
version_x: ${{ steps.bump.outputs.version_x }}
release_id: ${{ inputs.release_type == 'reupload' && steps.find-release.outputs.result || steps.create-release.outputs.id }}
permissions:
contents: write # To push release commit/tag
steps:
@ -60,7 +62,7 @@ jobs:
console.log(`sorted: ${sorted}`);
return sorted.at(-1);
result-encoding: string
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
@ -76,7 +78,7 @@ jobs:
git config --global user.name "qutebrowser bot"
git config --global user.email "bot@qutebrowser.org"
- name: Switch to release branch
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
ref: ${{ steps.find-branch.outputs.result }}
- name: Import GPG Key
@ -84,7 +86,7 @@ jobs:
gpg --import <<< "${{ secrets.QUTEBROWSER_BOT_GPGKEY }}"
- name: Bump version
id: bump
run: "tox -e update-version -- ${{ github.event.inputs.release_type }}"
run: "tox -e update-version -- ${{ inputs.release_type }}"
- name: Check milestone
uses: actions/github-script@v8
with:
@ -101,34 +103,59 @@ jobs:
core.setFailed(`Found open milestone ${milestone.title} with ${milestone.open_issues} open and ${milestone.closed_issues} closed issues!`);
}
- name: Push release commit/tag
if: ${{ inputs.release_type != 'reupload' }}
run: |
git push origin ${{ steps.find-branch.outputs.result }}
git push origin v${{ steps.bump.outputs.version }}
- name: Cherry-pick release commit
if: ${{ github.event.inputs.release_type == 'patch' }}
if: ${{ inputs.release_type == 'patch' }}
run: |
git fetch origin main
git checkout main
git cherry-pick -x v${{ steps.bump.outputs.version }}
git push origin main
git checkout v${{ steps.bump.outputs.version_x }}
- name: Create release branch
if: ${{ github.event.inputs.release_type != 'patch' }}
if: ${{ inputs.release_type == 'minor' || inputs.release_type == 'major' }}
run: |
git checkout -b v${{ steps.bump.outputs.version_x }}
git push --set-upstream origin v${{ steps.bump.outputs.version_x }}
- name: Create GitHub draft release
if: ${{ inputs.release_type != 'reupload' }}
id: create-release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.bump.outputs.version }}
draft: true
body: "*Release artifacts for this release are currently being uploaded...*"
- name: Find GitHub draft release
if: ${{ inputs.release_type == 'reupload' }}
id: find-release
uses: actions/github-script@v8
with:
script: |
const releases = await github.paginate(github.rest.repos.listReleases, {
owner: context.repo.owner,
repo: context.repo.repo,
});
const names = releases.map(release => release.name);
console.log(`releases: ${names}`);
const release = releases.find(release => release.tag_name === "v${{ steps.bump.outputs.version }}");
if (release === undefined) {
core.setFailed(`No release found with tag v${{ steps.bump.outputs.version }}!`);
}
if (!release.draft) {
core.setFailed(`Release ${release.tag_name} is not a draft release!`);
}
return release.id;
result-encoding: string
release:
strategy:
matrix:
include:
- os: macos-13
- os: macos-14
- os: macos-14-large # Intel
- os: macos-14 # Apple Silicon
- os: windows-2022
- os: ubuntu-24.04
runs-on: "${{ matrix.os }}"
@ -137,13 +164,13 @@ jobs:
permissions:
contents: write # To upload release artifacts
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
ref: v${{ needs.prepare.outputs.version }}
ref: v${{ inputs.release_type == 'reupload' && needs.prepare.outputs.version_x || needs.prepare.outputs.version }}
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ github.event.inputs.python_version }}
python-version: ${{ inputs.python_version }}
- name: Import GPG Key
if: ${{ startsWith(matrix.os, 'ubuntu-') }}
run: |
@ -168,7 +195,7 @@ jobs:
# FIXME consider switching to trusted publishers:
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
- name: Build and upload release
run: "tox -e build-release -- --upload --no-confirm"
run: "tox -e build-release -- --upload --no-confirm ${{ inputs.release_type == 'reupload' && '--reupload' || '' }}"
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.QUTEBROWSER_BOT_PYPI_TOKEN }}

View File

@ -15,6 +15,60 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
[[v3.6.4]]
v3.6.4 (unreleased)
-------------------
Fixed
~~~~~
- datalist dropdowns not opening correctly on Wayland/Sway (#8831).
This was caused by an old workaround for a different QtWebEngine issue,
which is now disabled for QtWebEngine 6.6.3 and newer.
[[v3.6.3]]
v3.6.3 (2025-11-30)
-------------------
Fixed
~~~~~
- New `qt.workarounds.disable_accessibility` setting, which disables Chromium
accessibility support. By default, is it set to `auto`, which only disables
accessibility on Qt versions with known issues. This works around a bug in Qt
6.10.1 causing frequent segfaults (#8797).
[[v3.6.2]]
v3.6.2 (2025-11-27)
-------------------
Changed
~~~~~~~
* Windows and macOS releases now ship with Qt 6.10.1, which include
security patches up to Chromium 142.0.7444.162.
Fixed
~~~~~
- The version info now includes the Wayland compositor name if wayland-client is
available under a different name than `libwayland-client.so` (#8771).
- The list of Chromium extensions in `--version` / `:version` now uses the
correct Chromium data profile, also fixing a crash with Qt 6.10.1 (#8785).
- With Qt 6.10.1, `qt.workarounds.disable_hangouts_extension` now doesn't apply
on private profiles, avoiding a Qt bug leading to a crash (#8785).
[[v3.6.1]]
v3.6.1 (2025-11-03)
-------------------
Fixed
~~~~~
- A regression in v3.6.0 where the page didn't have keyboard focus after closing
the completion, so e.g. typing in an input field after hinting didn't work.
(#8750)
[[v3.6.0]]
v3.6.0 (2025-10-24)
-------------------

View File

@ -602,6 +602,7 @@ Info pages:
- chrome://device-log/ (QtWebEngine >= 6.3)
- chrome://gpu/
- chrome://sandbox/ (Linux only)
- chrome://qt/ (QtWebEngine >= 6.7)
Misc. / Debugging pages:
@ -612,6 +613,7 @@ Misc. / Debugging pages:
- chrome://ukm/ (QtWebEngine >= 5.15.3)
- chrome://user-actions/ (QtWebEngine >= 5.15.3)
- chrome://webrtc-logs/ (QtWebEngine >= 5.15.3)
- chrome://extensions/ (QtWebEngine >= 6.10)
Internals pages:
@ -802,7 +804,8 @@ qutebrowser release
**Automatic release via GitHub Actions (starting with v3.0.0):**
* Double check Python version in `.github/workflows/release.yml`
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=major` (`release_type` can be `major`, `minor` or `patch`; you can also override `python_version`)
* Run the `release` workflow on the `main` branch, e.g. via `gh workflow run release -f release_type=minor` (`release_type` can be `major`, `minor` or `patch`; you can also override `python_version`)
* Consider running `gh run watch` or `gh run view --web` to watch the progress
**Manual release:**

View File

@ -141,7 +141,7 @@ The comma prefix is used to make sure user-defined bindings don't conflict with
the built-in ones.
+
Note that you might need an additional package (e.g.
https://www.archlinux.org/packages/community/any/youtube-dl/[youtube-dl] on
https://archlinux.org/packages/extra/any/yt-dlp/[yt-dlp] on
Archlinux) to play web videos with mpv.
+
There is a very useful script for mpv, which emulates "unique application"

View File

@ -179,7 +179,7 @@ customizable for a given <<patterns,URL patterns>>.
[source,python]
----
config.set('content.images', False, '*://example.com/')
config.set('content.images', False, '*://example.com/*')
----
Alternatively, you can use `with config.pattern(...) as p:` to get a shortcut
@ -187,7 +187,7 @@ similar to `c.` which is scoped to the given domain:
[source,python]
----
with config.pattern('*://example.com/') as p:
with config.pattern('*://example.com/*') as p:
p.content.images = False
----

View File

@ -302,6 +302,7 @@
|<<qt.force_software_rendering,qt.force_software_rendering>>|Force software rendering for QtWebEngine.
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|<<qt.workarounds.disable_accelerated_2d_canvas,qt.workarounds.disable_accelerated_2d_canvas>>|Disable accelerated 2d canvas to avoid graphical glitches.
|<<qt.workarounds.disable_accessibility,qt.workarounds.disable_accessibility>>|Disable accessibility to avoid crashes on Qt 6.10.1.
|<<qt.workarounds.disable_hangouts_extension,qt.workarounds.disable_hangouts_extension>>|Disable the Hangouts extension.
|<<qt.workarounds.locale,qt.workarounds.locale>>|Work around locale parsing issues in QtWebEngine 5.15.3.
|<<qt.workarounds.remove_service_workers,qt.workarounds.remove_service_workers>>|Delete the QtWebEngine Service Worker directory on every start.
@ -3996,6 +3997,24 @@ Valid values:
Default: +pass:[auto]+
[[qt.workarounds.disable_accessibility]]
=== qt.workarounds.disable_accessibility
Disable accessibility to avoid crashes on Qt 6.10.1.
This setting requires a restart.
This setting is only available with the QtWebEngine backend.
Type: <<types,String>>
Valid values:
* +always+: Disable renderer accessibility
* +auto+: Disable on Qt versions with known issues, enable otherwise
* +never+: Enable renderer accessibility
Default: +pass:[auto]+
[[qt.workarounds.disable_hangouts_extension]]
=== qt.workarounds.disable_hangouts_extension
Disable the Hangouts extension.

View File

@ -44,6 +44,9 @@
</content_rating>
<releases>
<!-- Add new releases here -->
<release version='3.6.3' date='2025-11-30'/>
<release version='3.6.2' date='2025-11-27'/>
<release version='3.6.1' date='2025-11-03'/>
<release version='3.6.0' date='2025-10-24'/>
<release version='3.5.1' date='2025-06-05'/>
<release version='3.5.0' date='2025-04-12'/>

View File

@ -1,19 +1,19 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
annotated-types==0.7.0
anyio==4.11.0
anyio==4.12.0
autocommand==2.2.2
backports.tarfile==1.2.0
bracex==2.6
build==1.3.0
bump-my-version==1.2.4
certifi==2025.10.5
bump-my-version==1.2.5
certifi==2025.11.12
cffi==2.0.0
charset-normalizer==3.4.4
click==8.1.8
cryptography==46.0.3
docutils==0.22.2
exceptiongroup==1.3.0
docutils==0.22.3
exceptiongroup==1.3.1
github3.py==4.0.1
h11==0.16.0
httpcore==1.0.9
@ -30,37 +30,36 @@ jaraco.context==6.0.1
jaraco.functools==4.0.1
jaraco.text==3.12.1
jeepney==0.9.0
keyring==25.6.0
keyring==25.7.0
manhole==1.8.1
markdown-it-py==3.0.0
mdurl==0.1.2
more-itertools==10.8.0
nh3==0.3.1
nh3==0.3.2
packaging==25.0
platformdirs==4.4.0
prompt_toolkit==3.0.52
pycparser==2.23
pydantic==2.12.3
pydantic==2.12.5
pydantic-settings==2.11.0
pydantic_core==2.41.4
pydantic_core==2.41.5
Pygments==2.19.2
PyJWT==2.10.1
Pympler==1.1
pyproject_hooks==1.2.0
PyQt-builder==1.19.0
PyQt-builder==1.19.1
python-dateutil==2.9.0.post0
python-dotenv==1.1.1
python-dotenv==1.2.1
questionary==2.1.1
readme_renderer==44.0
requests==2.32.5
requests-toolbelt==1.0.0
rfc3986==2.0.0
rich==14.2.0
rich-click==1.9.3
rich-click==1.9.4
SecretStorage==3.3.3
sip==6.14.0
six==1.17.0
sniffio==1.3.1
tomli==2.3.0
tomlkit==0.13.3
twine==6.2.0
@ -68,7 +67,7 @@ typeguard==4.3.0
typing-inspection==0.4.2
typing_extensions==4.15.0
uritemplate==4.2.0
# urllib3==2.5.0
# urllib3==2.6.2
wcmatch==10.1
wcwidth==0.2.14
zipp==3.23.0

View File

@ -1,11 +1,12 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
chardet==5.2.0
diff_cover==9.7.1
diff_cover==10.0.0
Jinja2==3.1.6
librt==0.7.3
lxml==6.0.2
MarkupSafe==3.0.3
mypy==1.18.2
mypy==1.19.0
mypy_extensions==1.1.0
pathspec==0.12.1
pluggy==1.6.0
@ -13,7 +14,7 @@ Pygments==2.19.2
PyQt5-stubs==5.15.6.0
tomli==2.3.0
types-colorama==0.4.15.20250801
types-docutils==0.22.2.20251006
types-Pygments==2.19.0.20250809
types-docutils==0.22.3.20251115
types-Pygments==2.19.0.20251121
types-PyYAML==6.0.12.20250915
typing_extensions==4.15.0

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
altgraph==0.17.4
altgraph==0.17.5
importlib_metadata==8.7.0
packaging==25.0
pyinstaller==6.16.0
pyinstaller-hooks-contrib==2025.9
pyinstaller==6.17.0
pyinstaller-hooks-contrib==2025.10
zipp==3.23.0

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==3.3.11
certifi==2025.10.5
certifi==2025.11.12
cffi==2.0.0
charset-normalizer==3.4.4
cryptography==46.0.3
@ -24,5 +24,5 @@ tomli==2.3.0
tomlkit==0.13.3
typing_extensions==4.15.0
uritemplate==4.2.0
# urllib3==2.5.0
# urllib3==2.6.2
zipp==3.23.0

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.11 # rq.filter: < 5.16
PyQt5-Qt5==5.15.17
PyQt5-Qt5==5.15.18
PyQt5_sip==12.17.1
PyQtWebEngine==5.15.7 # rq.filter: < 5.16
PyQtWebEngine-Qt5==5.15.17
PyQtWebEngine-Qt5==5.15.18

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.11
PyQt5-Qt5==5.15.17
PyQt5-Qt5==5.15.18
PyQt5_sip==12.17.1
PyQtWebEngine==5.15.7
PyQtWebEngine-Qt5==5.15.17
PyQtWebEngine-Qt5==5.15.18

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt6==6.10.0
PyQt6-Qt6==6.10.0
PyQt6==6.10.1
PyQt6-Qt6==6.10.1
PyQt6-WebEngine==6.10.0
PyQt6-WebEngine-Qt6==6.10.0
PyQt6-WebEngine-Qt6==6.10.1
PyQt6_sip==13.10.2
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt6==6.10.0
PyQt6-Qt6==6.10.0
PyQt6==6.10.1
PyQt6-Qt6==6.10.1
PyQt6-WebEngine==6.10.0
PyQt6-WebEngine-Qt6==6.10.0
PyQt6-WebEngine-Qt6==6.10.1
PyQt6_sip==13.10.2
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt6==6.10.0
PyQt6-Qt6==6.10.0
PyQt6==6.10.1
PyQt6-Qt6==6.10.1
PyQt6-WebEngine==6.10.0
PyQt6-WebEngine-Qt6==6.10.0
PyQt6-WebEngine-Qt6==6.10.1
PyQt6_sip==13.10.2
--extra-index-url https://www.riverbankcomputing.com/pypi/simple/

View File

@ -1,18 +1,18 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
build==1.3.0
certifi==2025.10.5
certifi==2025.11.12
charset-normalizer==3.4.4
check-manifest==0.51
docutils==0.22.2
docutils==0.22.3
idna==3.11
importlib_metadata==8.7.0
packaging==25.0
Pygments==2.19.2
pyproject_hooks==1.2.0
pyroma==5.0
pyroma==5.0.1
requests==2.32.5
tomli==2.3.0
trove-classifiers==2025.9.11.17
urllib3==2.5.0
trove-classifiers==2025.12.1.14
urllib3==2.6.2
zipp==3.23.0

View File

@ -2,7 +2,7 @@
alabaster==0.7.16
babel==2.17.0
certifi==2025.10.5
certifi==2025.11.12
charset-normalizer==3.4.4
docutils==0.21.2
idna==3.11
@ -22,5 +22,5 @@ sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.3.0
urllib3==2.5.0
urllib3==2.6.2
zipp==3.23.0

View File

@ -3,15 +3,15 @@
attrs==25.4.0
autocommand==2.2.2
backports.tarfile==1.2.0
beautifulsoup4==4.14.2
beautifulsoup4==4.14.3
blinker==1.9.0
certifi==2025.10.5
certifi==2025.11.12
charset-normalizer==3.4.4
cheroot==11.0.0
cheroot==11.1.2
click==8.1.8
coverage==7.10.7
exceptiongroup==1.3.0
execnet==2.1.1
exceptiongroup==1.3.1
execnet==2.1.2
filelock==3.19.1
Flask==3.1.2
gherkin-official==29.0.0
@ -42,7 +42,7 @@ py-cpuinfo==9.0.0
Pygments==2.19.2
pytest==8.4.2
pytest-bdd==8.1.0
pytest-benchmark==5.1.0
pytest-benchmark==5.2.3
pytest-cov==7.0.0
pytest-instafail==0.5.0
pytest-mock==3.15.1
@ -61,7 +61,7 @@ tldextract==5.3.0
tomli==2.3.0
typeguard==4.3.0
typing_extensions==4.15.0
urllib3==2.5.0
urllib3==2.6.2
vulture==2.14
Werkzeug==3.1.3
Werkzeug==3.1.4
zipp==3.23.0

View File

@ -1,12 +1,12 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
cachetools==6.2.1
cachetools==6.2.3
chardet==5.2.0
colorama==0.4.6
distlib==0.4.0
filelock==3.19.1
packaging==25.0
pip==25.2
pip==25.3
platformdirs==4.4.0
pluggy==1.6.0
pyproject-api==1.9.1
@ -14,6 +14,6 @@ setuptools==80.9.0
tomli==2.3.0
tox==4.30.3 ; python_full_version!="3.14.0b1"
typing_extensions==4.15.0
virtualenv==20.35.3
virtualenv==20.35.4
wheel==0.45.1
tox @ git+https://github.com/tox-dev/tox ; python_full_version=="3.14.0b1"

View File

@ -14,7 +14,7 @@ __copyright__ = "Copyright 2013-{} Florian Bruhin (The Compiler)".format(_year)
__license__ = "GPL-3.0-or-later"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version__ = "3.6.0"
__version__ = "3.6.3"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on Python and Qt."

View File

@ -10,7 +10,7 @@ from qutebrowser.qt.gui import QKeyEvent
from qutebrowser.qt.widgets import QWidget
from qutebrowser.config import config
from qutebrowser.utils import log, message, usertypes, qtutils
from qutebrowser.utils import log, message, usertypes, qtutils, version, utils
from qutebrowser.keyinput import modeman, keyutils
@ -55,16 +55,16 @@ class ChildEventFilter(QObject):
# - This is a child event filter on a tab (self._widget is not None)
# - We find an old existing child which is a QQuickWidget and is
# currently focused.
# - We're using QtWebEngine >= 6.4 (older versions are not affected)
# - We're using an affected QtWebEngine version
children = [
c for c in self._widget.findChildren(
QWidget, "", Qt.FindChildOption.FindDirectChildrenOnly)
if c is not child and
c.hasFocus() and
c.metaObject() is not None and
c.metaObject().className() == "QQuickWidget"
c.metaObject().className() == "QQuickWidget" # Qt 6.4+
]
if children:
if children and version.qtwebengine_versions().webengine < utils.VersionNumber(6, 6, 3):
log.misc.debug("Focusing new child")
child.setFocus()

View File

@ -431,6 +431,17 @@ def _maybe_disable_hangouts_extension(profile: QWebEngineProfile) -> None:
except AttributeError:
return # added in QtWebEngine 6.10
qtwe_versions = version.qtwebengine_versions(avoid_init=True)
if (
qtwe_versions.webengine == utils.VersionNumber(6, 10, 1)
and profile.isOffTheRecord()
):
# WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/8785
log.misc.warning(
"Not disabling Hangouts extension on private profile to avoid "
"QtWebEngine crash with Qt 6.10.1")
return
assert ext_manager is not None # mypy
for info in ext_manager.extensions():
if info.id() == pakjoy.HANGOUTS_EXT_ID:
@ -458,14 +469,18 @@ def _clear_webengine_permissions_json():
)
def default_qt_profile() -> QWebEngineProfile:
"""Get the default profile from Qt."""
if machinery.IS_QT6:
return QWebEngineProfile("Default")
else:
return QWebEngineProfile.defaultProfile()
def _init_default_profile():
"""Init the default QWebEngineProfile."""
global default_profile
if machinery.IS_QT6:
default_profile = QWebEngineProfile("Default")
else:
default_profile = QWebEngineProfile.defaultProfile()
default_profile = default_qt_profile()
assert not default_profile.isOffTheRecord()
assert parsed_user_agent is None # avoid earlier profile initialization
@ -526,7 +541,7 @@ def _init_site_specific_quirks():
# "{qt_key}/{qt_version} "
# "{upstream_browser_key}/{upstream_browser_version_short} "
# "Safari/{webkit_version}")
firefox_ua = "Mozilla/5.0 ({os_info}; rv:144.0) Gecko/20100101 Firefox/144.0"
firefox_ua = "Mozilla/5.0 ({os_info}; rv:145.0) Gecko/20100101 Firefox/145.0"
# Needed for gitlab.gnome.org which blocks old Chromium versions outright,
# except when QtWebEngine/... is in the UA.

View File

@ -333,8 +333,13 @@ class Config(QObject):
pattern, hide_userconfig=hide_userconfig)
self.changed.emit(opt.name)
log.config.debug("Config option changed: {} = {}".format(
opt.name, value))
if pattern is not None:
log.config.debug("Config option changed: {} = {} for {}".format(
opt.name, value, pattern))
else:
log.config.debug("Config option changed: {} = {}".format(
opt.name, value))
def _check_yaml(self, opt: 'configdata.Option', save_yaml: bool) -> None:
"""Make sure the given option may be set in autoconfig.yml."""

View File

@ -422,6 +422,19 @@ qt.workarounds.disable_hangouts_extension:
disabled to avoid crashes on Qt 6.5.0 to 6.5.3 if dark mode is enabled,
as well as on Qt 6.6.0.
qt.workarounds.disable_accessibility:
type:
name: String
valid_values:
- always: Disable renderer accessibility
- auto: Disable on Qt versions with known issues, enable otherwise
- never: Enable renderer accessibility
default: auto
backend: QtWebEngine
restart: true
desc: >-
Disable accessibility to avoid crashes on Qt 6.10.1.
## auto_save
auto_save.interval:
@ -773,14 +786,14 @@ content.headers.user_agent:
# Vim-protip: Place your cursor below this comment and run
# :r!python scripts/dev/ua_fetch.py
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36"
- Chrome 141 macOS
(KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
- Chrome 142 macOS
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/141.0.0.0 Safari/537.36"
- Chrome 141 Win10
like Gecko) Chrome/142.0.0.0 Safari/537.36"
- Chrome 142 Win10
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/141.0.0.0 Safari/537.36"
- Chrome 141 Linux
Gecko) Chrome/142.0.0.0 Safari/537.36"
- Chrome 142 Linux
supports_pattern: true
desc: |
User agent to send.

View File

@ -159,10 +159,9 @@ def _qtwebengine_features( # noqa: C901
# TODO adjust if fixed in Qt 6.9.2+
disabled_features.append('DocumentPictureInPictureAPI')
if versions.webengine >= utils.VersionNumber(6, 9):
if utils.VersionNumber(6, 9) <= versions.webengine < utils.VersionNumber(6, 10, 1):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-135787
# and https://bugreports.qt.io/browse/QTBUG-141096
# TODO adjust if fixed in Qt 6.9.2+
disabled_features.append('PermissionElement')
if not config.val.input.media_keys:
@ -363,6 +362,16 @@ _WEBENGINE_SETTINGS: dict[str, dict[Any, Optional[_SettingValueType]]] = {
and versions.webengine < utils.VersionNumber(6, 8, 2)
else None,
},
'qt.workarounds.disable_accessibility': {
'always': '--disable-renderer-accessibility',
'never': None,
# WORKAROUND for https://qt-project.atlassian.net/browse/QTBUG-142320
'auto': lambda versions: '--disable-renderer-accessibility'
if machinery.IS_QT6
and versions.webengine
and versions.webengine == utils.VersionNumber(6, 10, 1)
else None,
},
}

View File

@ -92,7 +92,7 @@ li {
the required packages for pdf.js are also installed.
<br/>
The package is named
<a href="https://archlinux.org/packages/community/any/pdfjs/"><b>pdfjs</b></a> on Archlinux
<a href="https://archlinux.org/packages/extra/any/pdfjs-legacy/"><b>pdfjs-legacy</b></a> on Archlinux
and <a href="https://packages.debian.org/bullseye/libjs-pdf"><b>libjs-pdf</b></a> on Debian.
</li>

View File

@ -286,20 +286,16 @@ class StatusBar(QWidget):
strategy = config.val.statusbar.show
tab = self._current_tab()
if tab is not None and tab.data.fullscreen:
self.release_focus.emit()
self.hide()
elif strategy == 'never':
self.release_focus.emit()
self.hide()
elif strategy == 'in-mode':
try:
mode_manager = modeman.instance(self._win_id)
except modeman.UnavailableError:
self.release_focus.emit()
self.hide()
else:
if mode_manager.mode == usertypes.KeyMode.normal:
self.release_focus.emit()
self.hide()
else:
self.show()
@ -371,6 +367,7 @@ class StatusBar(QWidget):
def _hide_cmd_widget(self):
"""Show temporary text instead of command widget."""
log.statusbar.debug("Hiding cmd widget")
self.release_focus.emit()
self._stack.setCurrentWidget(self.txt)
self.maybe_hide()

View File

@ -48,8 +48,8 @@ def parse_fatal_stacktrace(text):
lines = [
r'(?P<type>Fatal Python error|Windows fatal exception): (?P<msg>.*)',
r' *',
r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *',
r' File ".*", line \d+ in (?P<func>.*)',
r'(Current )?[Tt]hread .* \(most recent call first\): *',
r' (File ".*", line \d+ in (?P<func>.*)|<no Python frame>)',
]
m = re.search('\n'.join(lines), text)
if m is None:
@ -58,7 +58,7 @@ def parse_fatal_stacktrace(text):
else:
msg = m.group('msg')
typ = m.group('type')
func = m.group('func')
func = m.group('func') or ''
if typ == 'Windows fatal exception':
msg = 'Windows ' + msg
return msg, func

View File

@ -23,6 +23,7 @@ import datetime
from typing import NoReturn
try:
import tkinter
import tkinter.messagebox
except ImportError:
tkinter = None # type: ignore[assignment]

View File

@ -26,12 +26,15 @@ class _WaylandDisplayStruct(ctypes.Structure):
_WaylandDisplay = NewType("_WaylandDisplay", "ctypes._Pointer[_WaylandDisplayStruct]")
def _load_libwayland_client() -> ctypes.CDLL:
"""Load the Wayland client library."""
def _load_library(name: str) -> ctypes.CDLL:
lib = ctypes.util.find_library(name)
if lib is None:
raise Error(f"{name} library not found")
try:
return ctypes.CDLL("libwayland-client.so")
return ctypes.CDLL(lib)
except OSError as e:
raise Error(f"Failed to load libwayland-client: {e}")
raise Error(f"Failed to load {name} library: {e}")
def _pid_from_fd(fd: int) -> int:
@ -113,7 +116,7 @@ def wayland_compositor_name() -> str:
Approach based on:
https://stackoverflow.com/questions/69302630/wayland-client-get-compositor-name
"""
wayland_client = _load_libwayland_client()
wayland_client = _load_library("wayland-client")
with _wayland_display(wayland_client) as display:
fd = _wayland_get_fd(wayland_client, display)
pid = _pid_from_fd(fd)
@ -136,18 +139,6 @@ _X11Display = NewType("_X11Display", "ctypes._Pointer[_X11DisplayStruct]")
_X11Window = NewType("_X11Window", int)
def _x11_load_lib() -> ctypes.CDLL:
"""Load the X11 library."""
lib = ctypes.util.find_library("X11")
if lib is None:
raise Error("X11 library not found")
try:
return ctypes.CDLL(lib)
except OSError as e:
raise Error(f"Failed to load X11 library: {e}")
@contextlib.contextmanager
def _x11_open_display(xlib: ctypes.CDLL) -> Iterator[_X11Display]:
"""Open a connection to the X11 display."""
@ -307,7 +298,7 @@ def _x11_get_wm_name(
def x11_wm_name() -> str:
"""Get the name of the running X11 window manager."""
xlib = _x11_load_lib()
xlib = _load_library("X11")
with _x11_open_display(xlib) as display:
atoms = _X11Atoms(
NET_SUPPORTING_WM_CHECK=_x11_intern_atom(

View File

@ -654,9 +654,11 @@ class WebEngineVersions:
utils.VersionNumber(6, 9): (_BASES[130], '133.0.6943.141'), # 2025-02-25
utils.VersionNumber(6, 9, 1): (_BASES[130], '136.0.7103.114'), # 2025-05-13
utils.VersionNumber(6, 9, 2): (_BASES[130], '139.0.7258.67'), # 2025-07-29
utils.VersionNumber(6, 9, 3): (_BASES[130], '140.0.7339.207'), # 2025-09-22
## Qt 6.10
utils.VersionNumber(6, 10): (_BASES[134], '140.0.7339.207'), # 2025-09-22
utils.VersionNumber(6, 10, 1): (_BASES[134], '142.0.7444.162'), # 2025-11-11
}
def __post_init__(self) -> None:
@ -930,12 +932,18 @@ def _webengine_extensions() -> Sequence[str]:
lines: list[str] = []
if (
objects.backend == usertypes.Backend.QtWebEngine
and "avoid-chromium-init" not in objects.debug_flags
and machinery.IS_QT6 # mypy; TODO early return once Qt 5 is dropped
):
from qutebrowser.qt.webenginecore import QWebEngineProfile
profile = QWebEngineProfile.defaultProfile()
assert profile is not None # mypy
from qutebrowser.browser.webengine import webenginesettings
lines.append("WebExtensions:")
if webenginesettings.default_profile:
profile = webenginesettings.default_profile
elif "avoid-chromium-init" in objects.debug_flags:
lines[0] += " unknown (avoiding init)"
return lines
else:
profile = webenginesettings.default_qt_profile()
try:
ext_manager = profile.extensionManager()
@ -944,7 +952,6 @@ def _webengine_extensions() -> Sequence[str]:
return []
assert ext_manager is not None # mypy
lines.append("WebExtensions:")
if not ext_manager.extensions():
lines[0] += " none"

View File

@ -6,6 +6,7 @@
"""Build a new release."""
from __future__ import annotations
import os
import sys
@ -20,7 +21,8 @@ import platform
import collections
import dataclasses
import re
from typing import Optional
import http
from typing import Optional, TYPE_CHECKING
from collections.abc import Iterable
try:
@ -28,6 +30,12 @@ try:
except ImportError:
pass
if TYPE_CHECKING:
import github3
import github3.repos.release
import requests
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO_ROOT))
@ -126,7 +134,7 @@ def _smoke_test_run(
return subprocess.run(argv, check=True, capture_output=True)
def smoke_test(executable: pathlib.Path, debug: bool) -> None:
def smoke_test(executable: pathlib.Path, debug_build: bool) -> None:
"""Try starting the given qutebrowser executable."""
stdout_whitelist = []
stderr_whitelist = [
@ -191,10 +199,24 @@ def smoke_test(executable: pathlib.Path, debug: bool) -> None:
# Qt 6.10
(r'\[.*:ERROR:direct_composition_support.cc\([0-9]*\)\] '
r'GetGpuDriverOverlayInfo: Failed to retrieve video device'),
(r'\[.*:ERROR:direct_composition_support.cc\([0-9]*\)\] QueryInterface '
r'to IDCompositionDevice4 failed: No such interface supported '
r'\(0x80004002\)'),
])
proc = _smoke_test_run(executable)
if debug:
try:
proc = _smoke_test_run(executable)
except subprocess.CalledProcessError as e:
print(f"Smoke test failed: {e}, running with --debug")
smoke_test_debug(
executable,
original_stdout=e.stdout.decode("utf-8"),
original_stderr=e.stderr.decode("utf-8"),
issue_description=str(e),
)
return
if debug_build:
print("Skipping output check for debug build")
return
@ -203,48 +225,64 @@ def smoke_test(executable: pathlib.Path, debug: bool) -> None:
if stdout or stderr:
print("Unexpected output, running with --debug")
proc = _smoke_test_run(executable, '--debug')
debug_stdout = proc.stdout.decode('utf-8')
debug_stderr = proc.stderr.decode('utf-8')
smoke_test_debug(
executable,
original_stdout=stdout,
original_stderr=stderr,
issue_description="Unexpected output",
)
lines = [
"Unexpected output!",
def smoke_test_debug(
executable: pathlib.Path,
*,
original_stdout: str,
original_stderr: str,
issue_description: str,
) -> None:
"""Run smoke test in debug mode to get more output."""
proc = _smoke_test_run(executable, '--debug')
debug_stdout = proc.stdout.decode('utf-8')
debug_stderr = proc.stderr.decode('utf-8')
lines = [
issue_description,
"",
]
if original_stdout:
lines += [
"stdout",
"------",
"",
original_stdout,
"",
]
if original_stderr:
lines += [
"stderr",
"------",
"",
original_stderr,
"",
]
if debug_stdout:
lines += [
"debug rerun stdout",
"------------------",
"",
debug_stdout,
"",
]
if debug_stderr:
lines += [
"debug rerun stderr",
"------------------",
"",
debug_stderr,
"",
]
if stdout:
lines += [
"stdout",
"------",
"",
stdout,
"",
]
if stderr:
lines += [
"stderr",
"------",
"",
stderr,
"",
]
if debug_stdout:
lines += [
"debug rerun stdout",
"------------------",
"",
debug_stdout,
"",
]
if debug_stderr:
lines += [
"debug rerun stderr",
"------------------",
"",
debug_stderr,
"",
]
raise Exception("\n".join(lines)) # pylint: disable=broad-exception-raised
raise Exception("\n".join(lines)) # pylint: disable=broad-exception-raised
def verify_windows_exe(exe_path: pathlib.Path) -> None:
@ -300,7 +338,7 @@ def build_mac(
dist_path = pathlib.Path("dist")
utils.print_title("Running pre-dmg smoke test")
smoke_test(_mac_bin_path(dist_path), debug=debug)
smoke_test(_mac_bin_path(dist_path), debug_build=debug)
if skip_packaging:
return []
@ -323,7 +361,7 @@ def build_mac(
subprocess.run(['hdiutil', 'attach', dmg_path,
'-mountpoint', tmp_path], check=True)
try:
smoke_test(_mac_bin_path(tmp_path), debug=debug)
smoke_test(_mac_bin_path(tmp_path), debug_build=debug)
finally:
print("Waiting 10s for dmg to be detachable...")
time.sleep(10)
@ -382,7 +420,7 @@ def _build_windows_single(
verify_windows_exe(exe_path)
utils.print_title("Running smoke test")
smoke_test(exe_path, debug=debug)
smoke_test(exe_path, debug_build=debug)
if skip_packaging:
return []
@ -558,11 +596,36 @@ def read_github_token(
return token
def _github_find_release(
gh: github3.GitHub, tag: str, experimental: bool
) -> github3.repos.release.Release:
if experimental:
repo = gh.repository('qutebrowser', 'experiments')
else:
repo = gh.repository('qutebrowser', 'qutebrowser')
assert repo is not None
for release in repo.releases():
if release.tag_name == tag:
return release
releases = ", ".join(r.tag_name for r in repo.releases())
raise Exception( # pylint: disable=broad-exception-raised
f"No release found for {tag!r} in {repo.full_name}, found: {releases}")
def _github_assets(
release: github3.repos.release.Release, artifact: Artifact
) -> list[github3.repos.release.Asset]:
return [asset for asset in release.assets() if asset.name == artifact.path.name]
def github_upload(
artifacts: list[Artifact],
tag: str,
gh_token: str,
experimental: bool,
skip_if_exists: bool,
) -> None:
"""Upload the given artifacts to GitHub.
@ -571,35 +634,25 @@ def github_upload(
tag: The name of the release tag
gh_token: The GitHub token to use
experimental: Upload to the experiments repo
skip_if_exists: Skip uploading artifacts that already exist
"""
# pylint: disable=broad-exception-raised
import github3
import github3.exceptions
utils.print_title("Uploading to github...")
gh = github3.login(token=gh_token)
if experimental:
repo = gh.repository('qutebrowser', 'experiments')
else:
repo = gh.repository('qutebrowser', 'qutebrowser')
release = None # to satisfy pylint
for release in repo.releases():
if release.tag_name == tag:
break
else:
releases = ", ".join(r.tag_name for r in repo.releases())
raise Exception(
f"No release found for {tag!r} in {repo.full_name}, found: {releases}")
assert gh is not None
release = _github_find_release(gh=gh, tag=tag, experimental=experimental)
for artifact in artifacts:
if _github_assets(release, artifact) and skip_if_exists:
print(f"Artifact {artifact.path.name} already exists, skipping")
continue
while True:
print(f"Uploading {artifact.path}")
assets = [asset for asset in release.assets()
if asset.name == artifact.path.name]
if assets:
if (assets := _github_assets(release, artifact)):
print(f"Assets already exist: {assets}")
if utils.ON_CI:
@ -627,9 +680,7 @@ def github_upload(
print("Retrying!")
assets = [asset for asset in release.assets()
if asset.name == artifact.path.name]
if assets:
if (assets := _github_assets(release, artifact)):
stray_asset = assets[0]
print(f"Deleting stray asset {stray_asset.name}")
stray_asset.delete()
@ -637,12 +688,29 @@ def github_upload(
break
def pypi_upload(artifacts: list[Artifact], experimental: bool) -> None:
def check_pypi_exists(version: str) -> bool:
"""Check whether the given version exists on PyPI."""
response = requests.get(
f"https://pypi.org/pypi/qutebrowser/{version}/json", timeout=30
)
if response.status_code == http.HTTPStatus.NOT_FOUND:
return False
response.raise_for_status()
return bool(response.json()["urls"])
def pypi_upload(
artifacts: list[Artifact], experimental: bool, skip_if_exists: bool
) -> None:
"""Upload the given artifacts to PyPI using twine."""
utils.print_title("Uploading to PyPI...")
if skip_if_exists and check_pypi_exists(qutebrowser.__version__):
print(f"Version {qutebrowser.__version__} already exists on PyPI, skipping")
return
# https://blog.pypi.org/posts/2023-05-23-removing-pgp/
artifacts = [a for a in artifacts if a.mimetype != 'application/pgp-signature']
utils.print_title("Uploading to PyPI...")
if experimental:
run_twine('upload', artifacts, "-r", "testpypi")
else:
@ -668,6 +736,8 @@ def main() -> None:
nargs='?')
parser.add_argument('--upload', action='store_true', required=False,
help="Toggle to upload the release to GitHub.")
parser.add_argument('--reupload', action='store_true', required=False,
help="Skip uploading artifacts that already exist.")
parser.add_argument('--no-confirm', action='store_true', required=False,
help="Skip confirmation before uploading.")
parser.add_argument('--skip-packaging', action='store_true', required=False,
@ -727,9 +797,16 @@ def main() -> None:
assert gh_token is not None
github_upload(
artifacts, version_tag, gh_token=gh_token, experimental=args.experimental)
artifacts,
version_tag,
gh_token=gh_token,
experimental=args.experimental,
skip_if_exists=args.reupload,
)
if upload_to_pypi:
pypi_upload(artifacts, experimental=args.experimental)
pypi_upload(
artifacts, experimental=args.experimental, skip_if_exists=args.reupload
)
else:
print()
utils.print_title("Artifacts")

View File

@ -23,6 +23,7 @@
"Mako": "https://docs.makotemplates.org/en/latest/changelog.html",
"hypothesis": "https://hypothesis.readthedocs.io/en/latest/changes.html",
"mypy": "https://github.com/python/mypy/blob/master/CHANGELOG.md",
"librt": "https://github.com/mypyc/librt/commits/master/",
"types-PyYAML": "https://github.com/python/typeshed/commits/main/stubs/PyYAML",
"types-colorama": "https://github.com/python/typeshed/commits/main/stubs/colorama",
"types-docutils": "https://github.com/python/typeshed/commits/main/stubs/docutils",
@ -101,7 +102,6 @@
"h11": "https://h11.readthedocs.io/en/latest/changes.html",
"httpcore": "https://github.com/encode/httpcore/blob/master/CHANGELOG.md",
"httpx": "https://github.com/encode/httpx/blob/master/CHANGELOG.md",
"sniffio": "https://sniffio.readthedocs.io/en/latest/history.html",
"six": "https://github.com/benjaminp/six/blob/master/CHANGES",
"altgraph": "https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst",
"urllib3": "https://github.com/urllib3/urllib3/blob/main/CHANGES.rst",

View File

@ -10,21 +10,12 @@ RUN pacman -Su --noconfirm \
python-tox \
python-distlib \
libxml2-legacy \
{% if qt6 %}
qt6-base \
qt6-declarative \
qt6-webengine \
python-pyqt6-webengine \
pdfjs \
python-pyqt6 \
{% else %}
qt5-base \
qt5-declarative \
openssl-1.1 \
qt5-webengine \
python-pyqtwebengine \
python-pyqt5 \
{% endif %}
qt6-base \
qt6-declarative \
qt6-webengine \
python-pyqt6-webengine \
pdfjs \
python-pyqt6 \
xorg-xinit \
xorg-server-xvfb \
ttf-bitstream-vera \
@ -36,12 +27,7 @@ RUN useradd user -u 1001 && \
mkdir /home/user && \
chown user:users /home/user
{% if qt6 %}
{% set pyqt_module = 'PyQt6' %}
{% else %}
{% set pyqt_module = 'PyQt5' %}
{% endif %}
RUN python3 -c "from {{ pyqt_module }} import QtWebEngineCore, QtWebEngineWidgets"
RUN python3 -c "from PyQt6 import QtWebEngineCore, QtWebEngineWidgets"
USER user
WORKDIR /home/user
@ -49,4 +35,4 @@ RUN git config --global --add safe.directory /outside/.git
CMD git clone /outside qutebrowser.git && \
cd qutebrowser.git && \
{{ python }} -m tox -e {% if qt6 %}py-qt6{% else %}py-qt5{% endif %}
{{ python }} -m tox -e py-qt6

View File

@ -7,17 +7,14 @@
"""Generate Dockerfiles for qutebrowser's CI."""
import sys
import argparse
import jinja2
CONFIGS = {
'archlinux-webengine': {'unstable': False, 'qt6': False},
'archlinux-webengine-qt6': {'unstable': False, 'qt6': True},
'archlinux-webengine-unstable': {'unstable': True, 'qt6': False},
'archlinux-webengine-unstable-qt6': {'unstable': True, 'qt6': True},
'archlinux-webengine': {'unstable': False},
'archlinux-webengine-unstable': {'unstable': True},
}

View File

@ -54,13 +54,14 @@ def show_commit():
git_args = ['git', 'show']
if utils.ON_CI:
git_args.append("--color")
git_args.append("--no-patch") # shows entire git tree on CI (shallow clone)
subprocess.run(git_args, check=True)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Update release version.")
parser.add_argument('bump', action="store",
choices=["major", "minor", "patch"],
choices=["major", "minor", "patch", "reupload"],
help="Update release version")
parser.add_argument('--commands', action="store_true",
help="Only show commands to run post-release.")
@ -70,7 +71,8 @@ if __name__ == "__main__":
if not args.commands:
verify_branch(args.bump)
bump_version(args.bump)
if args.bump != "reupload":
bump_version(args.bump)
show_commit()
import qutebrowser
@ -87,15 +89,16 @@ if __name__ == "__main__":
print(f"Outputs for {version} written to GitHub Actions output file")
else:
print("Run the following commands to create a new release:")
print("* git push origin; git push origin v{v}".format(v=version))
if args.bump == 'patch':
print("* git checkout main && git cherry-pick -x v{v} && "
"git push origin".format(v=version))
else:
print("* git branch v{x} v{v} && git push --set-upstream origin v{x}"
.format(v=version, x=version_x))
print("* Create new release via GitHub (required to upload release "
"artifacts)")
if args.bump != 'reupload':
print("* git push origin; git push origin v{v}".format(v=version))
if args.bump == 'patch':
print("* git checkout main && git cherry-pick -x v{v} && "
"git push origin".format(v=version))
else:
print("* git branch v{x} v{v} && git push --set-upstream origin v{x}"
.format(v=version, x=version_x))
print("* Create new release via GitHub (required to upload release "
"artifacts)")
print("* Linux: git fetch && git checkout v{v} && "
"tox -e build-release -- --upload"
.format(v=version))

View File

@ -15,9 +15,11 @@
console.log("[PASS] Positions equal: " + old_position);
}
}
requestAnimationFrame(() => console.log('simple loaded'))
</script>
</head>
<body onload="console.log('simple loaded')">
<body>
<a href="/data/hello.txt" id="link">Just a link</a>
<button>blub</button>
<p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. Donec non enim in turpis pulvinar facilisis. Ut felis. Praesent dapibus, neque id cursus faucibus, tortor neque egestas augue, eu vulputate magna eros eu erat. Aliquam erat volutpat. Nam dui mi, tincidunt quis, accumsan porttitor, facilisis luctus, metus</p>

View File

@ -103,3 +103,11 @@ Feature: Using completion
And I run :completion-item-focus next
And I run :cmd-set-text -s :set
Then the completion model should be option
Scenario: Page focus after using completion (#8750)
When I open data/insert_mode_settings/html/input.html
And I run :cmd-set-text :
And I run :mode-leave
And I run :click-element id qute-input
And I run :fake-key -g someinput
Then the javascript message "contents: someinput" should be logged

View File

@ -75,6 +75,7 @@ Feature: Saving and loading sessions
@qtwebkit_skip
Scenario: Scrolling (qtwebengine)
When I open data/scroll/simple.html
And I wait for "* simple loaded" in the log
And I run :scroll-px 10 20
And I wait until the scroll position changed to 10/20
Then the session should look like:

View File

@ -50,13 +50,13 @@ def fresh_instance(quteproc):
# on PyQt6.8 we disable that with the new API, otherwise restart the
# browser to make it forget previous prompts.
#
# Qt 6.10 Beta 4 accidentally persists some permissions;
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-140194
# Starting with Qt 6.10, QtWebEngine unconditionally persists some permissions;
# see https://bugreports.qt.io/browse/QTBUG-140194
if (
qtutils.version_check("6.8", compiled=False)
and PYQT_WEBENGINE_VERSION
and PYQT_WEBENGINE_VERSION < 0x60800
) or qtutils.version_check("6.10", compiled=False, exact=True):
) or qtutils.version_check("6.10", compiled=False):
quteproc.terminate()
quteproc.start()

View File

@ -263,6 +263,14 @@ def is_ignored_chromium_message(line):
"GetGpuDriverOverlayInfo: Failed to retrieve video device",
# [1784:7100:1022/150434.202:ERROR:direct_composition_support.cc(1122)]
"QueryInterface to IDCompositionDevice4 failed: No such interface supported (0x80004002)",
# Qt 6.10 on Windows + GitHub Actions
# [3508:6056:1103/172403.602:ERROR:cache_util_win.cc(20)]
"Unable to move the cache: The system cannot find the file specified. (0x2)",
# [3508:5516:1103/172403.608:ERROR:disk_cache.cc(216)]
"Unable to create cache",
# [3508:5516:1103/172403.608:ERROR:gpu_disk_cache.cc(711)]
"Gpu Cache Creation failed: -2",
]
return any(testutils.pattern_match(pattern=pattern, value=message)
for pattern in ignored_messages)

View File

@ -21,6 +21,7 @@ import pytest
from qutebrowser.qt.core import QProcess, QPoint
from helpers import testutils
from end2end.fixtures import quteprocess
from qutebrowser.utils import qtutils, utils, version
@ -251,6 +252,7 @@ def test_optimize(request, quteproc_new, capfd, level):
def test_version(request):
"""Test invocation with --version argument."""
args = ['-m', 'qutebrowser', '--version'] + _base_args(request.config)
args.remove("--json-logging")
# can't use quteproc_new here because it's confused by
# early process termination
proc = QProcess()
@ -611,6 +613,26 @@ def test_service_worker_workaround(
assert not service_worker_dir.exists()
@pytest.mark.qt6_only
def test_disable_hangouts_extension_crash(
quteproc_new: quteprocess.QuteProc,
request: pytest.FixtureRequest,
webengine_versions: version.WebEngineVersions,
):
"""Make sure disabling the Hangouts extension doesn't crash."""
args = _base_args(request.config) + [
'--temp-basedir',
'-s', 'qt.workarounds.disable_hangouts_extension', 'true',
]
quteproc_new.start(args)
if webengine_versions.webengine == utils.VersionNumber(6, 10, 1):
line = quteproc_new.wait_for(message="Not disabling Hangouts extension *")
line.expected = True
quteproc_new.send_cmd(':quit')
quteproc_new.wait_for_quit()
@pytest.mark.parametrize('store', [True, False])
def test_cookies_store(quteproc_new, request, short_tmpdir, store):
# Start test process

View File

@ -4,7 +4,6 @@
import string
import functools
import itertools
import operator
import pytest
@ -76,7 +75,7 @@ def test_match_benchmark(benchmark, tabbed_browser, qtbot, mode_manager, qapp,
@pytest.mark.parametrize('min_len', [0, 3])
@pytest.mark.parametrize('num_chars', [5, 9])
@pytest.mark.parametrize('num_elements', itertools.chain(range(1, 26), [125]))
@pytest.mark.parametrize('num_elements', [*range(1, 26), 125])
def test_scattered_hints_count(min_len, num_chars, num_elements):
"""Test scattered hints function.

View File

@ -12,7 +12,7 @@ import pytest
from qutebrowser.qt import machinery
from qutebrowser import qutebrowser
from qutebrowser.config import qtargs, configdata
from qutebrowser.utils import usertypes, version
from qutebrowser.utils import usertypes, version, utils
@pytest.fixture
@ -52,6 +52,7 @@ def reduce_args(config_stub, version_patcher, monkeypatch):
config_stub.val.scrolling.bar = 'never'
config_stub.val.qt.chromium.experimental_web_platform_features = 'never'
config_stub.val.qt.workarounds.disable_accelerated_2d_canvas = 'never'
config_stub.val.qt.workarounds.disable_accessibility = 'never'
monkeypatch.setattr(qtargs.utils, 'is_mac', False)
# Avoid WebRTC pipewire feature
monkeypatch.setattr(qtargs.utils, 'is_linux', False)
@ -117,6 +118,14 @@ def test_no_webengine_available(monkeypatch, config_stub, parser, stubs):
assert args == [sys.argv[0]]
_XFAIL_FUTURE_QT = (
pytest.mark.xfail(
utils.VersionNumber(6, 11) not in version.WebEngineVersions._CHROMIUM_VERSIONS,
reason="Unknown security patch version for Qt 6.11 so far",
),
)
@pytest.mark.usefixtures('reduce_args')
class TestWebEngineArgs:
@ -190,6 +199,40 @@ class TestWebEngineArgs:
args = qtargs.qt_args(parsed)
assert ('--disable-accelerated-2d-canvas' in args) == has_arg
@pytest.mark.parametrize(
"qt_version, qt6, value, has_arg",
[
("5.15.2", False, "auto", False),
# 6.8.5 is broken too, but commercial-only
("6.10.0", True, "always", True),
("6.10.0", True, "auto", False),
("6.10.1", True, "auto", True),
("6.10.1", True, "never", False),
("6.10.2", True, "always", True),
("6.10.2", True, "auto", False),
pytest.param("6.11.0", True, "always", True, marks=_XFAIL_FUTURE_QT),
pytest.param("6.11.0", True, "auto", False, marks=_XFAIL_FUTURE_QT),
],
)
def test_disable_accessibility(
self,
parser,
version_patcher,
config_stub,
monkeypatch,
qt_version,
qt6,
value,
has_arg,
):
version_patcher(qt_version)
config_stub.val.qt.workarounds.disable_accessibility = value
monkeypatch.setattr(machinery, 'IS_QT6', qt6)
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
assert ('--disable-renderer-accessibility' in args) == has_arg
@pytest.mark.parametrize('flags, args', [
([], []),
(['--debug-flag', 'chromium'], ['--enable-logging', '--v=1']),
@ -471,6 +514,9 @@ class TestWebEngineArgs:
# Qt 6.9
('6.9.0', "DocumentPictureInPictureAPI,PermissionElement"),
('6.9.1', "DocumentPictureInPictureAPI,PermissionElement"),
# Qt 6.10
('6.10.0', "DocumentPictureInPictureAPI,PermissionElement"),
('6.10.1', "DocumentPictureInPictureAPI"),
])
def test_disable_feature_workaround(
self, parser, version_patcher, qt_version, disabled

View File

@ -32,6 +32,31 @@ Thread 0x00007fa135ac7700 (most recent call first):
File "", line 1 in testfunc
"""
VALID_CRASH_TEXT_PY314 = """
Fatal Python error: Segmentation fault
_
Current thread 0x00000001fe53e140 [CrBrowserMain] (most recent call first):
File "qutebrowser/app.py", line 126 in qt_mainloop
File "qutebrowser/app.py", line 116 in run
File "qutebrowser/qutebrowser.py", line 234 in main
File "__main__.py", line 15 in <module>
_
Current thread's C stack trace (most recent call first):
Binary file "...", at _Py_DumpStack+0x48 [0x10227cc9c]
<truncated rest of calls>
"""
VALID_CRASH_TEXT_PY314_NO_PY = """
Fatal Python error: Segmentation fault
_
Current thread 0x00007f0dc805cbc0 [qutebrowser] (most recent call first):
<no Python frame>
_
Current thread's C stack trace (most recent call first):
Binary file "/lib64/libpython3.14.so.1.0", at _Py_DumpStack+0x4c [0x7f0dc7b2127b]
<truncated rest of calls>
"""
WINDOWS_CRASH_TEXT = r"""
Windows fatal exception: access violation
_
@ -45,13 +70,32 @@ Hello world!
"""
@pytest.mark.parametrize('text, typ, func', [
(VALID_CRASH_TEXT, 'Segmentation fault', 'testfunc'),
(VALID_CRASH_TEXT_THREAD, 'Segmentation fault', 'testfunc'),
(VALID_CRASH_TEXT_EMPTY, 'Aborted', ''),
(WINDOWS_CRASH_TEXT, 'Windows access violation', 'tabopen'),
(INVALID_CRASH_TEXT, '', ''),
])
@pytest.mark.parametrize(
"text, typ, func",
[
pytest.param(VALID_CRASH_TEXT, "Segmentation fault", "testfunc", id="valid"),
pytest.param(
VALID_CRASH_TEXT_THREAD, "Segmentation fault", "testfunc", id="valid-thread"
),
pytest.param(
VALID_CRASH_TEXT_PY314,
"Segmentation fault",
"qt mainloop",
id="valid-py314",
),
pytest.param(
VALID_CRASH_TEXT_PY314_NO_PY,
"Segmentation fault",
"",
id="valid-py314-no-py",
),
pytest.param(VALID_CRASH_TEXT_EMPTY, "Aborted", "", id="valid-empty"),
pytest.param(
WINDOWS_CRASH_TEXT, "Windows access violation", "tabopen", id="windows"
),
pytest.param(INVALID_CRASH_TEXT, "", "", id="invalid"),
],
)
def test_parse_fatal_stacktrace(text, typ, func):
text = text.strip().replace('_', ' ')
assert crashdialog.parse_fatal_stacktrace(text) == (typ, func)

View File

@ -21,17 +21,18 @@ from qutebrowser.misc import wmname
def test_load_libwayland_client():
"""Test loading the Wayland client library, which might or might not exist."""
try:
wmname._load_libwayland_client()
wmname._load_library("wayland-client")
except wmname.Error:
pass
def test_load_libwayland_client_error(mocker: pytest_mock.MockerFixture):
"""Test that an error in loading the Wayland client library raises an error."""
mocker.patch.object(ctypes.util, "find_library", return_value="libwayland-client.so.6")
mocker.patch("ctypes.CDLL", side_effect=OSError("Library not found"))
with pytest.raises(wmname.Error, match="Failed to load libwayland-client"):
wmname._load_libwayland_client()
with pytest.raises(wmname.Error, match="Failed to load wayland-client"):
wmname._load_library("wayland-client")
@pytest.fixture
@ -177,7 +178,7 @@ def test_wayland_real():
def test_load_xlib():
"""Test loading Xlib, which might or might not exist."""
try:
wmname._x11_load_lib()
wmname._load_library("X11")
except wmname.Error:
pass
@ -187,7 +188,7 @@ def test_load_xlib_not_found(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(ctypes.util, "find_library", lambda x: None)
with pytest.raises(wmname.Error, match="X11 library not found"):
wmname._x11_load_lib()
wmname._load_library("X11")
def test_load_xlib_error(mocker: pytest_mock.MockerFixture):
@ -198,7 +199,7 @@ def test_load_xlib_error(mocker: pytest_mock.MockerFixture):
with pytest.raises(
wmname.Error, match="Failed to load X11 library: Failed to load library"
):
wmname._x11_load_lib()
wmname._load_library("X11")
@pytest.fixture
@ -289,7 +290,7 @@ def test_x11_get_wm_name(
qtbot.add_widget(w)
w.setWindowTitle("Test Window")
xlib = wmname._x11_load_lib()
xlib = wmname._load_library("X11")
with wmname._x11_open_display(xlib) as display:
atoms = wmname._X11Atoms(
NET_SUPPORTING_WM_CHECK=-1,

View File

@ -1449,11 +1449,9 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False)
if machinery.IS_QT6:
monkeypatch.setattr(
QWebEngineProfile,
"defaultProfile",
lambda: FakeExtensionProfile(
FakeExtensionManager([FakeExtensionInfo("ext1")])
),
webenginesettings,
"default_profile",
FakeExtensionProfile(FakeExtensionManager([FakeExtensionInfo("ext1")])),
)
substitutions['webextensions'] = (
"\n"
@ -1592,20 +1590,35 @@ class TestOpenGLInfo:
class TestWebEngineExtensions:
def test_qtwebkit(self, monkeypatch: pytest.MonkeyPatch) -> None:
assert webenginesettings.default_profile is None # -> default_qt_profile() used
monkeypatch.setattr(version.objects, "backend", usertypes.Backend.QtWebKit)
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", lambda: 1/0)
monkeypatch.setattr(webenginesettings, "default_qt_profile", lambda: 1 / 0)
assert not version._webengine_extensions()
def test_avoid_chromium_init(self, monkeypatch: pytest.MonkeyPatch) -> None:
assert webenginesettings.default_profile is None # -> default_qt_profile() used
monkeypatch.setattr(version.objects, "backend", usertypes.Backend.QtWebEngine)
monkeypatch.setattr(objects, "debug_flags", {"avoid-chromium-init"})
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", lambda: 1/0)
assert not version._webengine_extensions()
monkeypatch.setattr(webenginesettings, "default_qt_profile", lambda: 1 / 0)
assert version._webengine_extensions() == [
"WebExtensions: unknown (avoiding init)"
]
def test_no_extension_manager(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(QWebEngineProfile, "defaultProfile", object)
assert webenginesettings.default_profile is None # -> default_qt_profile() used
monkeypatch.setattr(webenginesettings, "default_qt_profile", object)
assert not version._webengine_extensions()
@pytest.mark.parametrize("avoid_init", [True, False])
def test_preexisting_profile(self, monkeypatch: pytest.MonkeyPatch, avoid_init: bool) -> None:
"""Test that we use the pre-existing profile if available."""
monkeypatch.setattr(webenginesettings, "default_profile", FakeExtensionProfile(FakeExtensionManager([])))
if avoid_init:
monkeypatch.setattr(objects, "debug_flags", {"avoid-chromium-init"})
result = version._webengine_extensions()
assert result == ["WebExtensions: none"]
@pytest.mark.parametrize(
"extensions, expected",
[
@ -1666,11 +1679,9 @@ class TestWebEngineExtensions:
expected: list[str],
) -> None:
monkeypatch.setattr(
QWebEngineProfile,
"defaultProfile",
lambda: FakeExtensionProfile(
FakeExtensionManager(extensions)
),
webenginesettings,
"default_profile",
FakeExtensionProfile(FakeExtensionManager(extensions)),
)
assert version._webengine_extensions() == expected