summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/bleeding.yml14
-rw-r--r--doc/changelog.asciidoc4
-rw-r--r--doc/help/commands.asciidoc10
-rwxr-xr-xmisc/nsis/install.nsh33
-rwxr-xr-xmisc/nsis/qutebrowser.nsi7
-rw-r--r--misc/qutebrowser.spec19
-rw-r--r--misc/requirements/requirements-dev.txt2
-rw-r--r--misc/requirements/requirements-mypy.txt6
-rw-r--r--misc/requirements/requirements-pyinstaller.txt2
-rw-r--r--misc/requirements/requirements-pyinstaller.txt-raw2
-rw-r--r--misc/requirements/requirements-pyroma.txt2
-rw-r--r--misc/requirements/requirements-tests.txt6
-rw-r--r--misc/requirements/requirements-tox.txt4
-rw-r--r--misc/requirements/requirements-vulture.txt2
-rw-r--r--qutebrowser/api/cmdutils.py2
-rw-r--r--qutebrowser/app.py11
-rw-r--r--qutebrowser/browser/browsertab.py19
-rw-r--r--qutebrowser/browser/commands.py26
-rw-r--r--qutebrowser/browser/eventfilter.py35
-rw-r--r--qutebrowser/browser/qutescheme.py3
-rw-r--r--qutebrowser/browser/urlmarks.py5
-rw-r--r--qutebrowser/browser/webengine/webengineelem.py13
-rw-r--r--qutebrowser/extensions/loader.py28
-rw-r--r--qutebrowser/html/warning-sandboxing.html16
-rw-r--r--qutebrowser/keyinput/modeman.py6
-rw-r--r--qutebrowser/keyinput/modeparsers.py6
-rw-r--r--qutebrowser/mainwindow/tabwidget.py1
-rw-r--r--qutebrowser/utils/qtutils.py32
-rw-r--r--qutebrowser/utils/resources.py5
-rw-r--r--qutebrowser/utils/urlutils.py3
-rw-r--r--requirements.txt2
-rwxr-xr-xscripts/dev/build_release.py67
-rw-r--r--tests/end2end/features/misc.feature8
-rw-r--r--tests/end2end/features/qutescheme.feature13
-rw-r--r--tests/end2end/features/test_misc_bdd.py5
-rw-r--r--tests/end2end/features/test_urlmarks_bdd.py11
-rw-r--r--tests/end2end/features/urlmarks.feature46
-rw-r--r--tests/end2end/fixtures/quteprocess.py2
-rw-r--r--tests/end2end/fixtures/webserver_sub.py2
-rw-r--r--tests/unit/extensions/test_loader.py8
-rw-r--r--tests/unit/utils/test_qtutils.py50
-rw-r--r--tox.ini12
42 files changed, 392 insertions, 158 deletions
diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml
index 59da1dfad..8f8cddc1e 100644
--- a/.github/workflows/bleeding.yml
+++ b/.github/workflows/bleeding.yml
@@ -12,12 +12,20 @@ jobs:
if: "github.repository == 'qutebrowser/qutebrowser'"
runs-on: ubuntu-20.04
timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - testenv: bleeding
+ image: "archlinux-webengine-unstable-qt6"
+ - testenv: bleeding-qt5
+ image: "archlinux-webengine-unstable"
container:
- image: "qutebrowser/ci:archlinux-webengine-unstable"
+ image: "qutebrowser/ci:${{ matrix.image }}"
env:
FORCE_COLOR: "1"
PY_COLORS: "1"
- DOCKER: "archlinux-webengine-unstable"
+ DOCKER: "${{ matrix.image }}"
CI: true
volumes:
# Hardcoded because we can't use ${{ runner.temp }} here apparently.
@@ -30,7 +38,7 @@ jobs:
- name: Set up problem matchers
run: "python scripts/dev/ci/problemmatchers.py py3 ${{ runner.temp }}"
- name: Run tox
- run: dbus-run-session tox -e bleeding
+ run: dbus-run-session tox -e ${{ matrix.testenv }}
irc:
timeout-minutes: 2
continue-on-error: true
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index b2b392a4c..96a4b42e7 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -67,6 +67,8 @@ Added
- New `colors.webpage.darkmode.increase_text_contrast` setting for Qt 6.3+
- New `fonts.tooltip`, `colors.tooltip.bg` and `colors.tooltip.fg` settings.
- New `log-qt-events` debug flag for `-D`
+- New `--all` flags for `:bookmark-del` and `:quickmark-del` to delete all
+ quickmarks/bookmarks.
Removed
~~~~~~~
@@ -86,7 +88,7 @@ Removed
* Qt 6.4 was the latest version to support macOS 10.14 and 10.15.
* It should be possible to build a custom .dmg with Qt 6.4, but this is
unsupported and not recommended.
-- Support for Windows 8 and for Windows 10 before 1809 is now dropped.
+- Support for Windows 8 and for Windows 10 before 1607 is now dropped.
* Support for older Windows 10 versions might still be present in Qt 6.0/6.1/6.2
* Support for Windows 8.1 is still present in Qt 5.15
* It should be possible to build a custom .exe with those versions, but this
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index d35d01079..4d1610970 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -204,7 +204,7 @@ If no url and title are provided, then save the current page as a bookmark. If a
[[bookmark-del]]
=== bookmark-del
-Syntax: +:bookmark-del ['url']+
+Syntax: +:bookmark-del [*--all*] ['url']+
Delete a bookmark.
@@ -212,6 +212,9 @@ Delete a bookmark.
* +'url'+: The url of the bookmark to delete. If not given, use the current page's url.
+==== optional arguments
+* +*-a*+, +*--all*+: If given, delete all bookmarks.
+
==== note
* This command does not split arguments after the last argument and handles quotes literally.
@@ -1064,7 +1067,7 @@ You can view all saved quickmarks on the link:qute://bookmarks[bookmarks page].
[[quickmark-del]]
=== quickmark-del
-Syntax: +:quickmark-del ['name']+
+Syntax: +:quickmark-del [*--all*] ['name']+
Delete a quickmark.
@@ -1073,6 +1076,9 @@ Delete a quickmark.
if there are more than one).
+==== optional arguments
+* +*-a*+, +*--all*+: Delete all quickmarks.
+
==== note
* This command does not split arguments after the last argument and handles quotes literally.
diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh
index e7d8b4956..282a254eb 100755
--- a/misc/nsis/install.nsh
+++ b/misc/nsis/install.nsh
@@ -430,8 +430,37 @@ SectionEnd
; Callbacks
Function .onInit
StrCpy $KeepReg 1
- !insertmacro CheckPlatform ${PLATFORM}
- !insertmacro CheckMinWinVer ${MIN_WIN_VER}
+
+; OS version check
+ ${If} ${RunningX64}
+ ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoa#remarks
+ GetWinVer $R0 Major
+ !if "${QT5}" == "True"
+ IntCmpU $R0 6 0 _os_check_fail _os_check_pass
+ GetWinVer $R1 Minor
+ IntCmpU $R1 2 _os_check_pass _os_check_fail _os_check_pass
+ !else
+ IntCmpU $R0 10 0 _os_check_fail _os_check_pass
+ GetWinVer $R1 Build
+ ${If} $R1 >= 22000 ; Windows 11 21H2
+ Goto _os_check_pass
+ ${ElseIf} $R1 >= 14393 ; Windows 10 1607
+ ${AndIf} ${IsNativeAMD64} ; Windows 10 has no x86_64 emulation on arm64
+ Goto _os_check_pass
+ ${EndIf}
+ !endif
+ ${EndIf}
+ _os_check_fail:
+ !if "${QT5}" == "True"
+ MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\
+ version of Windows 8 or later."
+ !else
+ MessageBox MB_OK|MB_ICONSTOP "This version of ${PRODUCT_NAME} requires a 64-bit$\r$\n\
+ version of Windows 10 1607 or later."
+ !endif
+ Abort
+ _os_check_pass:
+
${ifnot} ${UAC_IsInnerInstance}
!insertmacro CheckSingleInstance "Setup" "Global" "${SETUP_MUTEX}"
!insertmacro CheckSingleInstance "Application" "Local" "${APP_MUTEX}"
diff --git a/misc/nsis/qutebrowser.nsi b/misc/nsis/qutebrowser.nsi
index 60b174bdd..bd5156e83 100755
--- a/misc/nsis/qutebrowser.nsi
+++ b/misc/nsis/qutebrowser.nsi
@@ -124,13 +124,16 @@ ShowUninstDetails hide
; If not defined, get VERSION from PROGEXE. Set DIST_DIR accordingly.
!ifndef VERSION
- !define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}-${ARCH}"
+ !define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}"
!getdllversion "${DIST_DIR}\${PROGEXE}" expv_
!define VERSION "${expv_1}.${expv_2}.${expv_3}"
!else
- !define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}-${VERSION}-${ARCH}"
+ !define /ifndef DIST_DIR ".\..\..\dist\${PRODUCT_NAME}-${VERSION}"
!endif
+; If not defined, assume Qt6 (requires a more recent windows version)
+!define /ifndef QT5 "False"
+
; Pack the exe header with upx if UPX is defined.
!ifdef UPX
!packhdr "$%TEMP%\exehead.tmp" '"upx" "--ultra-brute" "$%TEMP%\exehead.tmp"'
diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec
index 1eee9161d..ecb9da68e 100644
--- a/misc/qutebrowser.spec
+++ b/misc/qutebrowser.spec
@@ -64,17 +64,17 @@ INFO_PLIST_UPDATES = {
def get_data_files():
data_files = [
- ('../qutebrowser/html', 'html'),
- ('../qutebrowser/img', 'img'),
- ('../qutebrowser/icons', 'icons'),
- ('../qutebrowser/javascript', 'javascript'),
- ('../qutebrowser/html/doc', 'html/doc'),
- ('../qutebrowser/git-commit-id', '.'),
- ('../qutebrowser/config/configdata.yml', 'config'),
+ ('../qutebrowser/html', 'qutebrowser/html'),
+ ('../qutebrowser/img', 'qutebrowser/img'),
+ ('../qutebrowser/icons', 'qutebrowser/icons'),
+ ('../qutebrowser/javascript', 'qutebrowser/javascript'),
+ ('../qutebrowser/html/doc', 'qutebrowser/html/doc'),
+ ('../qutebrowser/git-commit-id', 'qutebrowser/git-commit-id'),
+ ('../qutebrowser/config/configdata.yml', 'qutebrowser/config'),
]
if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
- data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
+ data_files.append(('../qutebrowser/3rdparty/pdfjs', 'qutebrowser/3rdparty/pdfjs'))
else:
print("Warning: excluding pdfjs as it's not present!")
@@ -137,5 +137,4 @@ app = BUNDLE(coll,
name='qutebrowser.app',
icon=icon,
info_plist=INFO_PLIST_UPDATES,
- # https://github.com/pyinstaller/pyinstaller/blob/b78bfe530cdc2904f65ce098bdf2de08c9037abb/PyInstaller/hooks/hook-PyQt5.QtWebEngineWidgets.py#L24
- bundle_identifier='org.qt-project.Qt.QtWebEngineCore')
+ bundle_identifier='org.qutebrowser.qutebrowser')
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index c9590f1c5..61a10c38e 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -12,7 +12,7 @@ github3.py==4.0.1
hunter==3.6.1
idna==3.4
importlib-metadata==6.8.0
-importlib-resources==6.0.0
+importlib-resources==6.0.1
jaraco.classes==3.3.0
jeepney==0.8.0
keyring==24.2.0
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index 1e18a7ab2..1faf875fd 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -2,18 +2,18 @@
chardet==5.2.0
diff-cover==7.7.0
-importlib-resources==6.0.0
+importlib-resources==6.0.1
Jinja2==3.1.2
lxml==4.9.3
MarkupSafe==2.1.3
-mypy==1.4.1
+mypy==1.5.0
mypy-extensions==1.0.0
pluggy==1.2.0
Pygments==2.16.1
PyQt5-stubs==5.15.6.0
tomli==2.0.1
types-colorama==0.4.15.12
-types-docutils==0.20.0.1
+types-docutils==0.20.0.2
types-Pygments==2.16.0.0
types-PyYAML==6.0.12.11
types-setuptools==68.0.0.3
diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt
index 912b38cd3..b112963b0 100644
--- a/misc/requirements/requirements-pyinstaller.txt
+++ b/misc/requirements/requirements-pyinstaller.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
altgraph==0.17.3
-pyinstaller==5.13.0
+pyinstaller @ git+https://github.com/pyinstaller/pyinstaller.git@79f62ef29822169ae00cd4271390d0e3175476ad
pyinstaller-hooks-contrib==2023.6
diff --git a/misc/requirements/requirements-pyinstaller.txt-raw b/misc/requirements/requirements-pyinstaller.txt-raw
index c313980b0..7b4c8c84c 100644
--- a/misc/requirements/requirements-pyinstaller.txt-raw
+++ b/misc/requirements/requirements-pyinstaller.txt-raw
@@ -1 +1 @@
-PyInstaller
+pyinstaller @ git+https://github.com/pyinstaller/pyinstaller.git@79f62ef29822169ae00cd4271390d0e3175476ad
diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt
index f574b4c26..0bed26a8d 100644
--- a/misc/requirements/requirements-pyroma.txt
+++ b/misc/requirements/requirements-pyroma.txt
@@ -11,5 +11,5 @@ pyproject_hooks==1.0.0
pyroma==4.2
requests==2.31.0
tomli==2.0.1
-trove-classifiers==2023.7.6
+trove-classifiers==2023.8.7
urllib3==2.0.4
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index abd6ea727..a51a099c7 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -7,13 +7,13 @@ certifi==2023.7.22
charset-normalizer==3.2.0
cheroot==10.0.0
click==8.1.6
-coverage==7.2.7
+coverage==7.3.0
exceptiongroup==1.1.2
execnet==2.0.2
filelock==3.12.2
Flask==2.3.2
hunter==3.6.1
-hypothesis==6.82.2
+hypothesis==6.82.4
idna==3.4
importlib-metadata==6.8.0
iniconfig==2.0.0
@@ -52,6 +52,6 @@ toml==0.10.2
tomli==2.0.1
typing_extensions==4.7.1
urllib3==2.0.4
-vulture==2.7
+vulture==2.8
Werkzeug==2.3.6
zipp==3.16.2
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index ae8fce6ff..064536480 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -12,6 +12,6 @@ pluggy==1.2.0
pyproject-api==1.5.3
setuptools==68.0.0
tomli==2.0.1
-tox==4.6.4
-virtualenv==20.24.2
+tox==4.8.0
+virtualenv==20.24.3
wheel==0.41.1
diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt
index f72d24a67..8c98a265e 100644
--- a/misc/requirements/requirements-vulture.txt
+++ b/misc/requirements/requirements-vulture.txt
@@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
toml==0.10.2
-vulture==2.7
+vulture==2.8
diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py
index 0c367c6bf..e5466f072 100644
--- a/qutebrowser/api/cmdutils.py
+++ b/qutebrowser/api/cmdutils.py
@@ -101,7 +101,7 @@ class _CmdHandlerType(Protocol):
Below, we cast the decorated function to _CmdHandlerType to make mypy aware of this.
"""
- qute_args: Optional[Dict[str, command.ArgInfo]]
+ qute_args: Optional[Dict[str, 'command.ArgInfo']]
def __call__(self, *args: Any, **kwargs: Any) -> Any:
...
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 94cc53c72..778c248c2 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -346,15 +346,6 @@ def _open_special_pages(args):
True,
'qute://warning/sessions'),
- ('sandboxing-warning-shown',
- (
- hasattr(sys, "frozen") and
- utils.is_mac and
- machinery.IS_QT6 and
- os.environ.get("QTWEBENGINE_DISABLE_SANDBOX") == "1"
- ),
- 'qute://warning/sandboxing'),
-
('qt5-warning-shown',
(
machinery.IS_QT5 and
@@ -570,7 +561,7 @@ class Application(QApplication):
@pyqtSlot(QObject)
def on_focus_object_changed(self, obj):
"""Log when the focus object changed."""
- output = repr(obj)
+ output = qtutils.qobj_repr(obj)
if self._last_focus_object != output:
log.misc.debug("Focus object changed: {}".format(output))
self._last_focus_object = output
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index b6cc303cf..495d4325d 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -30,7 +30,7 @@ if TYPE_CHECKING:
from qutebrowser.keyinput import modeman
from qutebrowser.config import config, websettings
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
- urlutils, message, jinja)
+ urlutils, message, jinja, version)
from qutebrowser.misc import miscwidgets, objects, sessions
from qutebrowser.browser import eventfilter, inspector
from qutebrowser.qt import sip
@@ -1169,6 +1169,23 @@ class AbstractTab(QWidget):
navigation.url.errorString()))
navigation.accepted = False
+ # WORKAROUND for QtWebEngine >= 6.2 not allowing form requests from
+ # qute:// to outside domains.
+ if (
+ self.url() == QUrl("qute://start/") and
+ navigation.navigation_type == navigation.Type.form_submitted and
+ navigation.url.matches(
+ QUrl(config.val.url.searchengines['DEFAULT']),
+ urlutils.FormatOption.REMOVE_QUERY) and
+ objects.backend == usertypes.Backend.QtWebEngine and
+ version.qtwebengine_versions().webengine >= utils.VersionNumber(6, 2)
+ ):
+ log.webview.debug(
+ "Working around qute://start loading issue for "
+ f"{navigation.url.toDisplayString()}")
+ navigation.accepted = False
+ self.load_url(navigation.url)
+
@pyqtSlot(bool)
def _on_load_finished(self, ok: bool) -> None:
assert self._widget is not None
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index 7efb69511..83a846b85 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -1235,21 +1235,31 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@cmdutils.argument('name', completion=miscmodels.quickmark)
- def quickmark_del(self, name=None):
+ def quickmark_del(self, name=None, all_=False):
"""Delete a quickmark.
Args:
name: The name of the quickmark to delete. If not given, delete the
quickmark for the current page (choosing one arbitrarily
if there are more than one).
+ all_: Delete all quickmarks.
"""
quickmark_manager = objreg.get('quickmark-manager')
+
+ if all_:
+ if name is not None:
+ raise cmdutils.CommandError("Cannot specify name and --all")
+ quickmark_manager.clear()
+ message.info("Quickmarks cleared.")
+ return
+
if name is None:
url = self._current_url()
try:
name = quickmark_manager.get_by_qurl(url)
except urlmarks.DoesNotExistError as e:
raise cmdutils.CommandError(str(e))
+
try:
quickmark_manager.delete(name)
except KeyError:
@@ -1320,18 +1330,28 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@cmdutils.argument('url', completion=miscmodels.bookmark)
- def bookmark_del(self, url=None):
+ def bookmark_del(self, url=None, all_=False):
"""Delete a bookmark.
Args:
url: The url of the bookmark to delete. If not given, use the
current page's url.
+ all_: If given, delete all bookmarks.
"""
+ bookmark_manager = objreg.get('bookmark-manager')
+ if all_:
+ if url is not None:
+ raise cmdutils.CommandError("Cannot specify url and --all")
+ bookmark_manager.clear()
+ message.info("Bookmarks cleared.")
+ return
+
if url is None:
url = self._current_url().toString(QUrl.UrlFormattingOption.RemovePassword |
QUrl.ComponentFormattingOption.FullyEncoded)
+
try:
- objreg.get('bookmark-manager').delete(url)
+ bookmark_manager.delete(url)
except KeyError:
raise cmdutils.CommandError("Bookmark '{}' not found!".format(url))
message.info("Removed bookmark {}".format(url))
diff --git a/qutebrowser/browser/eventfilter.py b/qutebrowser/browser/eventfilter.py
index 8dbfbd008..6404608b3 100644
--- a/qutebrowser/browser/eventfilter.py
+++ b/qutebrowser/browser/eventfilter.py
@@ -6,10 +6,12 @@
from qutebrowser.qt import machinery
from qutebrowser.qt.core import QObject, QEvent, Qt, QTimer
+from qutebrowser.qt.widgets import QWidget
from qutebrowser.config import config
-from qutebrowser.utils import log, message, usertypes
+from qutebrowser.utils import log, message, usertypes, qtutils, version, utils
from qutebrowser.keyinput import modeman
+from qutebrowser.misc import objects
class ChildEventFilter(QObject):
@@ -35,17 +37,42 @@ class ChildEventFilter(QObject):
"""Act on ChildAdded events."""
if event.type() == QEvent.Type.ChildAdded:
child = event.child()
- log.misc.debug("{} got new child {}, installing filter"
- .format(obj, child))
+ log.misc.debug(
+ f"{qtutils.qobj_repr(obj)} got new child {qtutils.qobj_repr(child)}, "
+ "installing filter")
# Additional sanity check, but optional
if self._widget is not None:
assert obj is self._widget
+ # WORKAROUND for unknown Qt bug losing focus on child change
+ # Carry on keyboard focus to the new child if:
+ # - 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)
+ 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"
+ ]
+ if (
+ children and
+ objects.backend == usertypes.Backend.QtWebEngine and
+ version.qtwebengine_versions().webengine >=
+ utils.VersionNumber(6, 4)
+ ):
+ log.misc.debug("Focusing new child")
+ child.setFocus()
+
child.installEventFilter(self._filter)
elif event.type() == QEvent.Type.ChildRemoved:
child = event.child()
- log.misc.debug("{}: removed child {}".format(obj, child))
+ log.misc.debug(
+ f"{qtutils.qobj_repr(obj)}: removed child {qtutils.qobj_repr(child)}")
return False
diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py
index dae862b8b..f325ff9e3 100644
--- a/qutebrowser/browser/qutescheme.py
+++ b/qutebrowser/browser/qutescheme.py
@@ -568,9 +568,6 @@ def qute_warning(url: QUrl) -> _HandlerRet:
title='Qt 5.15 sessions warning',
datadir=standarddir.data(),
sep=os.sep)
- elif path == '/sandboxing':
- src = jinja.render('warning-sandboxing.html',
- title='Qt 6 macOS sandboxing warning')
elif path == '/qt5':
is_venv = hasattr(sys, 'real_prefix') or sys.base_prefix != sys.prefix
src = jinja.render('warning-qt5.html',
diff --git a/qutebrowser/browser/urlmarks.py b/qutebrowser/browser/urlmarks.py
index 2a060f5ef..2d2563a1a 100644
--- a/qutebrowser/browser/urlmarks.py
+++ b/qutebrowser/browser/urlmarks.py
@@ -98,6 +98,11 @@ class UrlMarkManager(QObject):
del self.marks[key]
self.changed.emit()
+ def clear(self):
+ """Delete all marks."""
+ self.marks.clear()
+ self.changed.emit()
+
class QuickmarkManager(UrlMarkManager):
diff --git a/qutebrowser/browser/webengine/webengineelem.py b/qutebrowser/browser/webengine/webengineelem.py
index d383c1aa7..20c3a36d4 100644
--- a/qutebrowser/browser/webengine/webengineelem.py
+++ b/qutebrowser/browser/webengine/webengineelem.py
@@ -11,7 +11,7 @@ from qutebrowser.qt.core import QRect, QEventLoop
from qutebrowser.qt.widgets import QApplication
from qutebrowser.qt.webenginecore import QWebEngineSettings
-from qutebrowser.utils import log, javascript, urlutils, usertypes, utils
+from qutebrowser.utils import log, javascript, urlutils, usertypes, utils, version
from qutebrowser.browser import webelem
if TYPE_CHECKING:
@@ -213,6 +213,17 @@ class WebEngineElement(webelem.AbstractWebElement):
return True
if baseurl.scheme() == url.scheme(): # e.g. a qute:// link
return False
+
+ # Qt 6.3+ needs a user interaction to allow navigations from qute:// to
+ # outside qute:// (like e.g. on qute://bookmarks).
+ versions = version.qtwebengine_versions()
+ if (
+ baseurl.scheme() == "qute" and
+ url.scheme() != "qute" and
+ versions.webengine >= utils.VersionNumber(6, 3)
+ ):
+ return True
+
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target: usertypes.ClickTarget) -> None:
diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py
index b5b232c5a..7ccdabc88 100644
--- a/qutebrowser/extensions/loader.py
+++ b/qutebrowser/extensions/loader.py
@@ -6,11 +6,12 @@
import pkgutil
import types
+import sys
import pathlib
import importlib
import argparse
import dataclasses
-from typing import Callable, Iterator, List, Optional, Tuple
+from typing import Callable, Iterator, List, Optional, Set, Tuple
from qutebrowser.qt.core import pyqtSlot
@@ -79,6 +80,14 @@ def load_components(*, skip_hooks: bool = False) -> None:
def walk_components() -> Iterator[ExtensionInfo]:
"""Yield ExtensionInfo objects for all modules."""
+ if hasattr(sys, 'frozen'):
+ yield from _walk_pyinstaller()
+ else:
+ yield from _walk_normal()
+
+
+def _walk_normal() -> Iterator[ExtensionInfo]:
+ """Walk extensions when not using PyInstaller."""
for _finder, name, ispkg in pkgutil.walk_packages(
path=components.__path__,
prefix=components.__name__ + '.',
@@ -93,6 +102,23 @@ def walk_components() -> Iterator[ExtensionInfo]:
yield ExtensionInfo(name=name)
+def _walk_pyinstaller() -> Iterator[ExtensionInfo]:
+ """Walk extensions when using PyInstaller.
+
+ See https://github.com/pyinstaller/pyinstaller/issues/1905
+
+ Inspired by:
+ https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py
+ """
+ toc: Set[str] = set()
+ for importer in pkgutil.iter_importers('qutebrowser'):
+ if hasattr(importer, 'toc'):
+ toc |= importer.toc
+ for name in toc:
+ if name.startswith(components.__name__ + '.'):
+ yield ExtensionInfo(name=name)
+
+
def _get_init_context() -> InitContext:
"""Get an InitContext object."""
return InitContext(data_dir=pathlib.Path(standarddir.data()),
diff --git a/qutebrowser/html/warning-sandboxing.html b/qutebrowser/html/warning-sandboxing.html
deleted file mode 100644
index 186d938e7..000000000
--- a/qutebrowser/html/warning-sandboxing.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% extends "styled.html" %}
-
-{% block content %}
-<h1>{{ title }}</h1>
-<span class="note">Note this warning will only appear once. Use <span class="mono">:open
-qute://warning/sandboxing</span> to show it again at a later time.</span>
-
-<p>
- Due to a <a href="https://github.com/pyinstaller/pyinstaller/pull/6903">PyInstaller issue</a>,
- Chromium's <a href="https://chromium.googlesource.com/chromium/src/+/HEAD/docs/design/sandbox_faq.md">sandboxing</a>
- is currently disabled for macOS builds with Qt 6. This means that there will be no additional layer of protection
- in case of Chromium security bugs. Thus, it's advised to
- <b>not use this build in production</b>. Hopefully, this situation will be
- resolved before the final 3.0.0 release.
-</p>
-{% endblock %}
diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py
index 582a1bf18..f0337ec88 100644
--- a/qutebrowser/keyinput/modeman.py
+++ b/qutebrowser/keyinput/modeman.py
@@ -16,7 +16,7 @@ from qutebrowser.commands import runners
from qutebrowser.keyinput import modeparsers, basekeyparser
from qutebrowser.config import config
from qutebrowser.api import cmdutils
-from qutebrowser.utils import usertypes, log, objreg, utils
+from qutebrowser.utils import usertypes, log, objreg, utils, qtutils
from qutebrowser.browser import hints
from qutebrowser.misc import objects
@@ -308,10 +308,10 @@ class ModeManager(QObject):
focus_widget = objects.qapp.focusWidget()
log.modes.debug("match: {}, forward_unbound_keys: {}, "
"passthrough: {}, is_non_alnum: {}, dry_run: {} "
- "--> filter: {} (focused: {!r})".format(
+ "--> filter: {} (focused: {})".format(
match, forward_unbound_keys,
parser.passthrough, is_non_alnum, dry_run,
- filter_this, focus_widget))
+ filter_this, qtutils.qobj_repr(focus_widget)))
return filter_this
def _handle_keyrelease(self, event: QKeyEvent) -> bool:
diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py
index a576b34ff..fdb085a1f 100644
--- a/qutebrowser/keyinput/modeparsers.py
+++ b/qutebrowser/keyinput/modeparsers.py
@@ -254,8 +254,10 @@ class RegisterKeyParser(CommandKeyParser):
mode: usertypes.KeyMode,
commandrunner: 'runners.CommandRunner',
parent: QObject = None) -> None:
- super().__init__(mode=usertypes.KeyMode.register, win_id=win_id,
- commandrunner=commandrunner, parent=parent,
+ super().__init__(mode=usertypes.KeyMode.register, # type: ignore[arg-type]
+ win_id=win_id,
+ commandrunner=commandrunner,
+ parent=parent,
supports_count=False)
self._register_mode = mode
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 2e90c46c4..c0c7ee2ad 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -396,6 +396,7 @@ class TabBar(QTabBar):
self._win_id = win_id
self._our_style = TabBarStyle()
self.setStyle(self._our_style)
+ self.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.vertical = False
self._auto_hide_timer = QTimer()
self._auto_hide_timer.setSingleShot(True)
diff --git a/qutebrowser/utils/qtutils.py b/qutebrowser/utils/qtutils.py
index 5e7c6d272..ebcd6578f 100644
--- a/qutebrowser/utils/qtutils.py
+++ b/qutebrowser/utils/qtutils.py
@@ -639,6 +639,38 @@ def extract_enum_val(val: Union[sip.simplewrapper, int, enum.Enum]) -> int:
return val
+def qobj_repr(obj: Optional[QObject]) -> str:
+ """Show nicer debug information for a QObject."""
+ py_repr = repr(obj)
+ if obj is None:
+ return py_repr
+
+ try:
+ object_name = obj.objectName()
+ meta_object = obj.metaObject()
+ except AttributeError:
+ # Technically not possible if obj is a QObject, but crashing when trying to get
+ # some debug info isn't helpful.
+ return py_repr
+
+ class_name = "" if meta_object is None else meta_object.className()
+
+ if py_repr.startswith("<") and py_repr.endswith(">"):
+ # With a repr such as <QObject object at 0x...>, we want to end up with:
+ # <QObject object at 0x..., objectName='...'>
+ # But if we have RichRepr() as existing repr, we want:
+ # <RichRepr(), objectName='...'>
+ py_repr = py_repr[1:-1]
+
+ parts = [py_repr]
+ if object_name:
+ parts.append(f"objectName={object_name!r}")
+ if class_name and f".{class_name} object at 0x" not in py_repr:
+ parts.append(f"className={class_name!r}")
+
+ return f"<{', '.join(parts)}>"
+
+
_T = TypeVar("_T")
diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py
index 494f01bff..60d90fd31 100644
--- a/qutebrowser/utils/resources.py
+++ b/qutebrowser/utils/resources.py
@@ -36,11 +36,6 @@ def _path(filename: str) -> _ResourceType:
assert not posixpath.isabs(filename), filename
assert os.path.pardir not in filename.split(posixpath.sep), filename
- if hasattr(sys, 'frozen'):
- # For PyInstaller, where we can't store resource files in a qutebrowser/ folder
- # because the executable is already named "qutebrowser" (at least on macOS).
- return pathlib.Path(sys.executable).parent / filename
-
return importlib_resources.files(qutebrowser) / filename
@contextlib.contextmanager
diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py
index c347ae53b..0b571946d 100644
--- a/qutebrowser/utils/urlutils.py
+++ b/qutebrowser/utils/urlutils.py
@@ -39,6 +39,7 @@ if machinery.IS_QT6:
REMOVE_SCHEME = QUrl.UrlFormattingOption.RemoveScheme
REMOVE_PASSWORD = QUrl.UrlFormattingOption.RemovePassword
+ REMOVE_QUERY = QUrl.UrlFormattingOption.RemoveQuery
else:
UrlFlagsType = Union[
QUrl.FormattingOptions,
@@ -74,6 +75,8 @@ else:
_QtFormattingOptions, QUrl.UrlFormattingOption.RemoveScheme)
REMOVE_PASSWORD = cast(
_QtFormattingOptions, QUrl.UrlFormattingOption.RemovePassword)
+ REMOVE_QUERY = cast(
+ _QtFormattingOptions, QUrl.UrlFormattingOption.RemoveQuery)
# URL schemes supported by QtWebEngine
diff --git a/requirements.txt b/requirements.txt
index f10ab6f9b..01f6236c7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
adblock==0.6.0
colorama==0.4.6
-importlib-resources==6.0.0 ; python_version=="3.8.*"
+importlib-resources==6.0.1 ; python_version=="3.8.*"
Jinja2==3.1.2
MarkupSafe==2.1.3
Pygments==2.16.1
diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py
index b55624b8d..55b3f5f1c 100755
--- a/scripts/dev/build_release.py
+++ b/scripts/dev/build_release.py
@@ -171,9 +171,6 @@ def smoke_test(executable: pathlib.Path, debug: bool, qt5: bool) -> None:
r'[0-9:]* WARNING: Qt WebEngine resources not found at .*',
(r'[0-9:]* WARNING: Installed Qt WebEngine locales directory not found at '
r'location /qtwebengine_locales\. Trying application directory\.\.\.'),
-
- # https://github.com/pyinstaller/pyinstaller/pull/6903
- r"[0-9:]* INFO: Sandboxing disabled by user\.",
])
elif IS_WINDOWS:
stderr_whitelist.extend([
@@ -245,66 +242,11 @@ def verify_windows_exe(exe_path: pathlib.Path) -> None:
assert pe.verify_checksum()
-def patch_mac_app(qt5: bool) -> None:
- """Patch .app to save some space and make it signable."""
- dist_path = pathlib.Path('dist')
- ver = '5' if qt5 else '6'
- app_path = dist_path / 'qutebrowser.app'
-
- contents_path = app_path / 'Contents'
- macos_path = contents_path / 'MacOS'
- resources_path = contents_path / 'Resources'
- pyqt_path = macos_path / f'PyQt{ver}'
-
- # Replace some duplicate files by symlinks
- framework_path = pyqt_path / f'Qt{ver}' / 'lib' / 'QtWebEngineCore.framework'
-
- framework_resource_path = framework_path / 'Resources'
- for file_path in framework_resource_path.iterdir():
- target = pathlib.Path(*[os.pardir] * 5, file_path.name)
- if file_path.is_dir():
- shutil.rmtree(file_path)
- else:
- file_path.unlink()
- file_path.symlink_to(target)
-
- if not qt5:
- # Symlinking QtWebEngineCore.framework does not seem to work with Qt 6.
- # Also, the symlinking/moving before signing doesn't seem to be required.
- return
-
- core_lib = framework_path / 'Versions' / '5' / 'QtWebEngineCore'
- core_lib.unlink()
- core_target = pathlib.Path(*[os.pardir] * 7, 'MacOS', 'QtWebEngineCore')
- core_lib.symlink_to(core_target)
-
- # Move stuff around to make things signable on macOS
- # See https://github.com/pyinstaller/pyinstaller/issues/6612
- pyqt_path_dest = resources_path / pyqt_path.name
- shutil.move(pyqt_path, pyqt_path_dest)
- pyqt_path_target = pathlib.Path("..") / pyqt_path_dest.relative_to(contents_path)
- pyqt_path.symlink_to(pyqt_path_target)
-
- for path in macos_path.glob("Qt*"):
- link_path = resources_path / path.name
- target_path = pathlib.Path("..") / path.relative_to(contents_path)
- link_path.symlink_to(target_path)
-
-
-def sign_mac_app() -> None:
+def verify_mac_app() -> None:
"""Re-sign and verify the Mac .app."""
app_path = pathlib.Path('dist') / 'qutebrowser.app'
subprocess.run([
'codesign',
- '-s', '-',
- '--force',
- '--timestamp',
- '--deep',
- '--verbose',
- app_path,
- ], check=True)
- subprocess.run([
- 'codesign',
'--verify',
'--strict',
'--deep',
@@ -341,10 +283,8 @@ def build_mac(
utils.print_title("Building .app via pyinstaller")
call_tox(f'pyinstaller{"-qt5" if qt5 else ""}', '-r', debug=debug)
- utils.print_title("Patching .app")
- patch_mac_app(qt5=qt5)
- utils.print_title("Re-signing .app")
- sign_mac_app()
+ utils.print_title("Verifying .app")
+ verify_mac_app()
dist_path = pathlib.Path("dist")
@@ -483,6 +423,7 @@ def _package_windows_single(
utils.print_subtitle("Building installer...")
subprocess.run(['makensis.exe',
f'/DVERSION={qutebrowser.__version__}',
+ f'/DQT5={qt5}',
'misc/nsis/qutebrowser.nsi'], check=True)
name_parts = [
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index 24122eb30..b54b22c9a 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -622,3 +622,11 @@ Feature: Various utility commands.
When I open data/invalid_resource.html in a new tab
Then "Ignoring invalid * URL: Invalid hostname (contains invalid characters); *" should be logged
And no crash should happen
+
+ Scenario: Keyboard focus after cross-origin navigation
+ When I turn on scroll logging
+ And I open qute://gpl in a new tab
+ And I run :tab-only
+ And I open data/scroll/simple.html
+ And I run :fake-key "<Space>"
+ Then the page should be scrolled vertically
diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature
index 85f68661a..e99b9af9d 100644
--- a/tests/end2end/features/qutescheme.feature
+++ b/tests/end2end/features/qutescheme.feature
@@ -300,3 +300,16 @@ Feature: Special qute:// pages
Scenario: Open qute://gpl
When I open qute://gpl
Then the page should contain the plaintext "GNU GENERAL PUBLIC LICENSE"
+
+ # qute://start
+
+ # QtWebKit doesn't support formaction
+ @qtwebkit_skip
+ Scenario: Searching on qute://start
+ When I set url.searchengines to {"DEFAULT": "http://localhost:(port)/data/title.html?q={}"}
+ And I open qute://start
+ And I run :click-element id search-field
+ And I wait for "Entering mode KeyMode.insert *" in the log
+ And I press the keys "test"
+ And I press the key "<Enter>"
+ Then data/title.html?q=test should be loaded
diff --git a/tests/end2end/features/test_misc_bdd.py b/tests/end2end/features/test_misc_bdd.py
index 7f899b6de..570afee64 100644
--- a/tests/end2end/features/test_misc_bdd.py
+++ b/tests/end2end/features/test_misc_bdd.py
@@ -16,6 +16,11 @@ def load_iframe(quteproc, server, ssl_server):
msg.expected = True
+@bdd.when("I turn on scroll logging")
+def turn_on_scroll_logging(quteproc):
+ quteproc.turn_on_scroll_logging(no_scroll_filtering=True)
+
+
@bdd.then(bdd.parsers.parse('the PDF {filename} should exist in the tmpdir'))
def pdf_exists(quteproc, tmpdir, filename):
path = tmpdir / filename
diff --git a/tests/end2end/features/test_urlmarks_bdd.py b/tests/end2end/features/test_urlmarks_bdd.py
index 1b21098cd..2a7b65d8c 100644
--- a/tests/end2end/features/test_urlmarks_bdd.py
+++ b/tests/end2end/features/test_urlmarks_bdd.py
@@ -4,6 +4,7 @@
import os.path
+import pytest
import pytest_bdd as bdd
from helpers import testutils
@@ -11,6 +12,16 @@ from helpers import testutils
bdd.scenarios('urlmarks.feature')
+@pytest.fixture(autouse=True)
+def clear_marks(quteproc):
+ """Clear all existing marks between tests."""
+ yield
+ quteproc.send_cmd(':quickmark-del --all')
+ quteproc.wait_for(message="Quickmarks cleared.")
+ quteproc.send_cmd(':bookmark-del --all')
+ quteproc.wait_for(message="Bookmarks cleared.")
+
+
def _check_marks(quteproc, quickmarks, expected, contains):
"""Make sure the given line does (not) exist in the bookmarks.
diff --git a/tests/end2end/features/urlmarks.feature b/tests/end2end/features/urlmarks.feature
index 00fd14fb6..05d07ae98 100644
--- a/tests/end2end/features/urlmarks.feature
+++ b/tests/end2end/features/urlmarks.feature
@@ -84,7 +84,24 @@ Feature: quickmarks and bookmarks
When I open data/numbers/5.txt
And I run :bookmark-add
And I run :bookmark-del http://localhost:(port)/data/numbers/5.txt
- Then the bookmark file should not contain "http://localhost:*/data/numbers/5.txt "
+ Then the bookmark file should not contain "http://localhost:*/data/numbers/5.txt *"
+
+ Scenario: Deleting all bookmarks
+ When I open data/numbers/1.txt
+ And I run :bookmark-add
+ And I open data/numbers/2.txt
+ And I run :bookmark-add
+ And I run :bookmark-del --all
+ Then the message "Bookmarks cleared." should be shown
+ And the bookmark file should not contain "http://localhost:*/data/numbers/1.txt *"
+ And the bookmark file should not contain "http://localhost:*/data/numbers/2.txt *"
+
+ Scenario: Deleting all bookmarks with url
+ When I open data/numbers/1.txt
+ And I run :bookmark-add
+ And I run :bookmark-del --all https://example.org
+ Then the error "Cannot specify url and --all" should be shown
+ And the bookmark file should contain "http://localhost:*/data/numbers/1.txt *"
Scenario: Deleting the current page's bookmark if it doesn't exist
When I open data/hello.txt
@@ -95,18 +112,18 @@ Feature: quickmarks and bookmarks
When I open data/numbers/6.txt
And I run :bookmark-add
And I run :bookmark-del
- Then the bookmark file should not contain "http://localhost:*/data/numbers/6.txt "
+ Then the bookmark file should not contain "http://localhost:*/data/numbers/6.txt *"
Scenario: Toggling a bookmark
When I open data/numbers/7.txt
And I run :bookmark-add
And I run :bookmark-add --toggle
- Then the bookmark file should not contain "http://localhost:*/data/numbers/7.txt "
+ Then the bookmark file should not contain "http://localhost:*/data/numbers/7.txt *"
Scenario: Loading a bookmark with --delete
When I run :bookmark-add http://localhost:(port)/data/numbers/8.txt "eight"
And I run :bookmark-load -d http://localhost:(port)/data/numbers/8.txt
- Then the bookmark file should not contain "http://localhost:*/data/numbers/8.txt "
+ Then the bookmark file should not contain "http://localhost:*/data/numbers/8.txt *"
## quickmarks
@@ -210,6 +227,20 @@ Feature: quickmarks and bookmarks
And I run :quickmark-del eighteen
Then the quickmark file should not contain "eighteen http://localhost:*/data/numbers/18.txt "
+ Scenario: Deleting all quickmarks
+ When I run :quickmark-add http://localhost:(port)/data/numbers/1.txt one
+ When I run :quickmark-add http://localhost:(port)/data/numbers/2.txt two
+ And I run :quickmark-del --all
+ Then the message "Quickmarks cleared." should be shown
+ And the quickmark file should not contain "one http://localhost:*/data/numbers/1.txt"
+ And the quickmark file should not contain "two http://localhost:*/data/numbers/2.txt"
+
+ Scenario: Deleting all quickmarks with name
+ When I run :quickmark-add http://localhost:(port)/data/numbers/1.txt one
+ And I run :quickmark-del --all invalid
+ Then the error "Cannot specify name and --all" should be shown
+ And the quickmark file should contain "one http://localhost:*/data/numbers/1.txt"
+
Scenario: Deleting the current page's quickmark if it has none
When I open data/hello.txt
And I run :quickmark-del
@@ -233,3 +264,10 @@ Feature: quickmarks and bookmarks
And I run :bookmark-add
And I open qute://bookmarks
Then the page should contain the plaintext "Test title"
+
+ Scenario: Following a bookmark
+ When I open data/numbers/1.txt in a new tab
+ And I run :bookmark-add
+ And I open qute://bookmarks
+ And I hint with args "links current" and follow a
+ Then data/numbers/1.txt should be loaded
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index a10fd5414..a3929ed7e 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -57,7 +57,7 @@ def is_ignored_lowlevel_message(message):
# 'style-src' was not explicitly set, so 'default-src' is used as a
# fallback.
# INVALID: ", source: userscript:_qute_stylesheet (65)
- '", source: userscript:_qute_stylesheet (65)',
+ '", source: userscript:_qute_stylesheet (*)',
# Randomly started showing up on Qt 5.15.2
'QPaintDevice: Cannot destroy paint device that is being painted',
diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py
index 7ad898d17..f716c7443 100644
--- a/tests/end2end/fixtures/webserver_sub.py
+++ b/tests/end2end/fixtures/webserver_sub.py
@@ -117,7 +117,7 @@ def redirect_to():
# header to the exact string supplied.
response = app.make_response('')
response.status_code = HTTPStatus.FOUND
- response.headers['Location'] = flask.request.args['url'].encode('utf-8')
+ response.headers['Location'] = flask.request.args['url']
return response
diff --git a/tests/unit/extensions/test_loader.py b/tests/unit/extensions/test_loader.py
index a2a99f305..fd15130ba 100644
--- a/tests/unit/extensions/test_loader.py
+++ b/tests/unit/extensions/test_loader.py
@@ -20,10 +20,16 @@ def test_on_walk_error():
def test_walk_normal():
- names = [info.name for info in loader.walk_components()]
+ names = [info.name for info in loader._walk_normal()]
assert 'qutebrowser.components.scrollcommands' in names
+def test_walk_pyinstaller():
+ # We can't test whether we get something back without being frozen by
+ # PyInstaller, but at least we can test that we don't crash.
+ list(loader._walk_pyinstaller())
+
+
def test_load_component(monkeypatch):
monkeypatch.setattr(objects, 'commands', {})
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index 5b173882b..541f4e4fe 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -13,8 +13,9 @@ import unittest.mock
import pytest
from qutebrowser.qt.core import (QDataStream, QPoint, QUrl, QByteArray, QIODevice,
- QTimer, QBuffer, QFile, QProcess, QFileDevice, QLibraryInfo, Qt)
+ QTimer, QBuffer, QFile, QProcess, QFileDevice, QLibraryInfo, Qt, QObject)
from qutebrowser.qt.gui import QColor
+from qutebrowser.qt import sip
from qutebrowser.utils import qtutils, utils, usertypes
import overflow_test_cases
@@ -1051,3 +1052,50 @@ class TestLibraryPath:
def test_extract_enum_val():
value = qtutils.extract_enum_val(Qt.KeyboardModifier.ShiftModifier)
assert value == 0x02000000
+
+
+class TestQObjRepr:
+
+ @pytest.mark.parametrize("obj", [QObject(), object(), None])
+ def test_simple(self, obj):
+ assert qtutils.qobj_repr(obj) == repr(obj)
+
+ def _py_repr(self, obj):
+ """Get the original repr of an object, with <> stripped off.
+
+ We do this in code instead of recreating it in tests because of output
+ differences between PyQt5/PyQt6 and between operating systems.
+ """
+ r = repr(obj)
+ if r.startswith("<") and r.endswith(">"):
+ return r[1:-1]
+ return r
+
+ def test_object_name(self):
+ obj = QObject()
+ obj.setObjectName("Tux")
+ expected = f"<{self._py_repr(obj)}, objectName='Tux'>"
+ assert qtutils.qobj_repr(obj) == expected
+
+ def test_class_name(self):
+ obj = QTimer()
+ hidden = sip.cast(obj, QObject)
+ expected = f"<{self._py_repr(hidden)}, className='QTimer'>"
+ assert qtutils.qobj_repr(hidden) == expected
+
+ def test_both(self):
+ obj = QTimer()
+ obj.setObjectName("Pomodoro")
+ hidden = sip.cast(obj, QObject)
+ expected = f"<{self._py_repr(hidden)}, objectName='Pomodoro', className='QTimer'>"
+ assert qtutils.qobj_repr(hidden) == expected
+
+ def test_rich_repr(self):
+ class RichRepr(QObject):
+ def __repr__(self):
+ return "RichRepr()"
+
+ obj = RichRepr()
+ assert repr(obj) == "RichRepr()" # sanity check
+ expected = "<RichRepr(), className='RichRepr'>"
+ assert qtutils.qobj_repr(obj) == expected
diff --git a/tox.ini b/tox.ini
index 541effc2c..cd26e19a9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -61,13 +61,17 @@ setenv =
PYTEST_QT_API=pyqt5
QUTE_QT_WRAPPER=PyQt5
-[testenv:bleeding]
+[testenv:bleeding{,-qt5}]
basepython = {env:PYTHON:python3}
+# Override default PyQt6 from [testenv]
setenv =
- PYTEST_QT_API=pyqt5
+ qt5: PYTEST_QT_API=pyqt5
+ qt5: QUTE_QT_WRAPPER=PyQt5
pip_pre = true
deps = -r{toxinidir}/misc/requirements/requirements-tests-bleeding.txt
-commands_pre = pip install --index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt5 PyQtWebEngine
+commands_pre =
+ qt5: pip install --index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt5 PyQtWebEngine
+ !qt5: pip install --index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt6 PyQt6-WebEngine
commands = {envpython} -bb -m pytest {posargs:tests}
# other envs
@@ -187,6 +191,7 @@ passenv =
APPDATA
HOME
PYINSTALLER_DEBUG
+ PYINSTALLER_COMPILE_BOOTLOADER
setenv =
qt5: PYINSTALLER_QT5=true
deps =
@@ -268,6 +273,7 @@ passenv = *
# Override default PyQt6 from [testenv]
setenv =
qt5: QUTE_QT_WRAPPER=PyQt5
+ PYINSTALLER_COMPILE_BOOTLOADER=true
usedevelop = true
deps =
-r{toxinidir}/requirements.txt