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
This commit is contained in:
toofar 2023-08-12 13:49:01 +12:00
commit 0f2d34623c
34 changed files with 358 additions and 145 deletions

View File

@ -33,6 +33,7 @@ jobs:
args: "-f gcc" # For problem matchers args: "-f gcc" # For problem matchers
- testenv: yamllint - testenv: yamllint
- testenv: actionlint - testenv: actionlint
- testenv: package
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:

View File

@ -19,47 +19,34 @@ jobs:
toxenv: build-release-qt5 toxenv: build-release-qt5
name: qt5-macos name: qt5-macos
- os: windows-2019 - os: windows-2019
args: --64bit
branch: main branch: main
toxenv: build-release-qt5 toxenv: build-release-qt5
name: qt5-windows-64bit name: qt5-windows
- os: windows-2019
args: --32bit
branch: main
toxenv: build-release-qt5
name: qt5-windows-32bit
- os: macos-11 - os: macos-11
args: --debug args: --debug
branch: main branch: main
toxenv: build-release-qt5 toxenv: build-release-qt5
name: qt5-macos-debug name: qt5-macos-debug
- os: windows-2019 - os: windows-2019
args: --64bit --debug args: --debug
branch: main branch: main
toxenv: build-release-qt5 toxenv: build-release-qt5
name: qt5-windows-64bit-debug name: qt5-windows-debug
- os: windows-2019
args: --32bit --debug
branch: main
toxenv: build-release-qt5
name: qt5-windows-32bit-debug
- os: macos-11 - os: macos-11
toxenv: build-release toxenv: build-release
name: macos name: macos
- os: windows-2019 - os: windows-2019
args: --64bit
toxenv: build-release toxenv: build-release
name: windows-64bit name: windows
- os: macos-11 - os: macos-11
args: --debug args: --debug
toxenv: build-release toxenv: build-release
name: macos-debug name: macos-debug
- os: windows-2019 - os: windows-2019
args: --64bit --debug args: --debug
toxenv: build-release toxenv: build-release
name: windows-64bit-debug name: windows-debug
runs-on: "${{ matrix.os }}" runs-on: "${{ matrix.os }}"
timeout-minutes: 45 timeout-minutes: 45
steps: steps:

View File

@ -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://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://vieb.dev/[Vieb] (JavaScript, Electron)
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2) * https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
* https://github.com/jun7/wyeb[wyeb] (C, GTK+ with WebKit2)
* Chrome/Chromium addons: * Chrome/Chromium addons:
https://vimium.github.io/[Vimium] https://vimium.github.io/[Vimium]
* Firefox addons (based on WebExtensions): * Firefox addons (based on WebExtensions):
@ -227,9 +228,8 @@ Active
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF]
* Addons for Firefox and Chrome: * Addons for Firefox and Chrome:
https://github.com/brookhong/Surfingkeys[Surfingkeys], https://github.com/brookhong/Surfingkeys[Surfingkeys],
https://lydell.github.io/LinkHints/[Link Hints] (hinting only) https://lydell.github.io/LinkHints/[Link Hints] (hinting only),
* Addons for Safari: https://github.com/ueokande/vimmatic[Vimmatic]
https://televator.net/vimari/[Vimari]
Inactive Inactive
~~~~~~~~ ~~~~~~~~
@ -246,7 +246,6 @@ main inspiration for qutebrowser)
* https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2) * https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
* https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1) * 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/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): * Firefox addons (not based on WebExtensions or no recent activity):
http://www.vimperator.org/[Vimperator], http://www.vimperator.org/[Vimperator],
http://bug.5digits.org/pentadactyl/index[Pentadactyl], 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/1995eaton/chromium-vim[cVim],
https://github.com/dcchambers/vb4c[vb4c] (fork of cVim, https://github.com/dcchambers/vb4c/issues/23#issuecomment-810694017[unmaintained]), https://github.com/dcchambers/vb4c[vb4c] (fork of cVim, https://github.com/dcchambers/vb4c/issues/23#issuecomment-810694017[unmaintained]),
https://glee.github.io/[GleeBox] https://glee.github.io/[GleeBox]
* Addons for Safari:
https://televator.net/vimari/[Vimari]
License License
------- -------

View File

@ -67,6 +67,8 @@ Added
- New `colors.webpage.darkmode.increase_text_contrast` setting for Qt 6.3+ - New `colors.webpage.darkmode.increase_text_contrast` setting for Qt 6.3+
- New `fonts.tooltip`, `colors.tooltip.bg` and `colors.tooltip.fg` settings. - New `fonts.tooltip`, `colors.tooltip.bg` and `colors.tooltip.fg` settings.
- New `log-qt-events` debug flag for `-D` - New `log-qt-events` debug flag for `-D`
- New `--all` flags for `:bookmark-del` and `:quickmark-del` to delete all
quickmarks/bookmarks.
Removed Removed
~~~~~~~ ~~~~~~~
@ -202,8 +204,16 @@ Fixed
- Crash when using QtWebKit with PAC and the file has an invalid encoding. - Crash when using QtWebKit with PAC and the file has an invalid encoding.
- Crash with the "tiramisu" notification server. - Crash with the "tiramisu" notification server.
- Crash when the "herbe" notification presenter doesn't start correctly. - 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 - Crash when using `:print --pdf` with a directory where its parent directory
did not exist. 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]]
v2.5.4 (2023-03-13) v2.5.4 (2023-03-13)

View File

@ -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]]
=== bookmark-del === bookmark-del
Syntax: +:bookmark-del ['url']+ Syntax: +:bookmark-del [*--all*] ['url']+
Delete a bookmark. 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. * +'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 ==== note
* This command does not split arguments after the last argument and handles quotes literally. * 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]]
=== quickmark-del === quickmark-del
Syntax: +:quickmark-del ['name']+ Syntax: +:quickmark-del [*--all*] ['name']+
Delete a quickmark. Delete a quickmark.
@ -1007,6 +1010,9 @@ Delete a quickmark.
if there are more than one). if there are more than one).
==== optional arguments
* +*-a*+, +*--all*+: Delete all quickmarks.
==== note ==== note
* This command does not split arguments after the last argument and handles quotes literally. * This command does not split arguments after the last argument and handles quotes literally.

View File

@ -6,7 +6,7 @@ bump2version==1.0.1
certifi==2023.7.22 certifi==2023.7.22
cffi==1.15.1 cffi==1.15.1
charset-normalizer==3.2.0 charset-normalizer==3.2.0
cryptography==41.0.2 cryptography==41.0.3
docutils==0.20.1 docutils==0.20.1
github3.py==4.0.1 github3.py==4.0.1
hunter==3.6.1 hunter==3.6.1
@ -19,12 +19,12 @@ keyring==24.2.0
manhole==1.8.0 manhole==1.8.0
markdown-it-py==3.0.0 markdown-it-py==3.0.0
mdurl==0.1.2 mdurl==0.1.2
more-itertools==9.1.0 more-itertools==10.1.0
packaging==23.1 packaging==23.1
pkginfo==1.9.6 pkginfo==1.9.6
ply==3.11 ply==3.11
pycparser==2.21 pycparser==2.21
Pygments==2.15.1 Pygments==2.16.1
PyJWT==2.8.0 PyJWT==2.8.0
Pympler==1.0.1 Pympler==1.0.1
pyproject_hooks==1.0.0 pyproject_hooks==1.0.0
@ -34,9 +34,9 @@ readme-renderer==40.0
requests==2.31.0 requests==2.31.0
requests-toolbelt==1.0.0 requests-toolbelt==1.0.0
rfc3986==2.0.0 rfc3986==2.0.0
rich==13.4.2 rich==13.5.2
SecretStorage==3.3.3 SecretStorage==3.3.3
sip==6.7.10 sip==6.7.11
six==1.16.0 six==1.16.0
tomli==2.0.1 tomli==2.0.1
twine==4.0.2 twine==4.0.2

View File

@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==23.1.0 attrs==23.1.0
flake8==6.0.0 flake8==6.1.0
flake8-bugbear==23.7.10 flake8-bugbear==23.7.10
flake8-builtins==2.1.0 flake8-builtins==2.1.0
flake8-comprehensions==3.14.0 flake8-comprehensions==3.14.0
@ -16,8 +16,8 @@ flake8-tidy-imports==4.10.0
flake8-tuple==0.4.1 flake8-tuple==0.4.1
mccabe==0.7.0 mccabe==0.7.0
pep8-naming==0.13.3 pep8-naming==0.13.3
pycodestyle==2.10.0 pycodestyle==2.11.0
pydocstyle==6.3.0 pydocstyle==6.3.0
pyflakes==3.0.1 pyflakes==3.1.0
six==1.16.0 six==1.16.0
snowballstemmer==2.2.0 snowballstemmer==2.2.0

View File

@ -1,6 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
chardet==5.1.0 chardet==5.2.0
diff-cover==7.7.0 diff-cover==7.7.0
importlib-resources==6.0.0 importlib-resources==6.0.0
Jinja2==3.1.2 Jinja2==3.1.2
@ -9,12 +9,12 @@ MarkupSafe==2.1.3
mypy==1.4.1 mypy==1.4.1
mypy-extensions==1.0.0 mypy-extensions==1.0.0
pluggy==1.2.0 pluggy==1.2.0
Pygments==2.15.1 Pygments==2.16.1
PyQt5-stubs==5.15.6.0 PyQt5-stubs==5.15.6.0
tomli==2.0.1 tomli==2.0.1
types-colorama==0.4.15.12 types-colorama==0.4.15.12
types-docutils==0.20.0.1 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-PyYAML==6.0.12.11
types-setuptools==68.0.0.3 types-setuptools==68.0.0.3
typing_extensions==4.7.1 typing_extensions==4.7.1

View File

@ -4,7 +4,7 @@ astroid==2.15.6
certifi==2023.7.22 certifi==2023.7.22
cffi==1.15.1 cffi==1.15.1
charset-normalizer==3.2.0 charset-normalizer==3.2.0
cryptography==41.0.2 cryptography==41.0.3
dill==0.3.7 dill==0.3.7
github3.py==4.0.1 github3.py==4.0.1
idna==3.4 idna==3.4
@ -12,16 +12,16 @@ isort==5.12.0
lazy-object-proxy==1.9.0 lazy-object-proxy==1.9.0
mccabe==0.7.0 mccabe==0.7.0
pefile==2023.2.7 pefile==2023.2.7
platformdirs==3.9.1 platformdirs==3.10.0
pycparser==2.21 pycparser==2.21
PyJWT==2.8.0 PyJWT==2.8.0
pylint==2.17.4 pylint==2.17.5
python-dateutil==2.8.2 python-dateutil==2.8.2
./scripts/dev/pylint_checkers ./scripts/dev/pylint_checkers
requests==2.31.0 requests==2.31.0
six==1.16.0 six==1.16.0
tomli==2.0.1 tomli==2.0.1
tomlkit==0.11.8 tomlkit==0.12.1
typing_extensions==4.7.1 typing_extensions==4.7.1
uritemplate==4.1.1 uritemplate==4.1.1
# urllib3==2.0.4 # urllib3==2.0.4

View File

@ -6,7 +6,7 @@ charset-normalizer==3.2.0
docutils==0.20.1 docutils==0.20.1
idna==3.4 idna==3.4
packaging==23.1 packaging==23.1
Pygments==2.15.1 Pygments==2.16.1
pyproject_hooks==1.0.0 pyproject_hooks==1.0.0
pyroma==4.2 pyroma==4.2
requests==2.31.0 requests==2.31.0

View File

@ -11,11 +11,11 @@ importlib-metadata==6.8.0
Jinja2==3.1.2 Jinja2==3.1.2
MarkupSafe==2.1.3 MarkupSafe==2.1.3
packaging==23.1 packaging==23.1
Pygments==2.15.1 Pygments==2.16.1
pytz==2023.3 pytz==2023.3
requests==2.31.0 requests==2.31.0
snowballstemmer==2.2.0 snowballstemmer==2.2.0
Sphinx==7.0.1 Sphinx==7.1.2
sphinxcontrib-applehelp==1.0.4 sphinxcontrib-applehelp==1.0.4
sphinxcontrib-devhelp==1.0.2 sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-htmlhelp==2.0.1

View File

@ -13,23 +13,23 @@ execnet==2.0.2
filelock==3.12.2 filelock==3.12.2
Flask==2.3.2 Flask==2.3.2
hunter==3.6.1 hunter==3.6.1
hypothesis==6.82.0 hypothesis==6.82.2
idna==3.4 idna==3.4
importlib-metadata==6.8.0 importlib-metadata==6.8.0
iniconfig==2.0.0 iniconfig==2.0.0
itsdangerous==2.1.2 itsdangerous==2.1.2
jaraco.functools==3.8.0 jaraco.functools==3.8.1
# Jinja2==3.1.2 # Jinja2==3.1.2
Mako==1.2.4 Mako==1.2.4
manhole==1.8.0 manhole==1.8.0
# MarkupSafe==2.1.3 # MarkupSafe==2.1.3
more-itertools==9.1.0 more-itertools==10.1.0
packaging==23.1 packaging==23.1
parse==1.19.1 parse==1.19.1
parse-type==0.6.2 parse-type==0.6.2
pluggy==1.2.0 pluggy==1.2.0
py-cpuinfo==9.0.0 py-cpuinfo==9.0.0
Pygments==2.15.1 Pygments==2.16.1
pytest==7.4.0 pytest==7.4.0
pytest-bdd==6.1.1 pytest-bdd==6.1.1
pytest-benchmark==4.0.0 pytest-benchmark==4.0.0

View File

@ -1,17 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
cachetools==5.3.1 cachetools==5.3.1
chardet==5.1.0 chardet==5.2.0
colorama==0.4.6 colorama==0.4.6
distlib==0.3.7 distlib==0.3.7
filelock==3.12.2 filelock==3.12.2
packaging==23.1 packaging==23.1
pip==23.2.1 pip==23.2.1
platformdirs==3.9.1 platformdirs==3.10.0
pluggy==1.2.0 pluggy==1.2.0
pyproject-api==1.5.3 pyproject-api==1.5.3
setuptools==68.0.0 setuptools==68.0.0
tomli==2.0.1 tomli==2.0.1
tox==4.6.4 tox==4.6.4
virtualenv==20.24.1 virtualenv==20.24.2
wheel==0.41.0 wheel==0.41.1

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
pathspec==0.11.1 pathspec==0.11.2
PyYAML==6.0.1 PyYAML==6.0.1
yamllint==1.32.0 yamllint==1.32.0

View File

@ -35,7 +35,7 @@ Possible values:
import inspect 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.utils import qtutils
from qutebrowser.commands import command, cmdexc 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)) 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 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. # The arguments to pass to Command.
self._kwargs = kwargs self._kwargs = kwargs
def __call__(self, func: _CmdHandlerType) -> _CmdHandlerType: def __call__(self, func: _CmdHandlerFunc) -> _CmdHandlerType:
"""Register the command before running the function. """Register the command before running the function.
Gets called when a function should be decorated. 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 # This is checked by future @cmdutils.argument calls so they fail
# (as they'd be silently ignored otherwise) # (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 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._argname = argname # The name of the argument to handle.
self._kwargs = kwargs # Valid ArgInfo members. self._kwargs = kwargs # Valid ArgInfo members.
def __call__(self, func: _CmdHandlerType) -> _CmdHandlerType: def __call__(self, func: _CmdHandlerFunc) -> _CmdHandlerType:
funcname = func.__name__ funcname = func.__name__
if self._argname not in inspect.signature(func).parameters: if self._argname not in inspect.signature(func).parameters:
raise ValueError("{} has no argument {}!".format(funcname, raise ValueError("{} has no argument {}!".format(funcname,
self._argname)) self._argname))
func = cast(_CmdHandlerType, func)
if not hasattr(func, 'qute_args'): if not hasattr(func, 'qute_args'):
func.qute_args = {} # type: ignore[attr-defined] func.qute_args = {}
elif func.qute_args is None: elif func.qute_args is None:
raise ValueError("@cmdutils.argument got called above (after) " raise ValueError("@cmdutils.argument got called above (after) "
"@cmdutils.register for {}!".format(funcname)) "@cmdutils.register for {}!".format(funcname))
arginfo = command.ArgInfo(**self._kwargs) arginfo = command.ArgInfo(**self._kwargs)
func.qute_args[self._argname] = arginfo # type: ignore[attr-defined] func.qute_args[self._argname] = arginfo
return func return func

View File

@ -30,7 +30,7 @@ if TYPE_CHECKING:
from qutebrowser.keyinput import modeman from qutebrowser.keyinput import modeman
from qutebrowser.config import config, websettings from qutebrowser.config import config, websettings
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, 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.misc import miscwidgets, objects, sessions
from qutebrowser.browser import eventfilter, inspector from qutebrowser.browser import eventfilter, inspector
from qutebrowser.qt import sip from qutebrowser.qt import sip
@ -1169,6 +1169,23 @@ class AbstractTab(QWidget):
navigation.url.errorString())) navigation.url.errorString()))
navigation.accepted = False 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) @pyqtSlot(bool)
def _on_load_finished(self, ok: bool) -> None: def _on_load_finished(self, ok: bool) -> None:
assert self._widget is not None assert self._widget is not None

View File

@ -1235,21 +1235,31 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window', @cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0) maxsplit=0)
@cmdutils.argument('name', completion=miscmodels.quickmark) @cmdutils.argument('name', completion=miscmodels.quickmark)
def quickmark_del(self, name=None): def quickmark_del(self, name=None, all_=False):
"""Delete a quickmark. """Delete a quickmark.
Args: Args:
name: The name of the quickmark to delete. If not given, delete the name: The name of the quickmark to delete. If not given, delete the
quickmark for the current page (choosing one arbitrarily quickmark for the current page (choosing one arbitrarily
if there are more than one). if there are more than one).
all_: Delete all quickmarks.
""" """
quickmark_manager = objreg.get('quickmark-manager') 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: if name is None:
url = self._current_url() url = self._current_url()
try: try:
name = quickmark_manager.get_by_qurl(url) name = quickmark_manager.get_by_qurl(url)
except urlmarks.DoesNotExistError as e: except urlmarks.DoesNotExistError as e:
raise cmdutils.CommandError(str(e)) raise cmdutils.CommandError(str(e))
try: try:
quickmark_manager.delete(name) quickmark_manager.delete(name)
except KeyError: except KeyError:
@ -1320,18 +1330,28 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window', @cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0) maxsplit=0)
@cmdutils.argument('url', completion=miscmodels.bookmark) @cmdutils.argument('url', completion=miscmodels.bookmark)
def bookmark_del(self, url=None): def bookmark_del(self, url=None, all_=False):
"""Delete a bookmark. """Delete a bookmark.
Args: Args:
url: The url of the bookmark to delete. If not given, use the url: The url of the bookmark to delete. If not given, use the
current page's url. 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: if url is None:
url = self._current_url().toString(QUrl.UrlFormattingOption.RemovePassword | url = self._current_url().toString(QUrl.UrlFormattingOption.RemovePassword |
QUrl.ComponentFormattingOption.FullyEncoded) QUrl.ComponentFormattingOption.FullyEncoded)
try: try:
objreg.get('bookmark-manager').delete(url) bookmark_manager.delete(url)
except KeyError: except KeyError:
raise cmdutils.CommandError("Bookmark '{}' not found!".format(url)) raise cmdutils.CommandError("Bookmark '{}' not found!".format(url))
message.info("Removed bookmark {}".format(url)) message.info("Removed bookmark {}".format(url))

View File

@ -98,6 +98,11 @@ class UrlMarkManager(QObject):
del self.marks[key] del self.marks[key]
self.changed.emit() self.changed.emit()
def clear(self):
"""Delete all marks."""
self.marks.clear()
self.changed.emit()
class QuickmarkManager(UrlMarkManager): class QuickmarkManager(UrlMarkManager):

View File

@ -10,7 +10,7 @@ from qutebrowser.qt.webenginecore import (QWebEngineUrlRequestInterceptor,
from qutebrowser.config import websettings, config from qutebrowser.config import websettings, config
from qutebrowser.browser import shared 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.extensions import interceptors
from qutebrowser.misc import objects from qutebrowser.misc import objects
@ -35,6 +35,11 @@ class WebEngineRequest(interceptors.Request):
if self._webengine_info is None: if self._webengine_info is None:
raise interceptors.RedirectException("Request improperly initialized.") 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. # Redirecting a request that contains payload data is not allowed.
# To be safe, abort on any request not in a whitelist. # To be safe, abort on any request not in a whitelist.
verb = self._webengine_info.requestMethod() verb = self._webengine_info.requestMethod()

View File

@ -113,6 +113,9 @@ class DBusError(Error):
# https://crashes.qutebrowser.org/view/de62220a # https://crashes.qutebrowser.org/view/de62220a
# after "Notification daemon did quit!" # after "Notification daemon did quit!"
"org.freedesktop.DBus.Error.UnknownObject", "org.freedesktop.DBus.Error.UnknownObject",
# notmuch-sha1-ef7b6e9e79e5f2f6cba90224122288895c1fe0d8
"org.freedesktop.DBus.Error.ServiceUnknown",
} }
def __init__(self, msg: QDBusMessage) -> None: def __init__(self, msg: QDBusMessage) -> None:
@ -856,12 +859,15 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
log.misc.debug(f"Enabling quirks {quirks}") log.misc.debug(f"Enabling quirks {quirks}")
self._quirks = quirks self._quirks = quirks
expected_spec_version = self._quirks.spec_version or self.SPEC_VERSION expected_spec_versions = [self.SPEC_VERSION]
if spec_version != expected_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( log.misc.warning(
f"Notification server ({name} {ver} by {vendor}) implements " f"Notification server ({name} {ver} by {vendor}) implements "
f"spec {spec_version}, but {expected_spec_version} was expected. " f"spec {spec_version}, but {'/'.join(expected_spec_versions)} was "
f"If {name} is up to date, please report a qutebrowser bug.") f"expected. If {name} is up to date, please report a qutebrowser bug.")
# https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html # https://specifications.freedesktop.org/notification-spec/latest/ar01s08.html
icon_key_overrides = { icon_key_overrides = {

View File

@ -11,7 +11,7 @@ from qutebrowser.qt.core import QRect, QEventLoop
from qutebrowser.qt.widgets import QApplication from qutebrowser.qt.widgets import QApplication
from qutebrowser.qt.webenginecore import QWebEngineSettings 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 from qutebrowser.browser import webelem
if TYPE_CHECKING: if TYPE_CHECKING:
@ -213,6 +213,17 @@ class WebEngineElement(webelem.AbstractWebElement):
return True return True
if baseurl.scheme() == url.scheme(): # e.g. a qute:// link if baseurl.scheme() == url.scheme(): # e.g. a qute:// link
return False 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 return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target: usertypes.ClickTarget) -> None: def _click_editable(self, click_target: usertypes.ClickTarget) -> None:

View File

@ -316,6 +316,8 @@ class TabbedBrowser(QWidget):
fields['id'] = self._win_id fields['id'] = self._win_id
title = title_format.format(**fields) title = title_format.format(**fields)
# prevent hanging WMs and similar issues with giant URLs
title = utils.elide(title, 1024)
self._window().setWindowTitle(title) self._window().setWindowTitle(title)

View File

@ -39,6 +39,7 @@ if machinery.IS_QT6:
REMOVE_SCHEME = QUrl.UrlFormattingOption.RemoveScheme REMOVE_SCHEME = QUrl.UrlFormattingOption.RemoveScheme
REMOVE_PASSWORD = QUrl.UrlFormattingOption.RemovePassword REMOVE_PASSWORD = QUrl.UrlFormattingOption.RemovePassword
REMOVE_QUERY = QUrl.UrlFormattingOption.RemoveQuery
else: else:
UrlFlagsType = Union[ UrlFlagsType = Union[
QUrl.FormattingOptions, QUrl.FormattingOptions,
@ -74,6 +75,8 @@ else:
_QtFormattingOptions, QUrl.UrlFormattingOption.RemoveScheme) _QtFormattingOptions, QUrl.UrlFormattingOption.RemoveScheme)
REMOVE_PASSWORD = cast( REMOVE_PASSWORD = cast(
_QtFormattingOptions, QUrl.UrlFormattingOption.RemovePassword) _QtFormattingOptions, QUrl.UrlFormattingOption.RemovePassword)
REMOVE_QUERY = cast(
_QtFormattingOptions, QUrl.UrlFormattingOption.RemoveQuery)
# URL schemes supported by QtWebEngine # URL schemes supported by QtWebEngine

View File

@ -381,7 +381,6 @@ class ModuleInfo:
def _create_module_info() -> Dict[str, ModuleInfo]: def _create_module_info() -> Dict[str, ModuleInfo]:
packages = [ packages = [
('sip', ['SIP_VERSION_STR']),
('colorama', ['VERSION', '__version__']), ('colorama', ['VERSION', '__version__']),
('jinja2', ['__version__']), ('jinja2', ['__version__']),
('pygments', ['__version__']), ('pygments', ['__version__']),
@ -395,9 +394,13 @@ def _create_module_info() -> Dict[str, ModuleInfo]:
('PyQt5.QtWebEngineWidgets', []), ('PyQt5.QtWebEngineWidgets', []),
('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']), ('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']),
('PyQt5.QtWebKitWidgets', []), ('PyQt5.QtWebKitWidgets', []),
('PyQt5.sip', ['SIP_VERSION_STR']),
] ]
elif machinery.IS_QT6: 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: else:
raise utils.Unreachable() raise utils.Unreachable()

View File

@ -5,7 +5,7 @@ colorama==0.4.6
importlib-resources==6.0.0 ; python_version=="3.8.*" importlib-resources==6.0.0 ; python_version=="3.8.*"
Jinja2==3.1.2 Jinja2==3.1.2
MarkupSafe==2.1.3 MarkupSafe==2.1.3
Pygments==2.15.1 Pygments==2.16.1
PyYAML==6.0.1 PyYAML==6.0.1
zipp==3.16.2 zipp==3.16.2
# Unpinned due to recompile_requirements.py limitations # Unpinned due to recompile_requirements.py limitations

View File

@ -282,7 +282,7 @@ def build_mac(
gh_token=gh_token) gh_token=gh_token)
utils.print_title("Building .app via pyinstaller") 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") utils.print_title("Verifying .app")
verify_mac_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.""" """Get the path to Python.exe on Windows."""
parts = str(sys.version_info.major), str(sys.version_info.minor) parts = str(sys.version_info.major), str(sys.version_info.minor)
ver = ''.join(parts) ver = ''.join(parts)
dot_ver = '.'.join(parts) dot_ver = '.'.join(parts)
if x64: path = rf'SOFTWARE\Python\PythonCore\{dot_ver}\InstallPath'
path = rf'SOFTWARE\Python\PythonCore\{dot_ver}\InstallPath' fallback = pathlib.Path('C:', f'Python{ver}', 'python.exe')
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')
try: try:
key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, path) 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( def _build_windows_single(
*, x64: bool, *,
qt5: bool, qt5: bool,
skip_packaging: bool, skip_packaging: bool,
debug: bool, debug: bool,
) -> List[Artifact]: ) -> List[Artifact]:
"""Build on Windows for a single architecture.""" """Build on Windows for a single build type."""
human_arch = '64-bit' if x64 else '32-bit' utils.print_title("Running pyinstaller")
utils.print_title(f"Running pyinstaller {human_arch}")
dist_path = pathlib.Path("dist") dist_path = pathlib.Path("dist")
arch = "x64" if x64 else "x86" out_path = dist_path / f'qutebrowser-{qutebrowser.__version__}'
out_path = dist_path / f'qutebrowser-{qutebrowser.__version__}-{arch}'
_maybe_remove(out_path) _maybe_remove(out_path)
python = _get_windows_python_path(x64=x64) python = _get_windows_python_path()
suffix = "64bit" if x64 else "32bit" # FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872?
if qt5: suffix = "-qt5" if qt5 else ""
# FIXME:qt6 does this regress 391623d5ec983ecfc4512c7305c4b7a293ac3872? call_tox(f'pyinstaller{suffix}', '-r', python=python, debug=debug)
suffix += "-qt5"
call_tox(f'pyinstaller-{suffix}', '-r', python=python, debug=debug)
out_pyinstaller = dist_path / "qutebrowser" out_pyinstaller = dist_path / "qutebrowser"
shutil.move(out_pyinstaller, out_path) shutil.move(out_pyinstaller, out_path)
exe_path = out_path / 'qutebrowser.exe' exe_path = out_path / 'qutebrowser.exe'
utils.print_title(f"Verifying {human_arch} exe") utils.print_title("Verifying exe")
verify_windows_exe(exe_path) 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) smoke_test(exe_path, debug=debug, qt5=qt5)
if skip_packaging: if skip_packaging:
return [] return []
utils.print_title(f"Packaging {human_arch}") utils.print_title("Packaging")
return _package_windows_single( return _package_windows_single(
nsis_flags=[] if x64 else ['/DX86'],
out_path=out_path, 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, debug=debug,
qt5=qt5, qt5=qt5,
) )
@ -398,8 +386,6 @@ def _build_windows_single(
def build_windows( def build_windows(
*, gh_token: str, *, gh_token: str,
skip_packaging: bool, skip_packaging: bool,
only_32bit: bool,
only_64bit: bool,
qt5: bool, qt5: bool,
debug: bool, debug: bool,
) -> List[Artifact]: ) -> List[Artifact]:
@ -410,37 +396,23 @@ def build_windows(
utils.print_title("Building Windows binaries") utils.print_title("Building Windows binaries")
artifacts = []
from scripts.dev import gen_versioninfo from scripts.dev import gen_versioninfo
utils.print_title("Updating VersionInfo file") utils.print_title("Updating VersionInfo file")
gen_versioninfo.main() gen_versioninfo.main()
if not only_32bit: artifacts = [
artifacts += _build_windows_single( _build_windows_single(
x64=True,
skip_packaging=skip_packaging, skip_packaging=skip_packaging,
debug=debug, debug=debug,
qt5=qt5, qt5=qt5,
) ),
if not only_64bit and qt5: ]
artifacts += _build_windows_single(
x64=False,
skip_packaging=skip_packaging,
debug=debug,
qt5=qt5,
)
return artifacts return artifacts
def _package_windows_single( def _package_windows_single(
*, *,
nsis_flags: List[str],
out_path: pathlib.Path, out_path: pathlib.Path,
desc_arch: str,
desc_suffix: str,
filename_arch: str,
debug: bool, debug: bool,
qt5: bool, qt5: bool,
) -> List[Artifact]: ) -> List[Artifact]:
@ -448,15 +420,14 @@ def _package_windows_single(
artifacts = [] artifacts = []
dist_path = pathlib.Path("dist") dist_path = pathlib.Path("dist")
utils.print_subtitle(f"Building {desc_arch} installer...") utils.print_subtitle("Building installer...")
subprocess.run(['makensis.exe', subprocess.run(['makensis.exe',
f'/DVERSION={qutebrowser.__version__}', *nsis_flags, f'/DVERSION={qutebrowser.__version__}',
'misc/nsis/qutebrowser.nsi'], check=True) 'misc/nsis/qutebrowser.nsi'], check=True)
name_parts = [ name_parts = [
'qutebrowser', 'qutebrowser',
str(qutebrowser.__version__), str(qutebrowser.__version__),
filename_arch,
] ]
if debug: if debug:
name_parts.append('debug') name_parts.append('debug')
@ -467,16 +438,15 @@ def _package_windows_single(
artifacts.append(Artifact( artifacts.append(Artifact(
path=dist_path / name, path=dist_path / name,
mimetype='application/vnd.microsoft.portable-executable', 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 = [ zip_name_parts = [
'qutebrowser', 'qutebrowser',
str(qutebrowser.__version__), str(qutebrowser.__version__),
'windows', 'windows',
'standalone', 'standalone',
filename_arch,
] ]
if debug: if debug:
zip_name_parts.append('debug') zip_name_parts.append('debug')
@ -489,7 +459,7 @@ def _package_windows_single(
artifacts.append(Artifact( artifacts.append(Artifact(
path=zip_path, path=zip_path,
mimetype='application/zip', mimetype='application/zip',
description=f'Windows {desc_arch} standalone{desc_suffix}', description='Windows standalone',
)) ))
return artifacts return artifacts
@ -660,10 +630,6 @@ def main() -> None:
help="Skip confirmation before uploading.") help="Skip confirmation before uploading.")
parser.add_argument('--skip-packaging', action='store_true', required=False, parser.add_argument('--skip-packaging', action='store_true', required=False,
help="Skip Windows installer/zip generation or macOS DMG.") 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, parser.add_argument('--debug', action='store_true', required=False,
help="Build a debug build.") help="Build a debug build.")
parser.add_argument('--qt5', action='store_true', required=False, parser.add_argument('--qt5', action='store_true', required=False,
@ -694,8 +660,6 @@ def main() -> None:
artifacts = build_windows( artifacts = build_windows(
gh_token=gh_token, gh_token=gh_token,
skip_packaging=args.skip_packaging, skip_packaging=args.skip_packaging,
only_32bit=args.only_32bit,
only_64bit=args.only_64bit,
qt5=args.qt5, qt5=args.qt5,
debug=args.debug, debug=args.debug,
) )

View File

@ -300,3 +300,14 @@ Feature: Special qute:// pages
Scenario: Open qute://gpl Scenario: Open qute://gpl
When I open qute://gpl When I open qute://gpl
Then the page should contain the plaintext "GNU GENERAL PUBLIC LICENSE" 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

View File

@ -4,6 +4,7 @@
import os.path import os.path
import pytest
import pytest_bdd as bdd import pytest_bdd as bdd
from helpers import testutils from helpers import testutils
@ -11,6 +12,14 @@ from helpers import testutils
bdd.scenarios('urlmarks.feature') 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): def _check_marks(quteproc, quickmarks, expected, contains):
"""Make sure the given line does (not) exist in the bookmarks. """Make sure the given line does (not) exist in the bookmarks.

View File

@ -86,6 +86,23 @@ Feature: quickmarks and bookmarks
And I run :bookmark-del http://localhost:(port)/data/numbers/5.txt 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 Scenario: Deleting the current page's bookmark if it doesn't exist
When I open data/hello.txt When I open data/hello.txt
And I run :bookmark-del And I run :bookmark-del
@ -210,6 +227,20 @@ Feature: quickmarks and bookmarks
And I run :quickmark-del eighteen And I run :quickmark-del eighteen
Then the quickmark file should not contain "eighteen http://localhost:*/data/numbers/18.txt " 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 Scenario: Deleting the current page's quickmark if it has none
When I open data/hello.txt When I open data/hello.txt
And I run :quickmark-del And I run :quickmark-del
@ -233,3 +264,10 @@ Feature: quickmarks and bookmarks
And I run :bookmark-add And I run :bookmark-add
And I open qute://bookmarks And I open qute://bookmarks
Then the page should contain the plaintext "Test title" 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

View File

@ -937,6 +937,7 @@ def test_restart(request, quteproc_new):
# If the new process hangs, this will hang too. # If the new process hangs, this will hang too.
# Still better than just ignoring it, so we can fix it if something is broken. # Still better than just ignoring it, so we can fix it if something is broken.
os.waitpid(pid, 0) # pid, options... positional-only :( os.waitpid(pid, 0) # pid, options... positional-only :(
except ChildProcessError: except (ChildProcessError, PermissionError):
# Already gone # Already gone. Even if not documented, Windows seems to raise PermissionError
# here...
pass pass

View File

@ -150,7 +150,7 @@ def partial_compare(val1, val2, *, indent=0):
if val2 is Ellipsis: if val2 is Ellipsis:
print_i("Ignoring ellipsis comparison", indent, error=True) print_i("Ignoring ellipsis comparison", indent, error=True)
return PartialCompareOutcome() return PartialCompareOutcome()
elif type(val1) != type(val2): # pylint: disable=unidiomatic-typecheck elif type(val1) is not type(val2):
outcome = PartialCompareOutcome( outcome = PartialCompareOutcome(
"Different types ({}, {}) -> False".format(type(val1).__name__, "Different types ({}, {}) -> False".format(type(val1).__name__,
type(val2).__name__)) type(val2).__name__))

View File

@ -6,12 +6,15 @@
import pytest 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.qt.webenginecore import QWebEngineUrlRequestInfo
from qutebrowser.browser.webengine import interceptor from qutebrowser.browser.webengine import interceptor
from qutebrowser.extensions import interceptors
from qutebrowser.utils import qtutils from qutebrowser.utils import qtutils
from helpers import testutils from helpers import testutils
@ -19,10 +22,12 @@ from helpers import testutils
def test_no_missing_resource_types(): def test_no_missing_resource_types():
request_interceptor = interceptor.RequestInterceptor() request_interceptor = interceptor.RequestInterceptor()
qb_keys = set(request_interceptor._resource_types.keys()) qb_keys = set(request_interceptor._resource_types.keys())
qt_keys = set(testutils.enum_members( qt_keys = set(
QWebEngineUrlRequestInfo, testutils.enum_members(
QWebEngineUrlRequestInfo.ResourceType, QWebEngineUrlRequestInfo,
).values()) QWebEngineUrlRequestInfo.ResourceType,
).values()
)
assert qt_keys == qb_keys assert qt_keys == qb_keys
@ -30,3 +35,85 @@ def test_resource_type_values():
request_interceptor = interceptor.RequestInterceptor() request_interceptor = interceptor.RequestInterceptor()
for qt_value, qb_item in request_interceptor._resource_types.items(): for qt_value, qb_item in request_interceptor._resource_types.items():
assert qtutils.extract_enum_val(qt_value) == qb_item.value 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)

View File

@ -644,8 +644,8 @@ class TestModuleVersions:
assert version._module_versions() == expected assert version._module_versions() == expected
@pytest.mark.parametrize('module, idx, expected', [ @pytest.mark.parametrize('module, idx, expected', [
('colorama', 1, 'colorama: no'), ('colorama', 0, 'colorama: no'),
('adblock', 5, 'adblock: no'), ('adblock', 4, 'adblock: no'),
]) ])
def test_missing_module(self, module, idx, expected, import_fake): def test_missing_module(self, module, idx, expected, import_fake):
"""Test with a module missing. """Test with a module missing.
@ -693,11 +693,11 @@ class TestModuleVersions:
assert not mod_info.is_usable() assert not mod_info.is_usable()
expected = f"adblock: {fake_version} (< {mod_info.min_version}, outdated)" 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', [ @pytest.mark.parametrize('attribute, expected_modules', [
('VERSION', ['colorama']), ('VERSION', ['colorama']),
('SIP_VERSION_STR', ['sip']), ('SIP_VERSION_STR', ['PyQt5.sip', 'PyQt6.sip']),
(None, []), (None, []),
]) ])
def test_version_attribute(self, attribute, expected_modules, import_fake): def test_version_attribute(self, attribute, expected_modules, import_fake):

11
tox.ini
View File

@ -181,7 +181,7 @@ commands =
{envpython} scripts/dev/check_doc_changes.py {posargs} {envpython} scripts/dev/check_doc_changes.py {posargs}
{envpython} scripts/asciidoc2html.py {posargs} {envpython} scripts/asciidoc2html.py {posargs}
[testenv:pyinstaller-{64bit,32bit}{,-qt5}] [testenv:pyinstaller{,-qt5}]
basepython = {env:PYTHON:python3} basepython = {env:PYTHON:python3}
passenv = passenv =
APPDATA APPDATA
@ -282,3 +282,12 @@ deps =
commands = commands =
!qt5: {envpython} {toxinidir}/scripts/dev/build_release.py {posargs} !qt5: {envpython} {toxinidir}/scripts/dev/build_release.py {posargs}
qt5: {envpython} {toxinidir}/scripts/dev/build_release.py --qt5 {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