diff options
70 files changed, 468 insertions, 233 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 83a5b985a..5ae20070b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 2.5.0 +current_version = 2.5.1 commit = True message = Release v{new_version} tag = True diff --git a/.coveragerc b/.coveragerc index 7c4f7b218..cb0619b80 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,6 +23,3 @@ exclude_lines = [xml] output=coverage.xml - -[html] -show_contexts = True diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 28f1d0971..288efbc02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,12 +198,12 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: javascript, python queries: +security-extended - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 irc: timeout-minutes: 2 @@ -23,10 +23,8 @@ py-version=3.7 [MESSAGES CONTROL] enable=all disable=locally-disabled, - locally-enabled, suppressed-message, fixme, - no-self-use, cyclic-import, blacklisted-name, logging-format-interpolation, @@ -51,7 +49,6 @@ disable=locally-disabled, too-many-statements, too-few-public-methods, import-outside-toplevel, - bad-continuation, # This lint disagrees with Black consider-using-f-string, logging-fstring-interpolation, raise-missing-from, diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index e26d5beec..dd685a149 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -19,6 +19,12 @@ breaking changes (such as renamed commands) can happen in minor releases. v3.0.0 (unreleased) ------------------- +Added +~~~~~ + +- On invalid commands/settings with a similarly spelled match, qutebrowser now + suggests the correct name in its error messages. + Removed ~~~~~~~ @@ -43,9 +49,31 @@ Changed resource system (rather than compiling them into a Qt resource file). Packagers are advised to use `misc/Makefile` if possible, which has been updated with the new paths. +- The `content.javascript.can_access_clipboard` setting got renamed to + `content.javascript.clipboard` and now understands three different values + rather than being a boolean: `none` (formerly `false`), `access` (formerly + `true`) and `access-paste` (additionally allows pasting content, needed for + websites like Photopea or GitHub Codespaces). +- The default `hints.selectors` now also match the `treeitem` ARIA roles. + +Fixed +~~~~~ + +- When the devtools are clicked but `input.insert_mode.auto_enter` is set to + `false`, insert mode now isn't entered anymore. + +[[v2.5.2]] +v2.5.2 (unreleased) +------------------- + +Fixed +~~~~~ + +- The `install` and `stacktrace` help pages are now included in the docs + shipped with qutebrowser when using the recommended packaging workflow. [[v2.5.1]] -v2.5.1 (unreleased) +v2.5.1 (2022-05-26) ------------------- Fixed @@ -53,11 +81,20 @@ Fixed - The `qute-pass` userscript is marked as executable again. - PDF.js now works properly again with the macOS and Windows releases. +- The MathML workaround for darkmode (e.g. black on black Wikipedia formula) + now also works for display (rather than inline) math. +- The `content.proxy` setting can now correctly be set to arbitrary values via + the `qute://settings` page again. +- Fixed issues with Chromium version detection on Archlinux with + qt5-webengine 5.15.9-3. +- Fixed a rare possible crash with invalid `Content-Disposition` headers. - Fixes for various notification-related crashes: * With the `tiramisu` notification server (due to invalid behavior of the server, now a non-fatal error) * With the `budgie` notification server when closing a notification (due to invalid behavior of the server, now worked around) * When a server exits with an unsuccessful exit status (now a non-fatal error) * When a server couldn't be started successfully (now a non-fatal error) + * With the `herbe` notification presenter, when the website tries to close + the notification after the user accepting (right-clicking) it. - Fixes in userscripts: * The `qute-bitwarden` userscript now correctly searches for entries for sites on a subdomain of an unrecognized TLD. subdomain names. Previously diff --git a/doc/faq.asciidoc b/doc/faq.asciidoc index 503893b66..4b3596285 100644 --- a/doc/faq.asciidoc +++ b/doc/faq.asciidoc @@ -354,9 +354,9 @@ There is a total of four possible approaches to get dark websites: of the Dark Reader extension. This is mostly untested, though. How do I make copy to clipboard buttons work?:: -You can `:set content.javascript.can_access_clipboard true`, or -`:set -u some.domain content.javascript.can_access_clipboard true` if you want to limit -the setting to `some.domain`. +You can `:set content.javascript.clipboard access` to allow this globally (not +recommended!), or `:set -u some.domain content.javascript.clipboad access` if +you want to limit the setting to `some.domain`. == Troubleshooting diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 2f60525f4..1236dc3ac 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -167,9 +167,9 @@ |<<content.hyperlink_auditing,content.hyperlink_auditing>>|Enable hyperlink auditing (`<a ping>`). |<<content.images,content.images>>|Load images automatically in web pages. |<<content.javascript.alert,content.javascript.alert>>|Show javascript alerts. -|<<content.javascript.can_access_clipboard,content.javascript.can_access_clipboard>>|Allow JavaScript to read from or write to the clipboard. |<<content.javascript.can_close_tabs,content.javascript.can_close_tabs>>|Allow JavaScript to close tabs. |<<content.javascript.can_open_tabs_automatically,content.javascript.can_open_tabs_automatically>>|Allow JavaScript to open new tabs without user interaction. +|<<content.javascript.clipboard,content.javascript.clipboard>>|Allow JavaScript to read from or write to the clipboard. |<<content.javascript.enabled,content.javascript.enabled>>|Enable JavaScript. |<<content.javascript.log,content.javascript.log>>|Log levels to use for JavaScript console logging messages. |<<content.javascript.modal_dialog,content.javascript.modal_dialog>>|Use the standard JavaScript modal dialog for `alert()` and `confirm()`. @@ -2333,17 +2333,6 @@ Type: <<types,Bool>> Default: +pass:[true]+ -[[content.javascript.can_access_clipboard]] -=== content.javascript.can_access_clipboard -Allow JavaScript to read from or write to the clipboard. -With QtWebEngine, writing the clipboard as response to a user interaction is always allowed. - -This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. - -Type: <<types,Bool>> - -Default: +pass:[false]+ - [[content.javascript.can_close_tabs]] === content.javascript.can_close_tabs Allow JavaScript to close tabs. @@ -2366,6 +2355,23 @@ Type: <<types,Bool>> Default: +pass:[false]+ +[[content.javascript.clipboard]] +=== content.javascript.clipboard +Allow JavaScript to read from or write to the clipboard. +With QtWebEngine, writing the clipboard as response to a user interaction is always allowed. + +This setting supports link:configuring{outfilesuffix}#patterns[URL patterns]. + +Type: <<types,String>> + +Valid values: + + * +none+: Disable access to clipboard. + * +access+: Allow reading from and writing to the clipboard. + * +access-paste+: Allow accessing the clipboard and pasting clipboard content. + +Default: +pass:[none]+ + [[content.javascript.enabled]] === content.javascript.enabled Enable JavaScript. @@ -3441,6 +3447,7 @@ Default: * +pass:[[role="menuitem"\]]+ * +pass:[[role="menuitemcheckbox"\]]+ * +pass:[[role="menuitemradio"\]]+ +* +pass:[[role="treeitem"\]]+ * +pass:[[ng-click\]]+ * +pass:[[ngClick\]]+ * +pass:[[data-ng-click\]]+ diff --git a/doc/install.asciidoc b/doc/install.asciidoc index bb4e08f5a..0f44e5a91 100644 --- a/doc/install.asciidoc +++ b/doc/install.asciidoc @@ -48,7 +48,7 @@ instructions! Note you'll need some basic libraries to use the virtualenv-installed PyQt: ---- -# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev libnss3 +# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev libnss3 libasound2 ---- // FIXME not needed anymore? @@ -69,6 +69,12 @@ from January 2018 is packaged, with QtWebEngine 5.9 based on a Chromium from Jan 2017. It's recommended to either upgrade to Ubuntu 20.04 LTS or <<tox,install qutebrowser in a virtualenv>> with a newer PyQt/Qt binary instead. +Note you'll need some basic libraries to use the virtualenv-installed PyQt: + +---- +# apt install --no-install-recommends git ca-certificates python3 python3-venv asciidoc libglib2.0-0 libgl1 libfontconfig1 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xkb1 libxkbcommon-x11-0 libdbus-1-3 libyaml-dev gcc python3-dev libnss3 libasound2 +---- + Ubuntu 20.04 LTS / Linux Mint 20 (or newer) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/stacktrace.asciidoc b/doc/stacktrace.asciidoc index 3d95aa25e..87410bd48 100644 --- a/doc/stacktrace.asciidoc +++ b/doc/stacktrace.asciidoc @@ -68,55 +68,13 @@ Then install the needed debuginfo packages: Archlinux ^^^^^^^^^ -For Archlinux, no debug information is provided. You can either compile Qt -yourself (which will take a few hours even on a modern machine) or use -debugging symbols compiled/packaged by me (x86_64 only). - -To install my pre-built packages -++++++++++++++++++++++++++++++++ - -First download and sign the key: - ----- -# pacman-key -r 0xD6A1C70FE80A0C82 -$ pacman-key -f 0xD6A1C70FE80A0C82 - Key fingerprint = 14AF EC28 70C6 4863 C5C7 ACCB D6A1 C70F E80A 0C82 -# pacman-key --lsign-key 0xD6A1C70FE80A0C82 ----- - -Then edit your `/etc/pacman.conf` to add the repository to the bottom: +For Archlinux, debug information is provided via their https://wiki.archlinux.org/title/Debuginfod[Debuginfod instance]. To use it, set the following in your environment: ---- -[qt-debug] -Server = https://qutebrowser.org/qt-debug/$arch +DEBUGINFOD_URLS="https://debuginfod.archlinux.org/" ---- -Then install the packages: - ----- -# pacman -Suy python-pyqt5-debug qt5-base-debug qt5-webkit-debug qt5-webengine-debug ----- - -The `-debug` packages conflict with the non-debug variants - it's safe to -remove them. - -To compile by yourself -++++++++++++++++++++++ - -Note that building Qt will likely take multiple hours, even on a recent system. -I'd also expect it to take around 6 GB of RAM and 30 GB of disk space for a -successful compile run. - ----- -$ git clone https://github.com/qutebrowser/qt-debug-pkgbuild.git -$ cd qt-debug-pkgbuild -$ export DEBUG_CFLAGS='-ggdb3 -fvar-tracking-assignments -Og' -$ export DEBUG_CXXFLAGS='-ggdb3 -fvar-tracking-assignments -Og' -$ cd qt5 -$ makepkg -si --pkg qt5-base-debug,qt5-webkit-debug,qt5-webengine-debug -$ cd ../pyqt5 -$ makepkg -si --pkg python-pyqt5-debug ----- +(Until early 2021, there was a custom [`qt-debug` repository](https://github.com/qutebrowser/qt-debug-pkgbuild). This is now archived.) Getting the stack trace ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml index a18511b73..f506bd5a8 100644 --- a/misc/org.qutebrowser.qutebrowser.appdata.xml +++ b/misc/org.qutebrowser.qutebrowser.appdata.xml @@ -44,6 +44,7 @@ </content_rating> <releases> <!-- Add new releases here --> +<release version="2.5.1" date="2022-05-26"/> <release version="2.5.0" date="2022-04-01"/> <release version="2.4.0" date="2021-10-21"/> <release version="2.3.1" date="2021-07-28"/> diff --git a/misc/qutebrowser.spec b/misc/qutebrowser.spec index 3c75d1b90..60729266e 100644 --- a/misc/qutebrowser.spec +++ b/misc/qutebrowser.spec @@ -2,7 +2,6 @@ import sys import os -import pathlib sys.path.insert(0, os.getcwd()) from scripts import setupcommon @@ -42,10 +41,7 @@ setupcommon.write_git_file() if os.name == 'nt': - # WORKAROUND for PyInstaller 5.0 bug: - # https://github.com/pyinstaller/pyinstaller/issues/6759 - icons_path = pathlib.Path.cwd() / 'qutebrowser' / 'icons' - icon = str(icons_path / 'qutebrowser.ico') + icon = '../qutebrowser/icons/qutebrowser.ico' elif sys.platform == 'darwin': icon = '../qutebrowser/icons/qutebrowser.icns' else: diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt index 012a3dc05..c51ef3d0e 100644 --- a/misc/requirements/requirements-check-manifest.txt +++ b/misc/requirements/requirements-check-manifest.txt @@ -1,8 +1,8 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==0.7.0 +build==0.8.0 check-manifest==0.48 packaging==21.3 pep517==0.12.0 -pyparsing==3.0.8 +pyparsing==3.0.9 tomli==2.0.1 diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt index 462ea6e9e..e4e768353 100644 --- a/misc/requirements/requirements-dev.txt +++ b/misc/requirements/requirements-dev.txt @@ -1,43 +1,43 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py bleach==5.0.0 -build==0.7.0 +build==0.8.0 bump2version==1.0.1 -certifi==2021.10.8 +certifi==2022.5.18.1 cffi==1.15.0 charset-normalizer==2.0.12 commonmark==0.9.1 -cryptography==37.0.1 +cryptography==37.0.2 docutils==0.18.1 github3.py==3.2.0 hunter==3.4.3 idna==3.3 -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 jeepney==0.8.0 -keyring==23.5.0 +keyring==23.6.0 manhole==1.8.0 packaging==21.3 pep517==0.12.0 -pkginfo==1.8.2 +pkginfo==1.8.3 ply==3.11 pycparser==2.21 Pygments==2.12.0 -PyJWT==2.3.0 +PyJWT==2.4.0 Pympler==1.0.1 -pyparsing==3.0.8 +pyparsing==3.0.9 PyQt-builder==1.12.2 python-dateutil==2.8.2 readme-renderer==35.0 -requests==2.27.1 +requests==2.28.0 requests-toolbelt==0.9.1 rfc3986==2.0.0 -rich==12.3.0 +rich==12.4.4 SecretStorage==3.3.2 sip==6.6.1 six==1.16.0 toml==0.10.2 tomli==2.0.1 -twine==4.0.0 +twine==4.0.1 typing_extensions==4.2.0 uritemplate==4.1.1 # urllib3==1.26.9 diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt index 8cfd81966..217089191 100644 --- a/misc/requirements/requirements-flake8.txt +++ b/misc/requirements/requirements-flake8.txt @@ -4,7 +4,7 @@ attrs==21.4.0 flake8==4.0.1 flake8-bugbear==22.4.25 flake8-builtins==1.5.3 -flake8-comprehensions==3.8.0 +flake8-comprehensions==3.10.0 flake8-copyright==0.2.2 flake8-debugger==4.1.2 flake8-deprecated==1.3 @@ -12,13 +12,12 @@ flake8-docstrings==1.6.0 flake8-future-import==0.4.6 flake8-mock==0.3 flake8-plugin-utils==1.3.2 -flake8-polyfill==1.0.2 flake8-pytest-style==1.6.0 flake8-string-format==0.3.0 -flake8-tidy-imports==4.6.0 +flake8-tidy-imports==4.8.0 flake8-tuple==0.4.1 mccabe==0.6.1 -pep8-naming==0.12.1 +pep8-naming==0.13.0 pycodestyle==2.8.0 pydocstyle==6.1.1 pyflakes==2.4.0 diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt index 2f62bf818..a4b555cf3 100644 --- a/misc/requirements/requirements-mypy.txt +++ b/misc/requirements/requirements-mypy.txt @@ -2,17 +2,17 @@ chardet==4.0.0 diff-cover==6.5.0 -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 importlib-resources==5.7.1 Jinja2==3.1.2 -lxml==4.8.0 +lxml==4.9.0 MarkupSafe==2.1.1 -mypy==0.950 +mypy==0.961 mypy-extensions==0.4.3 pluggy==1.0.0 Pygments==2.12.0 PyQt5-stubs==5.15.6.0 tomli==2.0.1 -types-PyYAML==6.0.7 +types-PyYAML==6.0.8 typing_extensions==4.2.0 zipp==3.8.0 diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt index 8700478e5..35e65b6da 100644 --- a/misc/requirements/requirements-pyinstaller.txt +++ b/misc/requirements/requirements-pyinstaller.txt @@ -1,5 +1,5 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py altgraph==0.17.2 -pyinstaller==5.0.1 -pyinstaller-hooks-contrib==2022.4 +pyinstaller==5.1 +pyinstaller-hooks-contrib==2022.7 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index 5a21eb210..38231fa12 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -1,29 +1,30 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -astroid==2.11.2 -certifi==2021.10.8 +astroid==2.11.5 +certifi==2022.5.18.1 cffi==1.15.0 charset-normalizer==2.0.12 -cryptography==37.0.1 -dill==0.3.4 +cryptography==37.0.2 +dill==0.3.5.1 future==0.18.2 github3.py==3.2.0 idna==3.3 isort==5.10.1 lazy-object-proxy==1.7.1 mccabe==0.7.0 -pefile==2021.9.3 +pefile==2022.5.30 platformdirs==2.5.2 pycparser==2.21 -PyJWT==2.3.0 -pylint==2.13.5 +PyJWT==2.4.0 +pylint==2.14.1 python-dateutil==2.8.2 ./scripts/dev/pylint_checkers -requests==2.27.1 +requests==2.28.0 six==1.16.0 tomli==2.0.1 -typed-ast==1.5.3 ; python_version<"3.8" +tomlkit==0.11.0 +typed-ast==1.5.4 ; python_version<"3.8" typing_extensions==4.2.0 uritemplate==4.1.1 # urllib3==1.26.9 -wrapt==1.14.0 +wrapt==1.14.1 diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw index 0d59fc96d..54e12a02a 100644 --- a/misc/requirements/requirements-pylint.txt-raw +++ b/misc/requirements/requirements-pylint.txt-raw @@ -1,6 +1,4 @@ pylint -# WORKAROUND for https://github.com/PyCQA/pylint/issues/6438#issuecomment-1108747642 -astroid != 2.11.3 ./scripts/dev/pylint_checkers requests github3.py diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt index d17b46593..382418dd9 100644 --- a/misc/requirements/requirements-pyroma.txt +++ b/misc/requirements/requirements-pyroma.txt @@ -1,15 +1,15 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -build==0.7.0 -certifi==2021.10.8 +build==0.8.0 +certifi==2022.5.18.1 charset-normalizer==2.0.12 docutils==0.18.1 idna==3.3 packaging==21.3 pep517==0.12.0 Pygments==2.12.0 -pyparsing==3.0.8 +pyparsing==3.0.9 pyroma==4.0 -requests==2.27.1 +requests==2.28.0 tomli==2.0.1 urllib3==1.26.9 diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt index abf2a2720..f100b6dc0 100644 --- a/misc/requirements/requirements-sphinx.txt +++ b/misc/requirements/requirements-sphinx.txt @@ -2,21 +2,21 @@ alabaster==0.7.12 Babel==2.10.1 -certifi==2021.10.8 +certifi==2022.5.18.1 charset-normalizer==2.0.12 -docutils==0.17.1 +docutils==0.18.1 idna==3.3 imagesize==1.3.0 -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 Jinja2==3.1.2 MarkupSafe==2.1.1 packaging==21.3 Pygments==2.12.0 -pyparsing==3.0.8 +pyparsing==3.0.9 pytz==2022.1 -requests==2.27.1 +requests==2.28.0 snowballstemmer==2.2.0 -Sphinx==4.5.0 +Sphinx==5.0.1 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.0 diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt index 72d6ad083..fca7328f8 100644 --- a/misc/requirements/requirements-tests-bleeding.txt +++ b/misc/requirements/requirements-tests-bleeding.txt @@ -24,8 +24,6 @@ git+https://github.com/pytest-dev/pytest-cov.git git+https://github.com/The-Compiler/pytest-xvfb.git git+https://github.com/pytest-dev/pytest-xdist.git git+https://github.com/john-kurkowski/tldextract -# https://github.com/hjwp/pytest-icdiff/pull/20 -# git+https://github.com/hjwp/pytest-icdiff.git # Problematic: needs rust (and some time to build) # git+https://github.com/ArniDagur/python-adblock.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index e77cf12d5..3e9f3233d 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,20 +2,20 @@ attrs==21.4.0 beautifulsoup4==4.11.1 -certifi==2021.10.8 +certifi==2022.5.18.1 charset-normalizer==2.0.12 cheroot==8.6.0 click==8.1.3 -coverage==6.3.2 +coverage==6.4.1 +exceptiongroup==1.0.0rc8 execnet==1.9.0 -filelock==3.6.0 +filelock==3.7.1 Flask==2.1.2 glob2==0.7 hunter==3.4.3 -hypothesis==6.46.1 -icdiff==2.0.5 +hypothesis==6.47.2 idna==3.3 -importlib-metadata==4.11.3 +importlib-metadata==4.11.4 iniconfig==1.1.1 itsdangerous==2.1.2 jaraco.functools==3.5.0 @@ -23,22 +23,20 @@ jaraco.functools==3.5.0 Mako==1.2.0 manhole==1.8.0 # MarkupSafe==2.1.1 -more-itertools==8.12.0 +more-itertools==8.13.0 packaging==21.3 parse==1.19.0 parse-type==0.6.0 pluggy==1.0.0 -pprintpp==0.4.0 py==1.11.0 py-cpuinfo==8.0.0 Pygments==2.12.0 -pyparsing==3.0.8 +pyparsing==3.0.9 pytest==7.1.2 pytest-bdd==4.1.0 pytest-benchmark==3.4.1 pytest-cov==3.0.0 pytest-forked==1.4.0 -pytest-icdiff==0.5 pytest-instafail==0.4.2 pytest-mock==3.7.0 pytest-qt==4.0.2 @@ -47,15 +45,15 @@ pytest-rerunfailures==10.2 pytest-xdist==2.5.0 pytest-xvfb==2.0.0 PyVirtualDisplay==3.0 -requests==2.27.1 +requests==2.28.0 requests-file==1.5.1 six==1.16.0 sortedcontainers==2.4.0 soupsieve==2.3.2.post1 -tldextract==3.2.1 +tldextract==3.3.0 toml==0.10.2 tomli==2.0.1 urllib3==1.26.9 -vulture==2.3 +vulture==2.4 Werkzeug==2.1.2 zipp==3.8.0 diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw index 5586a86ef..f5edc9b6d 100644 --- a/misc/requirements/requirements-tests.txt-raw +++ b/misc/requirements/requirements-tests.txt-raw @@ -28,8 +28,6 @@ pytest-xvfb PyVirtualDisplay # To run on multiple cores with -n pytest-xdist -# For nicer output -pytest-icdiff # Needed to test misc/userscripts/qute-lastpass tldextract diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index f6c14de9f..533e91e82 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,14 +1,14 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py distlib==0.3.4 -filelock==3.6.0 +filelock==3.7.1 packaging==21.3 -pip==22.0.4 +pip==22.1.2 platformdirs==2.5.2 pluggy==1.0.0 py==1.11.0 -pyparsing==3.0.8 -setuptools==62.1.0 +pyparsing==3.0.9 +setuptools==62.3.4 six==1.16.0 toml==0.10.2 tox==3.25.0 diff --git a/misc/requirements/requirements-vulture.txt b/misc/requirements/requirements-vulture.txt index 433500bf0..ac5016c99 100644 --- a/misc/requirements/requirements-vulture.txt +++ b/misc/requirements/requirements-vulture.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py toml==0.10.2 -vulture==2.3 +vulture==2.4 diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md index e800de0ce..5a7a3d51d 100644 --- a/misc/userscripts/README.md +++ b/misc/userscripts/README.md @@ -74,6 +74,8 @@ The following userscripts can be found on their own repositories. and retrieve they when you want. - [doi](https://github.com/cadadr/configuration/blob/master/qutebrowser/userscripts/doi): Opens DOIs on Sci-Hub. +- [qute-1password](https://github.com/fmartingr/qute-1password): + Qutebrowser userscript to fill 1password credentials - [1password](https://github.com/tomoakley/dotfiles/blob/master/qutebrowser/userscripts/1password): Integration with 1password on macOS. - [localhost](https://github.com/SidharthArya/.qutebrowser/blob/master/userscripts/localhost): diff --git a/misc/userscripts/readability-js b/misc/userscripts/readability-js index 485957ddb..752b759bb 100755 --- a/misc/userscripts/readability-js +++ b/misc/userscripts/readability-js @@ -59,9 +59,6 @@ const HEADER = ` width: 100%; margin: 0 0; } - a.reader-title { - color: #FFFFFF !important; - } img { max-width:100%; height:auto; diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py index c38435242..75d4e0532 100644 --- a/qutebrowser/__init__.py +++ b/qutebrowser/__init__.py @@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2021 Florian Bruhin (The Compiler)" __license__ = "GPL" __maintainer__ = __author__ __email__ = "mail@qutebrowser.org" -__version__ = "2.5.0" +__version__ = "2.5.1" __version_info__ = tuple(int(part) for part in __version__.split('.')) __description__ = "A keyboard-driven, vim-like browser based on Python and Qt." diff --git a/qutebrowser/browser/inspector.py b/qutebrowser/browser/inspector.py index 5908890ba..0eafa0536 100644 --- a/qutebrowser/browser/inspector.py +++ b/qutebrowser/browser/inspector.py @@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent from PyQt5.QtGui import QCloseEvent from qutebrowser.browser import eventfilter -from qutebrowser.config import configfiles +from qutebrowser.config import configfiles, config from qutebrowser.utils import log, usertypes from qutebrowser.keyinput import modeman from qutebrowser.misc import miscwidgets @@ -137,7 +137,8 @@ class AbstractWebInspector(QWidget): @pyqtSlot() def _on_clicked(self) -> None: """Enter insert mode if a docked inspector was clicked.""" - if self._position != Position.window: + if (self._position != Position.window and + config.val.input.insert_mode.auto_enter): modeman.enter(self._win_id, usertypes.KeyMode.insert, reason='Inspector clicked', only_if_normal=True) diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index f195bbf28..3ea323d96 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -341,7 +341,8 @@ def get_user_stylesheet(searching=False): 'misc-mathml-darkmode' not in config.val.content.site_specific_quirks.skip): # WORKAROUND for MathML-output on Wikipedia being black on black. # See https://bugs.chromium.org/p/chromium/issues/detail?id=1126606 - css += '\nimg.mwe-math-fallback-image-inline { filter: invert(100%); }' + css += ('\nimg.mwe-math-fallback-image-inline, ' + 'img.mwe-math-fallback-image-display { filter: invert(100%); }') return css diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index e4ad4d763..3571bb24d 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -621,18 +621,19 @@ class HerbeNotificationAdapter(AbstractNotificationAdapter): so there's no point. """ if status == QProcess.CrashExit: - return - - if code == 0: + pass + elif code == 0: self.click_id.emit(pid) elif code == 2: - self.close_id.emit(pid) + pass else: proc = self.sender() assert isinstance(proc, QProcess), proc stderr = proc.readAllStandardError() raise Error(f'herbe exited with status {code}: {stderr}') + self.close_id.emit(pid) + @pyqtSlot(QProcess.ProcessError) def _on_error(self, error: QProcess.ProcessError) -> None: if error == QProcess.Crashed: diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py index 0b25726c0..ad374ed9b 100644 --- a/qutebrowser/browser/webengine/webenginesettings.py +++ b/qutebrowser/browser/webengine/webenginesettings.py @@ -117,8 +117,6 @@ class WebEngineSettings(websettings.AbstractSettings): Attr(QWebEngineSettings.JavascriptEnabled), 'content.javascript.can_open_tabs_automatically': Attr(QWebEngineSettings.JavascriptCanOpenWindows), - 'content.javascript.can_access_clipboard': - Attr(QWebEngineSettings.JavascriptCanAccessClipboard), 'content.plugins': Attr(QWebEngineSettings.PluginsEnabled), 'content.hyperlink_auditing': @@ -199,26 +197,47 @@ class WebEngineSettings(websettings.AbstractSettings): QWebEngineSettings.FantasyFont: QFont.Fantasy, } - def set_unknown_url_scheme_policy( - self, policy: Union[str, usertypes.Unset]) -> bool: - """Set the UnknownUrlSchemePolicy to use. + _JS_CLIPBOARD_SETTINGS = { + 'none': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: False, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: False, + }, + 'access': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: True, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: False, + }, + 'access-paste': { + QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard: True, + QWebEngineSettings.WebAttribute.JavascriptCanPaste: True, + }, + } - Return: - True if there was a change, False otherwise. - """ - old_value = self._settings.unknownUrlSchemePolicy() + def set_unknown_url_scheme_policy( + self, policy: Union[str, usertypes.Unset]) -> None: + """Set the UnknownUrlSchemePolicy to use.""" if isinstance(policy, usertypes.Unset): self._settings.resetUnknownUrlSchemePolicy() - new_value = self._settings.unknownUrlSchemePolicy() else: new_value = self._UNKNOWN_URL_SCHEME_POLICY[policy] self._settings.setUnknownUrlSchemePolicy(new_value) - return old_value != new_value + + def _set_js_clipboard(self, value: Union[str, usertypes.Unset]) -> None: + attr_access = QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard + attr_paste = QWebEngineSettings.WebAttribute.JavascriptCanPaste + + if isinstance(value, usertypes.Unset): + self._settings.resetAttribute(attr_access) + self._settings.resetAttribute(attr_paste) + else: + for attr, attr_val in self._JS_CLIPBOARD_SETTINGS[value].items(): + self._settings.setAttribute(attr, attr_val) def _update_setting(self, setting, value): if setting == 'content.unknown_url_scheme_policy': - return self.set_unknown_url_scheme_policy(value) - return super()._update_setting(setting, value) + self.set_unknown_url_scheme_policy(value) + elif setting == 'content.javascript.clipboard': + self._set_js_clipboard(value) + super()._update_setting(setting, value) def init_settings(self): super().init_settings() diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 15729ccdc..0a2333afc 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -93,9 +93,7 @@ class WebEnginePrinting(browsertab.AbstractPrinting): def to_pdf(self, filename): self._widget.page().printToPdf(filename) - def to_printer(self, printer, callback=None): - if callback is None: - callback = lambda _ok: None + def to_printer(self, printer, callback=lambda ok: None): self._widget.page().print(printer, callback) diff --git a/qutebrowser/browser/webkit/http.py b/qutebrowser/browser/webkit/http.py index d13471277..a38cd358a 100644 --- a/qutebrowser/browser/webkit/http.py +++ b/qutebrowser/browser/webkit/http.py @@ -89,13 +89,16 @@ class ContentDisposition: try: parsed = reg('Content-Disposition', decoded) except IndexError: # pragma: no cover - # WORKAROUND for https://bugs.python.org/issue37491 + # WORKAROUND for https://github.com/python/cpython/issues/81672 # Fixed in Python 3.7.5 and 3.8.0. # Still getting failures on 3.10 on CI though raise ContentDispositionError("Missing closing quote character") except ValueError: # pragma: no cover - # WORKAROUND for https://bugs.python.org/issue42946 + # WORKAROUND for https://github.com/python/cpython/issues/87112 raise ContentDispositionError("Non-ASCII digit") + except AttributeError: + # WORKAROUND for https://github.com/python/cpython/issues/93010 + raise ContentDispositionError("Section number has an invalid leading 0") if parsed.defects: defects = list(parsed.defects) diff --git a/qutebrowser/browser/webkit/webkitsettings.py b/qutebrowser/browser/webkit/webkitsettings.py index cac6236ce..1348c849b 100644 --- a/qutebrowser/browser/webkit/webkitsettings.py +++ b/qutebrowser/browser/webkit/webkitsettings.py @@ -57,8 +57,9 @@ class WebKitSettings(websettings.AbstractSettings): Attr(QWebSettings.JavascriptCanOpenWindows), 'content.javascript.can_close_tabs': Attr(QWebSettings.JavascriptCanCloseWindows), - 'content.javascript.can_access_clipboard': - Attr(QWebSettings.JavascriptCanAccessClipboard), + 'content.javascript.clipboard': + Attr(QWebSettings.JavascriptCanAccessClipboard, + converter=lambda val: val != "none"), 'content.plugins': Attr(QWebSettings.PluginsEnabled), 'content.webgl': diff --git a/qutebrowser/commands/cmdexc.py b/qutebrowser/commands/cmdexc.py index fdd06537f..50488f1e5 100644 --- a/qutebrowser/commands/cmdexc.py +++ b/qutebrowser/commands/cmdexc.py @@ -22,6 +22,9 @@ Defined here to avoid circular dependency hell. """ +from typing import List +import difflib + class Error(Exception): @@ -32,6 +35,24 @@ class NoSuchCommandError(Error): """Raised when a command isn't found.""" + @classmethod + def for_cmd(cls, cmd: str, all_commands: List[str] = None) -> "NoSuchCommandError": + """Raise an exception for the given command.""" + suffix = '' + if all_commands: + matches = difflib.get_close_matches(cmd, all_commands, n=1) + if matches: + suffix = f' (did you mean :{matches[0]}?)' + return cls(f"{cmd}: no such command{suffix}") + + +class EmptyCommandError(NoSuchCommandError): + + """Raised when no command was given.""" + + def __init__(self): + super().__init__("No command given") + class ArgumentTypeError(Error): diff --git a/qutebrowser/commands/parser.py b/qutebrowser/commands/parser.py index 06a20cdf6..5ef46f5e5 100644 --- a/qutebrowser/commands/parser.py +++ b/qutebrowser/commands/parser.py @@ -43,10 +43,18 @@ class CommandParser: Attributes: _partial_match: Whether to allow partial command matches. + _find_similar: Whether to find similar matches on unknown commands. + If we use this for completion, errors are not shown in the UI, + so we don't need to search. """ - def __init__(self, partial_match: bool = False) -> None: + def __init__( + self, + partial_match: bool = False, + find_similar: bool = False, + ) -> None: self._partial_match = partial_match + self._find_similar = find_similar def _get_alias(self, text: str, *, default: str) -> str: """Get an alias from the config. @@ -95,7 +103,7 @@ class CommandParser: """ text = text.strip().lstrip(':').strip() if not text: - raise cmdexc.NoSuchCommandError("No command given") + raise cmdexc.EmptyCommandError if aliases: text = self._get_alias(text, default=text) @@ -128,7 +136,7 @@ class CommandParser: cmdstr, sep, argstr = text.partition(' ') if not cmdstr: - raise cmdexc.NoSuchCommandError("No command given") + raise cmdexc.EmptyCommandError if self._partial_match: cmdstr = self._completion_match(cmdstr) @@ -136,7 +144,10 @@ class CommandParser: try: cmd = objects.commands[cmdstr] except KeyError: - raise cmdexc.NoSuchCommandError(f'{cmdstr}: no such command') + raise cmdexc.NoSuchCommandError.for_cmd( + cmdstr, + all_commands=list(objects.commands) if self._find_similar else [], + ) args = self._split_args(cmd, argstr, keep) if keep and args: diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 5fb054455..e3cd0cc97 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -138,9 +138,12 @@ class CommandRunner(AbstractCommandRunner): _win_id: The window this CommandRunner is associated with. """ - def __init__(self, win_id, partial_match=False, parent=None): + def __init__(self, win_id, partial_match=False, find_similar=True, parent=None): super().__init__(parent) - self._parser = parser.CommandParser(partial_match=partial_match) + self._parser = parser.CommandParser( + partial_match=partial_match, + find_similar=find_similar, + ) self._win_id = win_id @contextlib.contextmanager diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index b94077c6d..cf6984288 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -40,7 +40,8 @@ class CompletionInfo: """Context passed into all completion functions.""" config: config.Config - keyconf: config.KeyConfig # pylint: disable=used-before-assignment + # pylint: disable-next=used-before-assignment + keyconf: config.KeyConfig # type: ignore[name-defined] win_id: int cur_tab: 'browsertab.AbstractTab' @@ -164,6 +165,7 @@ class Completer(QObject): # cursor is in a space between two existing words parts.insert(i, '') prefix = [x.strip() for x in parts[:i]] + # pylint: disable-next=unnecessary-list-index-lookup center = parts[i].strip() # strip trailing whitespace included as a separate token postfix = [x.strip() for x in parts[i+1:] if not x.isspace()] diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py index 7c8473b3f..6c85fbb29 100644 --- a/qutebrowser/completion/models/configmodel.py +++ b/qutebrowser/completion/models/configmodel.py @@ -44,16 +44,22 @@ def customized_option(*, info): def list_option(*, info): """A CompletionModel filled with settings whose values are lists.""" - predicate = lambda opt: (isinstance(info.config.get_obj(opt.name), - list) and not opt.no_autoconfig) - return _option(info, "List options", predicate) + return _option( + info, + "List options", + lambda opt: (isinstance(info.config.get_obj(opt.name), list) and + not opt.no_autoconfig) + ) def dict_option(*, info): """A CompletionModel filled with settings whose values are dicts.""" - predicate = lambda opt: (isinstance(info.config.get_obj(opt.name), - dict) and not opt.no_autoconfig) - return _option(info, "Dict options", predicate) + return _option( + info, + "Dict options", + lambda opt: (isinstance(info.config.get_obj(opt.name), dict) and + not opt.no_autoconfig) + ) def _option(info, title, predicate): diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 834709ae6..918c9b894 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -373,7 +373,11 @@ class Config(QObject): deleted = name in configdata.MIGRATIONS.deleted renamed = configdata.MIGRATIONS.renamed.get(name) exception = configexc.NoOptionError( - name, deleted=deleted, renamed=renamed) + name, + deleted=deleted, + renamed=renamed, + all_names=list(configdata.DATA), + ) raise exception from None def ensure_has_opt(self, name: str) -> None: diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 771cf0493..e91d9aaf1 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -869,9 +869,14 @@ content.javascript.alert: type: Bool desc: Show javascript alerts. -content.javascript.can_access_clipboard: - default: false - type: Bool +content.javascript.clipboard: + default: none + type: + name: String + valid_values: + - none: Disable access to clipboard. + - access: Allow reading from and writing to the clipboard. + - access-paste: Allow accessing the clipboard and pasting clipboard content. supports_pattern: true desc: >- Allow JavaScript to read from or write to the clipboard. @@ -1680,6 +1685,7 @@ hints.selectors: - '[role="menuitem"]' - '[role="menuitemcheckbox"]' - '[role="menuitemradio"]' + - '[role="treeitem"]' - '[ng-click]' - '[ngClick]' - '[data-ng-click]' diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 27a1001b5..58e5ad67a 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -19,8 +19,9 @@ """Exceptions related to config parsing.""" +import difflib import dataclasses -from typing import Any, Mapping, Optional, Sequence, Union +from typing import Any, Mapping, Optional, Sequence, Union, List from qutebrowser.utils import usertypes, log @@ -91,6 +92,7 @@ class NoOptionError(Error): """Raised when an option was not found.""" def __init__(self, option: str, *, + all_names: List[str] = None, deleted: bool = False, renamed: str = None) -> None: if deleted: @@ -98,6 +100,12 @@ class NoOptionError(Error): suffix = ' (this option was removed from qutebrowser)' elif renamed is not None: suffix = ' (this option was renamed to {!r})'.format(renamed) + elif all_names: + matches = difflib.get_close_matches(option, all_names, n=1) + if matches: + suffix = f' (did you mean {matches[0]!r}?)' + else: + suffix = '' else: suffix = '' diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py index d97771fad..f2651d1cd 100644 --- a/qutebrowser/config/configfiles.py +++ b/qutebrowser/config/configfiles.py @@ -422,6 +422,12 @@ class YamlMigrations(QObject): false_value='load-insecurely', ask_value='ask', ) + self._migrate_renamed_bool( + old_name='content.javascript.can_access_clipboard', + new_name='content.javascript.clipboard', + true_value='access', + false_value='none', + ) for setting in ['colors.webpage.force_dark_color_scheme', 'colors.webpage.prefers_color_scheme_dark']: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index d3d5e3fb8..97011b7cf 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -109,6 +109,8 @@ class ValidValues: values: A list with the allowed untransformed values. descriptions: A dict with value/desc mappings. generate_docs: Whether to show the values in the docs. + others_permitted: Whether arbitrary values are permitted. + Used to show buttons in qute://settings. """ def __init__( @@ -119,12 +121,14 @@ class ValidValues: Tuple[str, Optional[str]], ], generate_docs: bool = True, + others_permitted: bool = False ) -> None: if not values: raise ValueError("ValidValues with no values makes no sense!") self.descriptions: DictType[str, str] = {} self.values: ListType[str] = [] self.generate_docs = generate_docs + self.others_permitted = others_permitted for value in values: if isinstance(value, str): # Value without description @@ -1638,7 +1642,9 @@ class Proxy(BaseType): super().__init__(none_ok=none_ok, completions=completions) self.valid_values = ValidValues( ('system', "Use the system wide proxy."), - ('none', "Don't use any proxy")) + ('none', "Don't use any proxy"), + others_permitted=True, + ) def to_py( self, diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py index 41aeec6a3..779ed0b6b 100644 --- a/qutebrowser/config/websettings.py +++ b/qutebrowser/config/websettings.py @@ -164,14 +164,10 @@ class AbstractSettings: assert encoding is not usertypes.UNSET # unclear how to reset self._settings.setDefaultTextEncoding(encoding) - def _update_setting(self, setting: str, value: Any) -> bool: + def _update_setting(self, setting: str, value: Any) -> None: """Update the given setting/value. - Unknown settings are ignored. - - Return: - True if there was a change, False otherwise. - """ + Unknown settings are ignored.""" if setting in self._ATTRIBUTES: self.set_attribute(setting, value) elif setting in self._FONT_SIZES: @@ -180,7 +176,6 @@ class AbstractSettings: self.set_font_family(setting, value) elif setting == 'content.default_encoding': self.set_default_text_encoding(value) - return False def update_setting(self, setting: str) -> None: """Update the given setting.""" diff --git a/qutebrowser/html/settings.html b/qutebrowser/html/settings.html index b06917fd5..f89aaa610 100644 --- a/qutebrowser/html/settings.html +++ b/qutebrowser/html/settings.html @@ -179,9 +179,10 @@ summary::selection { </div> {% endif %} </td> - {% if option.typ.valid_values is not none %} + {% set valid_values = option.typ.valid_values %} + {% if valid_values is not none and not valid_values.others_permitted %} <td class="valid-value"> - {% for value in option.typ.valid_values.values %} + {% for value in valid_values.values %} <div class="radio-button"> <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}" name="{{ option.name }}" value="{{ value }}" diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index d7229bf31..b247da632 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -249,8 +249,8 @@ class MainWindow(QWidget): log.init.debug("Initializing modes...") modeman.init(win_id=self.win_id, parent=self) - self._commandrunner = runners.CommandRunner(self.win_id, - partial_match=True) + self._commandrunner = runners.CommandRunner( + self.win_id, partial_match=True, find_similar=True) self._keyhint = keyhintwidget.KeyHintView(self.win_id, self) self._add_overlay(self._keyhint, self._keyhint.update_geometry) diff --git a/qutebrowser/misc/elf.py b/qutebrowser/misc/elf.py index bf824880a..8fadbcffd 100644 --- a/qutebrowser/misc/elf.py +++ b/qutebrowser/misc/elf.py @@ -270,7 +270,7 @@ def _find_versions(data: bytes) -> Versions: correctly: https://github.com/python/typeshed/issues/1467 """ match = re.search( - br'QtWebEngine/([0-9.]+) Chrome/([0-9.]+)', + br'\x00QtWebEngine/([0-9.]+) Chrome/([0-9.]+)\x00', data, ) if match is None: diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py index ac565b68d..16a14d4bd 100644 --- a/qutebrowser/misc/throttle.py +++ b/qutebrowser/misc/throttle.py @@ -85,6 +85,7 @@ class Throttle(QObject): (cur_time_ms - self._last_call_ms)) # Disconnect any existing calls, continue if no connections. try: + # pylint: disable=no-value-for-parameter self._timer.timeout.disconnect() except TypeError: pass diff --git a/requirements.txt b/requirements.txt index 228d637a1..129fe402a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ adblock==0.5.2 colorama==0.4.4 -importlib-metadata==4.11.3 ; python_version=="3.7.*" +importlib-metadata==4.11.4 ; python_version=="3.7.*" importlib-resources==5.7.1 ; python_version=="3.7.*" or python_version=="3.8.*" Jinja2==3.1.2 MarkupSafe==2.1.1 diff --git a/scripts/asciidoc2html.py b/scripts/asciidoc2html.py index 6b4e3fb0d..ba8493247 100755 --- a/scripts/asciidoc2html.py +++ b/scripts/asciidoc2html.py @@ -43,7 +43,10 @@ class AsciiDoc: """Abstraction of an asciidoc subprocess.""" - FILES = ['faq', 'changelog', 'contributing', 'quickstart', 'userscripts'] + FILES = [ + 'faq', 'changelog', 'contributing', 'quickstart', 'userscripts', + 'install', 'stacktrace' + ] def __init__(self, asciidoc: Optional[str], diff --git a/scripts/dev/changelog_urls.json b/scripts/dev/changelog_urls.json index cc7a6f639..6b33f15ef 100644 --- a/scripts/dev/changelog_urls.json +++ b/scripts/dev/changelog_urls.json @@ -1,6 +1,7 @@ { "pyparsing": "https://github.com/pyparsing/pyparsing/blob/master/CHANGES", - "pylint": "https://pylint.pycqa.org/en/latest/whatsnew/changelog.html", + "pylint": "https://pylint.pycqa.org/en/latest/whatsnew/2/index.html", + "tomlkit": "https://github.com/sdispater/tomlkit/blob/master/CHANGELOG.md", "dill": "https://github.com/uqfoundation/dill/commits/master", "isort": "https://pycqa.github.io/isort/CHANGELOG/", "lazy-object-proxy": "https://github.com/ionelmc/python-lazy-object-proxy/blob/master/CHANGELOG.rst", @@ -13,7 +14,7 @@ "execnet": "https://execnet.readthedocs.io/en/latest/changelog.html", "pytest-rerunfailures": "https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst", "pytest-repeat": "https://github.com/pytest-dev/pytest-repeat/blob/master/CHANGES.rst", - "requests": "https://github.com/psf/requests/blob/master/HISTORY.md", + "requests": "https://github.com/psf/requests/blob/main/HISTORY.md", "requests-file": "https://github.com/dashea/requests-file/blob/master/CHANGES.rst", "Werkzeug": "https://werkzeug.palletsprojects.com/en/latest/changes/", "click": "https://click.palletsprojects.com/en/latest/changes/", @@ -25,6 +26,7 @@ "Mako": "https://docs.makotemplates.org/en/latest/changelog.html", "glob2": "https://github.com/miracle2k/python-glob2/blob/master/CHANGES", "hypothesis": "https://hypothesis.readthedocs.io/en/latest/changes.html", + "exceptiongroup": "https://github.com/agronholm/exceptiongroup/blob/main/CHANGES.rst", "mypy": "https://mypy-lang.blogspot.com/", "types-PyYAML": "https://github.com/python/typeshed/commits/master/stubs/PyYAML", "pytest": "https://docs.pytest.org/en/latest/changelog.html", @@ -51,7 +53,6 @@ "flake8-deprecated": "https://github.com/gforcada/flake8-deprecated/blob/master/CHANGES.rst", "flake8-future-import": "https://github.com/xZise/flake8-future-import#changes", "flake8-mock": "https://github.com/aleGpereira/flake8-mock#changes", - "flake8-polyfill": "https://gitlab.com/pycqa/flake8-polyfill/-/blob/master/CHANGELOG.rst", "flake8-string-format": "https://github.com/xZise/flake8-string-format#changes", "flake8-plugin-utils": "https://github.com/afonasev/flake8-plugin-utils#change-log", "flake8-pytest-style": "https://github.com/m-burst/flake8-pytest-style#change-log", @@ -119,9 +120,6 @@ "tldextract": "https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md", "typing_extensions": "https://github.com/python/typing/blob/master/typing_extensions/CHANGELOG", "diff-cover": "https://github.com/Bachmann1234/diff_cover/blob/master/CHANGELOG", - "pytest-icdiff": "https://github.com/hjwp/pytest-icdiff/blob/master/HISTORY.rst", - "icdiff": "https://github.com/jeffkaufman/icdiff/blob/master/ChangeLog", - "pprintpp": "https://github.com/wolever/pprintpp/blob/master/CHANGELOG.txt", "beautifulsoup4": "https://bazaar.launchpad.net/~leonardr/beautifulsoup/bs4/view/head:/CHANGELOG", "check-manifest": "https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst", "yamllint": "https://github.com/adrienverge/yamllint/blob/master/CHANGELOG.rst", diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py index 28c6e32c9..e044de976 100644 --- a/scripts/dev/run_pylint_on_tests.py +++ b/scripts/dev/run_pylint_on_tests.py @@ -64,6 +64,8 @@ def main(): 'import-error', # tests/helpers imports 'wrong-import-order', + # __tracebackhide__ + 'unnecessary-lambda-assignment', ] toxinidir = sys.argv[1] diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index febd2bf8a..1267a278a 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -176,13 +176,15 @@ def _get_setting_quickref(): def _get_configtypes(): """Get configtypes classes to document.""" - predicate = lambda e: ( - inspect.isclass(e) and - # pylint: disable=protected-access - e not in [configtypes.BaseType, configtypes.MappingType, - configtypes._Numeric, configtypes.FontBase] and - # pylint: enable=protected-access - issubclass(e, configtypes.BaseType)) + def predicate(e): + return ( + inspect.isclass(e) and + # pylint: disable=protected-access + e not in [configtypes.BaseType, configtypes.MappingType, + configtypes._Numeric, configtypes.FontBase] and + # pylint: enable=protected-access + issubclass(e, configtypes.BaseType) + ) yield from inspect.getmembers(configtypes, predicate) diff --git a/tests/end2end/data/darkmode/mathml-display.html b/tests/end2end/data/darkmode/mathml-display.html new file mode 100644 index 000000000..f0c280cca --- /dev/null +++ b/tests/end2end/data/darkmode/mathml-display.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>MathML-like SVG</title> + </head> + <body style="margin: 0; background-color: #ffff99"> + <!-- + Image based on: https://en.wikipedia.org/wiki/Pythagorean_theorem + with a black square added for testing. + + onload based on: + https://stackoverflow.com/questions/53423742/waiting-for-an-image-to-finish-rendering + --> + <img + class="mwe-math-fallback-image-display" + src="mathml.svg" + alt="Pythagorean theorem" + onload="requestAnimationFrame(() => requestAnimationFrame(() => console.log('Image loaded')));" + > + <!-- --> + </body> +</html> diff --git a/tests/end2end/data/darkmode/mathml.html b/tests/end2end/data/darkmode/mathml-inline.html index fa2371638..fa2371638 100644 --- a/tests/end2end/data/darkmode/mathml.html +++ b/tests/end2end/data/darkmode/mathml-inline.html diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index e6a02e038..bd8ada576 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -389,7 +389,7 @@ Feature: Various utility commands. Scenario: Partial commandline matching with startup command When I run :message-i "Hello World" (invalid command) - Then the error "message-i: no such command" should be shown + Then the error "message-i: no such command (did you mean :message-info?)" should be shown Scenario: Multiple leading : in command When I run :::::set-cmd-text ::::message-i "Hello World" diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 0d49ff109..5a34d0357 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -723,7 +723,8 @@ def test_dark_mode(webengine_versions, quteproc_new, request, ) -def test_dark_mode_mathml(quteproc_new, request, qtbot): +@pytest.mark.parametrize("suffix", ["inline", "display"]) +def test_dark_mode_mathml(quteproc_new, request, qtbot, suffix): if not request.config.webengine: pytest.skip("Skipped with QtWebKit") @@ -734,7 +735,7 @@ def test_dark_mode_mathml(quteproc_new, request, qtbot): ] quteproc_new.start(args) - quteproc_new.open_path('data/darkmode/mathml.html') + quteproc_new.open_path(f'data/darkmode/mathml-{suffix}.html') quteproc_new.wait_for_js('Image loaded') # First make sure loading finished by looking outside of the image diff --git a/tests/unit/browser/webkit/http/test_http.py b/tests/unit/browser/webkit/http/test_http.py index 4db78f4ff..d50f1c277 100644 --- a/tests/unit/browser/webkit/http/test_http.py +++ b/tests/unit/browser/webkit/http/test_http.py @@ -44,6 +44,21 @@ def test_no_content_disposition(stubs, url, expected): assert filename == expected +@pytest.mark.parametrize('value', [ + # https://github.com/python/cpython/issues/87112 + 'inline; 0*²'.encode("iso-8859-1"), + # https://github.com/python/cpython/issues/81672 + b'"', + # https://github.com/python/cpython/issues/93010 + b'attachment; 0*00="foo"', + # FIXME: Should probably have more tests if this is still relevant after + # dropping QtWebKit. +]) +def test_parse_content_disposition_invalid(value): + with pytest.raises(http.ContentDispositionError): + http.ContentDisposition.parse(value) + + @pytest.mark.parametrize('template', [ '{}', 'attachment; filename="{}"', diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py index f7cc3e8c2..7df91922d 100644 --- a/tests/unit/browser/webkit/test_webkitelem.py +++ b/tests/unit/browser/webkit/test_webkitelem.py @@ -195,6 +195,7 @@ class SelectionAndFilterTests: ('<p role="menuitem" foo="bar"/>', ['all']), ('<p role="menuitemcheckbox" foo="bar"/>', ['all']), ('<p role="menuitemradio" foo="bar"/>', ['all']), + ('<p role="treeitem" foo="bar"/>', ['all']), ('<p role="button" foo="bar"/>', ['all']), ('<p role="button" href="bar"/>', ['all', 'url']), diff --git a/tests/unit/commands/test_cmdexc.py b/tests/unit/commands/test_cmdexc.py new file mode 100644 index 000000000..f80131e29 --- /dev/null +++ b/tests/unit/commands/test_cmdexc.py @@ -0,0 +1,40 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2022 Florian Bruhin (The Compiler) <mail@qutebrowser.org> +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. + +"""Tests for qutebrowser.commands.cmdexc.""" + +import re +import pytest + +from qutebrowser.commands import cmdexc + + +def test_empty_command_error(): + with pytest.raises(cmdexc.NoSuchCommandError, match="No command given"): + raise cmdexc.EmptyCommandError + + +@pytest.mark.parametrize("all_commands, msg", [ + ([], "testcmd: no such command"), + (["fastcmd"], "testcmd: no such command (did you mean :fastcmd?)"), + (["thisdoesnotmatch"], "testcmd: no such command"), +]) +def test_no_such_command_error(all_commands, msg): + with pytest.raises(cmdexc.NoSuchCommandError, match=re.escape(msg)): + raise cmdexc.NoSuchCommandError.for_cmd("testcmd", all_commands=all_commands) diff --git a/tests/unit/commands/test_parser.py b/tests/unit/commands/test_parser.py index b851ad3b0..a0c2fe8f3 100644 --- a/tests/unit/commands/test_parser.py +++ b/tests/unit/commands/test_parser.py @@ -19,6 +19,8 @@ """Tests for qutebrowser.commands.parser.""" +import re + import pytest from qutebrowser.misc import objects @@ -64,7 +66,7 @@ class TestCommandParser: and https://github.com/qutebrowser/qutebrowser/issues/1773 """ p = parser.CommandParser() - with pytest.raises(cmdexc.NoSuchCommandError): + with pytest.raises(cmdexc.EmptyCommandError): p.parse_all(command) @pytest.mark.parametrize('command, name, args', [ @@ -135,3 +137,13 @@ class TestCompletions: result = p.parse('tw') assert result.cmd.name == 'two' + + +@pytest.mark.parametrize("find_similar, msg", [ + (True, "tabfocus: no such command (did you mean :tab-focus?)"), + (False, "tabfocus: no such command"), +]) +def test_find_similar(find_similar, msg): + p = parser.CommandParser(find_similar=find_similar) + with pytest.raises(cmdexc.NoSuchCommandError, match=re.escape(msg)): + p.parse_all("tabfocus", aliases=False) diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index b88bc2f8d..ab1fba789 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -782,7 +782,7 @@ class TestContainer: assert len(configapi.errors) == 1 error = configapi.errors[0] assert error.text == "While getting 'tabs.foobar'" - assert str(error.exception) == "No option 'tabs.foobar'" + assert str(error.exception).startswith("No option 'tabs.foobar'") def test_confapi_missing_prefix(self, container): configapi = types.SimpleNamespace(errors=[]) @@ -793,7 +793,7 @@ class TestContainer: error1 = configapi.errors[0] assert error1.text == "While getting 'content.host_blocking'" - assert str(error1.exception) == "No option 'content.host_blocking'" + assert str(error1.exception).startswith("No option 'content.host_blocking'") error2 = configapi.errors[1] assert error2.text == "While setting 'content.host_blocking.lists'" diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py index a236494f9..b289d3cc8 100644 --- a/tests/unit/config/test_configexc.py +++ b/tests/unit/config/test_configexc.py @@ -32,13 +32,27 @@ def test_validation_error(): assert str(e) == "Invalid value 'val' - msg" -@pytest.mark.parametrize('deleted, renamed, expected', [ - (False, None, "No option 'opt'"), - (True, None, "No option 'opt' (this option was removed from qutebrowser)"), - (False, 'new', "No option 'opt' (this option was renamed to 'new')"), +@pytest.mark.parametrize('deleted, renamed, all_names, expected', [ + (False, None, [], "No option 'opt'"), + (True, None, [], "No option 'opt' (this option was removed from qutebrowser)"), + (False, 'new', [], "No option 'opt' (this option was renamed to 'new')"), + (False, None, ["opto"], "No option 'opt' (did you mean 'opto'?)"), + (False, None, ["thisdoesnotmatch"], "No option 'opt'"), + ( + True, + None, + ["opto"], + "No option 'opt' (this option was removed from qutebrowser)", + ), + (False, 'new', ["opto"], "No option 'opt' (this option was renamed to 'new')"), ]) -def test_no_option_error(deleted, renamed, expected): - e = configexc.NoOptionError('opt', deleted=deleted, renamed=renamed) +def test_no_option_error(deleted, renamed, all_names, expected): + e = configexc.NoOptionError( + 'opt', + deleted=deleted, + renamed=renamed, + all_names=all_names, + ) assert e.option == 'opt' assert str(e) == expected diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index c34efce54..e4351d619 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -223,6 +223,7 @@ class TestAll: """Patch aliases so Command works.""" config_stub.val.aliases = {} + # pylint: disable-next=too-many-function-args @pytest.fixture(params=list(gen_classes())) def klass(self, request): return request.param diff --git a/tests/unit/misc/test_elf.py b/tests/unit/misc/test_elf.py index 86060bbde..7d3248da2 100644 --- a/tests/unit/misc/test_elf.py +++ b/tests/unit/misc/test_elf.py @@ -75,6 +75,23 @@ def test_result(qapp, caplog): assert ua.upstream_browser_version == versions.chromium +@pytest.mark.parametrize("data, expected", [ + # Simple match + ( + b"\x00QtWebEngine/5.15.9 Chrome/87.0.4280.144\x00", + elf.Versions("5.15.9", "87.0.4280.144"), + ), + # Ignoring garbage string-like data + ( + b"\x00QtWebEngine/5.15.9 Chrome/87.0.4xternalclearkey\x00\x00" + b"QtWebEngine/5.15.9 Chrome/87.0.4280.144\x00", + elf.Versions("5.15.9", "87.0.4280.144"), + ), +]) +def test_find_versions(data, expected): + assert elf._find_versions(data) == expected + + @hypothesis.given(data=hst.builds( lambda *a: b''.join(a), hst.sampled_from([b'', b'\x7fELF', b'\x7fELF\x02\x01\x01']), diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py index bbc6b02db..20938d6fb 100644 --- a/tests/unit/utils/test_log.py +++ b/tests/unit/utils/test_log.py @@ -236,6 +236,7 @@ class TestInitLog: """Tests for init_log.""" def _get_default_args(self): + # pylint: disable-next=unused-variable return argparse.Namespace(debug=True, loglevel='debug', color=True, loglines=10, logfilter=None, force_color=False, json_logging=False, diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index 4620c2198..595aa6426 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -603,6 +603,15 @@ class TestSanitizeFilename: LONG_EXTENSION = (LONG_FILENAME.replace("filename", ".extension") .replace(".txt", "")) + # first four-byte unicode char + U10K = "\U00010000" + + LONG_4BYTE = U10K * 64 + LONG_4BYTE_SHORTENED = U10K * 60 + + LONG_4BYTE_EXT = f"{U10K * 8}.{U10K * 64}" + LONG_4BYTE_EXT_SHORTENED = f"{U10K}.{U10K * 59}" + @pytest.mark.parametrize('inp, expected', [ pytest.param('normal.txt', 'normal.txt', marks=pytest.mark.fake_os('windows')), @@ -629,6 +638,14 @@ class TestSanitizeFilename: LONG_EXTENSION.replace("this is a very long .extension", "this .extension"), ), + ( + LONG_4BYTE, + LONG_4BYTE_SHORTENED, + ), + ( + LONG_4BYTE_EXT, + LONG_4BYTE_EXT_SHORTENED, + ) ]) @pytest.mark.linux def test_shorten(self, inp, expected): diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 7b616d8b7..64df0ece2 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -1463,7 +1463,11 @@ def test_uptime(monkeypatch, qapp): monkeypatch.setattr(qapp, "launch_time", launch_time, raising=False) class FakeDateTime(datetime.datetime): - now = lambda x=datetime.datetime(1, 1, 1, 1, 1, 1, 2): x + + @classmethod + def now(cls, tz=None): + return datetime.datetime(1, 1, 1, 1, 1, 1, 2) + monkeypatch.setattr(datetime, 'datetime', FakeDateTime) uptime_delta = version._uptime() |