summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoofar <toofar@spalge.com>2023-08-12 13:49:01 +1200
committertoofar <toofar@spalge.com>2023-08-12 13:49:01 +1200
commit0f2d34623cb6194846db70c8549a066e9a5bab17 (patch)
treed5387b4913c91c05c6200c8b21c9c0702f7b9ea3
parentb018f3f081db60029be35ee800170e3c3a32d3e7 (diff)
parentfc843f39440e10f918d57b86c7043658a48d7366 (diff)
downloadqutebrowser-0f2d34623cb6194846db70c8549a066e9a5bab17.tar.gz
qutebrowser-0f2d34623cb6194846db70c8549a066e9a5bab17.zip
Merge remote-tracking branch 'upstream/main' into feat/mac_sandbox_pre_release_pyinstaller
Only conflict was the removal of support for 32bit builds in build_release.py
-rw-r--r--.github/workflows/ci.yml1
-rw-r--r--.github/workflows/nightly.yml25
-rw-r--r--README.asciidoc9
-rw-r--r--doc/changelog.asciidoc10
-rw-r--r--doc/help/commands.asciidoc10
-rw-r--r--misc/requirements/requirements-dev.txt10
-rw-r--r--misc/requirements/requirements-flake8.txt6
-rw-r--r--misc/requirements/requirements-mypy.txt6
-rw-r--r--misc/requirements/requirements-pylint.txt8
-rw-r--r--misc/requirements/requirements-pyroma.txt2
-rw-r--r--misc/requirements/requirements-sphinx.txt4
-rw-r--r--misc/requirements/requirements-tests.txt8
-rw-r--r--misc/requirements/requirements-tox.txt8
-rw-r--r--misc/requirements/requirements-yamllint.txt2
-rw-r--r--qutebrowser/api/cmdutils.py31
-rw-r--r--qutebrowser/browser/browsertab.py19
-rw-r--r--qutebrowser/browser/commands.py26
-rw-r--r--qutebrowser/browser/urlmarks.py5
-rw-r--r--qutebrowser/browser/webengine/interceptor.py7
-rw-r--r--qutebrowser/browser/webengine/notification.py14
-rw-r--r--qutebrowser/browser/webengine/webengineelem.py13
-rw-r--r--qutebrowser/mainwindow/tabbedbrowser.py2
-rw-r--r--qutebrowser/utils/urlutils.py3
-rw-r--r--qutebrowser/utils/version.py7
-rw-r--r--requirements.txt2
-rwxr-xr-xscripts/dev/build_release.py84
-rw-r--r--tests/end2end/features/qutescheme.feature11
-rw-r--r--tests/end2end/features/test_urlmarks_bdd.py9
-rw-r--r--tests/end2end/features/urlmarks.feature38
-rw-r--r--tests/end2end/test_invocations.py5
-rw-r--r--tests/helpers/testutils.py2
-rw-r--r--tests/unit/browser/webengine/test_webengineinterceptor.py97
-rw-r--r--tests/unit/utils/test_version.py8
-rw-r--r--tox.ini11
34 files changed, 358 insertions, 145 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a1612a4eb..14d642491 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,6 +33,7 @@ jobs:
args: "-f gcc" # For problem matchers
- testenv: yamllint
- testenv: actionlint
+ - testenv: package
steps:
- uses: actions/checkout@v3
with:
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 2502d017b..18c12e053 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -19,47 +19,34 @@ jobs:
toxenv: build-release-qt5
name: qt5-macos
- os: windows-2019
- args: --64bit
branch: main
toxenv: build-release-qt5
- name: qt5-windows-64bit
- - os: windows-2019
- args: --32bit
- branch: main
- toxenv: build-release-qt5
- name: qt5-windows-32bit
-
+ name: qt5-windows
- os: macos-11
args: --debug
branch: main
toxenv: build-release-qt5
name: qt5-macos-debug
- os: windows-2019
- args: --64bit --debug
- branch: main
- toxenv: build-release-qt5
- name: qt5-windows-64bit-debug
- - os: windows-2019
- args: --32bit --debug
+ args: --debug
branch: main
toxenv: build-release-qt5
- name: qt5-windows-32bit-debug
+ name: qt5-windows-debug
- os: macos-11
toxenv: build-release
name: macos
- os: windows-2019
- args: --64bit
toxenv: build-release
- name: windows-64bit
+ name: windows
- os: macos-11
args: --debug
toxenv: build-release
name: macos-debug
- os: windows-2019
- args: --64bit --debug
+ args: --debug
toxenv: build-release
- name: windows-64bit-debug
+ name: windows-debug
runs-on: "${{ matrix.os }}"
timeout-minutes: 45
steps:
diff --git a/README.asciidoc b/README.asciidoc
index 910a6b987..2b6bdfdd6 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -220,6 +220,7 @@ Active
* https://nyxt.atlas.engineer/[Nyxt browser] (formerly "Next browser", Lisp, Emacs-like but also offers Vim bindings, QtWebEngine or GTK+/WebKit2 - note there was a https://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution in 2019] which was handled quite badly)
* https://vieb.dev/[Vieb] (JavaScript, Electron)
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
+* https://github.com/jun7/wyeb[wyeb] (C, GTK+ with WebKit2)
* Chrome/Chromium addons:
https://vimium.github.io/[Vimium]
* Firefox addons (based on WebExtensions):
@@ -227,9 +228,8 @@ Active
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF]
* Addons for Firefox and Chrome:
https://github.com/brookhong/Surfingkeys[Surfingkeys],
- https://lydell.github.io/LinkHints/[Link Hints] (hinting only)
-* Addons for Safari:
- https://televator.net/vimari/[Vimari]
+ https://lydell.github.io/LinkHints/[Link Hints] (hinting only),
+ https://github.com/ueokande/vimmatic[Vimmatic]
Inactive
~~~~~~~~
@@ -246,7 +246,6 @@ main inspiration for qutebrowser)
* https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
* https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1)
* https://github.com/linkdd/cream-browser[Cream Browser] (C, GTK+ with WebKit1)
-* https://github.com/jun7/wyeb[wyeb] (C, GTK+ with WebKit2)
* Firefox addons (not based on WebExtensions or no recent activity):
http://www.vimperator.org/[Vimperator],
http://bug.5digits.org/pentadactyl/index[Pentadactyl],
@@ -263,6 +262,8 @@ main inspiration for qutebrowser)
https://github.com/1995eaton/chromium-vim[cVim],
https://github.com/dcchambers/vb4c[vb4c] (fork of cVim, https://github.com/dcchambers/vb4c/issues/23#issuecomment-810694017[unmaintained]),
https://glee.github.io/[GleeBox]
+* Addons for Safari:
+ https://televator.net/vimari/[Vimari]
License
-------
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index 03dbbeeae..5deb381f7 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
~~~~~~~
@@ -202,8 +204,16 @@ Fixed
- Crash when using QtWebKit with PAC and the file has an invalid encoding.
- Crash with the "tiramisu" notification server.
- Crash when the "herbe" notification presenter doesn't start correctly.
+- Crash when no notification server is installed/available.
+- Warning with recent versions of the "deadd" (aka "linux notification center") notification server.
- Crash when using `:print --pdf` with a directory where its parent directory
did not exist.
+- The `PyQt{5,6}.sip` version is now shown correctly in the :version|--version
+ output. Previously that showed the version from the standalone `sip` module
+ which was only set for PyQt5. (#7805)
+- When a `config.py` calls `.redirect()` via a request interceptor (which is
+ unsupported) and supplies an invalid redirect target URL, an exception is now
+ raised for the `.redirect()` call instead of later inside qutebrowser.
[[v2.5.4]]
v2.5.4 (2023-03-13)
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 7f0abc71d..6577a9ddf 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.
@@ -998,7 +1001,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.
@@ -1007,6 +1010,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/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 6655cbfde..c9590f1c5 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -6,7 +6,7 @@ bump2version==1.0.1
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==3.2.0
-cryptography==41.0.2
+cryptography==41.0.3
docutils==0.20.1
github3.py==4.0.1
hunter==3.6.1
@@ -19,12 +19,12 @@ keyring==24.2.0
manhole==1.8.0
markdown-it-py==3.0.0
mdurl==0.1.2
-more-itertools==9.1.0
+more-itertools==10.1.0
packaging==23.1
pkginfo==1.9.6
ply==3.11
pycparser==2.21
-Pygments==2.15.1
+Pygments==2.16.1
PyJWT==2.8.0
Pympler==1.0.1
pyproject_hooks==1.0.0
@@ -34,9 +34,9 @@ readme-renderer==40.0
requests==2.31.0
requests-toolbelt==1.0.0
rfc3986==2.0.0
-rich==13.4.2
+rich==13.5.2
SecretStorage==3.3.3
-sip==6.7.10
+sip==6.7.11
six==1.16.0
tomli==2.0.1
twine==4.0.2
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index 685542224..e16d6860f 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==23.1.0
-flake8==6.0.0
+flake8==6.1.0
flake8-bugbear==23.7.10
flake8-builtins==2.1.0
flake8-comprehensions==3.14.0
@@ -16,8 +16,8 @@ flake8-tidy-imports==4.10.0
flake8-tuple==0.4.1
mccabe==0.7.0
pep8-naming==0.13.3
-pycodestyle==2.10.0
+pycodestyle==2.11.0
pydocstyle==6.3.0
-pyflakes==3.0.1
+pyflakes==3.1.0
six==1.16.0
snowballstemmer==2.2.0
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index a47f25d3f..1e18a7ab2 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -1,6 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-chardet==5.1.0
+chardet==5.2.0
diff-cover==7.7.0
importlib-resources==6.0.0
Jinja2==3.1.2
@@ -9,12 +9,12 @@ MarkupSafe==2.1.3
mypy==1.4.1
mypy-extensions==1.0.0
pluggy==1.2.0
-Pygments==2.15.1
+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-Pygments==2.15.0.2
+types-Pygments==2.16.0.0
types-PyYAML==6.0.12.11
types-setuptools==68.0.0.3
typing_extensions==4.7.1
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index 62d7696eb..cd09e4bf2 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -4,7 +4,7 @@ astroid==2.15.6
certifi==2023.7.22
cffi==1.15.1
charset-normalizer==3.2.0
-cryptography==41.0.2
+cryptography==41.0.3
dill==0.3.7
github3.py==4.0.1
idna==3.4
@@ -12,16 +12,16 @@ isort==5.12.0
lazy-object-proxy==1.9.0
mccabe==0.7.0
pefile==2023.2.7
-platformdirs==3.9.1
+platformdirs==3.10.0
pycparser==2.21
PyJWT==2.8.0
-pylint==2.17.4
+pylint==2.17.5
python-dateutil==2.8.2
./scripts/dev/pylint_checkers
requests==2.31.0
six==1.16.0
tomli==2.0.1
-tomlkit==0.11.8
+tomlkit==0.12.1
typing_extensions==4.7.1
uritemplate==4.1.1
# urllib3==2.0.4
diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt
index 50078baeb..f574b4c26 100644
--- a/misc/requirements/requirements-pyroma.txt
+++ b/misc/requirements/requirements-pyroma.txt
@@ -6,7 +6,7 @@ charset-normalizer==3.2.0
docutils==0.20.1
idna==3.4
packaging==23.1
-Pygments==2.15.1
+Pygments==2.16.1
pyproject_hooks==1.0.0
pyroma==4.2
requests==2.31.0
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 35b0e6257..3557eab6c 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -11,11 +11,11 @@ importlib-metadata==6.8.0
Jinja2==3.1.2
MarkupSafe==2.1.3
packaging==23.1
-Pygments==2.15.1
+Pygments==2.16.1
pytz==2023.3
requests==2.31.0
snowballstemmer==2.2.0
-Sphinx==7.0.1
+Sphinx==7.1.2
sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.1
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 9b99a577a..abd6ea727 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -13,23 +13,23 @@ execnet==2.0.2
filelock==3.12.2
Flask==2.3.2
hunter==3.6.1
-hypothesis==6.82.0
+hypothesis==6.82.2
idna==3.4
importlib-metadata==6.8.0
iniconfig==2.0.0
itsdangerous==2.1.2
-jaraco.functools==3.8.0
+jaraco.functools==3.8.1
# Jinja2==3.1.2
Mako==1.2.4
manhole==1.8.0
# MarkupSafe==2.1.3
-more-itertools==9.1.0
+more-itertools==10.1.0
packaging==23.1
parse==1.19.1
parse-type==0.6.2
pluggy==1.2.0
py-cpuinfo==9.0.0
-Pygments==2.15.1
+Pygments==2.16.1
pytest==7.4.0
pytest-bdd==6.1.1
pytest-benchmark==4.0.0
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index a4c7c4948..ae8fce6ff 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -1,17 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
cachetools==5.3.1
-chardet==5.1.0
+chardet==5.2.0
colorama==0.4.6
distlib==0.3.7
filelock==3.12.2
packaging==23.1
pip==23.2.1
-platformdirs==3.9.1
+platformdirs==3.10.0
pluggy==1.2.0
pyproject-api==1.5.3
setuptools==68.0.0
tomli==2.0.1
tox==4.6.4
-virtualenv==20.24.1
-wheel==0.41.0
+virtualenv==20.24.2
+wheel==0.41.1
diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt
index a35c0ff58..fd9ea256f 100644
--- a/misc/requirements/requirements-yamllint.txt
+++ b/misc/requirements/requirements-yamllint.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-pathspec==0.11.1
+pathspec==0.11.2
PyYAML==6.0.1
yamllint==1.32.0
diff --git a/qutebrowser/api/cmdutils.py b/qutebrowser/api/cmdutils.py
index 067ebca92..0c367c6bf 100644
--- a/qutebrowser/api/cmdutils.py
+++ b/qutebrowser/api/cmdutils.py
@@ -35,7 +35,7 @@ Possible values:
import inspect
-from typing import Any, Callable, Iterable
+from typing import Any, Callable, Iterable, Protocol, Optional, Dict, cast
from qutebrowser.utils import qtutils
from qutebrowser.commands import command, cmdexc
@@ -90,7 +90,21 @@ def check_exclusive(flags: Iterable[bool], names: Iterable[str]) -> None:
raise CommandError("Only one of {} can be given!".format(argstr))
-_CmdHandlerType = Callable[..., Any]
+_CmdHandlerFunc = Callable[..., Any]
+
+
+class _CmdHandlerType(Protocol):
+
+ """A qutebrowser command function, which had qute_args patched on it.
+
+ Applying @cmdutils.argument to a function will patch it with a qute_args attribute.
+ Below, we cast the decorated function to _CmdHandlerType to make mypy aware of this.
+ """
+
+ qute_args: Optional[Dict[str, command.ArgInfo]]
+
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
+ ...
class register: # noqa: N801,N806 pylint: disable=invalid-name
@@ -118,7 +132,7 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name
# The arguments to pass to Command.
self._kwargs = kwargs
- def __call__(self, func: _CmdHandlerType) -> _CmdHandlerType:
+ def __call__(self, func: _CmdHandlerFunc) -> _CmdHandlerType:
"""Register the command before running the function.
Gets called when a function should be decorated.
@@ -158,7 +172,8 @@ class register: # noqa: N801,N806 pylint: disable=invalid-name
# This is checked by future @cmdutils.argument calls so they fail
# (as they'd be silently ignored otherwise)
- func.qute_args = None # type: ignore[attr-defined]
+ func = cast(_CmdHandlerType, func)
+ func.qute_args = None
return func
@@ -210,19 +225,21 @@ class argument: # noqa: N801,N806 pylint: disable=invalid-name
self._argname = argname # The name of the argument to handle.
self._kwargs = kwargs # Valid ArgInfo members.
- def __call__(self, func: _CmdHandlerType) -> _CmdHandlerType:
+ def __call__(self, func: _CmdHandlerFunc) -> _CmdHandlerType:
funcname = func.__name__
if self._argname not in inspect.signature(func).parameters:
raise ValueError("{} has no argument {}!".format(funcname,
self._argname))
+
+ func = cast(_CmdHandlerType, func)
if not hasattr(func, 'qute_args'):
- func.qute_args = {} # type: ignore[attr-defined]
+ func.qute_args = {}
elif func.qute_args is None:
raise ValueError("@cmdutils.argument got called above (after) "
"@cmdutils.register for {}!".format(funcname))
arginfo = command.ArgInfo(**self._kwargs)
- func.qute_args[self._argname] = arginfo # type: ignore[attr-defined]
+ func.qute_args[self._argname] = arginfo
return func
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index b6cc303cf..442628717 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.3 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, 3)
+ ):
+ 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/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/interceptor.py b/qutebrowser/browser/webengine/interceptor.py
index ac0795803..161f5ffab 100644
--- a/qutebrowser/browser/webengine/interceptor.py
+++ b/qutebrowser/browser/webengine/interceptor.py
@@ -10,7 +10,7 @@ from qutebrowser.qt.webenginecore import (QWebEngineUrlRequestInterceptor,
from qutebrowser.config import websettings, config
from qutebrowser.browser import shared
-from qutebrowser.utils import debug, log
+from qutebrowser.utils import debug, log, qtutils
from qutebrowser.extensions import interceptors
from qutebrowser.misc import objects
@@ -35,6 +35,11 @@ class WebEngineRequest(interceptors.Request):
if self._webengine_info is None:
raise interceptors.RedirectException("Request improperly initialized.")
+ try:
+ qtutils.ensure_valid(url)
+ except qtutils.QtValueError as e:
+ raise interceptors.RedirectException(f"Redirect to invalid URL: {e}")
+
# Redirecting a request that contains payload data is not allowed.
# To be safe, abort on any request not in a whitelist.
verb = self._webengine_info.requestMethod()
diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py
index 2edb2d538..e8b2e27f1 100644
--- a/qutebrowser/browser/webengine/notification.py
+++ b/qutebrowser/browser/webengine/notification.py
@@ -113,6 +113,9 @@ class DBusError(Error):
# https://crashes.qutebrowser.org/view/de62220a
# after "Notification daemon did quit!"
"org.freedesktop.DBus.Error.UnknownObject",
+
+ # notmuch-sha1-ef7b6e9e79e5f2f6cba90224122288895c1fe0d8
+ "org.freedesktop.DBus.Error.ServiceUnknown",
}
def __init__(self, msg: QDBusMessage) -> None:
@@ -856,12 +859,15 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
log.misc.debug(f"Enabling quirks {quirks}")
self._quirks = quirks
- expected_spec_version = self._quirks.spec_version or self.SPEC_VERSION
- if spec_version != expected_spec_version:
+ expected_spec_versions = [self.SPEC_VERSION]
+ if self._quirks.spec_version is not None:
+ expected_spec_versions.append(self._quirks.spec_version)
+
+ if spec_version not in expected_spec_versions:
log.misc.warning(
f"Notification server ({name} {ver} by {vendor}) implements "
- f"spec {spec_version}, but {expected_spec_version} was expected. "
- f"If {name} is up to date, please report a qutebrowser bug.")
+ f"spec {spec_version}, but {'/'.join(expected_spec_versions)} was "
+ f"expected. If {name} is up to date, please report a qutebrowser bug.")
# https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html
icon_key_overrides = {
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/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index 85f683133..98cb67cb2 100644
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -316,6 +316,8 @@ class TabbedBrowser(QWidget):
fields['id'] = self._win_id
title = title_format.format(**fields)
+ # prevent hanging WMs and similar issues with giant URLs
+ title = utils.elide(title, 1024)
self._window().setWindowTitle(title)
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/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index fdaa12efb..ce816b9fd 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -381,7 +381,6 @@ class ModuleInfo:
def _create_module_info() -> Dict[str, ModuleInfo]:
packages = [
- ('sip', ['SIP_VERSION_STR']),
('colorama', ['VERSION', '__version__']),
('jinja2', ['__version__']),
('pygments', ['__version__']),
@@ -395,9 +394,13 @@ def _create_module_info() -> Dict[str, ModuleInfo]:
('PyQt5.QtWebEngineWidgets', []),
('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']),
('PyQt5.QtWebKitWidgets', []),
+ ('PyQt5.sip', ['SIP_VERSION_STR']),
]
elif machinery.IS_QT6:
- packages.append(('PyQt6.QtWebEngineCore', ['PYQT_WEBENGINE_VERSION_STR']))
+ packages += [
+ ('PyQt6.QtWebEngineCore', ['PYQT_WEBENGINE_VERSION_STR']),
+ ('PyQt6.sip', ['SIP_VERSION_STR']),
+ ]
else:
raise utils.Unreachable()
diff --git a/requirements.txt b/requirements.txt
index b5bab3296..f10ab6f9b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@ colorama==0.4.6
importlib-resources==6.0.0 ; python_version=="3.8.*"
Jinja2==3.1.2
MarkupSafe==2.1.3
-Pygments==2.15.1
+Pygments==2.16.1
PyYAML==6.0.1
zipp==3.16.2
# Unpinned due to recompile_requirements.py limitations
diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py
index f602cae43..4c443136f 100755
--- a/scripts/dev/build_release.py
+++ b/scripts/dev/build_release.py
@@ -282,7 +282,7 @@ def build_mac(
gh_token=gh_token)
utils.print_title("Building .app via pyinstaller")
- call_tox(f'pyinstaller-64bit{"-qt5" if qt5 else ""}', '-r', debug=debug)
+ call_tox(f'pyinstaller{"-qt5" if qt5 else ""}', '-r', debug=debug)
utils.print_title("Verifying .app")
verify_mac_app()
@@ -328,18 +328,14 @@ def build_mac(
]
-def _get_windows_python_path(x64: bool) -> pathlib.Path:
+def _get_windows_python_path() -> pathlib.Path:
"""Get the path to Python.exe on Windows."""
parts = str(sys.version_info.major), str(sys.version_info.minor)
ver = ''.join(parts)
dot_ver = '.'.join(parts)
- if x64:
- path = rf'SOFTWARE\Python\PythonCore\{dot_ver}\InstallPath'
- fallback = pathlib.Path('C:', f'Python{ver}', 'python.exe')
- else:
- path = rf'SOFTWARE\WOW6432Node\Python\PythonCore\{dot_ver}-32\InstallPath'
- fallback = pathlib.Path('C:', f'Python{ver}-32', 'python.exe')
+ path = rf'SOFTWARE\Python\PythonCore\{dot_ver}\InstallPath'
+ fallback = pathlib.Path('C:', f'Python{ver}', 'python.exe')
try:
key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, path)
@@ -349,47 +345,39 @@ def _get_windows_python_path(x64: bool) -> pathlib.Path:
def _build_windows_single(
- *, x64: bool,
+ *,
qt5: bool,
skip_packaging: bool,
debug: bool,
) -> List[Artifact]:
- """Build on Windows for a single architecture."""
- human_arch = '64-bit' if x64 else '32-bit'
- utils.print_title(f"Running pyinstaller {human_arch}")
+ """Build on Windows for a single build type."""
+ utils.print_title("Running pyinstaller")
dist_path = pathlib.Path("dist")
- arch = "x64" if x64 else "x86"
- out_path = dist_path / f'qutebrowser-{qutebrowser.__version__}-{arch}'
+ out_path = dist_path / f'qutebrowser-{qutebrowser.__version__}'
_maybe_remove(out_path)
- python = _get_windows_python_path(x64=x64)
- suffix = "64bit" if x64 else "32bit"
- if qt5:
- # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872?
- suffix += "-qt5"
- call_tox(f'pyinstaller-{suffix}', '-r', python=python, debug=debug)
+ python = _get_windows_python_path()
+ # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872?
+ suffix = "-qt5" if qt5 else ""
+ call_tox(f'pyinstaller{suffix}', '-r', python=python, debug=debug)
out_pyinstaller = dist_path / "qutebrowser"
shutil.move(out_pyinstaller, out_path)
exe_path = out_path / 'qutebrowser.exe'
- utils.print_title(f"Verifying {human_arch} exe")
+ utils.print_title("Verifying exe")
verify_windows_exe(exe_path)
- utils.print_title(f"Running {human_arch} smoke test")
+ utils.print_title("Running smoke test")
smoke_test(exe_path, debug=debug, qt5=qt5)
if skip_packaging:
return []
- utils.print_title(f"Packaging {human_arch}")
+ utils.print_title("Packaging")
return _package_windows_single(
- nsis_flags=[] if x64 else ['/DX86'],
out_path=out_path,
- filename_arch='amd64' if x64 else 'win32',
- desc_arch=human_arch,
- desc_suffix='' if x64 else ' (only for 32-bit Windows!)',
debug=debug,
qt5=qt5,
)
@@ -398,8 +386,6 @@ def _build_windows_single(
def build_windows(
*, gh_token: str,
skip_packaging: bool,
- only_32bit: bool,
- only_64bit: bool,
qt5: bool,
debug: bool,
) -> List[Artifact]:
@@ -410,37 +396,23 @@ def build_windows(
utils.print_title("Building Windows binaries")
- artifacts = []
-
from scripts.dev import gen_versioninfo
utils.print_title("Updating VersionInfo file")
gen_versioninfo.main()
- if not only_32bit:
- artifacts += _build_windows_single(
- x64=True,
- skip_packaging=skip_packaging,
- debug=debug,
- qt5=qt5,
- )
- if not only_64bit and qt5:
- artifacts += _build_windows_single(
- x64=False,
+ artifacts = [
+ _build_windows_single(
skip_packaging=skip_packaging,
debug=debug,
qt5=qt5,
- )
-
+ ),
+ ]
return artifacts
def _package_windows_single(
*,
- nsis_flags: List[str],
out_path: pathlib.Path,
- desc_arch: str,
- desc_suffix: str,
- filename_arch: str,
debug: bool,
qt5: bool,
) -> List[Artifact]:
@@ -448,15 +420,14 @@ def _package_windows_single(
artifacts = []
dist_path = pathlib.Path("dist")
- utils.print_subtitle(f"Building {desc_arch} installer...")
+ utils.print_subtitle("Building installer...")
subprocess.run(['makensis.exe',
- f'/DVERSION={qutebrowser.__version__}', *nsis_flags,
+ f'/DVERSION={qutebrowser.__version__}',
'misc/nsis/qutebrowser.nsi'], check=True)
name_parts = [
'qutebrowser',
str(qutebrowser.__version__),
- filename_arch,
]
if debug:
name_parts.append('debug')
@@ -467,16 +438,15 @@ def _package_windows_single(
artifacts.append(Artifact(
path=dist_path / name,
mimetype='application/vnd.microsoft.portable-executable',
- description=f'Windows {desc_arch} installer{desc_suffix}',
+ description='Windows installer',
))
- utils.print_subtitle(f"Zipping {desc_arch} standalone...")
+ utils.print_subtitle("Zipping standalone...")
zip_name_parts = [
'qutebrowser',
str(qutebrowser.__version__),
'windows',
'standalone',
- filename_arch,
]
if debug:
zip_name_parts.append('debug')
@@ -489,7 +459,7 @@ def _package_windows_single(
artifacts.append(Artifact(
path=zip_path,
mimetype='application/zip',
- description=f'Windows {desc_arch} standalone{desc_suffix}',
+ description='Windows standalone',
))
return artifacts
@@ -660,10 +630,6 @@ def main() -> None:
help="Skip confirmation before uploading.")
parser.add_argument('--skip-packaging', action='store_true', required=False,
help="Skip Windows installer/zip generation or macOS DMG.")
- parser.add_argument('--32bit', action='store_true', required=False,
- help="Skip Windows 64 bit build.", dest='only_32bit')
- parser.add_argument('--64bit', action='store_true', required=False,
- help="Skip Windows 32 bit build.", dest='only_64bit')
parser.add_argument('--debug', action='store_true', required=False,
help="Build a debug build.")
parser.add_argument('--qt5', action='store_true', required=False,
@@ -694,8 +660,6 @@ def main() -> None:
artifacts = build_windows(
gh_token=gh_token,
skip_packaging=args.skip_packaging,
- only_32bit=args.only_32bit,
- only_64bit=args.only_64bit,
qt5=args.qt5,
debug=args.debug,
)
diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature
index 85f68661a..76686162c 100644
--- a/tests/end2end/features/qutescheme.feature
+++ b/tests/end2end/features/qutescheme.feature
@@ -300,3 +300,14 @@ 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
+
+ Scenario: Seaching 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_urlmarks_bdd.py b/tests/end2end/features/test_urlmarks_bdd.py
index 1b21098cd..6d4172085 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,14 @@ 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.send_cmd(':bookmark-del --all')
+
+
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..1c97ec322 100644
--- a/tests/end2end/features/urlmarks.feature
+++ b/tests/end2end/features/urlmarks.feature
@@ -86,6 +86,23 @@ Feature: quickmarks and bookmarks
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 "
+ 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
And I run :bookmark-del
@@ -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/test_invocations.py b/tests/end2end/test_invocations.py
index e31aa3ecb..af81781f6 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -937,6 +937,7 @@ def test_restart(request, quteproc_new):
# If the new process hangs, this will hang too.
# Still better than just ignoring it, so we can fix it if something is broken.
os.waitpid(pid, 0) # pid, options... positional-only :(
- except ChildProcessError:
- # Already gone
+ except (ChildProcessError, PermissionError):
+ # Already gone. Even if not documented, Windows seems to raise PermissionError
+ # here...
pass
diff --git a/tests/helpers/testutils.py b/tests/helpers/testutils.py
index f6d7fcc4b..dc6fdac32 100644
--- a/tests/helpers/testutils.py
+++ b/tests/helpers/testutils.py
@@ -150,7 +150,7 @@ def partial_compare(val1, val2, *, indent=0):
if val2 is Ellipsis:
print_i("Ignoring ellipsis comparison", indent, error=True)
return PartialCompareOutcome()
- elif type(val1) != type(val2): # pylint: disable=unidiomatic-typecheck
+ elif type(val1) is not type(val2):
outcome = PartialCompareOutcome(
"Different types ({}, {}) -> False".format(type(val1).__name__,
type(val2).__name__))
diff --git a/tests/unit/browser/webengine/test_webengineinterceptor.py b/tests/unit/browser/webengine/test_webengineinterceptor.py
index d579d60f7..099ba69d7 100644
--- a/tests/unit/browser/webengine/test_webengineinterceptor.py
+++ b/tests/unit/browser/webengine/test_webengineinterceptor.py
@@ -6,12 +6,15 @@
import pytest
+import pytest_mock
-pytest.importorskip('qutebrowser.qt.webenginecore')
+pytest.importorskip("qutebrowser.qt.webenginecore")
+from qutebrowser.qt.core import QUrl, QByteArray
from qutebrowser.qt.webenginecore import QWebEngineUrlRequestInfo
from qutebrowser.browser.webengine import interceptor
+from qutebrowser.extensions import interceptors
from qutebrowser.utils import qtutils
from helpers import testutils
@@ -19,10 +22,12 @@ from helpers import testutils
def test_no_missing_resource_types():
request_interceptor = interceptor.RequestInterceptor()
qb_keys = set(request_interceptor._resource_types.keys())
- qt_keys = set(testutils.enum_members(
- QWebEngineUrlRequestInfo,
- QWebEngineUrlRequestInfo.ResourceType,
- ).values())
+ qt_keys = set(
+ testutils.enum_members(
+ QWebEngineUrlRequestInfo,
+ QWebEngineUrlRequestInfo.ResourceType,
+ ).values()
+ )
assert qt_keys == qb_keys
@@ -30,3 +35,85 @@ def test_resource_type_values():
request_interceptor = interceptor.RequestInterceptor()
for qt_value, qb_item in request_interceptor._resource_types.items():
assert qtutils.extract_enum_val(qt_value) == qb_item.value
+
+
+@pytest.fixture
+def we_request( # a shrubbery!
+ mocker: pytest_mock.MockerFixture,
+) -> interceptor.WebEngineRequest:
+ qt_info = mocker.Mock(spec=QWebEngineUrlRequestInfo)
+ qt_info.requestMethod.return_value = QByteArray(b"GET")
+ first_party_url = QUrl("https://firstparty.example.org/")
+ request_url = QUrl("https://request.example.org/")
+ return interceptor.WebEngineRequest(
+ first_party_url=first_party_url,
+ request_url=request_url,
+ webengine_info=qt_info,
+ )
+
+
+def test_block(we_request: interceptor.WebEngineRequest):
+ assert not we_request.is_blocked
+ we_request.block()
+ assert we_request.is_blocked
+
+
+class TestRedirect:
+ REDIRECT_URL = QUrl("https://redirect.example.com/")
+
+ def test_redirect(self, we_request: interceptor.WebEngineRequest):
+ assert not we_request._redirected
+ we_request.redirect(self.REDIRECT_URL)
+ assert we_request._redirected
+ we_request._webengine_info.redirect.assert_called_once_with(self.REDIRECT_URL)
+
+ def test_twice(self, we_request: interceptor.WebEngineRequest):
+ we_request.redirect(self.REDIRECT_URL)
+ with pytest.raises(
+ interceptors.RedirectException,
+ match=r"Request already redirected.",
+ ):
+ we_request.redirect(self.REDIRECT_URL)
+ we_request._webengine_info.redirect.assert_called_once_with(self.REDIRECT_URL)
+
+ def test_invalid_method(self, we_request: interceptor.WebEngineRequest):
+ we_request._webengine_info.requestMethod.return_value = QByteArray(b"POST")
+ with pytest.raises(
+ interceptors.RedirectException,
+ match=(
+ r"Request method b'POST' for https://request.example.org/ does not "
+ r"support redirection."
+ ),
+ ):
+ we_request.redirect(self.REDIRECT_URL)
+ assert not we_request._webengine_info.redirect.called
+
+ def test_invalid_method_ignore_unsupported(
+ self,
+ we_request: interceptor.WebEngineRequest,
+ caplog: pytest.LogCaptureFixture,
+ ):
+ we_request._webengine_info.requestMethod.return_value = QByteArray(b"POST")
+ we_request.redirect(self.REDIRECT_URL, ignore_unsupported=True)
+ assert caplog.messages == [
+ "Request method b'POST' for https://request.example.org/ does not support "
+ "redirection."
+ ]
+ assert not we_request._webengine_info.redirect.called
+
+ def test_improperly_initialized(self, we_request: interceptor.WebEngineRequest):
+ we_request._webengine_info = None
+ with pytest.raises(
+ interceptors.RedirectException,
+ match=r"Request improperly initialized.",
+ ):
+ we_request.redirect(self.REDIRECT_URL)
+
+ def test_invalid_url(self, we_request: interceptor.WebEngineRequest):
+ url = QUrl()
+ assert not url.isValid()
+ with pytest.raises(
+ interceptors.RedirectException,
+ match=r"Redirect to invalid URL: PyQt\d\.QtCore\.QUrl\(''\) is not valid",
+ ):
+ we_request.redirect(url)
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index f78a6f12d..486270d70 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -644,8 +644,8 @@ class TestModuleVersions:
assert version._module_versions() == expected
@pytest.mark.parametrize('module, idx, expected', [
- ('colorama', 1, 'colorama: no'),
- ('adblock', 5, 'adblock: no'),
+ ('colorama', 0, 'colorama: no'),
+ ('adblock', 4, 'adblock: no'),
])
def test_missing_module(self, module, idx, expected, import_fake):
"""Test with a module missing.
@@ -693,11 +693,11 @@ class TestModuleVersions:
assert not mod_info.is_usable()
expected = f"adblock: {fake_version} (< {mod_info.min_version}, outdated)"
- assert version._module_versions()[5] == expected
+ assert version._module_versions()[4] == expected
@pytest.mark.parametrize('attribute, expected_modules', [
('VERSION', ['colorama']),
- ('SIP_VERSION_STR', ['sip']),
+ ('SIP_VERSION_STR', ['PyQt5.sip', 'PyQt6.sip']),
(None, []),
])
def test_version_attribute(self, attribute, expected_modules, import_fake):
diff --git a/tox.ini b/tox.ini
index af59910f2..0014f261c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -181,7 +181,7 @@ commands =
{envpython} scripts/dev/check_doc_changes.py {posargs}
{envpython} scripts/asciidoc2html.py {posargs}
-[testenv:pyinstaller-{64bit,32bit}{,-qt5}]
+[testenv:pyinstaller{,-qt5}]
basepython = {env:PYTHON:python3}
passenv =
APPDATA
@@ -282,3 +282,12 @@ deps =
commands =
!qt5: {envpython} {toxinidir}/scripts/dev/build_release.py {posargs}
qt5: {envpython} {toxinidir}/scripts/dev/build_release.py --qt5 {posargs}
+
+[testenv:package]
+basepython = {env:PYTHON:python3}
+setenv =
+ PYTHONWARNINGS=error,default:pkg_resources is deprecated as an API.:DeprecationWarning
+deps =
+ -r{toxinidir}/requirements.txt
+ -r{toxinidir}/misc/requirements/requirements-dev.txt
+commands = {envpython} -m build