Compare commits

...

25 Commits
main ... v3.6.2

Author SHA1 Message Date
qutebrowser bot b2c5d5fa0d Release v3.6.2 2025-11-27 20:57:11 +00:00
Florian Bruhin 73ae3abfc2 Update user agents
(cherry picked from commit 500a8df209)
2025-11-27 21:55:52 +01:00
Florian Bruhin e8af559bd7 Update changelog from main 2025-11-27 21:29:46 +01:00
Florian Bruhin 9816e176a7 qtargs: Remove old workaround with Qt 6.10.1
(cherry picked from commit b3e4dba731)
2025-11-27 21:29:34 +01:00
Florian Bruhin f7e7f8b168 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

(cherry picked from commit 81d7b6a74c)
2025-11-27 17:59:10 +01:00
Rebecca 7dc9768976 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.

(cherry picked from commit 1cbb6fccf0)
2025-11-27 17:58:13 +01:00
Florian Bruhin dd790c3b72 Update requirements to Qt 6.10.1 2025-11-27 17:22:17 +01:00
Florian Bruhin f6aa7f6ba8 tests: Skip hangouts extension test on Qt 5 2025-11-23 11:43:09 +01:00
Florian Bruhin 4367cc65fe 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:40:53 +01:00
gesh 22335c849a 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/

(cherry picked from commit 2f8234ee2e)
2025-11-21 23:43:59 +01:00
Florian Bruhin 56548455d5 tests: Improve test_version output
(cherry picked from commit 4f40a8b46b)
2025-11-21 23:42:52 +01:00
Florian Bruhin 4c6f9b4255 tests: Adjust permissions storage workaround for Qt 6.10.1
(cherry picked from commit 59a64af67f)
2025-11-21 23:42:52 +01:00
Florian Bruhin 300b075d79 Add QtWebEngine 6.10.1 security patch version
(cherry picked from commit 66cbe0d9c9)
2025-11-21 23:42:52 +01:00
Florian Bruhin b723a02087 tests: Stabilize flaky session scrolling test
Equivalent of d8079515fa
See #5390

(cherry picked from commit 0ef5053a65)
2025-11-21 23:42:52 +01:00
Florian Bruhin 86ed433b97 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/

(cherry picked from commit 9316d428ef)
2025-11-21 22:55:00 +01:00
Florian Bruhin 0c34bf2f79 version: Use correct profile for extension list
See #8785

(cherry picked from commit 8ae5e3d83b)
2025-11-21 22:51:33 +01:00
Jan Palus f86728e972 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

(cherry picked from commit 25dc019886)
2025-11-11 09:10:04 +01:00
qutebrowser bot 2e5f805cce Release v3.6.1 2025-11-03 15:29:59 +00:00
Florian Bruhin 155b5cb241 Fix changelog
(cherry picked from commit 6e8e24050d)
2025-11-03 16:27:06 +01:00
Florian Bruhin 0deadea17f Adjust stack trace parsing for newer Python
(cherry picked from commit aa93eb1614)
2025-11-03 16:26:26 +01:00
Florian Bruhin b3377fccff 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.

(cherry picked from commit b646d606d7)
2025-10-25 16:45:13 +02:00
Florian Bruhin 11acdd2fdf ci: Fix finding existing draft release
(cherry picked from commit 1e4ddc2c6b)
2025-10-24 17:03:00 +02:00
Florian Bruhin 8f4e6ec06e scripts: Avoid showing entire file tree diff in CI log
(cherry picked from commit 3fce0518bd)
2025-10-24 17:02:59 +02:00
Florian Bruhin 52fd43c95e ci: Find existing draft release for reuploads
(cherry picked from commit 3808ebfdb3)
2025-10-24 16:23:37 +02:00
Florian Bruhin 26df4ce7e3 ci: Check out release branch for reuploads 2025-10-24 15:44:26 +02:00
34 changed files with 258 additions and 118 deletions

View File

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

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 }}"
@ -40,7 +38,6 @@ jobs:
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

View File

@ -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:

View File

@ -15,8 +15,6 @@ jobs:
image:
- archlinux-webengine
- archlinux-webengine-unstable
- archlinux-webengine-unstable-qt6
- archlinux-webengine-qt6
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6

View File

@ -32,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:
@ -126,6 +127,28 @@ jobs:
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:
@ -142,7 +165,7 @@ jobs:
steps:
- uses: actions/checkout@v5
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:

View File

@ -15,6 +15,37 @@ 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.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

@ -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

@ -44,6 +44,8 @@
</content_rating>
<releases>
<!-- Add new releases here -->
<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,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt6==6.10.0
PyQt6-Qt6==6.10.0
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-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-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

@ -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.2"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on Python and Qt."

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

@ -773,14 +773,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:

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

@ -26,12 +26,20 @@ class _WaylandDisplayStruct(ctypes.Structure):
_WaylandDisplay = NewType("_WaylandDisplay", "ctypes._Pointer[_WaylandDisplayStruct]")
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(lib)
except OSError as e:
raise Error(f"Failed to load {name} library: {e}")
def _load_libwayland_client() -> ctypes.CDLL:
"""Load the Wayland client library."""
try:
return ctypes.CDLL("libwayland-client.so")
except OSError as e:
raise Error(f"Failed to load libwayland-client: {e}")
return _load_library("wayland-client")
def _pid_from_fd(fd: int) -> int:
@ -138,14 +146,7 @@ _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}")
return _load_library("X11")
@contextlib.contextmanager

View File

@ -657,6 +657,7 @@ class WebEngineVersions:
## 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 +931,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 +951,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

@ -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,6 +54,7 @@ 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)

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

@ -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

@ -471,6 +471,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

@ -28,9 +28,10 @@ def test_load_libwayland_client():
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"):
with pytest.raises(wmname.Error, match="Failed to load wayland-client"):
wmname._load_libwayland_client()

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