summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.github/workflows/bleeding.yml2
-rw-r--r--.gitignore1
-rw-r--r--.pylintrc26
-rw-r--r--doc/changelog.asciidoc59
-rw-r--r--doc/help/commands.asciidoc1
-rw-r--r--doc/help/configuring.asciidoc1
-rw-r--r--doc/help/settings.asciidoc12
-rw-r--r--doc/qutebrowser.1.asciidoc3
-rwxr-xr-xmisc/nsis/install.nsh3
-rw-r--r--misc/org.qutebrowser.qutebrowser.appdata.xml1
-rw-r--r--misc/org.qutebrowser.qutebrowser.desktop2
-rw-r--r--misc/requirements/requirements-check-manifest.txt8
-rw-r--r--misc/requirements/requirements-dev.txt43
-rw-r--r--misc/requirements/requirements-dev.txt-raw2
-rw-r--r--misc/requirements/requirements-flake8.txt11
-rw-r--r--misc/requirements/requirements-mypy.txt18
-rw-r--r--misc/requirements/requirements-pyinstaller.txt4
-rw-r--r--misc/requirements/requirements-pylint.txt29
-rw-r--r--misc/requirements/requirements-pylint.txt-raw4
-rw-r--r--misc/requirements/requirements-pyqt-5.15.txt4
-rw-r--r--misc/requirements/requirements-pyqt-pyinstaller.txt7
-rw-r--r--misc/requirements/requirements-pyqt-pyinstaller.txt-raw2
-rw-r--r--misc/requirements/requirements-pyqt.txt4
-rw-r--r--misc/requirements/requirements-pyroma.txt6
-rw-r--r--misc/requirements/requirements-sphinx.txt16
-rw-r--r--misc/requirements/requirements-tests-bleeding.txt2
-rw-r--r--misc/requirements/requirements-tests.txt28
-rw-r--r--misc/requirements/requirements-tests.txt-raw3
-rw-r--r--misc/requirements/requirements-tox.txt16
-rw-r--r--misc/requirements/requirements-yamllint.txt2
-rwxr-xr-xmisc/userscripts/password_fill6
-rw-r--r--qutebrowser/__init__.py2
-rw-r--r--qutebrowser/browser/browsertab.py1
-rw-r--r--qutebrowser/browser/commands.py29
-rw-r--r--qutebrowser/browser/downloads.py2
-rw-r--r--qutebrowser/browser/greasemonkey.py1
-rw-r--r--qutebrowser/browser/hints.py11
-rw-r--r--qutebrowser/browser/history.py1
-rw-r--r--qutebrowser/browser/navigate.py3
-rw-r--r--qutebrowser/browser/qtnetworkdownloads.py5
-rw-r--r--qutebrowser/browser/shared.py8
-rw-r--r--qutebrowser/browser/signalfilter.py1
-rw-r--r--qutebrowser/browser/webelem.py5
-rw-r--r--qutebrowser/browser/webengine/notification.py6
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py17
-rw-r--r--qutebrowser/browser/webkit/webkittab.py21
-rw-r--r--qutebrowser/browser/webkit/webview.py9
-rw-r--r--qutebrowser/commands/argparser.py2
-rw-r--r--qutebrowser/commands/command.py12
-rw-r--r--qutebrowser/commands/userscripts.py5
-rw-r--r--qutebrowser/completion/completer.py2
-rw-r--r--qutebrowser/completion/models/completionmodel.py1
-rw-r--r--qutebrowser/completion/models/configmodel.py3
-rw-r--r--qutebrowser/completion/models/miscmodels.py4
-rw-r--r--qutebrowser/components/braveadblock.py15
-rw-r--r--qutebrowser/components/hostblock.py7
-rw-r--r--qutebrowser/components/misccommands.py8
-rw-r--r--qutebrowser/components/utils/blockutils.py2
-rw-r--r--qutebrowser/config/config.py4
-rw-r--r--qutebrowser/config/configcommands.py2
-rw-r--r--qutebrowser/config/configdata.yml25
-rw-r--r--qutebrowser/config/configfiles.py2
-rw-r--r--qutebrowser/config/configutils.py2
-rw-r--r--qutebrowser/config/qtargs.py5
-rw-r--r--qutebrowser/config/websettings.py1
-rw-r--r--qutebrowser/extensions/loader.py36
-rw-r--r--qutebrowser/html/settings.html150
-rw-r--r--qutebrowser/keyinput/basekeyparser.py5
-rw-r--r--qutebrowser/keyinput/keyutils.py2
-rw-r--r--qutebrowser/mainwindow/tabbedbrowser.py12
-rw-r--r--qutebrowser/mainwindow/tabwidget.py1
-rw-r--r--qutebrowser/misc/crashsignal.py3
-rw-r--r--qutebrowser/misc/earlyinit.py18
-rw-r--r--qutebrowser/misc/guiprocess.py8
-rw-r--r--qutebrowser/misc/keyhintwidget.py1
-rw-r--r--qutebrowser/misc/lineparser.py1
-rw-r--r--qutebrowser/misc/miscwidgets.py2
-rw-r--r--qutebrowser/misc/msgbox.py1
-rw-r--r--qutebrowser/misc/quitter.py2
-rw-r--r--qutebrowser/misc/savemanager.py1
-rw-r--r--qutebrowser/misc/sessions.py9
-rw-r--r--qutebrowser/misc/split.py1
-rw-r--r--qutebrowser/misc/sql.py3
-rw-r--r--qutebrowser/qutebrowser.py25
-rw-r--r--qutebrowser/utils/log.py4
-rw-r--r--qutebrowser/utils/message.py9
-rw-r--r--qutebrowser/utils/resources.py2
-rw-r--r--qutebrowser/utils/urlutils.py2
-rw-r--r--qutebrowser/utils/usertypes.py1
-rw-r--r--qutebrowser/utils/utils.py16
-rw-r--r--qutebrowser/utils/version.py2
-rw-r--r--requirements.txt10
-rwxr-xr-xscripts/dev/build_release.py63
-rw-r--r--scripts/dev/pylint_checkers/qute_pylint/modeline.py6
-rw-r--r--scripts/dev/pylint_checkers/qute_pylint/openencoding.py83
-rw-r--r--scripts/dev/recompile_requirements.py24
-rw-r--r--scripts/dev/run_pylint_on_tests.py3
-rwxr-xr-xscripts/dev/update_3rdparty.py10
-rwxr-xr-xscripts/dictcli.py14
-rw-r--r--scripts/hostblock_blame.py4
-rw-r--r--tests/conftest.py70
-rw-r--r--tests/end2end/conftest.py2
-rw-r--r--tests/end2end/data/brave-adblock/generate.py6
-rw-r--r--tests/end2end/data/hints/html/README.md2
-rw-r--r--tests/end2end/data/hints/html/invisible.html (renamed from tests/end2end/data/hints/invisible.html)2
-rw-r--r--tests/end2end/data/hints/html/tabindex-negative.html13
-rw-r--r--tests/end2end/features/hints.feature5
-rw-r--r--tests/end2end/features/search.feature20
-rw-r--r--tests/end2end/features/tabs.feature58
-rw-r--r--tests/end2end/fixtures/quteprocess.py14
-rw-r--r--tests/end2end/fixtures/test_webserver.py5
-rw-r--r--tests/end2end/test_hints_html.py15
-rw-r--r--tests/helpers/fixtures.py2
-rw-r--r--tests/helpers/logfail.py3
-rw-r--r--tests/unit/api/test_cmdutils.py3
-rw-r--r--tests/unit/completion/test_completionwidget.py11
-rw-r--r--tests/unit/components/test_hostblock.py2
-rw-r--r--tests/unit/extensions/test_loader.py8
-rw-r--r--tests/unit/keyinput/test_basekeyparser.py11
-rw-r--r--tests/unit/mainwindow/statusbar/test_textbase.py6
-rw-r--r--tests/unit/misc/test_guiprocess.py14
-rw-r--r--tests/unit/misc/test_keyhints.py4
-rw-r--r--tests/unit/test_qutebrowser.py60
-rw-r--r--tests/unit/utils/test_log.py9
-rw-r--r--tests/unit/utils/test_qtutils.py5
-rw-r--r--tests/unit/utils/test_urlmatch.py157
-rw-r--r--tests/unit/utils/test_urlutils.py1
-rw-r--r--tests/unit/utils/test_version.py23
-rw-r--r--tox.ini4
130 files changed, 1046 insertions, 575 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index e1e31afc5..cf1c019f7 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 2.3.1
+current_version = 2.4.0
commit = True
message = Release v{new_version}
tag = True
diff --git a/.github/workflows/bleeding.yml b/.github/workflows/bleeding.yml
index 766f535d7..435141e56 100644
--- a/.github/workflows/bleeding.yml
+++ b/.github/workflows/bleeding.yml
@@ -58,7 +58,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: 3.9
+ python-version: "3.10"
- name: Get asciidoc
uses: actions/checkout@v2
with:
diff --git a/.gitignore b/.gitignore
index 31c4ca3b7..ccfc12ccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@ TODO
/scripts/testbrowser/cpp/webengine/testbrowser
/scripts/testbrowser/cpp/webengine/.qmake.stash
/scripts/dev/pylint_checkers/qute_pylint.egg-info
+/scripts/dev/pylint_checkers/build
/misc/file_version_info.txt
/doc/extapi/_build
/misc/nsis/include
diff --git a/.pylintrc b/.pylintrc
index b771078f3..a3ac3d82a 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -5,15 +5,19 @@ ignore=resources.py
extension-pkg-whitelist=PyQt5,sip
load-plugins=qute_pylint.config,
qute_pylint.modeline,
- qute_pylint.openencoding,
pylint.extensions.docstyle,
pylint.extensions.emptystring,
- pylint.extensions.broad_try_clause,
pylint.extensions.overlapping_exceptions,
-persistent=n
+ pylint.extensions.code_style,
+ pylint.extensions.comparison_placement,
+ pylint.extensions.for_any_all,
+ pylint.extensions.docstyle,
+ pylint.extensions.check_elif,
+ pylint.extensions.typing,
+ pylint.extensions.docparams,
-[broad_try_clause]
-max-try-statements=7
+persistent=n
+py-version=3.6
[MESSAGES CONTROL]
enable=all
@@ -46,7 +50,16 @@ disable=locally-disabled,
too-many-statements,
too-few-public-methods,
import-outside-toplevel,
- bad-continuation # This lint disagrees with Black
+ bad-continuation, # This lint disagrees with Black
+ consider-using-f-string,
+ logging-fstring-interpolation,
+ raise-missing-from,
+ consider-using-tuple,
+ consider-using-namedtuple-or-dataclass,
+ missing-raises-doc,
+ missing-type-doc,
+ missing-param-doc,
+ useless-param-doc,
[BASIC]
function-rgx=[a-z_][a-z0-9_]{2,50}$
@@ -58,6 +71,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$
variable-rgx=[a-z_][a-z0-9_]{0,30}$
docstring-min-length=3
no-docstring-rgx=(^_|^main$)
+class-const-naming-style = snake_case
[FORMAT]
max-line-length=88
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index c17f35eec..50d5a9d45 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -15,10 +15,67 @@ breaking changes (such as renamed commands) can happen in minor releases.
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
+[[v2.5.0]]
+v2.5.0 (unreleased)
+-------------------
+
+Changed
+~~~~~~~
+
+- Improved message if a spawned process wasn't found and a Flatpak container is
+ in use.
+- The `:tab-move` command now takes `start` and `end` as `index` to move a tab
+ to the first/last position.
+- Tests now automatically pick the backend (QtWebKit/QtWebEngine) based on
+ what's available. The `QUTE_BDD_WEBENGINE` environment variable and
+ `--qute-bdd-webengine` argument got replaced by `QUTE_TESTS_BACKEND` and
+ `--qute-backend` respectively, which can be set to either `webengine` or
+ `webkit`.
+- Using `:tab-give` or `:tab-take` on the last tab in a window now always
+ closes that window, no matter what `tabs.last_close` is set to.
+- Redesigned `qute://settings` (`:set`) page with buttons for options with
+ fixed values.
+
+Added
+~~~~~
+
+- New `input.match_counts` option which allows to turn off count matching for
+ more emacs-like bindings.
+
+Fixed
+~~~~~
+
+- When `search.incremental` is disabled, searching using `/text` followed by a
+ backwards search via `?text` (or vice-versa) now correctly changes the search
+ direction.
+- Elements getting a hint due to a `tabindex` now are skipped if it's set to
+ `-1`, reducing some false-positives.
+
+[[v2.4.1]]
+v2.4.1 (unreleased)
+-------------------
+
+Fixed
+~~~~~
+
+- Speculative fix for an immediate crash at start with the macOS/Windows
+ binaries (in certain rare environments).
+- Speculative fix for a qutebrowser crash when the notification daemon crashes
+ while showing the notification.
+- Fix crash when using `:screenshot` with an invalid `--rect` argument.
+
[[v2.4.0]]
-v2.4.0 (unreleased)
+v2.4.0 (2021-10-21)
-------------------
+Security
+~~~~~~~~
+
+- **CVE-2021-41146**: Fix arbitrary command execution on Windows via URL handler
+ argument injection. See the
+ https://github.com/qutebrowser/qutebrowser/security/advisories/GHSA-vw27-fwjf-5qxm[security advisory]
+ for details.
+
Added
~~~~~
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 8c11e15cc..442c136a7 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -1431,6 +1431,7 @@ If neither is given, move it to the first position.
==== positional arguments
* +'index'+: `+` or `-` to move relative to the current tab by count, or a default of 1 space.
A tab index to move to that index.
+ `start` and `end` to move to the start and the end.
==== count
diff --git a/doc/help/configuring.asciidoc b/doc/help/configuring.asciidoc
index 23894ddc4..552145023 100644
--- a/doc/help/configuring.asciidoc
+++ b/doc/help/configuring.asciidoc
@@ -412,6 +412,7 @@ Pre-built colorschemes
- https://github.com/dracula/qutebrowser-dracula-theme[Dracula]
- https://gitlab.com/lovetocode999/selenized-qutebrowser[Selenized]
- https://github.com/morhetz/gruvbox[gruvbox]: https://github.com/The-Compiler/dotfiles/blob/master/qutebrowser/gruvbox.py[The-Compiler], https://gitlab.com/shaneyost/dots-popos-september-2020/-/blob/master/qutebrowser/config.py[Shane Yost]
+- https://www.opencode.net/wakellor957/qb-breath/-/blob/main/qb-breath.py[Manjaro Breath-like]
Avoiding flake8 errors
^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 60c229078..12ef48d88 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -269,6 +269,7 @@
|<<input.insert_mode.leave_on_load,input.insert_mode.leave_on_load>>|Leave insert mode when starting a new page load.
|<<input.insert_mode.plugins,input.insert_mode.plugins>>|Switch to insert mode when clicking flash and other plugins.
|<<input.links_included_in_focus_chain,input.links_included_in_focus_chain>>|Include hyperlinks in the keyboard focus chain when tabbing.
+|<<input.match_counts,input.match_counts>>|Interpret number prefixes as counts for bindings.
|<<input.media_keys,input.media_keys>>|Whether the underlying Chromium should handle media keys.
|<<input.mouse.back_forward_buttons,input.mouse.back_forward_buttons>>|Enable back and forward buttons on the mouse.
|<<input.mouse.rocker_gestures,input.mouse.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
@@ -3428,7 +3429,7 @@ Default:
* +pass:[[ngClick\]]+
* +pass:[[data-ng-click\]]+
* +pass:[[x-ng-click\]]+
-* +pass:[[tabindex\]]+
+* +pass:[[tabindex\]:not([tabindex=&quot;-1&quot;\])]+
- +pass:[images]+:
* +pass:[img]+
@@ -3557,6 +3558,15 @@ Type: <<types,Bool>>
Default: +pass:[true]+
+[[input.match_counts]]
+=== input.match_counts
+Interpret number prefixes as counts for bindings.
+This enables for vi-like bindings that can be prefixed with a number to indicate a count. Disabling it allows for emacs-like bindings where number keys are passed through (according to `input.forward_unbound_keys`) instead.
+
+Type: <<types,Bool>>
+
+Default: +pass:[true]+
+
[[input.media_keys]]
=== input.media_keys
Whether the underlying Chromium should handle media keys.
diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc
index 8db231add..bc312f108 100644
--- a/doc/qutebrowser.1.asciidoc
+++ b/doc/qutebrowser.1.asciidoc
@@ -65,6 +65,9 @@ show it.
*--desktop-file-name* 'DESKTOP_FILE_NAME'::
Set the base name of the desktop entry for this application. Used to set the app_id under Wayland. See https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop
+*--untrusted-args*::
+ Mark all following arguments as untrusted, which enforces that they are URLs/search terms (and not flags or commands)
+
=== debug arguments
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
Override the configured console loglevel
diff --git a/misc/nsis/install.nsh b/misc/nsis/install.nsh
index f29a0a9a8..9f0cdf446 100755
--- a/misc/nsis/install.nsh
+++ b/misc/nsis/install.nsh
@@ -351,13 +351,12 @@ Section "Register with Windows" SectionWindowsRegister
!insertmacro UpdateRegDWORD SHCTX "SOFTWARE\Classes\$2" "EditFlags" 0x00000002
!insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\DefaultIcon" "" "$1,0"
!insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell" "" "open"
- !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell\open\command" "" "$\"$1$\" $\"%1$\""
+ !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell\open\command" "" "$\"$1$\" --untrusted-args $\"%1$\""
!insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2\shell\open\ddeexec" "" ""
StrCmp $2 "${PRODUCT_NAME}HTML" 0 +4
StrCpy $2 "${PRODUCT_NAME}URL"
StrCpy $3 "${PRODUCT_NAME} URL"
Goto WriteRegHandler
- !insertmacro UpdateRegStr SHCTX "SOFTWARE\Classes\$2" "URL Protocol" ""
${endif}
SectionEnd
diff --git a/misc/org.qutebrowser.qutebrowser.appdata.xml b/misc/org.qutebrowser.qutebrowser.appdata.xml
index 7c382cbb3..9930514d0 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.4.0" date="2021-10-21"/>
<release version="2.3.1" date="2021-07-28"/>
<release version="2.3.0" date="2021-06-28"/>
<release version="2.2.3" date="2021-06-01"/>
diff --git a/misc/org.qutebrowser.qutebrowser.desktop b/misc/org.qutebrowser.qutebrowser.desktop
index 52144b3c5..d999496ee 100644
--- a/misc/org.qutebrowser.qutebrowser.desktop
+++ b/misc/org.qutebrowser.qutebrowser.desktop
@@ -45,7 +45,7 @@ Comment[it]= Un browser web vim-like utilizzabile da tastiera basato su PyQt5
Icon=qutebrowser
Type=Application
Categories=Network;WebBrowser;
-Exec=qutebrowser %u
+Exec=qutebrowser --untrusted-args %u
Terminal=false
StartupNotify=true
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/qute;
diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt
index 9a783f8b2..c61218ba3 100644
--- a/misc/requirements/requirements-check-manifest.txt
+++ b/misc/requirements/requirements-check-manifest.txt
@@ -2,8 +2,8 @@
build==0.7.0
check-manifest==0.47
-packaging==21.0
-pep517==0.11.0
-pyparsing==2.4.7
+packaging==21.3
+pep517==0.12.0
+pyparsing==3.0.6
toml==0.10.2
-tomli==1.2.1
+tomli==1.2.2
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 000ed39aa..126924092 100644
--- a/misc/requirements/requirements-dev.txt
+++ b/misc/requirements/requirements-dev.txt
@@ -1,26 +1,45 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
+bleach==4.1.0
+build==0.7.0
bump2version==1.0.1
certifi==2021.10.8
-cffi==1.14.6
-charset-normalizer==2.0.6
-cryptography==35.0.0
+cffi==1.15.0
+charset-normalizer==2.0.8
+colorama==0.4.4
+cryptography==36.0.0
Deprecated==1.2.13
-github3.py==2.0.0
+docutils==0.18.1
+github3.py==3.0.0
hunter==3.3.8
-idna==3.2
+idna==3.3
+importlib-metadata==4.8.2
+jeepney==0.7.1
jwcrypto==1.0
+keyring==23.4.0
manhole==1.8.0
-packaging==21.0
-pycparser==2.20
+packaging==21.3
+pep517==0.12.0
+pkginfo==1.8.2
+pycparser==2.21
+Pygments==2.10.0
Pympler==0.9
-pyparsing==2.4.7
-PyQt-builder==1.11.0
+pyparsing==3.0.6
+PyQt-builder==1.12.2
python-dateutil==2.8.2
+readme-renderer==30.0
requests==2.26.0
-sip==6.2.0
+requests-toolbelt==0.9.1
+rfc3986==1.5.0
+SecretStorage==3.3.1
+sip==6.5.0
six==1.16.0
toml==0.10.2
-uritemplate==4.0.0
+tomli==1.2.2
+tqdm==4.62.3
+twine==3.7.0
+uritemplate==4.1.1
# urllib3==1.26.7
-wrapt==1.13.1
+webencodings==0.5.1
+wrapt==1.13.3
+zipp==3.6.0
diff --git a/misc/requirements/requirements-dev.txt-raw b/misc/requirements/requirements-dev.txt-raw
index fd840bab1..261f4459f 100644
--- a/misc/requirements/requirements-dev.txt-raw
+++ b/misc/requirements/requirements-dev.txt-raw
@@ -4,6 +4,8 @@ github3.py
bump2version
requests
pyqt-builder
+build
+twine
# Already included via test requirements
#@ ignore: urllib3
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index 9d5c0e170..fc7568e9a 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -1,11 +1,10 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==21.2.0
-cached-property==1.5.2
-flake8==4.0.0
-flake8-bugbear==21.9.2
+flake8==4.0.1
+flake8-bugbear==21.11.29
flake8-builtins==1.5.3
-flake8-comprehensions==2.3.0
+flake8-comprehensions==3.7.0
flake8-copyright==0.2.2
flake8-debugger==4.0.0
flake8-deprecated==1.3
@@ -14,7 +13,7 @@ flake8-future-import==0.4.6
flake8-mock==0.3
flake8-polyfill==1.0.2
flake8-string-format==0.3.0
-flake8-tidy-imports==3.0.0
+flake8-tidy-imports==4.5.0
flake8-tuple==0.4.1
mccabe==0.6.1
pep8-naming==0.12.1
@@ -22,4 +21,4 @@ pycodestyle==2.8.0
pydocstyle==6.1.1
pyflakes==2.4.0
six==1.16.0
-snowballstemmer==2.1.0
+snowballstemmer==2.2.0
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index e3a05eac7..367e039b7 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -1,13 +1,11 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
chardet==4.0.0
-diff-cover==6.4.2
-importlib-metadata==4.8.1
-importlib-resources==5.2.2
-inflect==5.3.0
-Jinja2==3.0.2
-jinja2-pluralize==0.3.0
-lxml==4.6.3
+diff-cover==6.4.3
+importlib-metadata==4.8.2
+importlib-resources==5.4.0
+Jinja2==3.0.3
+lxml==4.6.4
MarkupSafe==2.0.1
mypy==0.910
mypy-extensions==0.4.3
@@ -15,7 +13,7 @@ pluggy==1.0.0
Pygments==2.10.0
PyQt5-stubs==5.15.2.0
toml==0.10.2
-types-dataclasses==0.1.7
-types-PyYAML==5.4.10
-typing-extensions==3.10.0.2
+types-dataclasses==0.6.1
+types-PyYAML==6.0.1
+typing_extensions==4.0.1
zipp==3.6.0
diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt
index 81b66393b..b7c84e3be 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==4.5.1
-pyinstaller-hooks-contrib==2021.3
+pyinstaller==4.7
+pyinstaller-hooks-contrib==2021.4
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index 9dc56ea29..d5247145d 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -1,26 +1,29 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-astroid==2.3.3 # rq.filter: < 2.4
+astroid==2.9.0
certifi==2021.10.8
-cffi==1.14.6
-charset-normalizer==2.0.6
-cryptography==35.0.0
+cffi==1.15.0
+charset-normalizer==2.0.8
+cryptography==36.0.0
Deprecated==1.2.13
future==0.18.2
-github3.py==2.0.0
-idna==3.2
-isort==4.3.21
+github3.py==3.0.0
+idna==3.3
+isort==5.10.1
jwcrypto==1.0
-lazy-object-proxy==1.4.3
+lazy-object-proxy==1.6.0
mccabe==0.6.1
pefile==2021.9.3
-pycparser==2.20
-pylint==2.4.4 # rq.filter: < 2.5
+platformdirs==2.4.0
+pycparser==2.21
+pylint==2.12.1
python-dateutil==2.8.2
./scripts/dev/pylint_checkers
requests==2.26.0
six==1.16.0
-typed-ast==1.4.3 ; python_version<"3.8"
-uritemplate==4.0.0
+toml==0.10.2
+typed-ast==1.5.0 ; python_version<"3.8"
+typing_extensions==4.0.1
+uritemplate==4.1.1
# urllib3==1.26.7
-wrapt==1.11.2
+wrapt==1.13.3
diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw
index ccee2ac10..9a2498267 100644
--- a/misc/requirements/requirements-pylint.txt-raw
+++ b/misc/requirements/requirements-pylint.txt-raw
@@ -1,4 +1,4 @@
-pylint<2.5
+pylint
./scripts/dev/pylint_checkers
requests
github3.py
@@ -7,8 +7,6 @@ pefile
# fix qute-pylint location
#@ replace: qute-pylint.* ./scripts/dev/pylint_checkers
#@ markers: typed-ast python_version<"3.8"
-#@ filter: pylint < 2.5
-#@ filter: astroid < 2.4
# Already included via test requirements
#@ ignore: urllib3
diff --git a/misc/requirements/requirements-pyqt-5.15.txt b/misc/requirements/requirements-pyqt-5.15.txt
index 8b7a53c44..3a3110c8b 100644
--- a/misc/requirements/requirements-pyqt-5.15.txt
+++ b/misc/requirements/requirements-pyqt-5.15.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-PyQt5==5.15.4 # rq.filter: < 5.16
+PyQt5==5.15.6 # rq.filter: < 5.16
PyQt5-Qt5==5.15.2
PyQt5-sip==12.9.0
-PyQtWebEngine==5.15.4 # rq.filter: < 5.16
+PyQtWebEngine==5.15.5 # rq.filter: < 5.16
PyQtWebEngine-Qt5==5.15.2
diff --git a/misc/requirements/requirements-pyqt-pyinstaller.txt b/misc/requirements/requirements-pyqt-pyinstaller.txt
deleted file mode 100644
index 678a1d7ea..000000000
--- a/misc/requirements/requirements-pyqt-pyinstaller.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-# This file is automatically generated by scripts/dev/recompile_requirements.py
-
-PyQt5==5.15.3
-PyQt5-Qt==5.15.2
-PyQt5-sip==12.9.0
-PyQtWebEngine==5.15.3
-PyQtWebEngine-Qt==5.15.2
diff --git a/misc/requirements/requirements-pyqt-pyinstaller.txt-raw b/misc/requirements/requirements-pyqt-pyinstaller.txt-raw
deleted file mode 100644
index 89b5644da..000000000
--- a/misc/requirements/requirements-pyqt-pyinstaller.txt-raw
+++ /dev/null
@@ -1,2 +0,0 @@
-PyQt5==5.15.3
-PyQtWebEngine==5.15.3
diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt
index 75ef27bf4..3953d27b3 100644
--- a/misc/requirements/requirements-pyqt.txt
+++ b/misc/requirements/requirements-pyqt.txt
@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-PyQt5==5.15.4
+PyQt5==5.15.6
PyQt5-Qt5==5.15.2
PyQt5-sip==12.9.0
-PyQtWebEngine==5.15.4
+PyQtWebEngine==5.15.5
PyQtWebEngine-Qt5==5.15.2
diff --git a/misc/requirements/requirements-pyroma.txt b/misc/requirements/requirements-pyroma.txt
index 82a00016c..a76402053 100644
--- a/misc/requirements/requirements-pyroma.txt
+++ b/misc/requirements/requirements-pyroma.txt
@@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2021.10.8
-charset-normalizer==2.0.6
-docutils==0.17.1
-idna==3.2
+charset-normalizer==2.0.8
+docutils==0.18.1
+idna==3.3
Pygments==2.10.0
pyroma==3.2
requests==2.26.0
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index fb01ec30c..b7f013853 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -3,19 +3,19 @@
alabaster==0.7.12
Babel==2.9.1
certifi==2021.10.8
-charset-normalizer==2.0.6
+charset-normalizer==2.0.8
docutils==0.17.1
-idna==3.2
-imagesize==1.2.0
-Jinja2==3.0.2
+idna==3.3
+imagesize==1.3.0
+Jinja2==3.0.3
MarkupSafe==2.0.1
-packaging==21.0
+packaging==21.3
Pygments==2.10.0
-pyparsing==2.4.7
+pyparsing==3.0.6
pytz==2021.3
requests==2.26.0
-snowballstemmer==2.1.0
-Sphinx==4.2.0
+snowballstemmer==2.2.0
+Sphinx==4.3.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 49911c48d..d2a7fcfb6 100644
--- a/misc/requirements/requirements-tests-bleeding.txt
+++ b/misc/requirements/requirements-tests-bleeding.txt
@@ -9,7 +9,7 @@ git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-py
git+https://github.com/pytest-dev/pytest.git
# Problematic: https://github.com/pytest-dev/pytest-bdd/issues/447
# git+https://github.com/pytest-dev/pytest-bdd.git
-pytest-bdd
+pytest-bdd<5
git+https://github.com/ionelmc/pytest-benchmark.git
git+https://github.com/pytest-dev/pytest-instafail.git
git+https://github.com/pytest-dev/pytest-mock.git
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index 83379d700..4a6eaeacc 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -3,36 +3,36 @@
attrs==21.2.0
beautifulsoup4==4.10.0
certifi==2021.10.8
-charset-normalizer==2.0.6
+charset-normalizer==2.0.8
cheroot==8.5.2
click==8.0.3
-coverage==6.0.1
+coverage==6.2
EasyProcess==0.3
execnet==1.9.0
-filelock==3.3.0
+filelock==3.4.0
Flask==2.0.2
glob2==0.7
hunter==3.3.8
-hypothesis==6.23.2
+hypothesis==6.30.0
icdiff==2.0.4
-idna==3.2
+idna==3.3
iniconfig==1.1.1
itsdangerous==2.0.1
-jaraco.functools==3.3.0
-# Jinja2==3.0.2
-Mako==1.1.5
+jaraco.functools==3.4.0
+# Jinja2==3.0.3
+Mako==1.1.6
manhole==1.8.0
# MarkupSafe==2.0.1
-more-itertools==8.10.0
-packaging==21.0
+more-itertools==8.12.0
+packaging==21.3
parse==1.19.0
parse-type==0.5.2
pluggy==1.0.0
pprintpp==0.4.0
-py==1.10.0
+py==1.11.0
py-cpuinfo==8.0.0
Pygments==2.10.0
-pyparsing==2.4.7
+pyparsing==3.0.6
pytest==6.2.5
pytest-bdd==4.1.0
pytest-benchmark==3.4.1
@@ -51,10 +51,10 @@ requests==2.26.0
requests-file==1.5.1
six==1.16.0
sortedcontainers==2.4.0
-soupsieve==2.2.1
+soupsieve==2.3.1
tldextract==3.1.2
toml==0.10.2
-tomli==1.2.1
+tomli==1.2.2
urllib3==1.26.7
vulture==2.3
Werkzeug==2.0.2
diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw
index ab580ac4b..5586a86ef 100644
--- a/misc/requirements/requirements-tests.txt-raw
+++ b/misc/requirements/requirements-tests.txt-raw
@@ -4,7 +4,8 @@ coverage
Flask
hypothesis
pytest
-pytest-bdd
+# https://github.com/pytest-dev/pytest-bdd/issues/447
+pytest-bdd<5
pytest-benchmark
pytest-instafail
pytest-mock
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 4c1cfbe27..a2a57808b 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -1,17 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-backports.entry-points-selectable==1.1.0
+backports.entry-points-selectable==1.1.1
distlib==0.3.3
-filelock==3.3.0
-packaging==21.0
-pip==21.2.4
+filelock==3.4.0
+packaging==21.3
+pip==21.3.1
platformdirs==2.4.0
pluggy==1.0.0
-py==1.10.0
-pyparsing==2.4.7
-setuptools==58.2.0
+py==1.11.0
+pyparsing==3.0.6
+setuptools==59.4.0
six==1.16.0
toml==0.10.2
tox==3.24.4
-virtualenv==20.8.1
+virtualenv==20.10.0
wheel==0.37.0
diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt
index 897184c74..12553f2b2 100644
--- a/misc/requirements/requirements-yamllint.txt
+++ b/misc/requirements/requirements-yamllint.txt
@@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
pathspec==0.9.0
-PyYAML==5.4.1
+PyYAML==6.0
yamllint==1.26.3
diff --git a/misc/userscripts/password_fill b/misc/userscripts/password_fill
index c46253d41..3ea8fd9f6 100755
--- a/misc/userscripts/password_fill
+++ b/misc/userscripts/password_fill
@@ -241,7 +241,7 @@ pass_backend() {
if $GPG "${GPG_OPTS[@]}" -d "$passfile" \
| grep --max-count=1 -iE "${match_line_pattern}${url}" > /dev/null
then
- passfile="${passfile#$PREFIX}"
+ passfile="${passfile#"$PREFIX"}"
passfile="${passfile#/}"
files+=( "${passfile%.gpg}" )
fi
@@ -250,7 +250,7 @@ pass_backend() {
if ((match_filename)) ; then
# add entries with matching filepath
while read -r passfile ; do
- passfile="${passfile#$PREFIX}"
+ passfile="${passfile#"$PREFIX"}"
passfile="${passfile#/}"
files+=( "${passfile%.gpg}" )
done < <(find -L "$PREFIX" -iname '*.gpg' | grep "$url")
@@ -267,7 +267,7 @@ pass_backend() {
else
if [[ $line =~ $user_pattern ]] ; then
# remove the matching prefix "user: " from the beginning of the line
- username=${line#${BASH_REMATCH[0]}}
+ username=${line#"${BASH_REMATCH[0]}"}
break
fi
fi
diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py
index 29a8e4836..c05215792 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.3.1"
+__version__ = "2.4.0"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py
index b1827dbf4..661c5f68b 100644
--- a/qutebrowser/browser/browsertab.py
+++ b/qutebrowser/browser/browsertab.py
@@ -779,6 +779,7 @@ class AbstractAudio(QObject):
"""Set this tab as muted or not.
Arguments:
+ muted: Whether the tab is currently muted.
override: If set to True, muting/unmuting was done manually and
overrides future automatic mute/unmute changes based on
the URL.
diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py
index f3438aaa8..395d8e8a4 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -451,7 +451,7 @@ class CommandDispatcher:
self._open(tab.url(), tab=True)
if not keep:
- tabbed_browser.close_tab(tab, add_undo=False)
+ tabbed_browser.close_tab(tab, add_undo=False, transfer=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('win_id', completion=miscmodels.window)
@@ -500,7 +500,8 @@ class CommandDispatcher:
tabbed_browser.tabopen(self._current_url())
if not keep:
self._tabbed_browser.close_tab(self._current_widget(),
- add_undo=False)
+ add_undo=False,
+ transfer=True)
def _back_forward(self, tab, bg, window, count, forward, index=None):
"""Helper function for :back/:forward."""
@@ -1004,11 +1005,10 @@ class CommandDispatcher:
raise cmdutils.CommandError("There's no tab with index {}!".format(
index))
- @cmdutils.register(instance='command-dispatcher', scope='window')
- @cmdutils.argument('index', choices=['+', '-'])
- @cmdutils.argument('count', value=cmdutils.Value.count)
- def tab_move(self, index: Union[str, int] = None,
- count: int = None) -> None:
+ @cmdutils.register(instance="command-dispatcher", scope="window")
+ @cmdutils.argument("index", choices=["+", "-", "start", "end"])
+ @cmdutils.argument("count", value=cmdutils.Value.count)
+ def tab_move(self, index: Union[str, int] = None, count: int = None) -> None:
"""Move the current tab according to the argument and [count].
If neither is given, move it to the first position.
@@ -1017,24 +1017,30 @@ class CommandDispatcher:
index: `+` or `-` to move relative to the current tab by
count, or a default of 1 space.
A tab index to move to that index.
+ `start` and `end` to move to the start and the end.
count: If moving relatively: Offset.
If moving absolutely: New position (default: 0). This
overrides the index argument, if given.
"""
- if index in ['+', '-']:
+ if index in ["+", "-"]:
# relative moving
new_idx = self._current_index()
delta = 1 if count is None else count
- if index == '-':
+ if index == "-":
new_idx -= delta
- elif index == '+': # pragma: no branch
+ elif index == "+": # pragma: no branch
new_idx += delta
if config.val.tabs.wrap:
new_idx %= self._count()
else:
+ # pylint: disable=else-if-used
# absolute moving
- if count is not None:
+ if index == "start":
+ new_idx = 0
+ elif index == "end":
+ new_idx = self._count() - 1
+ elif count is not None:
new_idx = count - 1
elif index is not None:
assert isinstance(index, int)
@@ -1515,6 +1521,7 @@ class CommandDispatcher:
Callback for GUIProcess when the edited text was updated.
Args:
+ ed: The editor.ExternalEditor instance
elem: The WebElementWrapper which was modified.
text: The new text to insert.
"""
diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py
index 4f7897c9d..32bfd2693 100644
--- a/qutebrowser/browser/downloads.py
+++ b/qutebrowser/browser/downloads.py
@@ -1352,6 +1352,7 @@ class TempDownloadManager:
The tempfile.TemporaryDirectory that is used.
"""
if self._tmpdir is None:
+ # pylint: disable=consider-using-with
self._tmpdir = tempfile.TemporaryDirectory(
prefix='qutebrowser-downloads-')
return self._tmpdir
@@ -1373,6 +1374,7 @@ class TempDownloadManager:
suggested_name = utils.sanitize_filename(suggested_name)
# Make sure that the filename is not too long
suggested_name = utils.elide_filename(suggested_name, 50)
+ # pylint: disable=consider-using-with
fobj = tempfile.NamedTemporaryFile(dir=tmpdir.name, delete=False,
suffix='_' + suggested_name)
self.files.append(fobj)
diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py
index d0245937f..5abb9a137 100644
--- a/qutebrowser/browser/greasemonkey.py
+++ b/qutebrowser/browser/greasemonkey.py
@@ -324,6 +324,7 @@ class GreasemonkeyManager(QObject):
"""Add a GreasemonkeyScript to this manager.
Args:
+ script: The GreasemonkeyScript to add.
force: Fetch and overwrite any dependencies which are
already locally cached.
"""
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py
index 6ac44adbc..2e4e8e4b4 100644
--- a/qutebrowser/browser/hints.py
+++ b/qutebrowser/browser/hints.py
@@ -299,7 +299,7 @@ class HintActions:
Args:
elem: The QWebElement to download.
- _context: The HintContext to use.
+ context: The HintContext to use.
"""
url = elem.resolve_url(context.baseurl)
if url is None:
@@ -596,6 +596,7 @@ class HintManager(QObject):
"'args' is required with target userscript/spawn/run/"
"fill.")
else:
+ # pylint: disable=else-if-used
if args:
raise cmdutils.CommandError(
"'args' is only allowed with target userscript/spawn.")
@@ -870,12 +871,11 @@ class HintManager(QObject):
label.update_text(matched, rest)
# Show label again if it was hidden before
label.show()
- else:
+ elif (not self._context.rapid or
+ config.val.hints.hide_unmatched_rapid_hints):
# element doesn't match anymore -> hide it, unless in rapid
# mode and hide_unmatched_rapid_hints is false (see #1799)
- if (not self._context.rapid or
- config.val.hints.hide_unmatched_rapid_hints):
- label.hide()
+ label.hide()
except webelem.Error:
pass
self._handle_auto_follow(keystr=keystr)
@@ -1154,7 +1154,6 @@ class WordHinter:
from the words arg as fallback.
Args:
- words: Words to use as fallback when no link text can be used.
elems: The elements to get hint strings for.
Return:
diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py
index 559992327..d2046345f 100644
--- a/qutebrowser/browser/history.py
+++ b/qutebrowser/browser/history.py
@@ -405,6 +405,7 @@ class WebHistory(sql.SqlTable):
Args:
url: A url (as QUrl) to add to the history.
+ title: The tab title to add.
redirect: Whether the entry was redirected to another URL
(hidden in completion)
atime: Override the atime used to add the entry
diff --git a/qutebrowser/browser/navigate.py b/qutebrowser/browser/navigate.py
index 82bf57136..6217c8d00 100644
--- a/qutebrowser/browser/navigate.py
+++ b/qutebrowser/browser/navigate.py
@@ -92,9 +92,6 @@ def incdec(url, count, inc_or_dec):
url: The current url.
count: How much to increment or decrement by.
inc_or_dec: Either 'increment' or 'decrement'.
- tab: Whether to open the link in a new tab.
- background: Open the link in a new background tab.
- window: Open the link in a new window.
"""
urlutils.ensure_valid(url)
segments: Optional[Set[str]] = (
diff --git a/qutebrowser/browser/qtnetworkdownloads.py b/qutebrowser/browser/qtnetworkdownloads.py
index f048d293d..8adb7ea20 100644
--- a/qutebrowser/browser/qtnetworkdownloads.py
+++ b/qutebrowser/browser/qtnetworkdownloads.py
@@ -110,6 +110,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
"""Create a file object using the internal filename."""
assert self._filename is not None
try:
+ # pylint: disable=consider-using-with
fileobj = open(self._filename, 'wb')
except OSError as e:
self._die(e.strerror)
@@ -502,6 +503,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
Args:
request: The QNetworkRequest to download.
target: Where to save the download as downloads.DownloadTarget.
+ suggested_fn: The filename to use for the file.
**kwargs: Passed to _fetch_request.
Return:
@@ -546,6 +548,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
target: Where to save the download as downloads.DownloadTarget.
auto_remove: Whether to remove the download even if
downloads.remove_finished is set to -1.
+ suggested_filename: The filename to use for the file.
+ prompt_download_directory: Whether to prompt for a location to
+ download the file to.
Return:
The created DownloadItem.
diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py
index 8d3ebe730..41c971642 100644
--- a/qutebrowser/browser/shared.py
+++ b/qutebrowser/browser/shared.py
@@ -418,12 +418,11 @@ def choose_file(qb_mode: FileSelectionMode) -> List[str]:
}[qb_mode]
use_tmp_file = any('{}' in arg for arg in command[1:])
if use_tmp_file:
- handle = tempfile.NamedTemporaryFile(
+ with tempfile.NamedTemporaryFile(
prefix='qutebrowser-fileselect-',
delete=False,
- )
- handle.close()
- tmpfilename = handle.name
+ ) as handle:
+ tmpfilename = handle.name
with utils.cleanup_file(tmpfilename):
command = (
command[:1] +
@@ -509,6 +508,7 @@ def _validated_selected_files(
)
continue
else:
+ # pylint: disable=else-if-used
if not os.path.isfile(selected_file):
message.warning(
f"Expected file but got folder, ignoring '{selected_file}'"
diff --git a/qutebrowser/browser/signalfilter.py b/qutebrowser/browser/signalfilter.py
index 0b002e345..88ac4a65d 100644
--- a/qutebrowser/browser/signalfilter.py
+++ b/qutebrowser/browser/signalfilter.py
@@ -88,6 +88,7 @@ class SignalFilter(QObject):
debug.dbg_signal(signal, args), tabidx))
signal.emit(*args)
else:
+ # pylint: disable=else-if-used
if log_signal:
log.signals.debug("ignoring: {} (tab {})".format(
debug.dbg_signal(signal, args), tabidx))
diff --git a/qutebrowser/browser/webelem.py b/qutebrowser/browser/webelem.py
index 9ec29ce07..05b0eadb3 100644
--- a/qutebrowser/browser/webelem.py
+++ b/qutebrowser/browser/webelem.py
@@ -132,6 +132,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
"""Dispatch an event to the element.
Args:
+ event: The name of the event.
bubbles: Whether this event should bubble.
cancelable: Whether this event can be cancelled.
composed: Whether the event will trigger listeners outside of a
@@ -160,9 +161,6 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
def is_content_editable(self) -> bool:
"""Check if an element has a contenteditable attribute.
- Args:
- elem: The QWebElement to check.
-
Return:
True if the element has a contenteditable attribute,
False otherwise.
@@ -233,6 +231,7 @@ class AbstractWebElement(collections.abc.MutableMapping): # type: ignore[type-a
'span': ['cm-'], # Jupyter Notebook
}
relevant_classes = classes[self.tag_name()]
+ # pylint: disable=consider-using-any-or-all
for klass in self.classes():
if any(klass.strip().startswith(e) for e in relevant_classes):
return True
diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py
index e40b3e736..e943e44e9 100644
--- a/qutebrowser/browser/webengine/notification.py
+++ b/qutebrowser/browser/webengine/notification.py
@@ -715,10 +715,14 @@ class DBusNotificationAdapter(AbstractNotificationAdapter):
# https://github.com/KDE/plasma-workspace/blob/v5.21.4/libnotificationmanager/server_p.cpp#L227-L237
# Created too many similar notifications in quick succession
"org.freedesktop.Notifications.Error.ExcessNotificationGeneration",
+
+ # From https://crashes.qutebrowser.org/view/b8c9838a - probably when
+ # notification daemon crashes?
+ "org.freedesktop.DBus.Error.Spawn.ChildSignaled",
}
def __init__(self, parent: QObject = None) -> None:
- super().__init__(bridge)
+ super().__init__(parent)
assert _notifications_supported()
if utils.is_windows:
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index ace23d14a..926ccf133 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -200,6 +200,14 @@ class WebEngineSearch(browsertab.AbstractSearch):
def _empty_flags(self):
return QWebEnginePage.FindFlags(0) # type: ignore[call-overload]
+ def _args_to_flags(self, reverse, ignore_case):
+ flags = self._empty_flags()
+ if self._is_case_sensitive(ignore_case):
+ flags |= QWebEnginePage.FindCaseSensitively
+ if reverse:
+ flags |= QWebEnginePage.FindBackward
+ return flags
+
def connect_signals(self):
self._wrap_handler.connect_signal(self._widget.page())
@@ -246,17 +254,14 @@ class WebEngineSearch(browsertab.AbstractSearch):
# Don't go to next entry on duplicate search
if self.text == text and self.search_displayed:
log.webview.debug("Ignoring duplicate search request"
- " for {}".format(text))
+ " for {}, but resetting flags".format(text))
+ self._flags = self._args_to_flags(reverse, ignore_case)
return
self.text = text
- self._flags = self._empty_flags()
+ self._flags = self._args_to_flags(reverse, ignore_case)
self._wrap_handler.reset_match_data()
self._wrap_handler.flag_wrap = wrap
- if self._is_case_sensitive(ignore_case):
- self._flags |= QWebEnginePage.FindCaseSensitively
- if reverse:
- self._flags |= QWebEnginePage.FindBackward
self._find(text, self._flags, result_cb, 'search')
diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py
index df3491ec2..7a41b995c 100644
--- a/qutebrowser/browser/webkit/webkittab.py
+++ b/qutebrowser/browser/webkit/webkittab.py
@@ -108,6 +108,16 @@ class WebKitSearch(browsertab.AbstractSearch):
def _empty_flags(self):
return QWebPage.FindFlags(0) # type: ignore[call-overload]
+ def _args_to_flags(self, reverse, ignore_case, wrap):
+ flags = self._empty_flags()
+ if self._is_case_sensitive(ignore_case):
+ flags |= QWebPage.FindCaseSensitively
+ if reverse:
+ flags |= QWebPage.FindBackward
+ if wrap:
+ flags |= QWebPage.FindWrapsAroundDocument
+ return flags
+
def _call_cb(self, callback, found, text, flags, caller):
"""Call the given callback if it's non-None.
@@ -150,7 +160,8 @@ class WebKitSearch(browsertab.AbstractSearch):
# Don't go to next entry on duplicate search
if self.text == text and self.search_displayed:
log.webview.debug("Ignoring duplicate search request"
- " for {}".format(text))
+ " for {}, but resetting flags".format(text))
+ self._flags = self._args_to_flags(reverse, ignore_case, wrap)
return
# Clear old search results, this is done automatically on QtWebEngine.
@@ -158,13 +169,7 @@ class WebKitSearch(browsertab.AbstractSearch):
self.text = text
self.search_displayed = True
- self._flags = self._empty_flags()
- if self._is_case_sensitive(ignore_case):
- self._flags |= QWebPage.FindCaseSensitively
- if reverse:
- self._flags |= QWebPage.FindBackward
- if wrap:
- self._flags |= QWebPage.FindWrapsAroundDocument
+ self._flags = self._args_to_flags(reverse, ignore_case, wrap)
# We actually search *twice* - once to highlight everything, then again
# to get a mark so we can navigate.
found = self._widget.findText(text, self._flags)
diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py
index 0242bed0c..289e29920 100644
--- a/qutebrowser/browser/webkit/webview.py
+++ b/qutebrowser/browser/webkit/webview.py
@@ -152,9 +152,6 @@ class WebView(QWebView):
Args:
e: The QPaintEvent.
-
- Return:
- The superclass event return value.
"""
frame = self.page().mainFrame()
new_pos = (frame.scrollBarValue(Qt.Horizontal),
@@ -186,9 +183,6 @@ class WebView(QWebView):
Args:
e: The QShowEvent.
-
- Return:
- The superclass event return value.
"""
super().showEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
@@ -198,9 +192,6 @@ class WebView(QWebView):
Args:
e: The QHideEvent.
-
- Return:
- The superclass event return value.
"""
super().hideEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
diff --git a/qutebrowser/commands/argparser.py b/qutebrowser/commands/argparser.py
index f8f083b72..2a11589f9 100644
--- a/qutebrowser/commands/argparser.py
+++ b/qutebrowser/commands/argparser.py
@@ -101,7 +101,7 @@ def type_conv(param, typ, value, *, str_choices=None):
Args:
param: The argparse.Parameter we're checking
- types: The allowed type
+ typ: The allowed type
value: The value to convert
str_choices: The allowed choices if the type ends up being a string
diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py
index 653f551ff..eee5b7cde 100644
--- a/qutebrowser/commands/command.py
+++ b/qutebrowser/commands/command.py
@@ -272,11 +272,10 @@ class Command:
if is_bool:
kwargs['action'] = 'store_true'
+ elif arg_info.metavar is not None:
+ kwargs['metavar'] = arg_info.metavar
else:
- if arg_info.metavar is not None:
- kwargs['metavar'] = arg_info.metavar
- else:
- kwargs['metavar'] = argparser.arg_name(param.name)
+ kwargs['metavar'] = argparser.arg_name(param.name)
if param.kind == inspect.Parameter.VAR_POSITIONAL:
kwargs['nargs'] = '*' if self._star_args_optional else '+'
@@ -320,9 +319,8 @@ class Command:
self.opt_args[param.name] = long_flag, short_flag
if not is_bool:
self.flags_with_args += [short_flag, long_flag]
- else:
- if not arg_info.hide:
- self.pos_args.append((param.name, name))
+ elif not arg_info.hide:
+ self.pos_args.append((param.name, name))
return args
def _get_type(self, param):
diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py
index 70c639207..8282aa7c7 100644
--- a/qutebrowser/commands/userscripts.py
+++ b/qutebrowser/commands/userscripts.py
@@ -342,9 +342,8 @@ class _WindowsUserscriptRunner(_BaseUserscriptRunner):
self._kwargs = kwargs
try:
- handle = tempfile.NamedTemporaryFile(delete=False)
- handle.close()
- self._filepath = handle.name
+ with tempfile.NamedTemporaryFile(delete=False) as handle:
+ self._filepath = handle.name
except OSError as e:
message.error("Error while creating tempfile: {}".format(e))
return
diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py
index 778333854..fb61e48fb 100644
--- a/qutebrowser/completion/completer.py
+++ b/qutebrowser/completion/completer.py
@@ -39,7 +39,7 @@ class CompletionInfo:
"""Context passed into all completion functions."""
config: config.Config
- keyconf: config.KeyConfig
+ keyconf: config.KeyConfig # pylint: disable=undefined-variable
win_id: int
cur_tab: 'browsertab.AbstractTab'
diff --git a/qutebrowser/completion/models/completionmodel.py b/qutebrowser/completion/models/completionmodel.py
index 81f4bba8e..236b25533 100644
--- a/qutebrowser/completion/models/completionmodel.py
+++ b/qutebrowser/completion/models/completionmodel.py
@@ -70,6 +70,7 @@ class CompletionModel(QAbstractItemModel):
Args:
index: The QModelIndex to get item flags for.
+ role: The Qt ItemRole to get the data for.
Return: The item data, or None on an invalid index.
"""
diff --git a/qutebrowser/completion/models/configmodel.py b/qutebrowser/completion/models/configmodel.py
index 736d09644..7c8473b3f 100644
--- a/qutebrowser/completion/models/configmodel.py
+++ b/qutebrowser/completion/models/configmodel.py
@@ -78,7 +78,7 @@ def value(optname, *values, info):
Args:
optname: The name of the config option this model shows.
- values: The values already provided on the command line.
+ *values: The values already provided on the command line.
info: A CompletionInfo instance.
"""
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
@@ -137,6 +137,7 @@ def bind(key, *, info):
Args:
key: the key being bound.
+ info: A CompletionInfo instance.
"""
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
data = _bind_current_default(key, info)
diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py
index 7642bf904..d8ebafb29 100644
--- a/qutebrowser/completion/models/miscmodels.py
+++ b/qutebrowser/completion/models/miscmodels.py
@@ -230,9 +230,7 @@ def _qdatetime_to_completion_format(qdate):
if not qdate.isValid():
ts = 0
else:
- ts = qdate.toMSecsSinceEpoch()
- if ts < 0:
- ts = 0
+ ts = max(qdate.toMSecsSinceEpoch(), 0)
pydate = datetime.datetime.fromtimestamp(ts / 1000)
return pydate.strftime(config.val.completion.timestamp_format)
diff --git a/qutebrowser/components/braveadblock.py b/qutebrowser/components/braveadblock.py
index 21319cb1b..b1e5b8a29 100644
--- a/qutebrowser/components/braveadblock.py
+++ b/qutebrowser/components/braveadblock.py
@@ -266,14 +266,13 @@ class BraveAdBlocker:
except DeserializationError:
message.error("Reading adblock filter data failed (corrupted data?). "
"Please run :adblock-update.")
- else:
- if (
- config.val.content.blocking.adblock.lists
- and not self._has_basedir
- and config.val.content.blocking.enabled
- and self.enabled
- ):
- message.info("Run :adblock-update to get adblock lists.")
+ elif (
+ config.val.content.blocking.adblock.lists
+ and not self._has_basedir
+ and config.val.content.blocking.enabled
+ and self.enabled
+ ):
+ message.info("Run :adblock-update to get adblock lists.")
def adblock_update(self) -> blockutils.BlocklistDownloads:
"""Update the adblock block lists."""
diff --git a/qutebrowser/components/hostblock.py b/qutebrowser/components/hostblock.py
index 1860b734c..191719f10 100644
--- a/qutebrowser/components/hostblock.py
+++ b/qutebrowser/components/hostblock.py
@@ -64,9 +64,10 @@ def get_fileobj(byte_io: IO[bytes]) -> IO[bytes]:
byte_io.seek(0) # rewind downloaded file
if zipfile.is_zipfile(byte_io):
byte_io.seek(0) # rewind what zipfile.is_zipfile did
- zf = zipfile.ZipFile(byte_io)
- filename = _guess_zip_filename(zf)
- byte_io = zf.open(filename, mode="r")
+ with zipfile.ZipFile(byte_io) as zf:
+ filename = _guess_zip_filename(zf)
+ # pylint: disable=consider-using-with
+ byte_io = zf.open(filename, mode="r")
else:
byte_io.seek(0) # rewind what zipfile.is_zipfile did
return byte_io
diff --git a/qutebrowser/components/misccommands.py b/qutebrowser/components/misccommands.py
index 120806bfe..fe908b7d2 100644
--- a/qutebrowser/components/misccommands.py
+++ b/qutebrowser/components/misccommands.py
@@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
+# To allow count being documented
+# pylint: disable=differing-param-doc
+
"""Various commands."""
import os
@@ -183,7 +186,10 @@ def screenshot(
raise cmdutils.CommandError(
f"File {filename} already exists (use --force to overwrite)")
- qrect = None if rect is None else utils.parse_rect(rect)
+ try:
+ qrect = None if rect is None else utils.parse_rect(rect)
+ except ValueError as e:
+ raise cmdutils.CommandError(str(e))
pic = tab.grab_pixmap(qrect)
if pic is None:
diff --git a/qutebrowser/components/utils/blockutils.py b/qutebrowser/components/utils/blockutils.py
index bd27baece..98681a488 100644
--- a/qutebrowser/components/utils/blockutils.py
+++ b/qutebrowser/components/utils/blockutils.py
@@ -125,7 +125,7 @@ class BlocklistDownloads(QObject):
filename: path to a local file to import.
"""
try:
- fileobj = open(filename, "rb")
+ fileobj = open(filename, "rb") # pylint: disable=consider-using-with
except OSError as e:
message.error(
"blockutils: Error while reading {}: {}".format(filename, e.strerror)
diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py
index 437a54a33..e054c8010 100644
--- a/qutebrowser/config/config.py
+++ b/qutebrowser/config/config.py
@@ -195,7 +195,7 @@ class KeyConfig:
See #5942.
"""
- cmd_to_keys: KeyConfig._ReverseBindings = {}
+ cmd_to_keys: "KeyConfig._ReverseBindings" = {}
bindings = self.get_bindings_for(mode)
for seq, full_cmd in sorted(bindings.items()):
for cmdtext in full_cmd.split(';;'):
@@ -387,6 +387,8 @@ class Config(QObject):
"""Get the given setting converted for Python code.
Args:
+ name: The name of the setting to get.
+ url: The URL to get the value for.
fallback: Use the global value if there's no URL-specific one.
"""
opt = self.get_opt(name)
diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py
index 62fc87f81..417ffce00 100644
--- a/qutebrowser/config/configcommands.py
+++ b/qutebrowser/config/configcommands.py
@@ -204,7 +204,7 @@ class ConfigCommands:
Args:
option: The name of the option.
- values: The values to cycle through.
+ *values: The values to cycle through.
pattern: The link:configuring{outfilesuffix}#patterns[URL pattern] to use.
temp: Set value temporarily until qutebrowser is closed.
print_: Print the value after setting.
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 5eb83955a..c2619ac1f 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -679,14 +679,14 @@ content.headers.user_agent:
# Vim-protip: Place your cursor below this comment and run
# :r!python scripts/dev/ua_fetch.py
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
- like Gecko) Chrome/90.0.4430.93 Safari/537.36"
- - Chrome 90 Win10
+ like Gecko) Chrome/92.0.4515.131 Safari/537.36"
+ - Chrome 92 Win10
- - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
- (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"
- - Chrome 90 macOS
+ (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
+ - Chrome 92 macOS
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
- Gecko) Chrome/90.0.4430.93 Safari/537.36"
- - Chrome 90 Linux
+ Gecko) Chrome/92.0.4515.131 Safari/537.36"
+ - Chrome 92 Linux
supports_pattern: true
desc: |
User agent to send.
@@ -1638,7 +1638,7 @@ hints.selectors:
- '[ngClick]'
- '[data-ng-click]'
- '[x-ng-click]'
- - '[tabindex]'
+ - '[tabindex]:not([tabindex="-1"])'
links:
- 'a[href]'
- 'area[href]'
@@ -1799,6 +1799,17 @@ input.media_keys:
On Linux, disabling this also disables Chromium's MPRIS integration.
+input.match_counts:
+ default: true
+ type: Bool
+ desc: >-
+ Interpret number prefixes as counts for bindings.
+
+ This enables for vi-like bindings that can be prefixed with a number to
+ indicate a count.
+ Disabling it allows for emacs-like bindings where number keys are passed
+ through (according to `input.forward_unbound_keys`) instead.
+
## keyhint
keyhint.blacklist:
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
index 6f0d0b13c..89100ad52 100644
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -70,7 +70,7 @@ class VersionChange(enum.Enum):
This is intended to use filters like "major" (show major only), "minor" (show
major/minor) or "patch" (show all changes).
"""
- allowed_values: Dict[str, List[VersionChange]] = {
+ allowed_values: Dict[str, List["VersionChange"]] = {
'major': [VersionChange.major],
'minor': [VersionChange.major, VersionChange.minor],
'patch': [VersionChange.major, VersionChange.minor, VersionChange.patch],
diff --git a/qutebrowser/config/configutils.py b/qutebrowser/config/configutils.py
index 480bbd85f..15f10b1ef 100644
--- a/qutebrowser/config/configutils.py
+++ b/qutebrowser/config/configutils.py
@@ -92,7 +92,7 @@ class Values:
values: Sequence[ScopedValue] = ()) -> None:
self.opt = opt
self._vmap: MutableMapping[
- Values._VmapKeyType, ScopedValue] = collections.OrderedDict()
+ "Values._VmapKeyType", ScopedValue] = collections.OrderedDict()
# A map from domain parts to rules that fall under them.
self._domain_map: Dict[
Optional[str], Set[ScopedValue]] = collections.defaultdict(set)
diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py
index c38ef5b01..2f93b7de5 100644
--- a/qutebrowser/config/qtargs.py
+++ b/qutebrowser/config/qtargs.py
@@ -260,9 +260,8 @@ def _qtwebengine_args(
# Only actually available in Qt 5.12.5, but let's save another
# check, as passing the option won't hurt.
yield '--enable-in-process-stack-traces'
- else:
- if 'stack' not in namespace.debug_flags:
- yield '--disable-in-process-stack-traces'
+ elif 'stack' not in namespace.debug_flags:
+ yield '--disable-in-process-stack-traces'
lang_override = _get_lang_override(
webengine_version=versions.webengine,
diff --git a/qutebrowser/config/websettings.py b/qutebrowser/config/websettings.py
index 7556d2b6d..41aeec6a3 100644
--- a/qutebrowser/config/websettings.py
+++ b/qutebrowser/config/websettings.py
@@ -238,6 +238,7 @@ def user_agent(url: QUrl = None) -> str:
def init(args: argparse.Namespace) -> None:
"""Initialize all QWeb(Engine)Settings."""
+ utils.unused(args)
if objects.backend == usertypes.Backend.QtWebEngine:
from qutebrowser.browser.webengine import webenginesettings
webenginesettings.init()
diff --git a/qutebrowser/extensions/loader.py b/qutebrowser/extensions/loader.py
index 7ae45023b..c7b619b3e 100644
--- a/qutebrowser/extensions/loader.py
+++ b/qutebrowser/extensions/loader.py
@@ -21,12 +21,11 @@
import pkgutil
import types
-import sys
import pathlib
import importlib
import argparse
import dataclasses
-from typing import Callable, Iterator, List, Optional, Set, Tuple
+from typing import Callable, Iterator, List, Optional, Tuple
from PyQt5.QtCore import pyqtSlot
@@ -95,18 +94,6 @@ def load_components(*, skip_hooks: bool = False) -> None:
def walk_components() -> Iterator[ExtensionInfo]:
"""Yield ExtensionInfo objects for all modules."""
- if hasattr(sys, 'frozen'):
- yield from _walk_pyinstaller()
- else:
- yield from _walk_normal()
-
-
-def _on_walk_error(name: str) -> None:
- raise ImportError("Failed to import {}".format(name))
-
-
-def _walk_normal() -> Iterator[ExtensionInfo]:
- """Walk extensions when not using PyInstaller."""
for _finder, name, ispkg in pkgutil.walk_packages(
# Only packages have a __path__ attribute,
# but we're sure this is one.
@@ -123,23 +110,6 @@ def _walk_normal() -> Iterator[ExtensionInfo]:
yield ExtensionInfo(name=name)
-def _walk_pyinstaller() -> Iterator[ExtensionInfo]:
- """Walk extensions when using PyInstaller.
-
- See https://github.com/pyinstaller/pyinstaller/issues/1905
-
- Inspired by:
- https://github.com/webcomics/dosage/blob/master/dosagelib/loader.py
- """
- toc: Set[str] = set()
- for importer in pkgutil.iter_importers('qutebrowser'):
- if hasattr(importer, 'toc'):
- toc |= importer.toc # type: ignore[union-attr]
- for name in toc:
- if name.startswith(components.__name__ + '.'):
- yield ExtensionInfo(name=name)
-
-
def _get_init_context() -> InitContext:
"""Get an InitContext object."""
return InitContext(data_dir=pathlib.Path(standarddir.data()),
@@ -190,3 +160,7 @@ def _on_config_changed(changed_name: str) -> None:
def init() -> None:
config.instance.changed.connect(_on_config_changed)
+
+
+def _on_walk_error(name: str) -> None:
+ raise ImportError("Failed to import {}".format(name))
diff --git a/qutebrowser/html/settings.html b/qutebrowser/html/settings.html
index 44824eeac..dfbc5c168 100644
--- a/qutebrowser/html/settings.html
+++ b/qutebrowser/html/settings.html
@@ -13,22 +13,112 @@ var cset = function(option, value) {
{% endblock %}
{% block style %}
-table { border: 1px solid grey; border-collapse: collapse; }
-pre { margin: 2px; }
-th, td { border: 1px solid grey; padding: 0px 5px; }
-th { background: lightgrey; }
-th pre { color: grey; text-align: left; }
-input { width: 98%; }
-.setting { width: 75%; }
-.value { width: 25%; text-align: center; }
-.noscript, .noscript-text { color:red; }
-.noscript-text { margin-bottom: 5cm; }
-.option_description { margin: .5ex 0; color: grey; font-size: 80%; font-style: italic; white-space: pre-line; }
+table {
+ border-spacing: 10px;
+}
+
+tbody tr:nth-child(odd) {
+ background: #eaf4fb;
+}
+
+pre {
+ margin: 2px;
+}
+
+th {
+ padding: 10px;
+ border-radius: 5px;
+ background: #a6dfff;
+ text-align: left;
+ font-weight: normal;
+ font-size: 1.5rem;
+ color: #084c88;
+}
+
+td {
+ padding: 5px 5px;
+}
+
+th pre {
+ color: grey;
+ text-align: left;
+}
+
+input {
+ padding: 8px;
+ width: 98%;
+ box-sizing: border-box;
+ border-radius: 4px;
+ border: 1px solid #01cdd0;
+ font-size: 0.9rem;
+ font-family: DejaVu, serif;
+}
+
+input:focus {
+ outline: none;
+ border: 2px solid #7a589ea6;
+}
+
+input[type="radio"] {
+ position: absolute; /* Positions the radio button relative to the edges of its containing element */
+ -webkit-appearance: none; /* Removes its native styling */
+ width: min-content;
+ margin: 0;
+ border: none;
+}
+
+label {
+ cursor: pointer;
+ margin-bottom: 2px;
+ padding: 5px 10px;
+ border-radius: 5px;
+ background-color: #dddddd;
+ color: #666666;
+}
+
+input[type="radio"]:checked + label {
+ background-color: #a6dfff;
+ color: #084c88;
+}
+
+.setting {
+ width: 60%;
+}
+
+.value {
+ width: 25%;
+ text-align: center;
+}
+
+.valid-value {
+ text-align: center;
+}
+
+.noscript, .noscript-text {
+ color: red;
+}
+
+.noscript-text {
+ margin-bottom: 5cm;
+}
+
+.option-description {
+ margin: .5ex 0;
+ color: #635d5dcf;
+ font-size: 80%;
+ font-style: italic;
+ white-space: pre-line;
+}
+
+.radio-button {
+ position: relative; /* The absolutely positioned element inside this tag (the radio button) gets positioned relative to it. */
+ display: inline-flex;
+ margin: 3px 1px;
+}
{% endblock %}
{% block content %}
<noscript><h1 class="noscript">View Only</h1><p class="noscript-text">Changing settings requires javascript to be enabled!</p></noscript>
-<header><h1>{{ title }}</h1></header>
<table>
<tr>
<th>Setting</th>
@@ -37,18 +127,36 @@ input { width: 98%; }
{% for option in configdata.DATA.values()|sort(attribute='name') if not option.no_autoconfig %}
<tr>
<!-- FIXME: convert to string properly -->
- <td class="setting">{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }})
+ <td class="setting">{{ option.name }}
{% if option.description %}
- <p class="option_description">{{ option.description|e }}</p>
+ <p class="option-description">{{ option.description|e }}</p>
{% endif %}
</td>
- <td class="value">
- <input type="text"
- id="input-{{ option.name }}"
- onblur="cset('{{ option.name }}', this.value)"
- value="{{ confget(option.name) }}">
- </input>
- </td>
+ {% if option.typ.valid_values is not none %}
+ <td class="valid-value">
+ {% for value in option.typ.valid_values.values %}
+ <div class="radio-button">
+ <input type="radio" id="input-{{ option.name }}-{{ loop.index0 }}"
+ name="{{ option.name }}" value="{{ value }}"
+ onclick="cset('{{ option.name }}', this.value)"
+ {% if confget(option.name) == value %}
+ checked
+ {% endif %}>
+ <label for="input-{{ option.name }}-{{ loop.index0 }}">
+ {{ value }}
+ </label>
+ </div>
+ {% endfor %}
+ </td>
+ {% else %}
+ <td class="value">
+ <input type="text"
+ id="input-{{ option.name }}"
+ onblur="cset('{{ option.name }}', this.value)"
+ value="{{ confget(option.name) }}">
+ </input>
+ </td>
+ {% endif %}
</tr>
{% endfor %}
</table>
diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py
index 7e688dab1..044c49278 100644
--- a/qutebrowser/keyinput/basekeyparser.py
+++ b/qutebrowser/keyinput/basekeyparser.py
@@ -75,7 +75,7 @@ class BindingTrie:
__slots__ = 'children', 'command'
def __init__(self) -> None:
- self.children: MutableMapping[keyutils.KeyInfo, BindingTrie] = {}
+ self.children: MutableMapping[keyutils.KeyInfo, "BindingTrie"] = {}
self.command: Optional[str] = None
def __setitem__(self, sequence: keyutils.KeySequence,
@@ -254,6 +254,9 @@ class BaseKeyParser(QObject):
def _match_count(self, sequence: keyutils.KeySequence,
dry_run: bool) -> bool:
"""Try to match a key as count."""
+ if not config.val.input.match_counts:
+ return False
+
txt = str(sequence[-1]) # To account for sequences changed above.
if (txt in string.digits and self._supports_count and
not (not self._count and txt == '0')):
diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py
index 6bd8c99b8..f74c59aa7 100644
--- a/qutebrowser/keyinput/keyutils.py
+++ b/qutebrowser/keyinput/keyutils.py
@@ -656,7 +656,6 @@ class KeySequence:
@classmethod
def parse(cls, keystr: str) -> 'KeySequence':
"""Parse a keystring like <Ctrl-x> or xyz and return a KeySequence."""
- # pylint: disable=protected-access
new = cls()
strings = list(_parse_keystring(keystr))
for sub in utils.chunk(strings, cls._MAX_LEN):
@@ -666,6 +665,5 @@ class KeySequence:
if keystr:
assert new, keystr
- # pylint: disable=protected-access
new._validate(keystr)
return new
diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py
index e081284ee..c3f06e185 100644
--- a/qutebrowser/mainwindow/tabbedbrowser.py
+++ b/qutebrowser/mainwindow/tabbedbrowser.py
@@ -406,15 +406,16 @@ class TabbedBrowser(QWidget):
else:
yes_action()
- def close_tab(self, tab, *, add_undo=True, new_undo=True):
+ def close_tab(self, tab, *, add_undo=True, new_undo=True, transfer=False):
"""Close a tab.
Args:
tab: The QWebView to be closed.
add_undo: Whether the tab close can be undone.
new_undo: Whether the undo entry should be a new item in the stack.
+ transfer: Whether the tab is closing because it is moving to a new window.
"""
- if config.val.tabs.tabs_are_windows:
+ if config.val.tabs.tabs_are_windows or transfer:
last_close = 'close'
else:
last_close = config.val.tabs.last_close
@@ -697,10 +698,9 @@ class TabbedBrowser(QWidget):
"""
if tab.data.keep_icon:
tab.data.keep_icon = False
- else:
- if (config.cache['tabs.tabs_are_windows'] and
- tab.data.should_show_icon()):
- self.widget.window().setWindowIcon(self.default_window_icon)
+ elif (config.cache['tabs.tabs_are_windows'] and
+ tab.data.should_show_icon()):
+ self.widget.window().setWindowIcon(self.default_window_icon)
@pyqtSlot()
def _on_load_status_changed(self, tab):
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index 7983127d5..07b1e5bef 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -486,7 +486,6 @@ class TabBar(QTabBar):
Args:
idx: The tab index to get the title for.
- handle_unset: Whether to return an empty string on KeyError.
"""
try:
return self.tab_data(idx, 'page-title')
diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py
index 45c52f54c..f7578a07f 100644
--- a/qutebrowser/misc/crashsignal.py
+++ b/qutebrowser/misc/crashsignal.py
@@ -150,6 +150,7 @@ class CrashHandler(QObject):
"""Start a new logfile and redirect faulthandler to it."""
logname = os.path.join(standarddir.data(), 'crash.log')
try:
+ # pylint: disable=consider-using-with
self._crash_log_file = open(logname, 'w', encoding='ascii')
except OSError:
log.init.exception("Error while opening crash log file!")
@@ -244,7 +245,7 @@ class CrashHandler(QObject):
if 'pdb-postmortem' in objects.debug_flags:
if tb is None:
- pdb.set_trace() # noqa: T100
+ pdb.set_trace() # noqa: T100 pylint: disable=forgotten-debug-statement
else:
pdb.post_mortem(tb)
diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py
index c4ff0bb85..034d7ff74 100644
--- a/qutebrowser/misc/earlyinit.py
+++ b/qutebrowser/misc/earlyinit.py
@@ -109,19 +109,25 @@ def init_faulthandler(fileobj=sys.__stderr__):
when sys.stderr got replaced, e.g. by "Python Tools for Visual Studio".
Args:
- fobj: An opened file object to write the traceback to.
+ fileobj: An opened file object to write the traceback to.
"""
- if fileobj is None:
+ try:
+ faulthandler.enable(fileobj)
+ except (RuntimeError, AttributeError):
# When run with pythonw.exe, sys.__stderr__ can be None:
# https://docs.python.org/3/library/sys.html#sys.__stderr__
- # If we'd enable faulthandler in that case, we just get a weird
- # exception, so we don't enable faulthandler if we have no stdout.
+ #
+ # With PyInstaller, it can be a NullWriter raising AttributeError on
+ # fileno: https://github.com/pyinstaller/pyinstaller/issues/4481
#
# Later when we have our data dir available we re-enable faulthandler
# to write to a file so we can display a crash to the user at the next
# start.
+ #
+ # Note that we don't have any logging initialized yet at this point, so
+ # this is a silent error.
return
- faulthandler.enable(fileobj)
+
if (hasattr(faulthandler, 'register') and hasattr(signal, 'SIGUSR1') and
sys.stderr is not None):
# If available, we also want a traceback on SIGUSR1.
@@ -205,7 +211,6 @@ def _check_modules(modules):
for name, text in modules.items():
try:
- # pylint: disable=bad-continuation
with log.py_warning_filter(
category=DeprecationWarning,
message=r'invalid escape sequence'
@@ -220,7 +225,6 @@ def _check_modules(modules):
category=DeprecationWarning,
message=r'Creating a LegacyVersion has been deprecated',
):
- # pylint: enable=bad-continuation
importlib.import_module(name)
except ImportError as e:
_die(text, e)
diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py
index e5ccd1b8b..8e9747ad8 100644
--- a/qutebrowser/misc/guiprocess.py
+++ b/qutebrowser/misc/guiprocess.py
@@ -27,7 +27,7 @@ from typing import Mapping, Sequence, Dict, Optional
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess,
QProcessEnvironment, QByteArray, QUrl, Qt)
-from qutebrowser.utils import message, log, utils, usertypes
+from qutebrowser.utils import message, log, utils, usertypes, version
from qutebrowser.api import cmdutils, apitypes
from qutebrowser.completion.models import miscmodels
@@ -262,7 +262,7 @@ class GUIProcess(QObject):
QProcess.Crashed: f"{what.capitalize()} crashed",
QProcess.Timedout: f"{what.capitalize()} timed out",
QProcess.WriteError: f"Write error for {what}",
- QProcess.WriteError: f"Read error for {what}",
+ QProcess.ReadError: f"Read error for {what}",
}
error_string = self._proc.errorString()
msg = ': '.join([error_descriptions[error], error_string])
@@ -273,7 +273,9 @@ class GUIProcess(QObject):
known_errors = ['No such file or directory', 'Permission denied']
if (': ' in error_string and # pragma: no branch
error_string.split(': ', maxsplit=1)[1] in known_errors):
- msg += f'\n(Hint: Make sure {self.cmd!r} exists and is executable)'
+ msg += f'\nHint: Make sure {self.cmd!r} exists and is executable'
+ if version.is_flatpak():
+ msg += ' inside the Flatpak container'
message.error(msg)
diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py
index 4fcef72e4..93d9af09d 100644
--- a/qutebrowser/misc/keyhintwidget.py
+++ b/qutebrowser/misc/keyhintwidget.py
@@ -87,6 +87,7 @@ class KeyHintView(QLabel):
"""Show hints for the given prefix (or hide if prefix is empty).
Args:
+ mode: The key mode to show the keyhints for.
prefix: The current partial keystring.
"""
match = re.fullmatch(r'(\d*)(.*)', prefix)
diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py
index 9d35692e9..fee87354f 100644
--- a/qutebrowser/misc/lineparser.py
+++ b/qutebrowser/misc/lineparser.py
@@ -99,6 +99,7 @@ class BaseLineParser(QObject):
self._opened = True
try:
if self._binary:
+ # pylint: disable=unspecified-encoding
with open(self._configfile, mode + 'b') as f:
yield f
else:
diff --git a/qutebrowser/misc/miscwidgets.py b/qutebrowser/misc/miscwidgets.py
index b89288fc7..4354ed2ab 100644
--- a/qutebrowser/misc/miscwidgets.py
+++ b/qutebrowser/misc/miscwidgets.py
@@ -213,7 +213,7 @@ class _FoldArrow(QWidget):
"""Paint the arrow.
Args:
- _paint: The QPaintEvent (unused).
+ _event: The QPaintEvent (unused).
"""
opt = QStyleOption()
opt.initFrom(self)
diff --git a/qutebrowser/misc/msgbox.py b/qutebrowser/misc/msgbox.py
index 9d5fbf601..4271c2639 100644
--- a/qutebrowser/misc/msgbox.py
+++ b/qutebrowser/misc/msgbox.py
@@ -42,6 +42,7 @@ def msgbox(parent, title, text, *, icon, buttons=QMessageBox.Ok,
parent: The parent to set for the message box.
title: The title to set.
text: The text to set.
+ icon: The QIcon to show in the box.
buttons: The buttons to set (QMessageBox::StandardButtons)
on_finished: A slot to connect to the 'finished' signal.
plain_text: Whether to force plain text (True) or rich text (False).
diff --git a/qutebrowser/misc/quitter.py b/qutebrowser/misc/quitter.py
index a51891685..905429989 100644
--- a/qutebrowser/misc/quitter.py
+++ b/qutebrowser/misc/quitter.py
@@ -194,7 +194,7 @@ class Quitter(QObject):
# Open a new process and immediately shutdown the existing one
try:
args = self._get_restart_args(pages, session, override_args)
- subprocess.Popen(args)
+ subprocess.Popen(args) # pylint: disable=consider-using-with
except OSError:
log.destroy.exception("Failed to restart")
return False
diff --git a/qutebrowser/misc/savemanager.py b/qutebrowser/misc/savemanager.py
index ee22ba14d..1b72734cb 100644
--- a/qutebrowser/misc/savemanager.py
+++ b/qutebrowser/misc/savemanager.py
@@ -157,6 +157,7 @@ class SaveManager(QObject):
"""Save a saveable by name.
Args:
+ name: The name of the saveable to save.
is_exit: Whether we're currently exiting qutebrowser.
explicit: Whether this save operation was triggered explicitly.
silent: Don't write information to log. Used to reduce log spam
diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py
index 11af329e0..a28f3a848 100644
--- a/qutebrowser/misc/sessions.py
+++ b/qutebrowser/misc/sessions.py
@@ -206,12 +206,11 @@ class SessionManager(QObject):
if item.title():
data['title'] = item.title()
- else:
+ elif tab.history.current_idx() == idx:
# https://github.com/qutebrowser/qutebrowser/issues/879
- if tab.history.current_idx() == idx:
- data['title'] = tab.title()
- else:
- data['title'] = data['url']
+ data['title'] = tab.title()
+ else:
+ data['title'] = data['url']
if item.originalUrl() != item.url():
encoded = item.originalUrl().toEncoded()
diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py
index 4db91360e..c7d93e76d 100644
--- a/qutebrowser/misc/split.py
+++ b/qutebrowser/misc/split.py
@@ -128,6 +128,7 @@ def split(s, keep=False):
"""Split a string via ShellLexer.
Args:
+ s: The string to split.
keep: Whether to keep special chars in the split output.
"""
lexer = ShellLexer(s)
diff --git a/qutebrowser/misc/sql.py b/qutebrowser/misc/sql.py
index 814eb2bb0..8f3282a2f 100644
--- a/qutebrowser/misc/sql.py
+++ b/qutebrowser/misc/sql.py
@@ -479,9 +479,6 @@ class SqlTable(QObject):
Args:
field: Field to use as the key.
value: Key value to delete.
-
- Return:
- The number of rows deleted.
"""
q = self.database.query(f"DELETE FROM {self._name} where {field} = :val")
q.run(val=value)
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
index d0819f832..c576c4a06 100644
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -87,6 +87,11 @@ def get_argparser():
help="Set the base name of the desktop entry for this "
"application. Used to set the app_id under Wayland. See "
"https://doc.qt.io/qt-5/qguiapplication.html#desktopFileName-prop")
+ parser.add_argument('--untrusted-args',
+ action='store_true',
+ help="Mark all following arguments as untrusted, which "
+ "enforces that they are URLs/search terms (and not flags or "
+ "commands)")
parser.add_argument('--json-args', help=argparse.SUPPRESS)
parser.add_argument('--temp-basedir-restarted',
@@ -207,7 +212,27 @@ def _unpack_json_args(args):
return argparse.Namespace(**new_args)
+def _validate_untrusted_args(argv):
+ # NOTE: Do not use f-strings here, as this should run with older Python
+ # versions (so that a proper error can be displayed)
+ try:
+ untrusted_idx = argv.index('--untrusted-args')
+ except ValueError:
+ return
+
+ rest = argv[untrusted_idx + 1:]
+ if len(rest) > 1:
+ sys.exit(
+ "Found multiple arguments ({}) after --untrusted-args, "
+ "aborting.".format(' '.join(rest)))
+
+ for arg in rest:
+ if arg.startswith(('-', ':')):
+ sys.exit("Found {} after --untrusted-args, aborting.".format(arg))
+
+
def main():
+ _validate_untrusted_args(sys.argv)
parser = get_argparser()
argv = sys.argv[1:]
args = parser.parse_args(argv)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 9cd07e2e3..54ca4029b 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -381,8 +381,8 @@ def qt_message_handler(msg_type: QtCore.QtMsgType,
"""Qt message handler to redirect qWarning etc. to the logging system.
Args:
- QtMsgType msg_type: The level of the message.
- QMessageLogContext context: The source code location of the message.
+ msg_type: The level of the message.
+ context: The source code location of the message.
msg: The message text.
"""
# Mapping from Qt logging levels to the matching logging module levels.
diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py
index 50a438637..c490aa4e8 100644
--- a/qutebrowser/utils/message.py
+++ b/qutebrowser/utils/message.py
@@ -18,7 +18,8 @@
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
# Because every method needs to have a log_stack argument
-# pylint: disable=unused-argument
+# and because we use *args a lot
+# pylint: disable=unused-argument,differing-param-doc
"""Message singleton so we don't have to define unneeded signals."""
@@ -125,7 +126,7 @@ def ask(*args: Any, **kwargs: Any) -> Any:
Return:
The answer the user gave or None if the prompt was cancelled.
"""
- question = _build_question(*args, **kwargs) # pylint: disable=missing-kwoa
+ question = _build_question(*args, **kwargs)
global_bridge.ask(question, blocking=True)
answer = question.answer
question.deleteLater()
@@ -139,7 +140,7 @@ def ask_async(title: str,
"""Ask an async question in the statusbar.
Args:
- message: The message to display to the user.
+ title: The message to display to the user.
mode: A PromptMode.
handler: The function to get called with the answer as argument.
default: The default value to display.
@@ -174,7 +175,7 @@ def confirm_async(*, yes_action: _ActionType,
The question object.
"""
kwargs['mode'] = usertypes.PromptMode.yesno
- question = _build_question(**kwargs) # pylint: disable=missing-kwoa
+ question = _build_question(**kwargs)
question.answered_yes.connect(yes_action)
if no_action is not None:
question.answered_no.connect(no_action)
diff --git a/qutebrowser/utils/resources.py b/qutebrowser/utils/resources.py
index ff5ec9d9a..f561d6747 100644
--- a/qutebrowser/utils/resources.py
+++ b/qutebrowser/utils/resources.py
@@ -82,7 +82,7 @@ def _glob(
else: # zipfile.Path or importlib_resources compat object
# Unfortunately, we can't tell mypy about resource_path being of type
# Union[pathlib.Path, zipfile.Path] because we set "python_version = 3.6" in
- # .mypy.ini, but the zipfiel stubs (correctly) only declare zipfile.Path with
+ # .mypy.ini, but the zipfile stubs (correctly) only declare zipfile.Path with
# Python 3.8...
assert glob_path.is_dir(), glob_path # type: ignore[unreachable]
for subpath in glob_path.iterdir():
diff --git a/qutebrowser/utils/urlutils.py b/qutebrowser/utils/urlutils.py
index 002f10411..cfba2c1d8 100644
--- a/qutebrowser/utils/urlutils.py
+++ b/qutebrowser/utils/urlutils.py
@@ -95,6 +95,7 @@ def _parse_search_term(s: str) -> Tuple[Optional[str], Optional[str]]:
engine = None
term = s
else:
+ # pylint: disable=else-if-used
if config.val.url.open_base_url and s in config.val.url.searchengines:
engine = s
term = None
@@ -328,6 +329,7 @@ def invalid_url_error(url: QUrl, action: str) -> None:
"""Display an error message for a URL.
Args:
+ url: The URL to display a message for.
action: The action which was interrupted by the error.
"""
if url.isValid():
diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py
index ee0f899cc..56c29899d 100644
--- a/qutebrowser/utils/usertypes.py
+++ b/qutebrowser/utils/usertypes.py
@@ -301,6 +301,7 @@ class Backend(enum.Enum):
"""The backend being used (usertypes.backend)."""
+ # pylint: disable=invalid-name
QtWebKit = enum.auto()
QtWebEngine = enum.auto()
diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py
index a56769255..9c68932f3 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -382,7 +382,7 @@ def get_repr(obj: Any, constructor: bool = False, **attrs: Any) -> str:
obj: The object to get a repr for.
constructor: If True, show the Foo(one=1, two=2) form instead of
<Foo one=1 two=2>.
- attrs: The attributes to add.
+ **attrs: The attributes to add.
"""
cls = qualname(obj.__class__)
parts = []
@@ -391,11 +391,10 @@ def get_repr(obj: Any, constructor: bool = False, **attrs: Any) -> str:
parts.append('{}={!r}'.format(name, val))
if constructor:
return '{}({})'.format(cls, ', '.join(parts))
+ elif parts:
+ return '<{} {}>'.format(cls, ' '.join(parts))
else:
- if parts:
- return '<{} {}>'.format(cls, ' '.join(parts))
- else:
- return '<{}>'.format(cls)
+ return '<{}>'.format(cls)
def qualname(obj: Any) -> str:
@@ -669,11 +668,12 @@ def yaml_load(f: Union[str, IO[str]]) -> Any:
r"of from 'collections\.abc' is deprecated.*"):
try:
data = yaml.load(f, Loader=YamlLoader)
- except ValueError as e:
- if str(e).startswith('could not convert string to float'):
+ except ValueError as e: # pragma: no cover
+ pyyaml_error = 'could not convert string to float'
+ if str(e).startswith(pyyaml_error):
# WORKAROUND for https://github.com/yaml/pyyaml/issues/168
raise yaml.YAMLError(e)
- raise # pragma: no cover
+ raise
end = datetime.datetime.now()
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 8cd244fca..3beb6fb83 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -773,8 +773,6 @@ def _backend() -> str:
if objects.backend == usertypes.Backend.QtWebKit:
return 'new QtWebKit (WebKit {})'.format(qWebKitVersion())
elif objects.backend == usertypes.Backend.QtWebEngine:
- webengine = usertypes.Backend.QtWebEngine
- assert objects.backend == webengine, objects.backend
return str(qtwebengine_versions(
avoid_init='avoid-chromium-init' in objects.debug_flags))
raise utils.Unreachable(objects.backend)
diff --git a/requirements.txt b/requirements.txt
index a158bdde6..204ccff9d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,11 +3,11 @@
adblock==0.5.0
colorama==0.4.4
dataclasses==0.6 ; python_version<"3.7"
-importlib-metadata==4.8.1 ; python_version<"3.8"
-importlib-resources==5.2.2 ; python_version<"3.9"
-Jinja2==3.0.2
+importlib-metadata==4.8.2 ; python_version<"3.8"
+importlib-resources==5.4.0 ; python_version<"3.9"
+Jinja2==3.0.3
MarkupSafe==2.0.1
Pygments==2.10.0
-PyYAML==5.4.1
-typing-extensions==3.10.0.2
+PyYAML==6.0
+typing_extensions==4.0.1 ; python_version<"3.8"
zipp==3.6.0
diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py
index a1c6646eb..5463441be 100755
--- a/scripts/dev/build_release.py
+++ b/scripts/dev/build_release.py
@@ -26,6 +26,7 @@ import os.path
import sys
import time
import shutil
+import pathlib
import plistlib
import subprocess
import argparse
@@ -227,7 +228,7 @@ def patch_mac_app():
# Replace some duplicate files by symlinks
framework_path = os.path.join(app_path, 'Contents', 'MacOS', 'PyQt5',
- 'Qt', 'lib', 'QtWebEngineCore.framework')
+ 'Qt5', 'lib', 'QtWebEngineCore.framework')
core_lib = os.path.join(framework_path, 'Versions', '5', 'QtWebEngineCore')
os.remove(core_lib)
@@ -480,24 +481,27 @@ def build_sdist():
"""Build an sdist and list the contents."""
utils.print_title("Building sdist")
- _maybe_remove('dist')
+ dist_path = pathlib.Path('dist')
+ _maybe_remove(dist_path)
- subprocess.run([sys.executable, 'setup.py', 'sdist'], check=True)
- dist_files = os.listdir(os.path.abspath('dist'))
- assert len(dist_files) == 1
+ subprocess.run([sys.executable, '-m', 'build'], check=True)
- dist_file = os.path.join('dist', dist_files[0])
- subprocess.run(['gpg', '--detach-sign', '-a', dist_file], check=True)
+ dist_files = list(dist_path.glob('*.tar.gz'))
+ filename = 'qutebrowser-{}.tar.gz'.format(qutebrowser.__version__)
+ assert dist_files == [dist_path / filename], dist_files
+ dist_file = dist_files[0]
+
+ subprocess.run(['gpg', '--detach-sign', '-a', str(dist_file)], check=True)
- tar = tarfile.open(dist_file)
by_ext = collections.defaultdict(list)
- for tarinfo in tar.getmembers():
- if not tarinfo.isfile():
- continue
- name = os.sep.join(tarinfo.name.split(os.sep)[1:])
- _base, ext = os.path.splitext(name)
- by_ext[ext].append(name)
+ with tarfile.open(dist_file) as tar:
+ for tarinfo in tar.getmembers():
+ if not tarinfo.isfile():
+ continue
+ name = os.sep.join(tarinfo.name.split(os.sep)[1:])
+ _base, ext = os.path.splitext(name)
+ by_ext[ext].append(name)
assert '.pyc' not in by_ext
@@ -507,11 +511,13 @@ def build_sdist():
utils.print_subtitle(ext)
print('\n'.join(files))
- filename = 'qutebrowser-{}.tar.gz'.format(qutebrowser.__version__)
artifacts = [
- (os.path.join('dist', filename), 'application/gzip', 'Source release'),
- (os.path.join('dist', filename + '.asc'), 'application/pgp-signature',
- 'Source release - PGP signature'),
+ (str(dist_file), 'application/gzip', 'Source release'),
+ (
+ str(dist_file.with_suffix(dist_file.suffix + '.asc')),
+ 'application/pgp-signature',
+ 'Source release - PGP signature',
+ ),
]
return artifacts
@@ -550,6 +556,7 @@ def github_upload(artifacts, tag, gh_token):
Args:
artifacts: A list of (filename, mimetype, description) tuples
tag: The name of the release tag
+ gh_token: The GitHub token to use
"""
import github3
import github3.exceptions
@@ -599,15 +606,19 @@ def github_upload(artifacts, tag, gh_token):
def pypi_upload(artifacts):
"""Upload the given artifacts to PyPI using twine."""
utils.print_title("Uploading to PyPI...")
- filenames = [a[0] for a in artifacts]
- subprocess.run([sys.executable, '-m', 'twine', 'upload'] + filenames,
- check=True)
+ run_twine('upload', artifacts)
-def upgrade_sdist_dependencies():
- """Make sure we have the latest tools for an sdist release."""
- subprocess.run([sys.executable, '-m', 'pip', 'install', '-U', 'twine',
- 'pip', 'wheel', 'setuptools'], check=True)
+def twine_check(artifacts):
+ """Check packages using 'twine check'."""
+ utils.print_title("Running twine check...")
+ run_twine('check', artifacts, '--strict')
+
+
+def run_twine(command, artifacts, *args):
+ filenames = [a[0] for a in artifacts]
+ subprocess.run([sys.executable, '-m', 'twine', command] + list(args) + filenames,
+ check=True)
def main():
@@ -667,9 +678,9 @@ def main():
elif sys.platform == 'darwin':
artifacts = build_mac(gh_token=gh_token, debug=args.debug)
else:
- upgrade_sdist_dependencies()
test_makefile()
artifacts = build_sdist()
+ twine_check(artifacts)
upload_to_pypi = True
if args.upload:
diff --git a/scripts/dev/pylint_checkers/qute_pylint/modeline.py b/scripts/dev/pylint_checkers/qute_pylint/modeline.py
index 114cfaf94..1df2c375e 100644
--- a/scripts/dev/pylint_checkers/qute_pylint/modeline.py
+++ b/scripts/dev/pylint_checkers/qute_pylint/modeline.py
@@ -31,9 +31,9 @@ class ModelineChecker(checkers.BaseChecker):
__implements__ = interfaces.IRawChecker
name = 'modeline'
- msgs = {'W9002': ('Does not have vim modeline', 'modeline-missing', None),
- 'W9003': ('Modeline is invalid', 'invalid-modeline', None),
- 'W9004': ('Modeline position is wrong', 'modeline-position', None)}
+ msgs = {'W9102': ('Does not have vim modeline', 'modeline-missing', None),
+ 'W9103': ('Modeline is invalid', 'invalid-modeline', None),
+ 'W9104': ('Modeline position is wrong', 'modeline-position', None)}
options = ()
priority = -1
diff --git a/scripts/dev/pylint_checkers/qute_pylint/openencoding.py b/scripts/dev/pylint_checkers/qute_pylint/openencoding.py
deleted file mode 100644
index 972a55db8..000000000
--- a/scripts/dev/pylint_checkers/qute_pylint/openencoding.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
-
-# Copyright 2014-2021 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/>.
-
-"""Make sure open() has an encoding set."""
-
-import astroid
-from pylint import interfaces, checkers
-from pylint.checkers import utils
-
-
-class OpenEncodingChecker(checkers.BaseChecker):
-
- """Checker to check open() has an encoding set."""
-
- __implements__ = interfaces.IAstroidChecker
- name = 'open-encoding'
-
- msgs = {
- 'W9400': ('open() called without encoding', 'open-without-encoding',
- None),
- }
-
- @utils.check_messages('open-without-encoding')
- def visit_call(self, node):
- """Visit a Call node."""
- if hasattr(node, 'func'):
- infer = utils.safe_infer(node.func)
- if infer and infer.root().name == '_io':
- if getattr(node.func, 'name', None) in ['open', 'file']:
- self._check_open_encoding(node)
-
- def _check_open_encoding(self, node):
- """Check that an open() call always has an encoding set."""
- try:
- mode_arg = utils.get_argument_from_call(node, position=1,
- keyword='mode')
- except utils.NoSuchArgumentError:
- mode_arg = None
- _encoding = None
- try:
- _encoding = utils.get_argument_from_call(node, position=2)
- except utils.NoSuchArgumentError:
- try:
- _encoding = utils.get_argument_from_call(node,
- keyword='encoding')
- except utils.NoSuchArgumentError:
- pass
- if _encoding is None:
- if mode_arg is None:
- mode = None
- else:
- mode = utils.safe_infer(mode_arg)
- if mode is not None and not isinstance(mode, astroid.Const):
- # We can't say what mode is exactly.
- return
- if mode is None:
- self.add_message('open-without-encoding', node=node)
- elif 'b' in getattr(mode, 'value', ''):
- # Files opened as binary don't need an encoding.
- return
- else:
- self.add_message('open-without-encoding', node=node)
-
-
-def register(linter):
- """Register this checker."""
- linter.register_checker(OpenEncodingChecker(linter))
diff --git a/scripts/dev/recompile_requirements.py b/scripts/dev/recompile_requirements.py
index a4cd81ad4..89b0fe515 100644
--- a/scripts/dev/recompile_requirements.py
+++ b/scripts/dev/recompile_requirements.py
@@ -98,7 +98,6 @@ CHANGELOG_URLS = {
'pep8-naming': 'https://github.com/PyCQA/pep8-naming/blob/master/CHANGELOG.rst',
'pycodestyle': 'https://github.com/PyCQA/pycodestyle/blob/master/CHANGES.txt',
'pyflakes': 'https://github.com/PyCQA/pyflakes/blob/master/NEWS.rst',
- 'cached-property': 'https://github.com/pydanny/cached-property/blob/master/HISTORY.md',
'cffi': 'https://github.com/python-cffi/release-doc/blob/master/doc/source/whatsnew.rst',
'astroid': 'https://github.com/PyCQA/astroid/blob/2.4/ChangeLog',
'pytest-instafail': 'https://github.com/pytest-dev/pytest-instafail/blob/master/CHANGES.rst',
@@ -134,7 +133,7 @@ CHANGELOG_URLS = {
'six': 'https://github.com/benjaminp/six/blob/master/CHANGES',
'altgraph': 'https://github.com/ronaldoussoren/altgraph/blob/master/doc/changelog.rst',
'urllib3': 'https://github.com/urllib3/urllib3/blob/master/CHANGES.rst',
- 'lxml': 'https://lxml.de/index.html#old-versions',
+ 'lxml': 'https://github.com/lxml/lxml/blob/master/CHANGES.txt',
'jwcrypto': 'https://github.com/latchset/jwcrypto/commits/master',
'wrapt': 'https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst',
'pep517': 'https://github.com/pypa/pep517/blob/master/doc/changelog.rst',
@@ -142,10 +141,8 @@ CHANGELOG_URLS = {
'toml': 'https://github.com/uiri/toml/releases',
'tomli': 'https://github.com/hukkin/tomli/blob/master/CHANGELOG.md',
'PyQt5': 'https://www.riverbankcomputing.com/news',
- 'PyQt5-Qt': 'https://www.riverbankcomputing.com/news',
'PyQt5-Qt5': 'https://www.riverbankcomputing.com/news',
'PyQtWebEngine': 'https://www.riverbankcomputing.com/news',
- 'PyQtWebEngine-Qt': 'https://www.riverbankcomputing.com/news',
'PyQtWebEngine-Qt5': 'https://www.riverbankcomputing.com/news',
'PyQt-builder': 'https://www.riverbankcomputing.com/news',
'PyQt5-sip': 'https://www.riverbankcomputing.com/news',
@@ -158,11 +155,11 @@ CHANGELOG_URLS = {
'cheroot': 'https://cheroot.cherrypy.org/en/latest/history.html',
'certifi': 'https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport',
'chardet': 'https://github.com/chardet/chardet/releases',
- 'charset-normalizer': 'https://github.com/Ousret/charset_normalizer/commits/master',
+ 'charset-normalizer': 'https://github.com/Ousret/charset_normalizer/blob/master/CHANGELOG.md',
'idna': 'https://github.com/kjd/idna/blob/master/HISTORY.rst',
'tldextract': 'https://github.com/john-kurkowski/tldextract/blob/master/CHANGELOG.md',
'backports.entry-points-selectable': 'https://github.com/jaraco/backports.entry_points_selectable/blob/main/CHANGES.rst',
- 'typing-extensions': 'https://github.com/python/typing/commits/master/typing_extensions',
+ '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',
@@ -171,15 +168,13 @@ CHANGELOG_URLS = {
'check-manifest': 'https://github.com/mgedmin/check-manifest/blob/master/CHANGES.rst',
'yamllint': 'https://github.com/adrienverge/yamllint/blob/master/CHANGELOG.rst',
'pathspec': 'https://github.com/cpburnz/python-path-specification/blob/master/CHANGES.rst',
- 'filelock': 'https://github.com/tox-dev/py-filelock/commits/main',
+ 'filelock': 'https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst',
'github3.py': 'https://github3py.readthedocs.io/en/master/release-notes/index.html',
'manhole': 'https://github.com/ionelmc/python-manhole/blob/master/CHANGELOG.rst',
'pycparser': 'https://github.com/eliben/pycparser/blob/master/CHANGES',
'python-dateutil': 'https://dateutil.readthedocs.io/en/stable/changelog.html',
'platformdirs': 'https://github.com/platformdirs/platformdirs/blob/main/CHANGES.rst',
'pluggy': 'https://github.com/pytest-dev/pluggy/blob/master/CHANGELOG.rst',
- 'inflect': 'https://github.com/jazzband/inflect/blob/master/CHANGES.rst',
- 'jinja2-pluralize': 'https://github.com/audreyfeldroy/jinja2_pluralize/blob/master/HISTORY.rst',
'mypy-extensions': 'https://github.com/python/mypy_extensions/commits/master',
'pyroma': 'https://github.com/regebro/pyroma/blob/master/HISTORY.txt',
'adblock': 'https://github.com/ArniDagur/python-adblock/blob/master/CHANGELOG.md',
@@ -193,6 +188,17 @@ CHANGELOG_URLS = {
'future': 'https://python-future.org/whatsnew.html',
'pefile': 'https://github.com/erocarrera/pefile/commits/master',
'Deprecated': 'https://github.com/tantale/deprecated/blob/master/CHANGELOG.rst',
+ 'SecretStorage': 'https://github.com/mitya57/secretstorage/blob/master/changelog',
+ 'bleach': 'https://github.com/mozilla/bleach/blob/main/CHANGES',
+ 'jeepney': 'https://gitlab.com/takluyver/jeepney/-/blob/master/docs/release-notes.rst',
+ 'keyring': 'https://github.com/jaraco/keyring/blob/main/CHANGES.rst',
+ 'pkginfo': 'https://bazaar.launchpad.net/~tseaver/pkginfo/trunk/view/head:/CHANGES.txt',
+ 'readme-renderer': 'https://github.com/pypa/readme_renderer/blob/main/CHANGES.rst',
+ 'requests-toolbelt': 'https://github.com/requests/toolbelt/blob/master/HISTORY.rst',
+ 'rfc3986': 'https://rfc3986.readthedocs.io/en/latest/release-notes/index.html',
+ 'tqdm': 'https://tqdm.github.io/releases/',
+ 'twine': 'https://twine.readthedocs.io/en/stable/changelog.html',
+ 'webencodings': 'https://github.com/gsnedders/python-webencodings/commits/master',
}
diff --git a/scripts/dev/run_pylint_on_tests.py b/scripts/dev/run_pylint_on_tests.py
index d0385bd17..28c6e32c9 100644
--- a/scripts/dev/run_pylint_on_tests.py
+++ b/scripts/dev/run_pylint_on_tests.py
@@ -59,8 +59,11 @@ def main():
'len-as-condition',
'compare-to-empty-string',
'pointless-statement',
+ 'use-implicit-booleaness-not-comparison',
# directories without __init__.py...
'import-error',
+ # tests/helpers imports
+ 'wrong-import-order',
]
toxinidir = sys.argv[1]
diff --git a/scripts/dev/update_3rdparty.py b/scripts/dev/update_3rdparty.py
index 88f56b7f3..60b72d110 100755
--- a/scripts/dev/update_3rdparty.py
+++ b/scripts/dev/update_3rdparty.py
@@ -160,11 +160,11 @@ def test_dicts():
print('Testing dictionary {}... '.format(lang.code), end='')
lang_url = urllib.parse.urljoin(dictcli.API_URL, lang.remote_filename)
request = urllib.request.Request(lang_url, method='HEAD')
- response = urllib.request.urlopen(request)
- if response.status == 200:
- print('OK')
- else:
- print('ERROR: {}'.format(response.status))
+ with urllib.request.urlopen(request) as response:
+ if response.status == 200:
+ print('OK')
+ else:
+ print('ERROR: {}'.format(response.status))
def run(nsis=False, ace=False, pdfjs=True, fancy_dmg=False, pdfjs_version=None,
diff --git a/scripts/dictcli.py b/scripts/dictcli.py
index 8cb93fb8a..a937fd31d 100755
--- a/scripts/dictcli.py
+++ b/scripts/dictcli.py
@@ -142,11 +142,11 @@ def parse_entry(entry):
def language_list_from_api():
"""Return a JSON with a list of available languages from Google API."""
listurl = API_URL + '?format=JSON'
- response = urllib.request.urlopen(listurl)
- # A special 5-byte prefix must be stripped from the response content
- # See: https://github.com/google/gitiles/issues/22
- # https://github.com/google/gitiles/issues/82
- json_content = response.read()[5:]
+ with urllib.request.urlopen(listurl) as response:
+ # A special 5-byte prefix must be stripped from the response content
+ # See: https://github.com/google/gitiles/issues/22
+ # https://github.com/google/gitiles/issues/82
+ json_content = response.read()[5:]
entries = json.loads(json_content.decode('utf-8'))['entries']
parsed_entries = [parse_entry(entry) for entry in entries]
return [entry for entry in parsed_entries if entry is not None]
@@ -176,8 +176,8 @@ def available_languages():
def download_dictionary(url, dest):
"""Download a decoded dictionary file."""
- response = urllib.request.urlopen(url)
- decoded = base64.decodebytes(response.read())
+ with urllib.request.urlopen(url) as response:
+ decoded = base64.decodebytes(response.read())
with open(dest, 'bw') as dict_file:
dict_file.write(decoded)
diff --git a/scripts/hostblock_blame.py b/scripts/hostblock_blame.py
index 38acaa58d..b18c62925 100644
--- a/scripts/hostblock_blame.py
+++ b/scripts/hostblock_blame.py
@@ -41,8 +41,8 @@ def main():
for url in configdata.DATA['content.blocking.hosts.lists'].default:
print("checking {}...".format(url))
- raw_file = urllib.request.urlopen(url)
- byte_io = io.BytesIO(raw_file.read())
+ with urllib.request.urlopen(url) as raw_file:
+ byte_io = io.BytesIO(raw_file.read())
f = hostblock.get_fileobj(byte_io)
for line in f:
line = line.decode('utf-8')
diff --git a/tests/conftest.py b/tests/conftest.py
index 40631af34..26cc04345 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -214,20 +214,74 @@ def pytest_addoption(parser):
help="Delay between qutebrowser commands.")
parser.addoption('--qute-profile-subprocs', action='store_true',
default=False, help="Run cProfile for subprocesses.")
- parser.addoption('--qute-bdd-webengine', action='store_true',
- help='Use QtWebEngine for BDD tests')
+ parser.addoption('--qute-backend', action='store',
+ choices=['webkit', 'webengine'], help='Set backend for BDD tests')
def pytest_configure(config):
- webengine_arg = config.getoption('--qute-bdd-webengine')
- webengine_env = os.environ.get('QUTE_BDD_WEBENGINE', 'false')
- config.webengine = webengine_arg or webengine_env == 'true'
- # Fail early if QtWebEngine is not available
- if config.webengine:
- import PyQt5.QtWebEngineWidgets
+ backend = _select_backend(config)
+ config.webengine = backend == 'webengine'
+
earlyinit.configure_pyqt()
+def _select_backend(config):
+ """Select the backend for running tests.
+
+ The backend is auto-selected in the following manner:
+ 1. Use QtWebKit if available
+ 2. Otherwise use QtWebEngine as a fallback
+
+ Auto-selection is overridden by either passing a backend via
+ `--qute-backend=<backend>` or setting the environment variable
+ `QUTE_TESTS_BACKEND=<backend>`.
+
+ Args:
+ config: pytest config
+
+ Raises:
+ ImportError if the selected backend is not available.
+
+ Returns:
+ The selected backend as a string (e.g. 'webkit').
+ """
+ backend_arg = config.getoption('--qute-backend')
+ backend_env = os.environ.get('QUTE_TESTS_BACKEND')
+
+ backend = backend_arg or backend_env or _auto_select_backend()
+
+ # Fail early if selected backend is not available
+ if backend == 'webkit':
+ import PyQt5.QtWebKitWidgets
+ elif backend == 'webengine':
+ import PyQt5.QtWebEngineWidgets
+ else:
+ raise utils.Unreachable(backend)
+
+ return backend
+
+
+def _auto_select_backend():
+ try:
+ # Try to use QtWebKit as the default backend
+ import PyQt5.QtWebKitWidgets
+ return 'webkit'
+ except ImportError:
+ # Try to use QtWebEngine as a fallback and fail early
+ # if that's also not available
+ import PyQt5.QtWebEngineWidgets
+ return 'webengine'
+
+
+def pytest_report_header(config):
+ if config.webengine:
+ backend_version = version.qtwebengine_versions(avoid_init=True)
+ else:
+ backend_version = version.qWebKitVersion()
+
+ return f'backend: {backend_version}'
+
+
@pytest.fixture(scope='session', autouse=True)
def check_display(request):
if utils.is_linux and not os.environ.get('DISPLAY', ''):
diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py
index a4a089cea..16170d460 100644
--- a/tests/end2end/conftest.py
+++ b/tests/end2end/conftest.py
@@ -165,7 +165,7 @@ if not getattr(sys, 'frozen', False):
def pytest_collection_modifyitems(config, items):
- """Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE."""
+ """Apply @qtwebengine_* markers."""
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
# (note this isn't actually fixed properly before Qt 5.15)
header_bug_fixed = qtutils.version_check('5.15', compiled=False)
diff --git a/tests/end2end/data/brave-adblock/generate.py b/tests/end2end/data/brave-adblock/generate.py
index 7a23a21ab..393cda4e7 100644
--- a/tests/end2end/data/brave-adblock/generate.py
+++ b/tests/end2end/data/brave-adblock/generate.py
@@ -53,9 +53,9 @@ def main():
else:
request = urllib.request.Request(URL)
print(f"Downloading {URL} ...")
- response = urllib.request.urlopen(request)
- assert response.status == 200
- data_str = response.read().decode("utf-8")
+ with urllib.request.urlopen(request) as response:
+ assert response.status == 200
+ data_str = response.read().decode("utf-8")
print(f"Saving to cache file {CACHE_PATH} ...")
CACHE_PATH.write_text(data_str, encoding="utf-8")
data = io.StringIO(data_str)
diff --git a/tests/end2end/data/hints/html/README.md b/tests/end2end/data/hints/html/README.md
index 2a6e97c24..5bbaecb4a 100644
--- a/tests/end2end/data/hints/html/README.md
+++ b/tests/end2end/data/hints/html/README.md
@@ -3,3 +3,5 @@ Tests in this directory are automatically picked up by `test_hints` in
They need to contain a special `<!-- target: foo.html -->` comment which
specifies where the hint in it will point to, and will then test that.
+
+With `<!-- target: null -->`, the page is expected to not generate any hints.
diff --git a/tests/end2end/data/hints/invisible.html b/tests/end2end/data/hints/html/invisible.html
index b0bfa9dd9..d382c80fa 100644
--- a/tests/end2end/data/hints/invisible.html
+++ b/tests/end2end/data/hints/html/invisible.html
@@ -1,3 +1,5 @@
+<!-- target: null -->
+
<!DOCTYPE html>
<html>
diff --git a/tests/end2end/data/hints/html/tabindex-negative.html b/tests/end2end/data/hints/html/tabindex-negative.html
new file mode 100644
index 000000000..03adb32bf
--- /dev/null
+++ b/tests/end2end/data/hints/html/tabindex-negative.html
@@ -0,0 +1,13 @@
+<!-- target: null -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Span with tabindex -1</title>
+ </head>
+ <body>
+ <p>This text should not get a hint:</p>
+ <span tabindex=-1>test</span>
+ </body>
+</html>
diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature
index cf35c5356..47153b741 100644
--- a/tests/end2end/features/hints.feature
+++ b/tests/end2end/features/hints.feature
@@ -241,11 +241,6 @@ Feature: Using hints
# The actual check is already done above
Then "No elements found." should not be logged
- Scenario: Hinting invisible elements
- When I open data/hints/invisible.html
- And I run :hint
- Then the error "No elements found." should be shown
-
Scenario: Clicking input with existing text
When I open data/hints/input.html
And I run :click-element id qute-input-existing
diff --git a/tests/end2end/features/search.feature b/tests/end2end/features/search.feature
index 5fafd19f0..305b45690 100644
--- a/tests/end2end/features/search.feature
+++ b/tests/end2end/features/search.feature
@@ -71,7 +71,25 @@ Feature: Searching on a page
When I run :search foo
And I wait for "search found foo" in the log
And I run :search foo
- Then "Ignoring duplicate search request for foo" should be logged
+ Then "Ignoring duplicate search request for foo, but resetting flags" should be logged
+
+ Scenario: Reset search direction on duplicate search, forward-to-back
+ When I run :search baz
+ And I wait for "search found baz" in the log
+ And I run :search -r baz
+ And I wait for "Ignoring duplicate search request for baz, but resetting flags" in the log
+ And I run :search-next
+ And I wait for "next_result found baz with flags FindBackward" in the log
+ Then "BAZ" should be found
+
+ Scenario: Reset search direction on duplicate search, back-to-forward
+ When I run :search -r baz
+ And I wait for "search found baz with flags FindBackward" in the log
+ And I run :search baz
+ And I wait for "Ignoring duplicate search request for baz, but resetting flags" in the log
+ And I run :search-next
+ And I wait for "next_result found baz" in the log
+ Then "baz" should be found
## search.ignore_case
diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature
index c9d983755..3715d5765 100644
--- a/tests/end2end/features/tabs.feature
+++ b/tests/end2end/features/tabs.feature
@@ -633,6 +633,27 @@ Feature: Tab management
- data/numbers/1.txt (active)
- data/numbers/3.txt
+ Scenario: :tab-move with absolute position
+ When I open data/numbers/1.txt
+ And I open data/numbers/2.txt in a new tab
+ And I open data/numbers/3.txt in a new tab
+ And I run :tab-focus 1
+ And I run :tab-move end
+ Then the following tabs should be open:
+ - data/numbers/2.txt
+ - data/numbers/3.txt
+ - data/numbers/1.txt (active)
+
+ Scenario: :tab-move with absolute position
+ When I open data/numbers/1.txt
+ And I open data/numbers/2.txt in a new tab
+ And I open data/numbers/3.txt in a new tab
+ And I run :tab-move start
+ Then the following tabs should be open:
+ - data/numbers/3.txt (active)
+ - data/numbers/1.txt
+ - data/numbers/2.txt
+
Scenario: Make sure :tab-move retains metadata
When I open data/title.html
And I open data/hello.txt in a new tab
@@ -1349,6 +1370,25 @@ Feature: Tab management
And I run :tab-take 0/1
Then the error "Can't take tabs when using windows as tabs" should be shown
+ @windows_skip
+ Scenario: Close the last tab of a window when taken by another window
+ Given I have a fresh instance
+ When I open data/numbers/1.txt
+ And I run :tab-only
+ And I open data/numbers/2.txt in a new window
+ And I set tabs.last_close to ignore
+ And I run :tab-take 1/1
+ And I wait until data/numbers/2.txt is loaded
+ Then the session should look like:
+ windows:
+ - tabs:
+ - history:
+ - url: about:blank
+ - url: http://localhost:*/data/numbers/1.txt
+ - active: true
+ history:
+ - url: http://localhost:*/data/numbers/2.txt
+
# :tab-give
@xfail_norun # Needs qutewm
@@ -1406,6 +1446,24 @@ Feature: Tab management
And I run :tab-give 0
Then the error "Can't give tabs when using windows as tabs" should be shown
+ @windows_skip
+ Scenario: Close the last tab of a window when given to another window
+ Given I have a fresh instance
+ When I open data/numbers/1.txt
+ And I run :tab-only
+ And I open data/numbers/2.txt in a new window
+ And I set tabs.last_close to ignore
+ And I run :tab-give 1
+ And I wait until data/numbers/1.txt is loaded
+ Then the session should look like:
+ windows:
+ - tabs:
+ - active: true
+ history:
+ - url: http://localhost:*/data/numbers/2.txt
+ - history:
+ - url: http://localhost:*/data/numbers/1.txt
+
# Other
Scenario: Using :tab-next after closing last tab (#1448)
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index 3cbea01ad..14f34b52c 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -48,10 +48,7 @@ instance_counter = itertools.count()
def is_ignored_qt_message(pytestconfig, message):
"""Check if the message is listed in qt_log_ignore."""
regexes = pytestconfig.getini('qt_log_ignore')
- for regex in regexes:
- if re.search(regex, message):
- return True
- return False
+ return any(re.search(regex, message) for regex in regexes)
def is_ignored_lowlevel_message(message):
@@ -609,8 +606,7 @@ class QuteProc(testprocess.Process):
self.wait_for(category='webview',
message='Scroll position changed to ' + point)
- def wait_for(self, timeout=None, # pylint: disable=arguments-differ
- **kwargs):
+ def wait_for(self, timeout=None, **kwargs):
"""Extend wait_for to add divisor if a test is xfailing."""
__tracebackhide__ = (lambda e:
e.errisinstance(testprocess.WaitForTimeout))
@@ -716,7 +712,7 @@ class QuteProc(testprocess.Process):
target_arg)
self._wait_for_ipc()
- def start(self, *args, **kwargs): # pylint: disable=arguments-differ
+ def start(self, *args, **kwargs):
try:
super().start(*args, **kwargs)
except testprocess.ProcessExited:
@@ -913,8 +909,8 @@ class QuteProc(testprocess.Process):
"""Get a screenshot of the current page.
Arguments:
- probe: If given, only continue if the pixel at the given position isn't
- black (or whatever is specified by probe_color).
+ probe_pos: If given, only continue if the pixel at the given
+ position isn't black (or whatever is specified by probe_color).
"""
for _ in range(5):
tmp_path = self.request.getfixturevalue('tmp_path')
diff --git a/tests/end2end/fixtures/test_webserver.py b/tests/end2end/fixtures/test_webserver.py
index 3c825e5bc..ed0c32ae5 100644
--- a/tests/end2end/fixtures/test_webserver.py
+++ b/tests/end2end/fixtures/test_webserver.py
@@ -37,7 +37,8 @@ def test_server(server, qtbot, path, content, expected):
with qtbot.wait_signal(server.new_request, timeout=100):
url = 'http://localhost:{}{}'.format(server.port, path)
try:
- response = urllib.request.urlopen(url)
+ with urllib.request.urlopen(url) as response:
+ data = response.read().decode('utf-8')
except urllib.error.HTTPError as e:
# "Though being an exception (a subclass of URLError), an HTTPError
# can also function as a non-exceptional file-like return value
@@ -46,8 +47,6 @@ def test_server(server, qtbot, path, content, expected):
print(e.read().decode('utf-8'))
raise
- data = response.read().decode('utf-8')
-
assert server.get_requests() == [server.ExpectedRequest('GET', path)]
assert (content in data) == expected
diff --git a/tests/end2end/test_hints_html.py b/tests/end2end/test_hints_html.py
index ebb2a7e33..f1cda97fe 100644
--- a/tests/end2end/test_hints_html.py
+++ b/tests/end2end/test_hints_html.py
@@ -40,7 +40,7 @@ def collect_tests():
@dataclasses.dataclass
class ParsedFile:
- target: str
+ target: Optional[str]
qtwebengine_todo: Optional[str]
@@ -107,11 +107,18 @@ def test_hints(test_name, zoom_text_only, zoom_level, find_implementation,
quteproc.set_setting('zoom.text_only', str(zoom_text_only))
quteproc.set_setting('hints.find_implementation', find_implementation)
quteproc.send_cmd(':zoom {}'.format(zoom_level))
+
# follow hint
quteproc.send_cmd(':hint all normal')
- quteproc.wait_for(message='hints: a', category='hints')
- quteproc.send_cmd(':hint-follow a')
- quteproc.wait_for_load_finished('data/' + parsed.target)
+
+ if parsed.target is None:
+ msg = quteproc.wait_for(message='No elements found.', category='message')
+ msg.expected = True
+ else:
+ quteproc.wait_for(message='hints: a', category='hints')
+ quteproc.send_cmd(':hint-follow a')
+ quteproc.wait_for_load_finished('data/' + parsed.target)
+
# reset
quteproc.send_cmd(':zoom 100')
if not request.config.webengine:
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index cd3778b8a..dd902eb7d 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -186,7 +186,7 @@ def testdata_scheme(qapp):
pass
@qutescheme.add_handler('testdata')
- def handler(url): # pylint: disable=unused-variable
+ def handler(url):
file_abs = os.path.abspath(os.path.dirname(__file__))
filename = os.path.join(file_abs, os.pardir, 'end2end',
url.path().lstrip('/'))
diff --git a/tests/helpers/logfail.py b/tests/helpers/logfail.py
index ae4ac9bc0..15d5a9253 100644
--- a/tests/helpers/logfail.py
+++ b/tests/helpers/logfail.py
@@ -39,8 +39,7 @@ class LogFailHandler(logging.Handler):
if logger.name == 'messagemock':
return
- if (logger.level == record.levelno or
- root_logger.level == record.levelno):
+ if record.levelno in (logger.level, root_logger.level):
# caplog.at_level(...) was used with the level of this message,
# i.e. it was expected.
return
diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py
index 77e648eed..b6650078c 100644
--- a/tests/unit/api/test_cmdutils.py
+++ b/tests/unit/api/test_cmdutils.py
@@ -17,8 +17,6 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
-# pylint: disable=unused-variable
-
"""Tests for qutebrowser.api.cmdutils."""
import sys
@@ -292,7 +290,6 @@ class TestRegister:
class Enum(enum.Enum):
- # pylint: disable=invalid-name
x = enum.auto()
y = enum.auto()
diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py
index 89390cbf1..9a1bc99dd 100644
--- a/tests/unit/completion/test_completionwidget.py
+++ b/tests/unit/completion/test_completionwidget.py
@@ -163,16 +163,7 @@ def test_completion_item_focus_no_model(which, completionview, model, qtbot):
@pytest.mark.skip("Seems to disagree with reality, see #5897")
def test_completion_item_focus_fetch(completionview, model, qtbot):
- """Test that on_next_prev_item moves the selection properly.
-
- Args:
- which: the direction in which to move the selection.
- tree: Each list represents a completion category, with each string
- being an item under that category.
- expected: expected argument from on_selection_changed for each
- successive movement. None implies no signal should be
- emitted.
- """
+ """Test that on_next_prev_item moves the selection properly."""
cat = mock.Mock(spec=[
'layoutChanged', 'layoutAboutToBeChanged', 'canFetchMore',
'fetchMore', 'rowCount', 'index', 'data'])
diff --git a/tests/unit/components/test_hostblock.py b/tests/unit/components/test_hostblock.py
index 6a71058ea..5949f92f8 100644
--- a/tests/unit/components/test_hostblock.py
+++ b/tests/unit/components/test_hostblock.py
@@ -427,7 +427,7 @@ def test_invalid_utf8(config_stub, tmp_path, caplog, host_blocker_factory, locat
with caplog.at_level(logging.ERROR):
current_download.successful = True
current_download.finished.emit()
- expected = r"Failed to decode: " r"b'https://www.example.org/\xa0localhost"
+ expected = r"Failed to decode: b'https://www.example.org/\xa0localhost"
assert caplog.messages[-2].startswith(expected)
else:
current_download.successful = True
diff --git a/tests/unit/extensions/test_loader.py b/tests/unit/extensions/test_loader.py
index feb5dd347..e9b8055aa 100644
--- a/tests/unit/extensions/test_loader.py
+++ b/tests/unit/extensions/test_loader.py
@@ -35,16 +35,10 @@ def test_on_walk_error():
def test_walk_normal():
- names = [info.name for info in loader._walk_normal()]
+ names = [info.name for info in loader.walk_components()]
assert 'qutebrowser.components.scrollcommands' in names
-def test_walk_pyinstaller():
- # We can't test whether we get something back without being frozen by
- # PyInstaller, but at least we can test that we don't crash.
- list(loader._walk_pyinstaller())
-
-
def test_load_component(monkeypatch):
monkeypatch.setattr(objects, 'commands', {})
diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py
index 30ee36301..84068bf47 100644
--- a/tests/unit/keyinput/test_basekeyparser.py
+++ b/tests/unit/keyinput/test_basekeyparser.py
@@ -346,3 +346,14 @@ def test_clear_keystring_empty(qtbot, keyparser):
keyparser._sequence = keyseq('')
with qtbot.assert_not_emitted(keyparser.keystring_updated):
keyparser.clear_keystring()
+
+
+def test_respect_config_when_matching_counts(keyparser, config_stub):
+ """Don't match counts if disabled in the config."""
+ config_stub.val.input.match_counts = False
+
+ info = keyutils.KeyInfo(Qt.Key_1, Qt.NoModifier)
+ keyparser.handle(info.to_event())
+
+ assert not keyparser._sequence
+ assert not keyparser._count
diff --git a/tests/unit/mainwindow/statusbar/test_textbase.py b/tests/unit/mainwindow/statusbar/test_textbase.py
index 631c6ce44..33c4ffd76 100644
--- a/tests/unit/mainwindow/statusbar/test_textbase.py
+++ b/tests/unit/mainwindow/statusbar/test_textbase.py
@@ -79,11 +79,11 @@ def test_text_elide_none(mocker, qtbot):
label = TextBase()
qtbot.add_widget(label)
label.setText('')
- mocker.patch('qutebrowser.mainwindow.statusbar.textbase.TextBase.'
- 'fontMetrics')
+ mock = mocker.patch(
+ 'qutebrowser.mainwindow.statusbar.textbase.TextBase.fontMetrics')
label._update_elided_text(20)
- assert not label.fontMetrics.called
+ assert not mock.called
def test_unset_text(qtbot):
diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py
index be86bf215..faf2006de 100644
--- a/tests/unit/misc/test_guiprocess.py
+++ b/tests/unit/misc/test_guiprocess.py
@@ -26,7 +26,7 @@ import pytest
from PyQt5.QtCore import QProcess, QUrl
from qutebrowser.misc import guiprocess
-from qutebrowser.utils import usertypes, utils
+from qutebrowser.utils import usertypes, utils, version
from qutebrowser.api import cmdutils
from qutebrowser.qt import sip
@@ -394,8 +394,11 @@ def test_running(qtbot, proc, py_proc):
proc.outcome.was_successful()
-def test_failing_to_start(qtbot, proc, caplog, message_mock):
+@pytest.mark.parametrize('is_flatpak', [True, False])
+def test_failing_to_start(qtbot, proc, caplog, message_mock, monkeypatch, is_flatpak):
"""Test the process failing to start."""
+ monkeypatch.setattr(version, 'is_flatpak', lambda: is_flatpak)
+
with caplog.at_level(logging.ERROR, 'message'):
with qtbot.wait_signal(proc.error, timeout=5000):
proc.start('this_does_not_exist_either', [])
@@ -405,8 +408,11 @@ def test_failing_to_start(qtbot, proc, caplog, message_mock):
"Testprocess 'this_does_not_exist_either' failed to start:")
if not utils.is_windows:
- assert msg.text.endswith(
- "(Hint: Make sure 'this_does_not_exist_either' exists and is executable)")
+ expected_msg = (
+ "Hint: Make sure 'this_does_not_exist_either' exists and is executable")
+ if is_flatpak:
+ expected_msg += ' inside the Flatpak container'
+ assert msg.text.endswith(expected_msg)
assert not proc.outcome.running
assert proc.outcome.status is None
diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py
index 8d9c53c93..922f39331 100644
--- a/tests/unit/misc/test_keyhints.py
+++ b/tests/unit/misc/test_keyhints.py
@@ -30,8 +30,8 @@ def expected_text(*args):
"""Helper to format text we expect the KeyHintView to generate.
Args:
- args: One tuple for each row in the expected output.
- Tuples are of the form: (prefix, color, suffix, command).
+ *args: One tuple for each row in the expected output.
+ Tuples are of the form: (prefix, color, suffix, command).
"""
text = '<table>'
for group in args:
diff --git a/tests/unit/test_qutebrowser.py b/tests/unit/test_qutebrowser.py
index d9275631d..36b4065a1 100644
--- a/tests/unit/test_qutebrowser.py
+++ b/tests/unit/test_qutebrowser.py
@@ -22,6 +22,8 @@
(Mainly commandline flag parsing)
"""
+import re
+
import pytest
from qutebrowser import qutebrowser
@@ -75,3 +77,61 @@ class TestJsonArgs:
# pylint: disable=no-member
assert args.debug
assert not args.temp_basedir
+
+
+class TestValidateUntrustedArgs:
+
+ @pytest.mark.parametrize('args', [
+ [],
+ [':nop'],
+ [':nop', '--untrusted-args'],
+ [':nop', '--debug', '--untrusted-args'],
+ [':nop', '--untrusted-args', 'foo'],
+ ['--debug', '--untrusted-args', 'foo'],
+ ['foo', '--untrusted-args', 'bar'],
+ ])
+ def test_valid(self, args):
+ qutebrowser._validate_untrusted_args(args)
+
+ @pytest.mark.parametrize('args, message', [
+ (
+ ['--untrusted-args', '--debug'],
+ "Found --debug after --untrusted-args, aborting.",
+ ),
+ (
+ ['--untrusted-args', ':nop'],
+ "Found :nop after --untrusted-args, aborting.",
+ ),
+ (
+ ['--debug', '--untrusted-args', '--debug'],
+ "Found --debug after --untrusted-args, aborting.",
+ ),
+ (
+ [':nop', '--untrusted-args', '--debug'],
+ "Found --debug after --untrusted-args, aborting.",
+ ),
+ (
+ [':nop', '--untrusted-args', ':nop'],
+ "Found :nop after --untrusted-args, aborting.",
+ ),
+ (
+ [
+ ':nop',
+ '--untrusted-args',
+ ':nop',
+ '--untrusted-args',
+ 'https://www.example.org',
+ ],
+ (
+ "Found multiple arguments (:nop --untrusted-args "
+ "https://www.example.org) after --untrusted-args, aborting."
+ )
+ ),
+ (
+ ['--untrusted-args', 'okay1', 'okay2'],
+ "Found multiple arguments (okay1 okay2) after --untrusted-args, aborting.",
+ ),
+ ])
+ def test_invalid(self, args, message):
+ with pytest.raises(SystemExit, match=re.escape(message)):
+ qutebrowser._validate_untrusted_args(args)
diff --git a/tests/unit/utils/test_log.py b/tests/unit/utils/test_log.py
index 7682c1156..7b0f5293e 100644
--- a/tests/unit/utils/test_log.py
+++ b/tests/unit/utils/test_log.py
@@ -53,8 +53,7 @@ def restore_loggers():
saved_level_to_name = logging._levelToName.copy()
logger_states = {}
for name in saved_loggers:
- logger_states[name] = getattr(saved_loggers[name], 'disabled',
- None)
+ logger_states[name] = getattr(saved_loggers[name], 'disabled', None)
finally:
logging._releaseLock()
@@ -86,9 +85,9 @@ def restore_loggers():
logger_dict = logging.getLogger().manager.loggerDict
logger_dict.clear()
logger_dict.update(saved_loggers)
- for name in logger_states:
- if logger_states[name] is not None:
- saved_loggers[name].disabled = logger_states[name]
+ for name, state in logger_states.items():
+ if state is not None:
+ saved_loggers[name].disabled = state
finally:
logging._releaseLock()
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index 2d98feece..6309e46b5 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -51,7 +51,6 @@ else:
test_file = None
-# pylint: disable=bad-continuation
@pytest.mark.parametrize(['qversion', 'compiled', 'pyqt', 'version', 'exact',
'expected'], [
# equal versions
@@ -75,7 +74,6 @@ else:
# dev suffix
('5.15.1', '5.15.1', '5.15.2.dev2009281246', '5.15.0', False, True),
])
-# pylint: enable=bad-continuation
def test_version_check(monkeypatch, qversion, compiled, pyqt, version, exact,
expected):
"""Test for version_check().
@@ -559,7 +557,8 @@ if test_file is not None:
qiodev.mode = mode
# Create empty TESTFN file because the Python tests try to unlink
# it.after the test.
- open(test_file.TESTFN, 'w', encoding='utf-8').close()
+ with open(test_file.TESTFN, 'w', encoding='utf-8'):
+ pass
return qiodev
class PyAutoFileTests(PyIODeviceTestMixin, test_file.AutoFileTests,
diff --git a/tests/unit/utils/test_urlmatch.py b/tests/unit/utils/test_urlmatch.py
index 35ccc94fe..caf52c76d 100644
--- a/tests/unit/utils/test_urlmatch.py
+++ b/tests/unit/utils/test_urlmatch.py
@@ -37,24 +37,30 @@ from PyQt5.QtCore import QUrl
from qutebrowser.utils import urlmatch
+# pylint: disable=line-too-long
+
@pytest.mark.parametrize('pattern, error', [
### Chromium: kMissingSchemeSeparator
## TEST(ExtensionURLPatternTest, ParseInvalid)
# ("http", "No scheme given"),
- ("http:", "Invalid port: Port is empty"),
- ("http:/", "Invalid port: Port is empty"),
- ("about://", "Pattern without path"),
- ("http:/bar", "Invalid port: Port is empty"),
+ pytest.param("http:", "Invalid port: Port is empty", id='scheme-no-slash'),
+ pytest.param("http:/", "Invalid port: Port is empty", id='scheme-single-slash'),
+ pytest.param("about://", "Pattern without path", id='scheme-no-path'),
+ pytest.param(
+ "http:/bar",
+ "Invalid port: Port is empty",
+ id='scheme-single-slash-path',
+ ),
### Chromium: kEmptyHost
## TEST(ExtensionURLPatternTest, ParseInvalid)
- ("http://", "Pattern without host"),
- ("http:///", "Pattern without host"),
- ("http://:1234/", "Pattern without host"),
- ("http://*./", "Pattern without host"),
+ pytest.param("http://", "Pattern without host", id='host-double-slash'),
+ pytest.param("http:///", "Pattern without host", id='host-triple-slash'),
+ pytest.param("http://:1234/", "Pattern without host", id='host-port'),
+ pytest.param("http://*./", "Pattern without host", id='host-pattern'),
## TEST(ExtensionURLPatternTest, IPv6Patterns)
- ("http://[]:8888/*", "Pattern without host"),
+ pytest.param("http://[]:8888/*", "Pattern without host", id='host-ipv6'),
### Chromium: kEmptyPath
## TEST(ExtensionURLPatternTest, ParseInvalid)
@@ -63,53 +69,132 @@ from qutebrowser.utils import urlmatch
### Chromium: kInvalidHost
## TEST(ExtensionURLPatternTest, ParseInvalid)
- ("http://\0www/", "May not contain NUL byte"),
+ pytest.param("http://\0www/", "May not contain NUL byte", id='host-nul'),
## TEST(ExtensionURLPatternTest, IPv6Patterns)
# No closing bracket (`]`).
- ("http://[2607:f8b0:4005:805::200e/*", "Invalid IPv6 URL"),
+ pytest.param(
+ "http://[2607:f8b0:4005:805::200e/*",
+ "Invalid IPv6 URL",
+ id='host-ipv6-no-closing',
+ ),
# Two closing brackets (`]]`).
- pytest.param("http://[2607:f8b0:4005:805::200e]]/*", "Invalid IPv6 URL", marks=pytest.mark.xfail(reason="https://bugs.python.org/issue34360")),
+ pytest.param(
+ "http://[2607:f8b0:4005:805::200e]]/*",
+ "Invalid IPv6 URL",
+ marks=pytest.mark.xfail(reason="https://bugs.python.org/issue34360"),
+ id='host-ipv6-two-closing',
+ ),
# Two open brackets (`[[`).
- ("http://[[2607:f8b0:4005:805::200e]/*", r"""Expected '\]' to match '\[' in hostname; source was "\[2607:f8b0:4005:805::200e"; host = """""),
+ pytest.param(
+ "http://[[2607:f8b0:4005:805::200e]/*",
+ r"""Expected '\]' to match '\[' in hostname; source was "\[2607:f8b0:4005:805::200e"; host = """"",
+ id='host-ipv6-two-open',
+ ),
# Too few colons in the last chunk.
- ("http://[2607:f8b0:4005:805:200e]/*", 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e"; host = ""'),
+ pytest.param(
+ "http://[2607:f8b0:4005:805:200e]/*",
+ 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e"; host = ""',
+ id='host-ipv6-colons',
+ ),
# Non-hex piece.
- ("http://[2607:f8b0:4005:805:200e:12:bogus]/*", 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e:12:bogus"; host = ""'),
+ pytest.param(
+ "http://[2607:f8b0:4005:805:200e:12:bogus]/*",
+ 'Invalid IPv6 address; source was "2607:f8b0:4005:805:200e:12:bogus"; host = ""',
+ id='host-ipv6-non-hex',
+ ),
### Chromium: kInvalidHostWildcard
## TEST(ExtensionURLPatternTest, ParseInvalid)
- ("http://*foo/bar", "Invalid host wildcard"),
- ("http://foo.*.bar/baz", "Invalid host wildcard"),
- ("http://fo.*.ba:123/baz", "Invalid host wildcard"),
- ("http://foo.*/bar", "Invalid host wildcard"),
+ pytest.param("http://*foo/bar", "Invalid host wildcard", id='host-wildcard-no-dot'),
+ pytest.param(
+ "http://foo.*.bar/baz",
+ "Invalid host wildcard",
+ id='host-wildcard-middle',
+ ),
+ pytest.param(
+ "http://fo.*.ba:123/baz",
+ "Invalid host wildcard",
+ id='host-wildcard-middle-port',
+ ),
+ pytest.param("http://foo.*/bar", "Invalid host wildcard", id='host-wildcard-end'),
### Chromium: kInvalidPort
## TEST(ExtensionURLPatternTest, Ports)
- ("http://foo:/", "Invalid port: Port is empty"),
- ("http://*.foo:/", "Invalid port: Port is empty"),
- ("http://foo:com/", "Invalid port: .* 'com'"),
- ("http://foo:123456/", "Invalid port: Port out of range 0-65535"),
- ("http://foo:80:80/monkey", "Invalid port: .* '80:80'"),
- ("chrome://foo:1234/bar", "Ports are unsupported with chrome scheme"),
+ pytest.param("http://foo:/", "Invalid port: Port is empty", id='port-empty'),
+ pytest.param(
+ "http://*.foo:/",
+ "Invalid port: Port is empty",
+ id='port-empty-wildcard',
+ ),
+ pytest.param("http://foo:com/", "Invalid port: .* 'com'", id='port-alpha'),
+ pytest.param(
+ "http://foo:123456/",
+ "Invalid port: Port out of range 0-65535",
+ id='port-range',
+ ),
+ pytest.param(
+ "http://foo:80:80/monkey",
+ "Invalid port: .* '80:80'",
+ id='port-double',
+ ),
+ pytest.param(
+ "chrome://foo:1234/bar",
+ "Ports are unsupported with chrome scheme",
+ id='port-chrome',
+ ),
# No port specified, but port separator.
- ("http://[2607:f8b0:4005:805::200e]:/*", "Invalid port: Port is empty"),
+ pytest.param(
+ "http://[2607:f8b0:4005:805::200e]:/*",
+ "Invalid port: Port is empty",
+ id='port-empty-ipv6',
+ ),
### Additional tests
- ("http://[", "Invalid IPv6 URL"),
- ("http://[fc2e::bb88::edac]", 'Invalid IPv6 address; source was "fc2e::bb88::edac"; host = ""'),
- ("http://[fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac]", 'Invalid IPv6 address; source was "fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac"; host = ""'),
- ("http://[fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac]", 'Invalid IPv6 address; source was "fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac"; host = ""'),
- ("http://[127.0.0.1:fc2e::bb88:edac]", r'Invalid IPv6 address; source was "127\.0\.0\.1:fc2e::bb88:edac'),
- ("http://[fc2e::bb88", "Invalid IPv6 URL"),
- ("http://[fc2e:bb88:edac]", 'Invalid IPv6 address; source was "fc2e:bb88:edac"; host = ""'),
- ("http://[fc2e:bb88:edac::z]", 'Invalid IPv6 address; source was "fc2e:bb88:edac::z"; host = ""'),
- ("http://[fc2e:bb88:edac::2]:2a2", "Invalid port: .* '2a2'"),
- ("://", "Missing scheme"),
+ pytest.param("http://[", "Invalid IPv6 URL", id='ipv6-single-open'),
+ pytest.param(
+ "http://[fc2e::bb88::edac]",
+ 'Invalid IPv6 address; source was "fc2e::bb88::edac"; host = ""',
+ id='ipv6-double-double',
+ ),
+ pytest.param(
+ "http://[fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac]",
+ 'Invalid IPv6 address; source was "fc2e:0e35:bb88::edac:fc2e:0e35:bb88:edac"; host = ""',
+ id='ipv6-long-double',
+ ),
+ pytest.param(
+ "http://[fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac]",
+ 'Invalid IPv6 address; source was "fc2e:0e35:bb88:af:edac:fc2e:0e35:bb88:edac"; host = ""',
+ id='ipv6-long',
+ ),
+ pytest.param(
+ "http://[127.0.0.1:fc2e::bb88:edac]",
+ r'Invalid IPv6 address; source was "127\.0\.0\.1:fc2e::bb88:edac',
+ id='ipv6-ipv4',
+ ),
+ pytest.param("http://[fc2e::bb88", "Invalid IPv6 URL", id='ipv6-trailing'),
+ pytest.param(
+ "http://[fc2e:bb88:edac]",
+ 'Invalid IPv6 address; source was "fc2e:bb88:edac"; host = ""',
+ id='ipv6-short',
+ ),
+ pytest.param(
+ "http://[fc2e:bb88:edac::z]",
+ 'Invalid IPv6 address; source was "fc2e:bb88:edac::z"; host = ""',
+ id='ipv6-z',
+ ),
+ pytest.param(
+ "http://[fc2e:bb88:edac::2]:2a2",
+ "Invalid port: .* '2a2'",
+ id='ipv6-port',
+ ),
+ pytest.param("://", "Missing scheme", id='scheme-naked'),
])
def test_invalid_patterns(pattern, error):
with pytest.raises(urlmatch.ParseError, match=error):
urlmatch.UrlPattern(pattern)
+# pylint: enable=line-too-long
+
@pytest.mark.parametrize('host', ['.', ' ', ' .', '. ', '. .', '. . .', ' . '])
def test_whitespace_hosts(host):
diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py
index 97ff268ca..e5773e25e 100644
--- a/tests/unit/utils/test_urlutils.py
+++ b/tests/unit/utils/test_urlutils.py
@@ -334,7 +334,6 @@ def test_get_search_url_open_base_url(config_stub, url, host):
Args:
url: The "URL" to enter.
host: The expected search machine host.
- query: The expected search query.
"""
config_stub.val.url.open_base_url = True
url = urlutils._get_search_url(url)
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 6c57cb3d3..7b616d8b7 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -484,17 +484,20 @@ class TestGitStrSubprocess:
@needs_git
def test_real_git(self, git_repo):
"""Test with a real git repository."""
- branch_name = subprocess.run(
- ['git', 'config', 'init.defaultBranch'],
- check=False,
- stdout=subprocess.PIPE,
- encoding='utf-8',
- ).stdout.strip()
- if not branch_name:
- branch_name = 'master'
+ def _get_git_setting(name, default):
+ return subprocess.run(
+ ['git', 'config', '--default', default, name],
+ check=True,
+ stdout=subprocess.PIPE,
+ encoding='utf-8',
+ ).stdout.strip()
ret = version._git_str_subprocess(str(git_repo))
- assert ret == f'6e4b65a on {branch_name} (1970-01-01 01:00:00 +0100)'
+ branch_name = _get_git_setting('init.defaultBranch', 'master')
+ abbrev_length = int(_get_git_setting('core.abbrev', '7'))
+ expected_sha = '6e4b65a529c0ab78fb370c1527d5809f7436b8f3'[:abbrev_length]
+
+ assert ret == f'{expected_sha} on {branch_name} (1970-01-01 01:00:00 +0100)'
def test_missing_dir(self, tmp_path):
"""Test with a directory which doesn't exist."""
@@ -718,7 +721,7 @@ class TestModuleVersions:
Args:
attribute: The name of the version attribute.
- expected: The expected return value.
+ expected_modules: The expected modules with that attribute.
"""
import_fake.version_attribute = attribute
diff --git a/tox.ini b/tox.ini
index 4be5b8620..f52d7b158 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,7 +13,6 @@ minversion = 3.15
setenv =
PYTEST_QT_API=pyqt5
pyqt{,512,513,514,515,5150}: LINK_PYQT_SKIP=true
- pyqt{,512,513,514,515,5150}: QUTE_BDD_WEBENGINE=true
cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report=
passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI XDG_* QUTE_* DOCKER QT_QUICK_BACKEND PY_COLORS DBUS_SESSION_BUS_ADDRESS
basepython =
@@ -42,7 +41,6 @@ commands =
basepython = {env:PYTHON:python3}
setenv =
PYTEST_QT_API=pyqt5
- QUTE_BDD_WEBENGINE=true
pip_pre = true
deps = -r{toxinidir}/misc/requirements/requirements-tests-bleeding.txt
commands_pre = pip install --index-url https://www.riverbankcomputing.com/pypi/simple/ --pre --upgrade PyQt5 PyQtWebEngine
@@ -160,7 +158,7 @@ passenv = APPDATA HOME PYINSTALLER_DEBUG
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/misc/requirements/requirements-pyinstaller.txt
- -r{toxinidir}/misc/requirements/requirements-pyqt-pyinstaller.txt
+ -r{toxinidir}/misc/requirements/requirements-pyqt.txt
commands =
{envbindir}/pyinstaller --noconfirm misc/qutebrowser.spec