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:
commit
0f2d34623c
|
|
@ -33,6 +33,7 @@ jobs:
|
|||
args: "-f gcc" # For problem matchers
|
||||
- testenv: yamllint
|
||||
- testenv: actionlint
|
||||
- testenv: package
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
args: --debug
|
||||
branch: main
|
||||
toxenv: build-release-qt5
|
||||
name: qt5-windows-64bit-debug
|
||||
- os: windows-2019
|
||||
args: --32bit --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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
artifacts = [
|
||||
_build_windows_single(
|
||||
skip_packaging=skip_packaging,
|
||||
debug=debug,
|
||||
qt5=qt5,
|
||||
)
|
||||
if not only_64bit and qt5:
|
||||
artifacts += _build_windows_single(
|
||||
x64=False,
|
||||
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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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__))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
11
tox.ini
11
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue