summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bumpversion.cfg2
-rw-r--r--.flake813
-rw-r--r--.github/CONTRIBUTING.asciidoc6
-rw-r--r--.github/workflows/bleeding.yml2
-rw-r--r--.gitignore1
-rw-r--r--.pylintrc26
-rw-r--r--README.asciidoc3
-rw-r--r--doc/changelog.asciidoc99
-rw-r--r--doc/help/commands.asciidoc15
-rw-r--r--doc/help/configuring.asciidoc25
-rw-r--r--doc/help/settings.asciidoc140
-rw-r--r--doc/img/cheatsheet-big.pngbin781120 -> 708315 bytes
-rw-r--r--doc/img/cheatsheet-small.pngbin30252 -> 26611 bytes
-rw-r--r--doc/qutebrowser.1.asciidoc3
-rwxr-xr-xmisc/nsis/install.nsh3
-rw-r--r--misc/org.qutebrowser.qutebrowser.appdata.xml2
-rw-r--r--misc/org.qutebrowser.qutebrowser.desktop2
-rw-r--r--misc/qutebrowser.rcc18
-rw-r--r--misc/requirements/requirements-check-manifest.txt12
-rw-r--r--misc/requirements/requirements-dev.txt55
-rw-r--r--misc/requirements/requirements-dev.txt-raw2
-rw-r--r--misc/requirements/requirements-flake8.txt20
-rw-r--r--misc/requirements/requirements-flake8.txt-raw1
-rw-r--r--misc/requirements/requirements-mypy.txt28
-rw-r--r--misc/requirements/requirements-pyinstaller.txt6
-rw-r--r--misc/requirements/requirements-pylint.txt40
-rw-r--r--misc/requirements/requirements-pylint.txt-raw11
-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.txt12
-rw-r--r--misc/requirements/requirements-qutebrowser.txt-raw7
-rw-r--r--misc/requirements/requirements-sphinx.txt24
-rw-r--r--misc/requirements/requirements-tests-bleeding.txt4
-rw-r--r--misc/requirements/requirements-tests.txt65
-rw-r--r--misc/requirements/requirements-tests.txt-raw13
-rw-r--r--misc/requirements/requirements-tox.txt28
-rw-r--r--misc/requirements/requirements-tox.txt-raw8
-rw-r--r--misc/requirements/requirements-yamllint.txt4
-rw-r--r--misc/userscripts/README.md2
-rwxr-xr-xmisc/userscripts/password_fill6
-rwxr-xr-xmisc/userscripts/qute-keepassxc2
-rwxr-xr-xmisc/userscripts/qute-pass29
-rw-r--r--qutebrowser/__init__.py2
-rw-r--r--qutebrowser/browser/browsertab.py1
-rw-r--r--qutebrowser/browser/commands.py38
-rw-r--r--qutebrowser/browser/downloads.py36
-rw-r--r--qutebrowser/browser/greasemonkey.py1
-rw-r--r--qutebrowser/browser/hints.py38
-rw-r--r--qutebrowser/browser/history.py1
-rw-r--r--qutebrowser/browser/navigate.py3
-rw-r--r--qutebrowser/browser/qtnetworkdownloads.py19
-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.py11
-rw-r--r--qutebrowser/browser/webengine/webenginedownloads.py9
-rw-r--r--qutebrowser/browser/webengine/webenginesettings.py8
-rw-r--r--qutebrowser/browser/webengine/webenginetab.py47
-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.py2
-rw-r--r--qutebrowser/config/configcommands.py13
-rw-r--r--qutebrowser/config/configdata.yml50
-rw-r--r--qutebrowser/config/qtargs.py5
-rw-r--r--qutebrowser/config/websettings.py1
-rw-r--r--qutebrowser/extensions/loader.py42
-rw-r--r--qutebrowser/html/bindings.html2
-rw-r--r--qutebrowser/html/settings.html150
-rw-r--r--qutebrowser/javascript/greasemonkey_wrapper.js20
-rw-r--r--qutebrowser/javascript/quirks/globalthis.user.js2
-rw-r--r--qutebrowser/keyinput/basekeyparser.py3
-rw-r--r--qutebrowser/keyinput/keyutils.py2
-rw-r--r--qutebrowser/mainwindow/mainwindow.py6
-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/debug.py4
-rw-r--r--qutebrowser/utils/log.py15
-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.py19
-rw-r--r--qutebrowser/utils/version.py5
-rw-r--r--requirements.txt18
-rwxr-xr-xscripts/dev/build_release.py63
-rw-r--r--scripts/dev/gen_resources.py8
-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.py81
-rw-r--r--scripts/dev/run_pylint_on_tests.py3
-rwxr-xr-xscripts/dev/src2asciidoc.py2
-rwxr-xr-xscripts/dev/update_3rdparty.py10
-rwxr-xr-xscripts/dictcli.py14
-rw-r--r--scripts/hostblock_blame.py4
-rw-r--r--scripts/opengl_info.py49
-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.feature80
-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/end2end/test_insert_mode.py2
-rw-r--r--tests/helpers/fixtures.py4
-rw-r--r--tests/helpers/logfail.py3
-rw-r--r--tests/unit/api/test_cmdutils.py3
-rw-r--r--tests/unit/browser/test_caret.py2
-rw-r--r--tests/unit/browser/test_history.py2
-rw-r--r--tests/unit/browser/test_inspector.py3
-rw-r--r--tests/unit/browser/webkit/test_webkitelem.py21
-rw-r--r--tests/unit/completion/test_completionwidget.py15
-rw-r--r--tests/unit/completion/test_models.py4
-rw-r--r--tests/unit/components/test_hostblock.py2
-rw-r--r--tests/unit/config/test_configcommands.py14
-rw-r--r--tests/unit/config/test_configfiles.py5
-rw-r--r--tests/unit/config/test_configtypes.py1
-rw-r--r--tests/unit/config/test_configutils.py12
-rw-r--r--tests/unit/config/test_qtargs.py2
-rw-r--r--tests/unit/config/test_websettings.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.py18
-rw-r--r--tests/unit/misc/test_keyhints.py4
-rw-r--r--tests/unit/misc/test_miscwidgets.py2
-rw-r--r--tests/unit/test_qutebrowser.py60
-rw-r--r--tests/unit/utils/test_log.py11
-rw-r--r--tests/unit/utils/test_qtutils.py10
-rw-r--r--tests/unit/utils/test_resources.py2
-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_utils.py5
-rw-r--r--tests/unit/utils/test_version.py23
-rw-r--r--tox.ini4
165 files changed, 1679 insertions, 833 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index a95ba918d..cf1c019f7 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 2.3.0
+current_version = 2.4.0
commit = True
message = Release v{new_version}
tag = True
diff --git a/.flake8 b/.flake8
index 5cfa2e76c..9110dc54c 100644
--- a/.flake8
+++ b/.flake8
@@ -41,6 +41,9 @@ exclude = .*,__pycache__,resources.py
# W503: like break before binary operator
# W504: line break after binary operator
# FI15: __future__ import "generator_stop" missing
+# PT004: fixture '{name}' does not return anything, add leading underscore
+# PT011: pytest.raises(ValueError) is too broad, set the match parameter or use a more specific exception
+# PT012: pytest.raises() block should contain a single simple statement
ignore =
B001,B008,B305,
E128,E226,E265,E501,E402,E266,E722,E731,
@@ -49,8 +52,11 @@ ignore =
P101,P102,P103,
D102,D103,D106,D107,D104,D105,D209,D211,D401,D402,D403,D412,D413,
A003,
- W503, W504
- FI15
+ W503, W504,
+ FI15,
+ PT004,
+ PT011,
+ PT012
min-version = 3.6.1
max-complexity = 12
per-file-ignores =
@@ -62,3 +68,6 @@ per-file-ignores =
copyright-check = True
copyright-regexp = # Copyright [\d-]+ .*
copyright-min-file-size = 110
+pytest-fixture-no-parentheses = True
+pytest-mark-no-parentheses = True
+pytest-parametrize-names-type = csv
diff --git a/.github/CONTRIBUTING.asciidoc b/.github/CONTRIBUTING.asciidoc
index bf9f0e66b..0d03af336 100644
--- a/.github/CONTRIBUTING.asciidoc
+++ b/.github/CONTRIBUTING.asciidoc
@@ -5,11 +5,11 @@ open pull requests.
- Before you start to work on something, please leave a comment on the relevant
issue (or open one). This makes sure there is no duplicate work done.
-- Either run the testsuite locally, or keep an eye on Travis CI / AppVeyor
- after pushing changes.
+- Either run the testsuite locally, or keep an eye on the CI at the end of the
+ pull request page after pushing changes.
- If you are stuck somewhere or have questions,
- https://github.com/qutebrowser/qutebrowser#getting-help[please ask]!
+ https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/index.asciidoc#getting-help[please ask]!
See the link:../doc/contributing.asciidoc[full contribution documentation] for
details and other useful hints.
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/README.asciidoc b/README.asciidoc
index 5c87df857..20644cb34 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -202,8 +202,9 @@ Active
~~~~~~
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
+* https://github.com/jun7/wyeb[wyeb] (C, GTK+ with WebKit2)
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
-* https://nyxt.atlas.engineer/[Nyxt browser] (formerly "Next browser", Lisp, Emacs-like but also offers Vim bindings, QtWebKit or GTK+/WebKit2 - note there was a https://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
+* https://nyxt.atlas.engineer/[Nyxt browser] (formerly "Next browser", Lisp, Emacs-like but also offers Vim bindings, QtWebEngine or GTK+/WebKit2 - note there was a https://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution in 2019] which was handled quite badly)
* https://vieb.dev/[Vieb] (JavaScript, Electron)
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
* Chrome/Chromium addons:
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index f7965a952..6b2746c96 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -15,24 +15,97 @@ 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.
+- The default `hint.selectors` now match more ARIA roles (`tab`, `checkbox`,
+ `menuitem`, `menuitemcheckbox` and `menuitemradio`).
+- Using e.g. `:bind --mode=passthrough` now scrolls to the passthrough section
+ on the `qute://bindings` page.
+
+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.
+- The audible indicator (`[A]`) now uses a 2s cooldown when the audio goes
+ silent, equivalent with the behavior of older QtWebEngine versions.
+- With `confirm_quit` set to `downloads`, the confirmation dialog is now only
+ shown when closing the last window (rather than closing any window, which
+ would continue running that window's downloads). Unfortunately, more issues
+ with `confirm_quit` and multiple windows remain.
+
+[[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.
+- Added a site-specific quirk to make cookie dialogs on StackExchange pages
+ (such as Stack Overflow) work on Qt 5.12.
+
[[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
~~~~~
- New `content.blocking.hosts.block_subdomains` setting which can be used to
disable the subdomain blocking for the hosts-based adblocker introduced in
v2.3.0.
+- New `downloads.prevent_mixed_content` setting to prevent insecure
+ mixed-content downloads (true by default).
+- New `--private` flag for `:tab-clone`, which clones a tab into a new private
+ window, mirroring the same flags for `:open` and `:tab-give`.
Fixed
~~~~~
- Switching tabs via mouse wheel scrolling now works properly on macOS. Set
`tabs.mousewheel_switching` to false if you prefer the previous behavior.
-- Crash when entering unicode surrogates into the filename prompt.
-- `UnboundLocalError` in `qute-keepass` when the database couldn't be opened.
+- Speculative fix for a crash when closing qutebrowser while a systray
+ notification is shown.
Changed
~~~~~~~
@@ -41,15 +114,33 @@ Changed
- When opening a file qutebrowser can't handle from a `file:///` directory
listing, qutebrowser now opens it with the default application rather than
displaying a download prompt.
+- In Greasemonkey scripts, using "overrideMimeType" with GM_xmlhttpRequest is
+ now supported.
+- `:hint --rapid` is now supported for the `tab` hinting target no matter what
+ `tabs.background` is set to, as there are various scenarios where tabs can
+ open in the background.
+- New flags for the `qute-pass` userscript:
+ * `--unfiltered` to show all secrets, not just the one matching the current
+ URL.
+ * `--always-show-selection` to confirm the password to be entered even if
+ there's only a single match.
+- In insert mode, `<Shift-Escape>` is now bound to `fake-key <Escape>` by
+ default, i.e., sends an Escape keypress to the website.
+- Using `GM_setClipboard` in Greasemonkey scripts is now supported.
[[v2.3.1]]
-v2.3.1 (unreleased)
+v2.3.1 (2021-07-28)
-------------------
Fixed
~~~~~
+- Updated the workaround for Google Account log in claiming that this browser
+ isn't secure. For an equivalent workaround on older versions, run:
+ `:set -u https://accounts.google.com/* content.headers.user_agent "Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0"`
- Corrupt cache file exceptions with `adblock` 0.5.0+ are now handled properly.
+- Crash when entering unicode surrogates into the filename prompt.
+- `UnboundLocalError` in `qute-keepass` when the database couldn't be opened.
[[v2.3.0]]
v2.3.0 (2021-06-28)
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index f570a3ffd..442c136a7 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -294,7 +294,7 @@ Cycle an option between multiple values.
* +'values'+: The values to cycle through.
==== optional arguments
-* +*-u*+, +*--pattern*+: The URL pattern to use.
+* +*-u*+, +*--pattern*+: The link:configuring{outfilesuffix}#patterns[URL pattern] to use.
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
* +*-p*+, +*--print*+: Print the value after setting.
@@ -391,7 +391,7 @@ This sets an option back to its default and removes it from autoconfig.yml.
* +'option'+: The name of the option.
==== optional arguments
-* +*-u*+, +*--pattern*+: The URL pattern to use.
+* +*-u*+, +*--pattern*+: The link:configuring{outfilesuffix}#patterns[URL pattern] to use.
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
[[config-write-py]]
@@ -679,9 +679,8 @@ Start hinting.
* +*-a*+, +*--add-history*+: Whether to add the spawned or yanked link to the browsing history.
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily
- open multiple links. This is only possible with targets
- `tab` (with `tabs.background=true`), `tab-bg`,
- `window`, `run`, `hover`, `userscript` and `spawn`.
+ open multiple links. Note this won't work with targets
+ `tab-fg`, `fill`, `delete` and `right-click`.
* +*-f*+, +*--first*+: Click the first hinted element without prompting.
@@ -1286,7 +1285,7 @@ If the option name ends with '?' or no value is provided, the value of the optio
==== optional arguments
* +*-t*+, +*--temp*+: Set value temporarily until qutebrowser is closed.
* +*-p*+, +*--print*+: Print the value after setting.
-* +*-u*+, +*--pattern*+: The URL pattern to use.
+* +*-u*+, +*--pattern*+: The link:configuring{outfilesuffix}#patterns[URL pattern] to use.
[[set-cmd-text]]
=== set-cmd-text
@@ -1357,13 +1356,14 @@ The tab index to stop.
[[tab-clone]]
=== tab-clone
-Syntax: +:tab-clone [*--bg*] [*--window*]+
+Syntax: +:tab-clone [*--bg*] [*--window*] [*--private*]+
Duplicate the current tab.
==== optional arguments
* +*-b*+, +*--bg*+: Open in a background tab.
* +*-w*+, +*--window*+: Open in a new window.
+* +*-p*+, +*--private*+: Open in a new private window.
[[tab-close]]
=== tab-close
@@ -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 ec6ffbcb1..552145023 100644
--- a/doc/help/configuring.asciidoc
+++ b/doc/help/configuring.asciidoc
@@ -22,6 +22,19 @@ exists, the `autoconfig.yml` file **is not read anymore** by default. You need
to <<configpy-autoconfig,load it from `config.py`>> if you want settings changed via
`:set`/`:bind` to persist between restarts.
+[[patterns]]
+URL pattern support
+-------------------
+
+Many settings are customizable depending on the page being visited by using URL
+patterns. The link:settings{outfilesuffix}[settings documentation] marks such
+settings with "This setting supports URL patterns.
+
+The syntax is based on Chromium's
+https://developer.chrome.com/docs/extensions/mv3/match_patterns/[URL pattern syntax].
+As an extension, the scheme and path can be left off as a short-hand syntax, so
+`example.com` is equivalent to `*://example.com/*`.
+
[[autoconfig]]
Configuring qutebrowser via the user interface
----------------------------------------------
@@ -45,9 +58,10 @@ customizable.
Using the link:commands{outfilesuffix}#set[`:set`] command and command completion, you
can quickly set settings interactively, for example `:set tabs.position left`.
-Some settings are also customizable for a given
-https://developer.chrome.com/apps/match_patterns[URL pattern] by doing e.g.
-`:set --pattern=*://example.com/ content.images false`.
+<<patterns,URL patterns>> can be used via
+`:set --pattern *://example.com/* content.images false`, or with shorthand
+syntax for both argument and pattern, `:set -u example.com content.images
+false`.
To get more help about a setting, use e.g. `:help tabs.position`.
@@ -160,8 +174,8 @@ color = config.get('colors.completion.fg')
Per-domain settings
~~~~~~~~~~~~~~~~~~~
-Using `config.set`, some settings are also customizable for a given
-https://developer.chrome.com/apps/match_patterns[URL pattern]:
+Using `config.set` instead of the `c.` shorthand, many settings are also
+customizable for a given <<patterns,URL patterns>>.
[source,python]
----
@@ -398,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 70ea2ab6e..b8705749a 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -209,6 +209,7 @@
|<<downloads.location.suggestion,downloads.location.suggestion>>|What to display in the download filename input.
|<<downloads.open_dispatcher,downloads.open_dispatcher>>|Default program used to open downloads.
|<<downloads.position,downloads.position>>|Where to show the downloaded files.
+|<<downloads.prevent_mixed_content,downloads.prevent_mixed_content>>|Automatically abort insecure (HTTP) downloads originating from secure (HTTPS) pages.
|<<downloads.remove_finished,downloads.remove_finished>>|Duration (in milliseconds) to wait before removing finished downloads.
|<<editor.command,editor.command>>|Editor (and arguments) to use for the `edit-*` commands.
|<<editor.encoding,editor.encoding>>|Encoding to use for the editor.
@@ -268,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.
@@ -545,6 +547,7 @@ Default:
* +pass:[&lt;Ctrl-E&gt;]+: +pass:[edit-text]+
* +pass:[&lt;Escape&gt;]+: +pass:[mode-leave]+
+* +pass:[&lt;Shift-Escape&gt;]+: +pass:[fake-key &lt;Escape&gt;]+
* +pass:[&lt;Shift-Ins&gt;]+: +pass:[insert-text -- {primary}]+
- +pass:[normal]+:
@@ -1957,7 +1960,7 @@ Default:
=== content.autoplay
Automatically start playing `<video>` elements.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -1989,7 +1992,7 @@ Default:
=== content.blocking.enabled
Enable the ad/host blocker
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2064,7 +2067,7 @@ Default: empty
Enable support for the HTML 5 web application cache feature.
An application cache acts like an HTTP cache in some sense. For documents that use the application cache via JavaScript, the loader engine will first ask the application cache for the contents, before hitting the network.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebKit backend.
@@ -2115,7 +2118,7 @@ If this setting is used with URL patterns, the pattern gets applied to the origi
With QtWebEngine 5.15.0+, paths will be stripped from URLs, so URL patterns using paths will not match. With QtWebEngine 5.15.2+, subdomains are additionally stripped as well, so you will typically need to set this setting for `example.com` when the cookie is set on `somesubdomain.example.com` for it to work properly.
To debug issues with this setting, start qutebrowser with `--debug --logfilter network --debug-flag log-cookies` which will show all cookies being set.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,String>>
@@ -2149,7 +2152,7 @@ Default: +pass:[iso-8859-1]+
=== content.desktop_capture
Allow websites to share screen content.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,BoolAsk>>
@@ -2165,7 +2168,7 @@ Default: +pass:[ask]+
=== content.dns_prefetch
Try to pre-fetch DNS entries to speed up browsing.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2178,7 +2181,7 @@ Default: +pass:[true]+
Expand each subframe to its contents.
This will flatten all the frames to become one scrollable page.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebKit backend.
@@ -2207,7 +2210,7 @@ Default: +pass:[false]+
=== content.geolocation
Allow websites to request geolocations.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,BoolAsk>>
@@ -2224,7 +2227,7 @@ Default: +pass:[ask]+
Value to send in the `Accept-Language` header.
Note that the value read from JavaScript is always the global value.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,String>>
@@ -2234,7 +2237,7 @@ Default: +pass:[en-US,en;q=0.9]+
=== content.headers.custom
Custom headers for qutebrowser HTTP requests.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Dict>>
@@ -2245,7 +2248,7 @@ Default: empty
Value to send in the `DNT` header.
When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2254,7 +2257,7 @@ Default: +pass:[true]+
[[content.headers.referer]]
=== content.headers.referer
When to send the Referer header.
-The Referer header tells websites from which website you were coming from when visiting them.
+The Referer header tells websites from which website you were coming from when visiting them. Note that with QtWebEngine, websites can override this preference by setting the `Referrer-Policy:` header, so that any websites visited from them get the full referer.
No restart is needed with QtWebKit.
This setting requires a restart.
@@ -2293,7 +2296,7 @@ QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
to JavaScript requires a restart.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FormatString>>
@@ -2303,7 +2306,7 @@ Default: +pass:[Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, lik
=== content.hyperlink_auditing
Enable hyperlink auditing (`<a ping>`).
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2313,7 +2316,7 @@ Default: +pass:[false]+
=== content.images
Load images automatically in web pages.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2332,7 +2335,7 @@ Default: +pass:[true]+
Allow JavaScript to read from or write to the clipboard.
With QtWebEngine, writing the clipboard as response to a user interaction is always allowed.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2342,7 +2345,7 @@ Default: +pass:[false]+
=== content.javascript.can_close_tabs
Allow JavaScript to close tabs.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebKit backend.
@@ -2354,7 +2357,7 @@ Default: +pass:[false]+
=== content.javascript.can_open_tabs_automatically
Allow JavaScript to open new tabs without user interaction.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2364,7 +2367,7 @@ Default: +pass:[false]+
=== content.javascript.enabled
Enable JavaScript.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2406,7 +2409,7 @@ Default: +pass:[true]+
=== content.local_content_can_access_file_urls
Allow locally loaded documents to access other local URLs.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2416,7 +2419,7 @@ Default: +pass:[true]+
=== content.local_content_can_access_remote_urls
Allow locally loaded documents to access remote URLs.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2426,7 +2429,7 @@ Default: +pass:[false]+
=== content.local_storage
Enable support for HTML 5 local storage and Web SQL.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2436,7 +2439,7 @@ Default: +pass:[true]+
=== content.media.audio_capture
Allow websites to record audio.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2454,7 +2457,7 @@ Default: +pass:[ask]+
=== content.media.audio_video_capture
Allow websites to record audio and video.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2472,7 +2475,7 @@ Default: +pass:[ask]+
=== content.media.video_capture
Allow websites to record video.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2490,7 +2493,7 @@ Default: +pass:[ask]+
=== content.mouse_lock
Allow websites to lock your mouse pointer.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2509,7 +2512,7 @@ Default: +pass:[ask]+
Automatically mute tabs.
Note that if the `:tab-mute` command is used, the mute status for the affected tab is now controlled manually, and this setting doesn't have any effect.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2528,7 +2531,7 @@ Default: empty
=== content.notifications.enabled
Allow websites to show notifications.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
On QtWebEngine, this setting requires Qt 5.13 or newer.
@@ -2578,7 +2581,7 @@ Whether to show the origin URL for notifications.
Note that URL patterns with this setting only get matched against the origin part of the URL, so e.g. paths in patterns will never match.
Note that with the `qt` presenter, origins are never shown.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
On QtWebEngine, this setting requires Qt 5.14 or newer.
@@ -2593,7 +2596,7 @@ Default: +pass:[true]+
Allow pdf.js to view PDF files in the browser.
Note that the files can still be downloaded by clicking the download button in the pdf.js viewer.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2603,7 +2606,7 @@ Default: +pass:[false]+
=== content.persistent_storage
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2621,7 +2624,7 @@ Default: +pass:[ask]+
=== content.plugins
Enable plugins in Web pages.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2647,7 +2650,7 @@ Default: +pass:[false]+
=== content.print_element_backgrounds
Draw the background color and images also when the page is printed.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2667,7 +2670,7 @@ Default: +pass:[false]+
=== content.proxy
Proxy to use.
In addition to the listed values, you can use a `socks://...` or `http://...` URL.
-Note that with QtWebEngine, it will take a couple of seconds until the change is applied, if this value is changed at runtime.
+Note that with QtWebEngine, it will take a couple of seconds until the change is applied, if this value is changed at runtime. Authentication for SOCKS proxies isn't supported due to Chromium limitations.
Type: <<types,Proxy>>
@@ -2692,7 +2695,7 @@ Default: +pass:[true]+
=== content.register_protocol_handler
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2745,7 +2748,7 @@ Default:
=== content.tls.certificate_errors
How to proceed on TLS certificate errors.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,String>>
@@ -2762,7 +2765,7 @@ Default: +pass:[ask]+
=== content.unknown_url_scheme_policy
How navigation requests to URLs with unknown schemes are handled.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebEngine backend.
@@ -2788,7 +2791,7 @@ Default: empty
=== content.webgl
Enable WebGL.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2819,7 +2822,7 @@ Monitor load requests for cross-site scripting attempts.
Suspicious scripts will be blocked and reported in the devtools JavaScript console.
Note that bypasses for the XSS auditor are widely known and it can be abused for cross-site info leaks in some scenarios, see: https://www.chromium.org/developers/design-documents/xss-auditor
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -2888,6 +2891,17 @@ Valid values:
Default: +pass:[top]+
+[[downloads.prevent_mixed_content]]
+=== downloads.prevent_mixed_content
+Automatically abort insecure (HTTP) downloads originating from secure (HTTPS) pages.
+For per-domain settings, the relevant URL is the URL initiating the download, not the URL the download itself is coming from. It's not recommended to set this setting to false globally.
+
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
+
+Type: <<types,Bool>>
+
+Default: +pass:[true]+
+
[[downloads.remove_finished]]
=== downloads.remove_finished
Duration (in milliseconds) to wait before removing finished downloads.
@@ -3125,7 +3139,7 @@ Default: +pass:[default_size default_family]+
=== fonts.web.family.cursive
Font family for cursive fonts.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FontFamily>>
@@ -3135,7 +3149,7 @@ Default: empty
=== fonts.web.family.fantasy
Font family for fantasy fonts.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FontFamily>>
@@ -3145,7 +3159,7 @@ Default: empty
=== fonts.web.family.fixed
Font family for fixed fonts.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FontFamily>>
@@ -3155,7 +3169,7 @@ Default: empty
=== fonts.web.family.sans_serif
Font family for sans-serif fonts.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FontFamily>>
@@ -3165,7 +3179,7 @@ Default: empty
=== fonts.web.family.serif
Font family for serif fonts.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FontFamily>>
@@ -3175,7 +3189,7 @@ Default: empty
=== fonts.web.family.standard
Font family for standard fonts.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,FontFamily>>
@@ -3185,7 +3199,7 @@ Default: empty
=== fonts.web.size.default
Default font size (in pixels) for regular text.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Int>>
@@ -3195,7 +3209,7 @@ Default: +pass:[16]+
=== fonts.web.size.default_fixed
Default font size (in pixels) for fixed-pitch text.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Int>>
@@ -3205,7 +3219,7 @@ Default: +pass:[13]+
=== fonts.web.size.minimum
Hard minimum font size (in pixels).
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Int>>
@@ -3215,7 +3229,7 @@ Default: +pass:[0]+
=== fonts.web.size.minimum_logical
Minimum logical font size (in pixels) that is applied when zooming out.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Int>>
@@ -3384,7 +3398,7 @@ Default: +pass:[true]+
=== hints.selectors
CSS selectors used to determine which elements on a page should have hints.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting can only be set in config.py.
@@ -3411,11 +3425,16 @@ Default:
* +pass:[[role=&quot;link&quot;\]]+
* +pass:[[role=&quot;option&quot;\]]+
* +pass:[[role=&quot;button&quot;\]]+
+* +pass:[[role=&quot;tab&quot;\]]+
+* +pass:[[role=&quot;checkbox&quot;\]]+
+* +pass:[[role=&quot;menuitem&quot;\]]+
+* +pass:[[role=&quot;menuitemcheckbox&quot;\]]+
+* +pass:[[role=&quot;menuitemradio&quot;\]]+
* +pass:[[ng-click\]]+
* +pass:[[ngClick\]]+
* +pass:[[data-ng-click\]]+
* +pass:[[x-ng-click\]]+
-* +pass:[[tabindex\]]+
+* +pass:[[tabindex\]:not([tabindex=&quot;-1&quot;\])]+
- +pass:[images]+:
* +pass:[img]+
@@ -3520,7 +3539,7 @@ Default: +pass:[false]+
Leave insert mode when starting a new page load.
Patterns may be unreliable on this setting, and they may match the url you are navigating to, or the URL you are navigating from.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -3538,7 +3557,16 @@ Default: +pass:[false]+
=== input.links_included_in_focus_chain
Include hyperlinks in the keyboard focus chain when tabbing.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
+
+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>>
@@ -3591,7 +3619,7 @@ Default: +pass:[0]+
Enable spatial navigation.
Spatial navigation consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. For example, if the user presses the Right key, heuristics determine whether there is an element he might be trying to reach towards the right and which element he probably wants.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -3876,7 +3904,7 @@ Default: +pass:[overlay]+
Enable smooth scrolling for web pages.
Note smooth scrolling does not work with the `:scroll-px` command.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
Type: <<types,Bool>>
@@ -4605,7 +4633,7 @@ Default: +pass:[512]+
=== zoom.text_only
Apply the zoom factor on a frame only to the text or to all content.
-This setting supports URL patterns.
+This setting supports link:configuring{outfilesuffix}#patterns[URL patterns].
This setting is only available with the QtWebKit backend.
diff --git a/doc/img/cheatsheet-big.png b/doc/img/cheatsheet-big.png
index 75e2abb89..d47ca913f 100644
--- a/doc/img/cheatsheet-big.png
+++ b/doc/img/cheatsheet-big.png
Binary files differ
diff --git a/doc/img/cheatsheet-small.png b/doc/img/cheatsheet-small.png
index e97d63367..49e5e33df 100644
--- a/doc/img/cheatsheet-small.png
+++ b/doc/img/cheatsheet-small.png
Binary files differ
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 49bc09786..9930514d0 100644
--- a/misc/org.qutebrowser.qutebrowser.appdata.xml
+++ b/misc/org.qutebrowser.qutebrowser.appdata.xml
@@ -44,6 +44,8 @@
</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"/>
<release version="2.2.2" date="2021-05-20"/>
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/qutebrowser.rcc b/misc/qutebrowser.rcc
index 2f73b3726..ffe9e47f6 100644
--- a/misc/qutebrowser.rcc
+++ b/misc/qutebrowser.rcc
@@ -1,13 +1,13 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
- <file>icons/qutebrowser-16x16.png</file>
- <file>icons/qutebrowser-24x24.png</file>
- <file>icons/qutebrowser-32x32.png</file>
- <file>icons/qutebrowser-48x48.png</file>
- <file>icons/qutebrowser-64x64.png</file>
- <file>icons/qutebrowser-96x96.png</file>
- <file>icons/qutebrowser-128x128.png</file>
- <file>icons/qutebrowser-256x256.png</file>
- <file>icons/qutebrowser-512x512.png</file>
+ <file>../icons/qutebrowser-16x16.png</file>
+ <file>../icons/qutebrowser-24x24.png</file>
+ <file>../icons/qutebrowser-32x32.png</file>
+ <file>../icons/qutebrowser-48x48.png</file>
+ <file>../icons/qutebrowser-64x64.png</file>
+ <file>../icons/qutebrowser-96x96.png</file>
+ <file>../icons/qutebrowser-128x128.png</file>
+ <file>../icons/qutebrowser-256x256.png</file>
+ <file>../icons/qutebrowser-512x512.png</file>
</qresource>
</RCC>
diff --git a/misc/requirements/requirements-check-manifest.txt b/misc/requirements/requirements-check-manifest.txt
index 72ade7715..bd32971c2 100644
--- a/misc/requirements/requirements-check-manifest.txt
+++ b/misc/requirements/requirements-check-manifest.txt
@@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-build==0.5.1
-check-manifest==0.46
-packaging==21.0
-pep517==0.11.0
-pyparsing==2.4.7
+build==0.7.0
+check-manifest==0.47
+packaging==21.3
+pep517==0.12.0
+pyparsing==3.0.6
toml==0.10.2
-tomli==1.0.4
+tomli==2.0.0
diff --git a/misc/requirements/requirements-dev.txt b/misc/requirements/requirements-dev.txt
index 2bad8090f..dc2041598 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.5.30
-cffi==1.14.6
-charset-normalizer==2.0.3
-cryptography==3.4.7
-Deprecated==1.2.12
-github3.py==2.0.0
-hunter==3.3.8
-idna==3.2
-jwcrypto==0.9.1
+certifi==2021.10.8
+cffi==1.15.0
+charset-normalizer==2.0.9
+colorama==0.4.4
+cryptography==36.0.1
+Deprecated==1.2.13
+docutils==0.18.1
+github3.py==3.0.0
+hunter==3.4.3
+idna==3.3
+importlib-metadata==4.10.0
+jeepney==0.7.1
+jwcrypto==1.0
+keyring==23.5.0
manhole==1.8.0
-packaging==21.0
-pycparser==2.20
-Pympler==0.9
-pyparsing==2.4.7
-PyQt-builder==1.10.3
+packaging==21.3
+pep517==0.12.0
+pkginfo==1.8.2
+pycparser==2.21
+Pygments==2.11.1
+Pympler==1.0.1
+pyparsing==3.0.6
+PyQt-builder==1.12.2
python-dateutil==2.8.2
+readme-renderer==32.0
requests==2.26.0
-sip==6.1.1
+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==3.0.1
-# urllib3==1.26.6
-wrapt==1.12.1
+tomli==2.0.0
+tqdm==4.62.3
+twine==3.7.1
+uritemplate==4.1.1
+# urllib3==1.26.7
+webencodings==0.5.1
+wrapt==1.13.3
+zipp==3.7.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 cc041b538..2f13b4733 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -1,24 +1,26 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-attrs==21.2.0
-flake8==3.9.2
-flake8-bugbear==21.4.3
+attrs==21.4.0
+flake8==4.0.1
+flake8-bugbear==21.11.29
flake8-builtins==1.5.3
-flake8-comprehensions==3.5.0
+flake8-comprehensions==3.7.0
flake8-copyright==0.2.2
flake8-debugger==4.0.0
flake8-deprecated==1.3
flake8-docstrings==1.6.0
flake8-future-import==0.4.6
flake8-mock==0.3
+flake8-plugin-utils==1.3.2
flake8-polyfill==1.0.2
+flake8-pytest-style==1.6.0
flake8-string-format==0.3.0
-flake8-tidy-imports==4.3.0
+flake8-tidy-imports==4.5.0
flake8-tuple==0.4.1
mccabe==0.6.1
-pep8-naming==0.12.0
-pycodestyle==2.7.0
+pep8-naming==0.12.1
+pycodestyle==2.8.0
pydocstyle==6.1.1
-pyflakes==2.3.1
+pyflakes==2.4.0
six==1.16.0
-snowballstemmer==2.1.0
+snowballstemmer==2.2.0
diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw
index 1bdca6974..59908897c 100644
--- a/misc/requirements/requirements-flake8.txt-raw
+++ b/misc/requirements/requirements-flake8.txt-raw
@@ -11,6 +11,7 @@ flake8-mock
flake8-string-format
flake8-tidy-imports
flake8-tuple
+flake8-pytest-style
pep8-naming
pydocstyle
pyflakes
diff --git a/misc/requirements/requirements-mypy.txt b/misc/requirements/requirements-mypy.txt
index 3d0beca70..0b40fa31f 100644
--- a/misc/requirements/requirements-mypy.txt
+++ b/misc/requirements/requirements-mypy.txt
@@ -1,21 +1,19 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
chardet==4.0.0
-diff-cover==6.2.0
-importlib-metadata==4.6.1
-importlib-resources==5.2.0
-inflect==5.3.0
-Jinja2==3.0.1
-jinja2-pluralize==0.3.0
-lxml==4.6.3
+diff-cover==6.4.4
+importlib-metadata==4.10.0
+importlib-resources==5.4.0
+Jinja2==3.0.3
+lxml==4.7.1
MarkupSafe==2.0.1
-mypy==0.910
+mypy==0.930
mypy-extensions==0.4.3
-pluggy==0.13.1
-Pygments==2.9.0
+pluggy==1.0.0
+Pygments==2.11.1
PyQt5-stubs==5.15.2.0
-toml==0.10.2
-types-dataclasses==0.1.5
-types-PyYAML==5.4.3
-typing-extensions==3.10.0.0
-zipp==3.5.0
+tomli==2.0.0
+types-dataclasses==0.6.2
+types-PyYAML==6.0.1
+typing_extensions==4.0.1
+zipp==3.7.0
diff --git a/misc/requirements/requirements-pyinstaller.txt b/misc/requirements/requirements-pyinstaller.txt
index 4aa157bad..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
-pyinstaller==4.4
-pyinstaller-hooks-contrib==2021.2
+altgraph==0.17.2
+pyinstaller==4.7
+pyinstaller-hooks-contrib==2021.4
diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt
index 6845768b2..2ea50cb8f 100644
--- a/misc/requirements/requirements-pylint.txt
+++ b/misc/requirements/requirements-pylint.txt
@@ -1,26 +1,30 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-astroid==2.3.3 # rq.filter: < 2.4
-certifi==2021.5.30
-cffi==1.14.6
-charset-normalizer==2.0.3
-cryptography==3.4.7
-Deprecated==1.2.12
+astroid==2.9.1
+certifi==2021.10.8
+cffi==1.15.0
+charset-normalizer==2.0.9
+cryptography==36.0.1
+Deprecated==1.2.13
future==0.18.2
-github3.py==2.0.0
-idna==3.2
-isort==4.3.21
-jwcrypto==0.9.1
-lazy-object-proxy==1.4.3
+github3.py==3.0.0
+idna==3.3
+isort==5.10.1
+jwcrypto==1.0
+lazy-object-proxy==1.7.1
mccabe==0.6.1
-pefile==2021.5.24
-pycparser==2.20
-pylint==2.4.4 # rq.filter: < 2.5
+pefile==2021.9.3
+platformdirs==2.4.1 ; python_version>="3.7"
+pycparser==2.21
+pylint==2.12.2
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==3.0.1
-# urllib3==1.26.6
-wrapt==1.11.2
+toml==0.10.2
+typed-ast==1.5.1 ; python_version<"3.8"
+typing_extensions==4.0.1
+uritemplate==4.1.1
+# urllib3==1.26.7
+wrapt==1.13.3
+platformdirs==2.4.0 ; python_version=="3.6.*"
diff --git a/misc/requirements/requirements-pylint.txt-raw b/misc/requirements/requirements-pylint.txt-raw
index 08d340665..273d07cf4 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,13 @@ 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
+
+# For pylint_checkers
+#@ pip_args: --use-feature=in-tree-build
+
+# Python 3.6
+#@ markers: platformdirs python_version>="3.7"
+#@ add: platformdirs==2.4.0 ; python_version=="3.6.*"
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 7c2087d2a..cdb3b388f 100644
--- a/misc/requirements/requirements-pyroma.txt
+++ b/misc/requirements/requirements-pyroma.txt
@@ -1,10 +1,10 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-certifi==2021.5.30
-charset-normalizer==2.0.3
-docutils==0.17.1
-idna==3.2
-Pygments==2.9.0
+certifi==2021.10.8
+charset-normalizer==2.0.9
+docutils==0.18.1
+idna==3.3
+Pygments==2.11.1
pyroma==3.2
requests==2.26.0
-urllib3==1.26.6
+urllib3==1.26.7
diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw
index e07ea992a..139d599f8 100644
--- a/misc/requirements/requirements-qutebrowser.txt-raw
+++ b/misc/requirements/requirements-qutebrowser.txt-raw
@@ -15,6 +15,11 @@ importlib-metadata # Determining PyQt version
typing_extensions # from importlib-metadata
#@ markers: importlib-resources python_version<"3.9"
-#@ markers: importlib-metadata python_version<"3.8"
+#@ markers: importlib-metadata python_version=="3.7.*"
#@ markers: typing_extensions python_version<"3.8"
#@ markers: dataclasses python_version<"3.7"
+
+# Python 3.6
+#@ add: importlib-metadata<4.9 ; python_version=="3.6.*"
+#@ markers: zipp python_version>="3.7"
+#@ add: zipp<3.7 ; python_version=="3.6.*"
diff --git a/misc/requirements/requirements-sphinx.txt b/misc/requirements/requirements-sphinx.txt
index 5e8faa230..687b1cd00 100644
--- a/misc/requirements/requirements-sphinx.txt
+++ b/misc/requirements/requirements-sphinx.txt
@@ -2,24 +2,24 @@
alabaster==0.7.12
Babel==2.9.1
-certifi==2021.5.30
-charset-normalizer==2.0.3
+certifi==2021.10.8
+charset-normalizer==2.0.9
docutils==0.17.1
-idna==3.2
-imagesize==1.2.0
-Jinja2==3.0.1
+idna==3.3
+imagesize==1.3.0
+Jinja2==3.0.3
MarkupSafe==2.0.1
-packaging==21.0
-Pygments==2.9.0
-pyparsing==2.4.7
-pytz==2021.1
+packaging==21.3
+Pygments==2.11.1
+pyparsing==3.0.6
+pytz==2021.3
requests==2.26.0
-snowballstemmer==2.1.0
-Sphinx==4.1.1
+snowballstemmer==2.2.0
+Sphinx==4.3.2
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
-urllib3==1.26.6
+urllib3==1.26.7
diff --git a/misc/requirements/requirements-tests-bleeding.txt b/misc/requirements/requirements-tests-bleeding.txt
index 5fbb05f05..d2a7fcfb6 100644
--- a/misc/requirements/requirements-tests-bleeding.txt
+++ b/misc/requirements/requirements-tests-bleeding.txt
@@ -7,7 +7,9 @@ git+https://github.com/pallets/flask.git
git+https://github.com/pallets/werkzeug.git # transitive dep, but needed to work
git+https://github.com/HypothesisWorks/hypothesis.git#subdirectory=hypothesis-python
git+https://github.com/pytest-dev/pytest.git
-git+https://github.com/pytest-dev/pytest-bdd.git
+# Problematic: https://github.com/pytest-dev/pytest-bdd/issues/447
+# git+https://github.com/pytest-dev/pytest-bdd.git
+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 401b37bb2..7d17cc661 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -1,59 +1,64 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-attrs==21.2.0
-beautifulsoup4==4.9.3
-certifi==2021.5.30
-charset-normalizer==2.0.3
+attrs==21.4.0
+beautifulsoup4==4.10.0
+certifi==2021.10.8
+charset-normalizer==2.0.9
cheroot==8.5.2
-click==8.0.1
-coverage==5.5
+click==8.0.3
+coverage==6.2
EasyProcess==0.3
execnet==1.9.0
-filelock==3.0.12
-Flask==2.0.1
+filelock==3.4.2 ; python_version>="3.7"
+Flask==2.0.2
glob2==0.7
-hunter==3.3.8
-hypothesis==6.14.3
-icdiff==1.9.1
-idna==3.2
+hunter==3.4.3
+hypothesis==6.34.1 ; python_version>="3.7"
+icdiff==2.0.4
+idna==3.3
iniconfig==1.1.1
itsdangerous==2.0.1
-jaraco.functools==3.3.0
-# Jinja2==3.0.1
-Mako==1.1.4
+jaraco.functools==3.5.0 ; python_version>="3.7"
+# Jinja2==3.0.3
+Mako==1.1.6
manhole==1.8.0
# MarkupSafe==2.0.1
-more-itertools==8.8.0
-packaging==21.0
+more-itertools==8.12.0
+packaging==21.3
parse==1.19.0
parse-type==0.5.2
-pluggy==0.13.1
+pluggy==1.0.0
pprintpp==0.4.0
-py==1.10.0
+py==1.11.0
py-cpuinfo==8.0.0
-Pygments==2.9.0
-pyparsing==2.4.7
-pytest==6.2.4
+Pygments==2.11.1
+pyparsing==3.0.6
+pytest==6.2.5
pytest-bdd==4.1.0
pytest-benchmark==3.4.1
-pytest-cov==2.12.1
-pytest-forked==1.3.0
+pytest-cov==3.0.0
+pytest-forked==1.4.0
pytest-icdiff==0.5
pytest-instafail==0.4.2
pytest-mock==3.6.1
pytest-qt==4.0.2
pytest-repeat==0.9.1
-pytest-rerunfailures==10.1
-pytest-xdist==2.3.0
+pytest-rerunfailures==10.2
+pytest-xdist==2.5.0
pytest-xvfb==2.0.0
PyVirtualDisplay==2.2
requests==2.26.0
requests-file==1.5.1
six==1.16.0
sortedcontainers==2.4.0
-soupsieve==2.2.1
-tldextract==3.1.0
+soupsieve==2.3.1
+tldextract==3.1.2
toml==0.10.2
-urllib3==1.26.6
+tomli==2.0.0 ; python_version>="3.7"
+urllib3==1.26.7
vulture==2.3
-Werkzeug==2.0.1
+Werkzeug==2.0.2
+jaraco.functools<3.5 ; python_version=="3.6.*"
+tomli<2 ; python_version=="3.6.*"
+filelock==3.4.1 ; python_version=="3.6.*"
+hypothesis<6.32 ; python_version=="3.6.*"
diff --git a/misc/requirements/requirements-tests.txt-raw b/misc/requirements/requirements-tests.txt-raw
index ab580ac4b..ff66280ff 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
@@ -34,3 +35,13 @@ pytest-icdiff
tldextract
#@ ignore: Jinja2, MarkupSafe, colorama
+
+# Python 3.6
+#@ markers: jaraco.functools python_version>="3.7"
+#@ add: jaraco.functools<3.5 ; python_version=="3.6.*"
+#@ markers: tomli python_version>="3.7"
+#@ add: tomli<2 ; python_version=="3.6.*"
+#@ markers: filelock python_version>="3.7"
+#@ add: filelock==3.4.1 ; python_version=="3.6.*"
+#@ markers: hypothesis python_version>="3.7"
+#@ add: hypothesis<6.32 ; python_version=="3.6.*"
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index 8dbc637a8..d1a85cebe 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -1,17 +1,19 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-backports.entry-points-selectable==1.1.0
-distlib==0.3.2
-filelock==3.0.12
-packaging==21.0
-pip==21.1.3
-platformdirs==2.0.2
-pluggy==0.13.1
-py==1.10.0
-pyparsing==2.4.7
-setuptools==57.4.0
+distlib==0.3.4
+filelock==3.4.2 ; python_version>="3.7"
+packaging==21.3
+pip==21.3.1
+platformdirs==2.4.1 ; python_version>="3.7"
+pluggy==1.0.0
+py==1.11.0
+pyparsing==3.0.6
+setuptools==60.2.0 ; python_version>="3.7"
six==1.16.0
toml==0.10.2
-tox==3.24.0
-virtualenv==20.6.0
-wheel==0.36.2
+tox==3.24.5
+virtualenv==20.13.0
+wheel==0.37.1
+setuptools<60 ; python_version=="3.6.*"
+filelock==3.4.1 ; python_version=="3.6.*"
+platformdirs==2.4.0 ; python_version=="3.6.*"
diff --git a/misc/requirements/requirements-tox.txt-raw b/misc/requirements/requirements-tox.txt-raw
index 27d58e1f4..9b1968480 100644
--- a/misc/requirements/requirements-tox.txt-raw
+++ b/misc/requirements/requirements-tox.txt-raw
@@ -1,2 +1,10 @@
tox
wheel
+
+# Python 3.6
+#@ markers: setuptools python_version>="3.7"
+#@ add: setuptools<60 ; python_version=="3.6.*"
+#@ markers: filelock python_version>="3.7"
+#@ add: filelock==3.4.1 ; python_version=="3.6.*"
+#@ markers: platformdirs python_version>="3.7"
+#@ add: platformdirs==2.4.0 ; python_version=="3.6.*"
diff --git a/misc/requirements/requirements-yamllint.txt b/misc/requirements/requirements-yamllint.txt
index cae97eeb6..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
-yamllint==1.26.1
+PyYAML==6.0
+yamllint==1.26.3
diff --git a/misc/userscripts/README.md b/misc/userscripts/README.md
index f5325127b..c67bf8c6f 100644
--- a/misc/userscripts/README.md
+++ b/misc/userscripts/README.md
@@ -83,6 +83,8 @@ The following userscripts can be found on their own repositories.
services (Invidious, Teddit, Nitter, Bibliogram, OpenStreetMap).
- [CIAvash/qutebrowser-userscripts](https://github.com/CIAvash/qutebrowser-userscripts),
various small userscripts written in Raku.
+- [bitwarden-rofi](https://github.com/haztecaso/bitwarden-rofi), rofi wrapper for bitwarden cli
+ interface using gpg instead of keyctl.
[Zotero]: https://www.zotero.org/
[Pocket]: https://getpocket.com/
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/misc/userscripts/qute-keepassxc b/misc/userscripts/qute-keepassxc
index 11d0a3384..f4e971d3c 100755
--- a/misc/userscripts/qute-keepassxc
+++ b/misc/userscripts/qute-keepassxc
@@ -62,7 +62,7 @@ Unlike most browser extensions which only use plain local storage, this one atte
by storing the key in encrypted form using GPG.
Therefore you need to have a public-key-pair readily set up.
-GPG might then ask for your private-key passwort whenever you query the database for login credentials.
+GPG might then ask for your private-key password whenever you query the database for login credentials.
[1]: https://keepassxc.org/
diff --git a/misc/userscripts/qute-pass b/misc/userscripts/qute-pass
index 71646eb69..76a82e9c3 100755
--- a/misc/userscripts/qute-pass
+++ b/misc/userscripts/qute-pass
@@ -23,11 +23,12 @@ demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif.
"""
USAGE = """The domain of the site has to appear as a segment in the pass path,
-for example: "github.com/cryzed" or "websites/github.com". How the username and
-password are determined is freely configurable using the CLI arguments. As an
-example, if you instead store the username as part of the secret (and use a
-site's name as filename), instead of the default configuration, use
-`--username-target secret` and `--username-pattern "username: (.+)"`.
+for example: "github.com/cryzed" or "websites/github.com". Alternatively the
+parameter `--unfiltered` may be used to get a list of all passwords. How the
+username and password are determined is freely configurable using the CLI
+arguments. As an example, if you instead store the username as part of the
+secret (and use a site's name as filename), instead of the default configuration,
+use `--username-target secret` and `--username-pattern "username: (.+)"`.
The login information is inserted by emulating key events using qutebrowser's
fake-key command in this manner: [USERNAME]<Tab>[PASSWORD], which is compatible
@@ -92,6 +93,10 @@ argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
help='Merge pass candidates for fully-qualified and registered domain name')
argument_parser.add_argument('--extra-url-suffixes', '-s', default='',
help='Comma-separated string containing extra suffixes (e.g local)')
+argument_parser.add_argument('--unfiltered', dest='unfiltered', action='store_true',
+ help='Show an unfiltered selection of all passwords in the store')
+argument_parser.add_argument('--always-show-selection', dest='always_show_selection', action='store_true',
+ help='Always show selection, even if there is only a single match')
group = argument_parser.add_mutually_exclusive_group()
group.add_argument('--username-only', '-e', action='store_true', help='Only insert username')
group.add_argument('--password-only', '-w', action='store_true', help='Only insert password')
@@ -123,14 +128,14 @@ def qute_command(command):
fifo.flush()
-def find_pass_candidates(domain):
+def find_pass_candidates(domain, unfiltered=False):
candidates = []
if arguments.mode == "gopass":
all_passwords = subprocess.run(["gopass", "list", "--flat" ], stdout=subprocess.PIPE).stdout.decode("UTF-8").splitlines()
for password in all_passwords:
- if domain in password:
+ if unfiltered or domain in password:
candidates.append(password)
else:
for path, directories, file_names in os.walk(arguments.password_store, followlinks=True):
@@ -143,7 +148,7 @@ def find_pass_candidates(domain):
split_path = pass_path.split(os.path.sep)
for secret in secrets:
secret_base = os.path.splitext(secret)[0]
- if domain not in (split_path + [secret_base]):
+ if not unfiltered and domain not in (split_path + [secret_base]):
continue
candidates.append(os.path.join(pass_path, secret_base))
@@ -223,7 +228,7 @@ def main(arguments):
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.ipv4, private_domain]):
attempted_targets.append(target)
- target_candidates = find_pass_candidates(target)
+ target_candidates = find_pass_candidates(target, unfiltered=arguments.unfiltered)
if not target_candidates:
continue
@@ -235,7 +240,11 @@ def main(arguments):
stderr('No pass candidates for URL {!r} found! (I tried {!r})'.format(arguments.url, attempted_targets))
return ExitCodes.NO_PASS_CANDIDATES
- selection = candidates.pop() if len(candidates) == 1 else dmenu(sorted(candidates), arguments.dmenu_invocation)
+ if len(candidates) == 1 and not arguments.always_show_selection:
+ selection = candidates.pop()
+ else:
+ selection = dmenu(sorted(candidates), arguments.dmenu_invocation)
+
# Nothing was selected, simply return
if not selection:
return ExitCodes.SUCCESS
diff --git a/qutebrowser/__init__.py b/qutebrowser/__init__.py
index d146eb8bf..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.0"
+__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 8cd73ae4f..395d8e8a4 100644
--- a/qutebrowser/browser/commands.py
+++ b/qutebrowser/browser/commands.py
@@ -383,17 +383,18 @@ class CommandDispatcher:
yield parsed
@cmdutils.register(instance='command-dispatcher', scope='window')
- def tab_clone(self, bg=False, window=False):
+ def tab_clone(self, bg=False, window=False, private=False):
"""Duplicate the current tab.
Args:
bg: Open in a background tab.
window: Open in a new window.
+ private: Open in a new private window.
Return:
The new QWebView.
"""
- cmdutils.check_exclusive((bg, window), 'bw')
+ cmdutils.check_exclusive((bg, window, private), 'bwp')
curtab = self._current_widget()
cur_title = self._tabbed_browser.widget.page_title(
self._current_index())
@@ -404,9 +405,9 @@ class CommandDispatcher:
# The new tab could be in a new tabbed_browser (e.g. because of
# tabs.tabs_are_windows being set)
- if window:
+ if window or private:
new_tabbed_browser = self._new_tabbed_browser(
- private=self._tabbed_browser.is_private)
+ private=self._tabbed_browser.is_private or private)
else:
new_tabbed_browser = self._tabbed_browser
newtab = new_tabbed_browser.tabopen(background=bg)
@@ -450,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)
@@ -499,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."""
@@ -1003,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.
@@ -1016,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)
@@ -1514,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 fa4ac9da2..32bfd2693 100644
--- a/qutebrowser/browser/downloads.py
+++ b/qutebrowser/browser/downloads.py
@@ -611,7 +611,11 @@ class AbstractDownloadItem(QObject):
message.error(str(e))
def url(self) -> QUrl:
- """Get the download's origin URL."""
+ """Get the download's URL (i.e. where the file is downloaded from)."""
+ raise NotImplementedError
+
+ def origin(self) -> QUrl:
+ """Get the download's origin URL (i.e. the page starting the download)."""
raise NotImplementedError
def _get_open_filename(self):
@@ -819,6 +823,34 @@ class AbstractDownloadItem(QObject):
self.pdfjs_requested.emit(os.path.basename(filename),
self.url())
+ def cancel_for_origin(self) -> bool:
+ """Cancel the download based on URL/origin.
+
+ For some special cases, we want to cancel downloads immediately, before
+ downloading:
+
+ - file:// downloads from file:// URLs (open the file instead)
+ - http:// downloads from https:// URLs (mixed content)
+ """
+ origin = self.origin()
+ url = self.url()
+ if not origin.isValid():
+ return False
+
+ if url.scheme() == "file" and origin.scheme() == "file":
+ utils.open_file(url.toLocalFile())
+ self.cancel()
+ return True
+
+ if (url.scheme() == "http" and
+ origin.isValid() and origin.scheme() == "https" and
+ config.instance.get("downloads.prevent_mixed_content", url=origin)):
+ self._die("Aborting insecure download from secure page "
+ "(see downloads.prevent_mixed_content).")
+ return True
+
+ return False
+
def set_target(self, target):
"""Set the target for a given download.
@@ -1320,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
@@ -1341,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 e127cd10a..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.")
@@ -684,9 +685,8 @@ class HintManager(QObject):
Args:
rapid: Whether to do rapid hinting. With rapid hinting, the hint
mode isn't left after a hint is followed, so you can easily
- open multiple links. This is only possible with targets
- `tab` (with `tabs.background=true`), `tab-bg`,
- `window`, `run`, `hover`, `userscript` and `spawn`.
+ open multiple links. Note this won't work with targets
+ `tab-fg`, `fill`, `delete` and `right-click`.
add_history: Whether to add the spawned or yanked link to the
browsing history.
first: Click the first hinted element without prompting.
@@ -754,18 +754,16 @@ class HintManager(QObject):
if mode_manager.mode == usertypes.KeyMode.hint:
modeman.leave(self._win_id, usertypes.KeyMode.hint, 're-hinting')
- if rapid:
- if target in [Target.tab_bg, Target.window, Target.run,
- Target.hover, Target.userscript, Target.spawn,
- Target.download, Target.normal, Target.current,
- Target.yank, Target.yank_primary]:
- pass
- elif target == Target.tab and config.val.tabs.background:
- pass
- else:
- name = target.name.replace('_', '-')
- raise cmdutils.CommandError("Rapid hinting makes no sense "
- "with target {}!".format(name))
+ no_rapid_targets = [
+ Target.tab_fg, # opens new tab
+ Target.fill, # exits hint mode
+ Target.right_click, # opens multiple context menus
+ Target.delete, # deleting elements shifts them
+ ]
+ if rapid and target in no_rapid_targets:
+ name = target.name.replace('_', '-')
+ raise cmdutils.CommandError(
+ f"Rapid hinting makes no sense with target {name}!")
self._check_args(target, *args)
@@ -873,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)
@@ -1157,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 e178fb0e5..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)
@@ -202,6 +203,17 @@ class DownloadItem(downloads.AbstractDownloadItem):
# Note: self._reply is deleted when the download finishes
return self._url
+ def origin(self) -> QUrl:
+ if self._reply is None:
+ return QUrl()
+ origin = self._reply.request().originatingObject()
+ try:
+ return origin.url()
+ except AttributeError:
+ # Raised either if origin is None or some object that doesn't
+ # have its own url.
+ return QUrl()
+
def _ensure_can_set_filename(self, filename):
if self.fileobj is not None: # pragma: no cover
raise ValueError("fileobj was already set! filename: {}, "
@@ -491,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:
@@ -535,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.
@@ -549,6 +565,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
download = DownloadItem(reply, manager=self)
self._init_item(download, auto_remove, suggested_filename)
+ if download.cancel_for_origin():
+ return download
+
if target is not None:
download.set_target(target)
return download
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 6b26157e6..e943e44e9 100644
--- a/qutebrowser/browser/webengine/notification.py
+++ b/qutebrowser/browser/webengine/notification.py
@@ -66,6 +66,7 @@ if TYPE_CHECKING:
from qutebrowser.config import config
from qutebrowser.misc import objects
from qutebrowser.utils import qtutils, log, utils, debug, message, version
+from qutebrowser.qt import sip
bridge: Optional['NotificationBridgePresenter'] = None
@@ -477,7 +478,9 @@ class SystrayNotificationAdapter(AbstractNotificationAdapter):
@pyqtSlot(int)
def on_web_closed(self, notification_id: int) -> None:
assert notification_id == self.NOTIFICATION_ID, notification_id
- self._systray.hide()
+ if not sip.isdeleted(self._systray):
+ # This can get called during shutdown
+ self._systray.hide()
class MessagesNotificationAdapter(AbstractNotificationAdapter):
@@ -712,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/webenginedownloads.py b/qutebrowser/browser/webengine/webenginedownloads.py
index fc7ed8ca2..a6a2a1b93 100644
--- a/qutebrowser/browser/webengine/webenginedownloads.py
+++ b/qutebrowser/browser/webengine/webenginedownloads.py
@@ -119,6 +119,10 @@ class DownloadItem(downloads.AbstractDownloadItem):
def url(self) -> QUrl:
return self._qt_item.url()
+ def origin(self) -> QUrl:
+ page = self._qt_item.page()
+ return page.url() if page else QUrl()
+
def _set_fileobj(self, fileobj, *, autoclose=True):
raise downloads.UnsupportedOperationError
@@ -251,7 +255,6 @@ class DownloadManager(downloads.AbstractDownloadManager):
qt_filename = os.path.basename(qt_item.path()) # FIXME use 5.14 API
mime_type = qt_item.mimeType()
url = qt_item.url()
- origin = qt_item.page().url() if qt_item.page() else QUrl()
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-90355
if version.qtwebengine_versions().webengine >= utils.VersionNumber(5, 15, 3):
@@ -293,9 +296,7 @@ class DownloadManager(downloads.AbstractDownloadManager):
download.set_target(target)
return
- if url.scheme() == "file" and origin.isValid() and origin.scheme() == "file":
- utils.open_file(url.toLocalFile())
- qt_item.cancel()
+ if download.cancel_for_origin():
return
# Ask the user for a filename - needs to be blocking!
diff --git a/qutebrowser/browser/webengine/webenginesettings.py b/qutebrowser/browser/webengine/webenginesettings.py
index d20a92014..5430cec77 100644
--- a/qutebrowser/browser/webengine/webenginesettings.py
+++ b/qutebrowser/browser/webengine/webenginesettings.py
@@ -431,11 +431,7 @@ def _init_site_specific_quirks():
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/99 "
"Safari/537.36")
- edge_ua = ("Mozilla/5.0 ({os_info}) "
- "AppleWebKit/{webkit_version} (KHTML, like Gecko) "
- "{upstream_browser_key}/{upstream_browser_version} "
- "Safari/{webkit_version} "
- "Edg/{upstream_browser_version}")
+ firefox_ua = "Mozilla/5.0 ({os_info}; rv:90.0) Gecko/20100101 Firefox/90.0"
user_agents = [
# Needed to avoid a ""WhatsApp works with Google Chrome 36+" error
@@ -447,7 +443,7 @@ def _init_site_specific_quirks():
# Needed to avoid a "you're using a browser [...] that doesn't allow us
# to keep your account secure" error.
# https://github.com/qutebrowser/qutebrowser/issues/5182
- ("ua-google", 'https://accounts.google.com/*', edge_ua),
+ ("ua-google", 'https://accounts.google.com/*', firefox_ua),
# Needed because Slack adds an error which prevents using it relatively
# aggressively, despite things actually working fine.
diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py
index ace23d14a..fa877f560 100644
--- a/qutebrowser/browser/webengine/webenginetab.py
+++ b/qutebrowser/browser/webengine/webenginetab.py
@@ -26,7 +26,8 @@ import re
import html as html_utils
from typing import cast, Union, Optional
-from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl, QObject
+from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QTimer, QUrl,
+ QObject)
from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript, QWebEngineHistory
@@ -200,6 +201,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 +255,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')
@@ -797,13 +803,38 @@ class WebEngineAudio(browsertab.AbstractAudio):
super().__init__(tab, parent)
self._overridden = False
+ # Implements the intended two-second delay specified at
+ # https://doc.qt.io/qt-5/qwebenginepage.html#recentlyAudibleChanged
+ delay_ms = 2000
+ self._silence_timer = QTimer(self)
+ self._silence_timer.setSingleShot(True)
+ self._silence_timer.setInterval(delay_ms)
+
def _connect_signals(self):
page = self._widget.page()
page.audioMutedChanged.connect(self.muted_changed)
- page.recentlyAudibleChanged.connect(self.recently_audible_changed)
+ page.recentlyAudibleChanged.connect(self._delayed_recently_audible_changed)
self._tab.url_changed.connect(self._on_url_changed)
config.instance.changed.connect(self._on_config_changed)
+ # WORKAROUND for recentlyAudibleChanged being emitted without delay from the moment
+ # that audio is dropped.
+ def _delayed_recently_audible_changed(self, recently_audible):
+ timer = self._silence_timer
+ # Stop any active timer and immediately display [A] if tab is audible,
+ # otherwise start a timer to update audio field
+ if recently_audible:
+ if timer.isActive():
+ timer.stop()
+ self.recently_audible_changed.emit(recently_audible)
+ else:
+ # Ignore all subsequent calls while the tab is muted with an active timer
+ if timer.isActive():
+ return
+ timer.timeout.connect(
+ functools.partial(self.recently_audible_changed.emit, recently_audible))
+ timer.start()
+
def set_muted(self, muted: bool, override: bool = False) -> None:
was_muted = self.is_muted()
self._overridden = override
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..834709ae6 100644
--- a/qutebrowser/config/config.py
+++ b/qutebrowser/config/config.py
@@ -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 143b02fca..407c74214 100644
--- a/qutebrowser/config/configcommands.py
+++ b/qutebrowser/config/configcommands.py
@@ -101,7 +101,7 @@ class ConfigCommands:
Args:
option: The name of the option.
value: The value to set.
- pattern: The URL pattern to use.
+ pattern: The link:configuring{outfilesuffix}#patterns[URL pattern] to use.
temp: Set value temporarily until qutebrowser is closed.
print_: Print the value after setting.
"""
@@ -151,9 +151,12 @@ class ConfigCommands:
default: If given, restore a default binding.
"""
if key is None:
+ url = QUrl('qute://bindings')
+ if mode != "normal":
+ url.setFragment(mode)
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
- tabbed_browser.load_url(QUrl('qute://bindings'), newtab=True)
+ tabbed_browser.load_url(url, newtab=True)
return
seq = self._parse_key(key)
@@ -204,8 +207,8 @@ class ConfigCommands:
Args:
option: The name of the option.
- values: The values to cycle through.
- pattern: The URL pattern to use.
+ *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.
"""
@@ -259,7 +262,7 @@ class ConfigCommands:
Args:
option: The name of the option.
- pattern: The URL pattern to use.
+ pattern: The link:configuring{outfilesuffix}#patterns[URL pattern] to use.
temp: Set value temporarily until qutebrowser is closed.
"""
parsed_pattern = self._parse_pattern(pattern)
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 147ec0b52..b0d9c6364 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -649,7 +649,9 @@ content.headers.referer:
When to send the Referer header.
The Referer header tells websites from which website you were coming from
- when visiting them.
+ when visiting them. Note that with QtWebEngine, websites can override this
+ preference by setting the `Referrer-Policy:` header, so that any websites
+ visited from them get the full referer.
No restart is needed with QtWebKit.
@@ -677,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.
@@ -1058,7 +1060,8 @@ content.proxy:
`http://...` URL.
Note that with QtWebEngine, it will take a couple of seconds until the
- change is applied, if this value is changed at runtime.
+ change is applied, if this value is changed at runtime. Authentication for
+ SOCKS proxies isn't supported due to Chromium limitations.
content.proxy_dns_requests:
default: true
@@ -1353,6 +1356,18 @@ downloads.position:
default: top
desc: Where to show the downloaded files.
+downloads.prevent_mixed_content:
+ type: Bool
+ default: true
+ supports_pattern: true
+ desc:
+ Automatically abort insecure (HTTP) downloads originating from secure
+ (HTTPS) pages.
+
+ For per-domain settings, the relevant URL is the URL initiating the
+ download, not the URL the download itself is coming from. It's not
+ recommended to set this setting to false globally.
+
downloads.remove_finished:
default: -1
type:
@@ -1614,11 +1629,16 @@ hints.selectors:
- '[role="link"]'
- '[role="option"]'
- '[role="button"]'
+ - '[role="tab"]'
+ - '[role="checkbox"]'
+ - '[role="menuitem"]'
+ - '[role="menuitemcheckbox"]'
+ - '[role="menuitemradio"]'
- '[ng-click]'
- '[ngClick]'
- '[data-ng-click]'
- '[x-ng-click]'
- - '[tabindex]'
+ - '[tabindex]:not([tabindex="-1"])'
links:
- 'a[href]'
- 'area[href]'
@@ -1779,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:
@@ -3642,6 +3673,7 @@ bindings.default:
<Ctrl-E>: edit-text
<Shift-Ins>: insert-text -- {primary}
<Escape>: mode-leave
+ <Shift-Escape>: fake-key <Escape>
hint:
<Return>: hint-follow
<Ctrl-R>: hint --rapid links tab-bg
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..793f131c8 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
@@ -84,7 +83,7 @@ def add_module_info(module: types.ModuleType) -> ModuleInfo:
# pylint: disable=protected-access
if not hasattr(module, '__qute_module_info'):
module.__qute_module_info = ModuleInfo() # type: ignore[attr-defined]
- return module.__qute_module_info # type: ignore[attr-defined]
+ return module.__qute_module_info
def load_components(*, skip_hooks: bool = False) -> None:
@@ -95,22 +94,8 @@ 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.
- path=components.__path__, # type: ignore[attr-defined]
+ path=components.__path__,
prefix=components.__name__ + '.',
onerror=_on_walk_error):
if ispkg:
@@ -123,23 +108,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 +158,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/bindings.html b/qutebrowser/html/bindings.html
index fe6913402..7bc441cbd 100644
--- a/qutebrowser/html/bindings.html
+++ b/qutebrowser/html/bindings.html
@@ -10,7 +10,7 @@ th { text-align:left; }
{% block content %}
<header><h1>{{ title }}</h1></header>
{% for mode, binding in bindings.items() %}
-<h2>{{ mode | capitalize }} mode</h2>
+<h2 id="{{ mode }}">{{ mode | capitalize }} mode</h2>
<table>
<tr>
<th>Key</th>
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/javascript/greasemonkey_wrapper.js b/qutebrowser/javascript/greasemonkey_wrapper.js
index 7bfabc635..ced4e1063 100644
--- a/qutebrowser/javascript/greasemonkey_wrapper.js
+++ b/qutebrowser/javascript/greasemonkey_wrapper.js
@@ -77,6 +77,9 @@
if ("onerror" in details) {
oXhr.onerror = function () { details.onerror(oXhr); };
}
+ if ("overrideMimeType" in details) {
+ oXhr.overrideMimeType(details.overrideMimeType);
+ }
oXhr.open(details.method, details.url, true);
@@ -107,6 +110,22 @@
}
}
+ // Based on GreaseMonkey:
+ // https://github.com/greasemonkey/greasemonkey/blob/4.11/src/bg/api-provider-source.js#L232-L249
+ function GM_setClipboard(text) {
+ function onCopy(event) {
+ document.removeEventListener('copy', onCopy, true);
+
+ event.stopImmediatePropagation();
+ event.preventDefault();
+
+ event.clipboardData.setData('text/plain', text);
+ }
+
+ document.addEventListener('copy', onCopy, true);
+ document.execCommand('copy');
+ }
+
// Stub these two so that the gm4 polyfill script doesn't try to
// create broken versions as attributes of window.
function GM_getResourceText(caption, commandFunc, accessKey) {
@@ -123,6 +142,7 @@
const entries = {
'log': GM_log,
'addStyle': GM_addStyle,
+ 'setClipboard': GM_setClipboard,
'deleteValue': GM_deleteValue,
'getValue': GM_getValue,
'listValues': GM_listValues,
diff --git a/qutebrowser/javascript/quirks/globalthis.user.js b/qutebrowser/javascript/quirks/globalthis.user.js
index 103681fda..b87a956e5 100644
--- a/qutebrowser/javascript/quirks/globalthis.user.js
+++ b/qutebrowser/javascript/quirks/globalthis.user.js
@@ -1,6 +1,8 @@
// ==UserScript==
// @include https://www.reddit.com/*
// @include https://open.spotify.com/*
+// @include https://*.stackexchange.com/*
+// @include https://stackoverflow.com/*
// @include https://test.qutebrowser.org/*
// ==/UserScript==
diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py
index 7e688dab1..4db1d5d76 100644
--- a/qutebrowser/keyinput/basekeyparser.py
+++ b/qutebrowser/keyinput/basekeyparser.py
@@ -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/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py
index d0723742a..7f62c2dc4 100644
--- a/qutebrowser/mainwindow/mainwindow.py
+++ b/qutebrowser/mainwindow/mainwindow.py
@@ -620,13 +620,15 @@ class MainWindow(QWidget):
True if closing is okay, False if a closeEvent should be ignored.
"""
tab_count = self.tabbed_browser.widget.count()
+ window_count = len(objreg.window_registry)
download_count = self._download_model.running_downloads()
quit_texts = []
# Ask if multiple-tabs are open
if 'multiple-tabs' in config.val.confirm_quit and tab_count > 1:
quit_texts.append("{} tabs are open.".format(tab_count))
- # Ask if multiple downloads running
- if 'downloads' in config.val.confirm_quit and download_count > 0:
+ # Ask if downloads running
+ if ('downloads' in config.val.confirm_quit and download_count > 0 and
+ window_count <= 1):
quit_texts.append("{} {} running.".format(
download_count,
"download is" if download_count == 1 else "downloads are"))
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 7b22b5c0a..4ac5e0379 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -500,7 +500,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/debug.py b/qutebrowser/utils/debug.py
index 7d069909a..0fa74f4e7 100644
--- a/qutebrowser/utils/debug.py
+++ b/qutebrowser/utils/debug.py
@@ -81,7 +81,7 @@ def log_signals(obj: QObject) -> QObject:
pass
if inspect.isclass(obj):
- old_init = obj.__init__ # type: ignore[misc]
+ old_init = obj.__init__
@functools.wraps(old_init)
def new_init(self: Any, *args: Any, **kwargs: Any) -> None:
@@ -89,7 +89,7 @@ def log_signals(obj: QObject) -> QObject:
old_init(self, *args, **kwargs)
connect_log_slot(self)
- obj.__init__ = new_init # type: ignore[misc]
+ obj.__init__ = new_init
else:
connect_log_slot(obj)
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 9cd07e2e3..4853d62ae 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -43,7 +43,9 @@ except ImportError:
colorama = None
if TYPE_CHECKING:
+ # pylint: disable=unused-import
from qutebrowser.config import config as configmodule
+ from typing import TextIO
_log_inited = False
_args = None
@@ -245,7 +247,9 @@ def disable_qt_msghandler() -> Iterator[None]:
@contextlib.contextmanager
def py_warning_filter(action: str = 'ignore', **kwargs: Any) -> Iterator[None]:
"""Contextmanager to temporarily disable certain Python warnings."""
- warnings.filterwarnings(action, **kwargs)
+ # FIXME Use Literal['default', 'error', 'ignore', 'always', 'module', 'once']
+ # once we use Python 3.8 or typing_extensions
+ warnings.filterwarnings(action, **kwargs) # type: ignore[arg-type]
yield
if _log_inited:
_init_py_warnings()
@@ -257,7 +261,7 @@ def _init_handlers(
force_color: bool,
json_logging: bool,
ram_capacity: int
-) -> Tuple[logging.StreamHandler, Optional['RAMHandler']]:
+) -> Tuple["logging.StreamHandler[TextIO]", Optional['RAMHandler']]:
"""Init log handlers.
Args:
@@ -381,8 +385,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.
@@ -727,7 +731,8 @@ class ColoredFormatter(logging.Formatter):
datefmt: str,
style: str, *,
use_colors: bool) -> None:
- super().__init__(fmt, datefmt, style)
+ # FIXME Use Literal["%", "{", "$"] once we use Python 3.8 or typing_extensions
+ super().__init__(fmt, datefmt, style) # type: ignore[arg-type]
self.use_colors = use_colors
def format(self, record: logging.LogRecord) -> str:
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..a899fa599 100644
--- a/qutebrowser/utils/utils.py
+++ b/qutebrowser/utils/utils.py
@@ -300,7 +300,8 @@ def fake_io(write_func: Callable[[str], int]) -> Iterator[None]:
def disabled_excepthook() -> Iterator[None]:
"""Run code with the exception hook temporarily disabled."""
old_excepthook = sys.excepthook
- sys.excepthook = sys.__excepthook__
+ # https://github.com/python/typeshed/pull/6678
+ sys.excepthook = sys.__excepthook__ # type: ignore[assignment]
try:
yield
finally:
@@ -382,7 +383,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 +392,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 +669,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..8da86dd00 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -514,7 +514,8 @@ def _get_pyqt_webengine_qt_version() -> Optional[str]:
import importlib_metadata
except ImportError:
try:
- import importlib.metadata as importlib_metadata # type: ignore[no-redef]
+ # pylint: disable=line-too-long
+ import importlib.metadata as importlib_metadata # type: ignore[import, no-redef]
except ImportError:
log.misc.debug("Neither importlib.metadata nor backport available")
return None
@@ -773,8 +774,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 e6b75b5ef..8d1151dd7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,13 +1,15 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-adblock==0.5.0
+adblock==0.5.1
colorama==0.4.4
dataclasses==0.6 ; python_version<"3.7"
-importlib-metadata==4.6.1 ; python_version<"3.8"
-importlib-resources==5.2.0 ; python_version<"3.9"
-Jinja2==3.0.1
+importlib-metadata==4.10.0 ; python_version=="3.7.*"
+importlib-resources==5.4.0 ; python_version<"3.9"
+Jinja2==3.0.3
MarkupSafe==2.0.1
-Pygments==2.9.0
-PyYAML==5.4.1
-typing-extensions==3.10.0.0
-zipp==3.5.0
+Pygments==2.11.1
+PyYAML==6.0
+typing_extensions==4.0.1 ; python_version<"3.8"
+zipp==3.7.0 ; python_version>="3.7"
+importlib-metadata<4.9 ; python_version=="3.6.*"
+zipp<3.7 ; python_version=="3.6.*"
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/gen_resources.py b/scripts/dev/gen_resources.py
index dd76c123e..efa6c6a3d 100644
--- a/scripts/dev/gen_resources.py
+++ b/scripts/dev/gen_resources.py
@@ -21,6 +21,10 @@
"""Generate Qt resources based on source files."""
import subprocess
+import pathlib
-with open('qutebrowser/resources.py', 'w', encoding='utf-8') as f:
- subprocess.run(['pyrcc5', 'qutebrowser.rcc'], stdout=f, check=True)
+ROOT = pathlib.Path(__file__).parents[2]
+OUTPUT = ROOT / 'qutebrowser' / 'resources.py'
+INPUT = ROOT / 'misc' / 'qutebrowser.rcc'
+
+subprocess.run(['pyrcc5', '-o', str(OUTPUT), str(INPUT)], check=True)
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 d8ed9974b..dc56380bf 100644
--- a/scripts/dev/recompile_requirements.py
+++ b/scripts/dev/recompile_requirements.py
@@ -27,6 +27,8 @@ import glob
import subprocess
import tempfile
import argparse
+import shutil
+import pathlib
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
os.pardir))
@@ -45,7 +47,7 @@ CHANGELOG_URLS = {
'mccabe': 'https://github.com/PyCQA/mccabe#changes',
'pytest-cov': 'https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst',
'pytest-xdist': 'https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst',
- 'pytest-forked': 'https://github.com/pytest-dev/pytest-forked/blob/master/CHANGELOG',
+ 'pytest-forked': 'https://github.com/pytest-dev/pytest-forked/blob/master/CHANGELOG.rst',
'pytest-xvfb': 'https://github.com/The-Compiler/pytest-xvfb/blob/master/CHANGELOG.rst',
'EasyProcess': 'https://github.com/ponty/EasyProcess/commits/master',
'PyVirtualDisplay': 'https://github.com/ponty/PyVirtualDisplay/commits/master',
@@ -79,7 +81,7 @@ CHANGELOG_URLS = {
'attrs': 'https://www.attrs.org/en/stable/changelog.html',
'Jinja2': 'https://jinja.palletsprojects.com/en/latest/changes/',
'MarkupSafe': 'https://markupsafe.palletsprojects.com/en/latest/changes/',
- 'flake8': 'https://gitlab.com/pycqa/flake8/tree/master/docs/source/release-notes',
+ 'flake8': 'https://github.com/PyCQA/flake8/tree/main/docs/source/release-notes',
'flake8-docstrings': 'https://pypi.org/project/flake8-docstrings/',
'flake8-debugger': 'https://github.com/JBKahn/flake8-debugger/',
'flake8-builtins': 'https://github.com/gforcada/flake8-builtins/blob/master/CHANGES.rst',
@@ -93,11 +95,13 @@ CHANGELOG_URLS = {
'flake8-mock': 'https://github.com/aleGpereira/flake8-mock#changes',
'flake8-polyfill': 'https://gitlab.com/pycqa/flake8-polyfill/-/blob/master/CHANGELOG.rst',
'flake8-string-format': 'https://github.com/xZise/flake8-string-format#changes',
+ 'flake8-plugin-utils': 'https://github.com/afonasev/flake8-plugin-utils#change-log',
+ 'flake8-pytest-style': 'https://github.com/m-burst/flake8-pytest-style#change-log',
'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',
'cffi': 'https://github.com/python-cffi/release-doc/blob/master/doc/source/whatsnew.rst',
- 'astroid': 'https://github.com/PyCQA/astroid/blob/2.4/ChangeLog',
+ 'astroid': 'https://github.com/PyCQA/astroid/blob/main/ChangeLog',
'pytest-instafail': 'https://github.com/pytest-dev/pytest-instafail/blob/master/CHANGES.rst',
'coverage': 'https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst',
'colorama': 'https://github.com/tartley/colorama/blob/master/CHANGELOG.rst',
@@ -131,7 +135,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',
@@ -139,10 +143,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',
@@ -150,16 +152,15 @@ CHANGELOG_URLS = {
'sip': 'https://www.riverbankcomputing.com/news',
'Pygments': 'https://pygments.org/docs/changelog/',
'vulture': 'https://github.com/jendrikseipp/vulture/blob/master/CHANGELOG.md',
- 'distlib': 'https://bitbucket.org/pypa/distlib/src/master/CHANGES.rst',
+ 'distlib': 'https://github.com/pypa/distlib/blob/master/CHANGES.rst',
'py-cpuinfo': 'https://github.com/workhorsy/py-cpuinfo/blob/master/ChangeLog',
'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',
@@ -168,15 +169,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/benediktschmitt/py-filelock/commits/master',
+ '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',
@@ -190,6 +189,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',
}
@@ -238,6 +248,7 @@ def read_comments(fobj):
'add': [],
'replace': {},
'pre': False,
+ 'pip_args': [],
}
for line in fobj:
if line.startswith('#@'):
@@ -267,6 +278,8 @@ def read_comments(fobj):
comments['add'].append(args)
elif command == 'pre':
comments['pre'] = True
+ elif command == 'pip_args':
+ comments['pip_args'] += args.split()
return comments
@@ -290,7 +303,7 @@ def run_pip(venv_dir, *args, quiet=False, **kwargs):
return subprocess.run([venv_python, '-m', 'pip'] + args, check=True, **kwargs)
-def init_venv(host_python, venv_dir, requirements, pre=False):
+def init_venv(host_python, venv_dir, requirements, pre=False, pip_args=None):
"""Initialize a new virtualenv and install the given packages."""
with utils.gha_group('Creating virtualenv'):
utils.print_col('$ python3 -m venv {}'.format(venv_dir), 'blue')
@@ -302,6 +315,8 @@ def init_venv(host_python, venv_dir, requirements, pre=False):
install_command = ['install', '-r', requirements]
if pre:
install_command.append('--pre')
+ if pip_args:
+ install_command += pip_args
with utils.gha_group('Installing requirements'):
run_pip(venv_dir, *install_command)
@@ -378,15 +393,21 @@ def _get_changed_files():
def parse_versioned_line(line):
"""Parse a requirements.txt line into name/version."""
- if '==' in line:
- if line[0] == '#': # ignored dependency
- line = line[1:].strip()
+ if line[0] == '#': # ignored dependency
+ line = line[1:].strip()
- # Strip comments and pip environment markers
- line = line.rsplit('#', maxsplit=1)[0]
- line = line.split(';')[0].strip()
+ # Strip comments and pip environment markers
+ line = line.rsplit('#', maxsplit=1)[0]
+ line = line.split(';')[0].strip()
- name, version = line.split('==')
+ ops = ["==", "~=", "!=", ">", "<", ">=", "<="]
+
+ if any(op in line for op in ops):
+ # strictly speaking, this version isn't necessarily correct, but it's
+ # enough for the table.
+ for op in ops:
+ if op in line:
+ name, version = line.split(op)
elif line.startswith('-e'):
rest, name = line.split('#egg=')
version = rest.split('@')[1][:7]
@@ -497,7 +518,8 @@ def build_requirements(name):
init_venv(host_python=host_python,
venv_dir=tmpdir,
requirements=filename,
- pre=comments['pre'])
+ pre=comments['pre'],
+ pip_args=comments['pip_args'])
with utils.gha_group('Freezing requirements'):
args = ['--all'] if name == 'tox' else []
proc = run_pip(tmpdir, 'freeze', *args, stdout=subprocess.PIPE)
@@ -556,9 +578,20 @@ def test_requirements(name, outfile, *, force=False):
print(f"Skipping test as there were no changes for {name}.")
return
+ in_file = os.path.join(REQ_DIR, 'requirements-{}.txt-raw'.format(name))
+ with open(in_file, 'r', encoding='utf-8') as f:
+ comments = read_comments(f)
+
host_python = get_host_python(name)
with tempfile.TemporaryDirectory() as tmpdir:
- init_venv(host_python, tmpdir, outfile)
+ init_venv(host_python, tmpdir, outfile, pip_args=comments['pip_args'])
+
+
+def cleanup_pylint_build():
+ """Clean up pylint_checkers build files."""
+ path = pathlib.Path(__file__).parent / 'pylint_checkers' / 'build'
+ utils.print_col(f'$ rm -r {path}', 'blue')
+ shutil.rmtree(path)
def main():
@@ -574,6 +607,8 @@ def main():
utils.print_title(name)
outfile = build_requirements(name)
test_requirements(name, outfile, force=args.force_test)
+ if name == 'pylint':
+ cleanup_pylint_build()
utils.print_title('Testing via tox')
if args.names and not args.force_test:
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/src2asciidoc.py b/scripts/dev/src2asciidoc.py
index 82bbdb167..375bb1eb7 100755
--- a/scripts/dev/src2asciidoc.py
+++ b/scripts/dev/src2asciidoc.py
@@ -444,7 +444,7 @@ def _generate_setting_option(f, opt):
if opt.restart:
f.write("\nThis setting requires a restart.\n")
if opt.supports_pattern:
- f.write("\nThis setting supports URL patterns.\n")
+ f.write("\nThis setting supports link:configuring{outfilesuffix}#patterns[URL patterns].\n")
if opt.no_autoconfig:
f.write("\nThis setting can only be set in config.py.\n")
_generate_setting_backend_info(f, opt)
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/scripts/opengl_info.py b/scripts/opengl_info.py
new file mode 100644
index 000000000..1bfdf22aa
--- /dev/null
+++ b/scripts/opengl_info.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2020-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/>.
+
+"""Show information about the OpenGL setup."""
+
+from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile,
+ QOffscreenSurface, QGuiApplication)
+
+app = QGuiApplication([])
+
+surface = QOffscreenSurface()
+surface.create()
+
+ctx = QOpenGLContext()
+ok = ctx.create()
+assert ok
+
+ok = ctx.makeCurrent(surface)
+assert ok
+
+print(f"GLES: {ctx.isOpenGLES()}")
+
+vp = QOpenGLVersionProfile()
+vp.setVersion(2, 0)
+
+vf = ctx.versionFunctions(vp)
+print(f"Vendor: {vf.glGetString(vf.GL_VENDOR)}")
+print(f"Renderer: {vf.glGetString(vf.GL_RENDERER)}")
+print(f"Version: {vf.glGetString(vf.GL_VERSION)}")
+print(f"Shading language version: {vf.glGetString(vf.GL_SHADING_LANGUAGE_VERSION)}")
+
+ctx.doneCurrent()
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 e8fc9e7bc..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
@@ -653,7 +674,7 @@ Feature: Tab management
Scenario: :tab-clone with -b and -w
When I run :tab-clone -b -w
- Then the error "Only one of -b/-w can be given!" should be shown.
+ Then the error "Only one of -b/-w/-p can be given!" should be shown.
Scenario: Cloning a tab with history and title
When I open data/title.html
@@ -737,6 +758,26 @@ Feature: Tab management
- url: http://localhost:*/data/title.html
title: Test title
+ Scenario: Cloning to private window
+ When I open data/title.html
+ And I run :tab-clone -p
+ And I wait until data/title.html is loaded
+ Then the session should look like:
+ windows:
+ - tabs:
+ - active: true
+ history:
+ - url: about:blank
+ - url: http://localhost:*/data/title.html
+ title: Test title
+ - private: true
+ tabs:
+ - active: true
+ history:
+ - url: about:blank
+ - url: http://localhost:*/data/title.html
+ title: Test title
+
# https://github.com/qutebrowser/qutebrowser/issues/2289
@qtwebkit_skip
@@ -1329,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
@@ -1386,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/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py
index e829f25a6..47e481b3d 100644
--- a/tests/end2end/test_insert_mode.py
+++ b/tests/end2end/test_insert_mode.py
@@ -22,7 +22,7 @@
import pytest
-@pytest.mark.parametrize(['file_name', 'elem_id', 'source', 'input_text'], [
+@pytest.mark.parametrize('file_name, elem_id, source, input_text', [
('textarea.html', 'qute-textarea', 'clipboard', 'qutebrowser'),
('textarea.html', 'qute-textarea', 'keypress', 'superqutebrowser'),
('input.html', 'qute-input', 'clipboard', 'amazingqutebrowser'),
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index cd3778b8a..cc362290f 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('/'))
@@ -632,7 +632,7 @@ def redirect_webengine_data(data_tmpdir, monkeypatch):
monkeypatch.setenv('HOME', str(data_tmpdir))
-@pytest.fixture()
+@pytest.fixture
def short_tmpdir():
"""A short temporary directory for a XDG_RUNTIME_DIR."""
with tempfile.TemporaryDirectory() as tdir:
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/browser/test_caret.py b/tests/unit/browser/test_caret.py
index 288471ea0..86014040d 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -70,7 +70,7 @@ class Selection:
self._qtbot.wait(50)
- assert False, 'Failed to get selection!'
+ pytest.fail('Failed to get selection!')
def check_multiline(self, expected, *, strip=False):
self.check(textwrap.dedent(expected).strip(), strip=strip)
diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py
index 7906d385c..51a9effb8 100644
--- a/tests/unit/browser/test_history.py
+++ b/tests/unit/browser/test_history.py
@@ -141,7 +141,7 @@ class TestDelete:
class TestAdd:
- @pytest.fixture()
+ @pytest.fixture
def mock_time(self, mocker):
m = mocker.patch('qutebrowser.browser.history.time')
m.time.return_value = 12345
diff --git a/tests/unit/browser/test_inspector.py b/tests/unit/browser/test_inspector.py
index f7f532050..61ac9510d 100644
--- a/tests/unit/browser/test_inspector.py
+++ b/tests/unit/browser/test_inspector.py
@@ -151,4 +151,5 @@ def test_detach_after_toggling(hidden_again, needs_recreate,
else:
with qtbot.assert_not_emitted(fake_inspector.recreate):
fake_inspector.set_position(inspector.Position.window)
- assert fake_inspector.isVisible() and fake_inspector.isWindow()
+ assert fake_inspector.isVisible()
+ assert fake_inspector.isWindow()
diff --git a/tests/unit/browser/webkit/test_webkitelem.py b/tests/unit/browser/webkit/test_webkitelem.py
index 593896e96..f7cc3e8c2 100644
--- a/tests/unit/browser/webkit/test_webkitelem.py
+++ b/tests/unit/browser/webkit/test_webkitelem.py
@@ -181,13 +181,30 @@ class SelectionAndFilterTests:
# We can't easily test <frame>/<iframe> as they vanish when setting
# them via QWebFrame::setHtml...
+ ('<img src="bar">', ['all', 'images', 'url']),
+ ('<summary>bar</summary>', ['all']),
+ ('<link />', ['all']),
+
+ ('<p contenteditable />', ['all', 'inputs']),
+ ('<p contenteditable="false" />', []),
('<p onclick="foo" foo="bar"/>', ['all']),
('<p onmousedown="foo" foo="bar"/>', ['all']),
('<p role="option" foo="bar"/>', ['all']),
+ ('<p role="tab" foo="bar"/>', ['all']),
+ ('<p role="checkbox" foo="bar"/>', ['all']),
+ ('<p role="menuitem" foo="bar"/>', ['all']),
+ ('<p role="menuitemcheckbox" foo="bar"/>', ['all']),
+ ('<p role="menuitemradio" foo="bar"/>', ['all']),
('<p role="button" foo="bar"/>', ['all']),
('<p role="button" href="bar"/>', ['all', 'url']),
- ('<span tabindex=0 />', ['all']),
+ ('<span tabindex="0" />', ['all']),
+ ('<span tabindex="-1" />', []),
+
+ ('<span ng-click=""></span>', ['all']),
+ ('<span ngClick=""></span>', ['all']),
+ ('<span data-ng-click=""></span>', ['all']),
+ ('<span x-ng-click=""></span>', ['all']),
]
GROUPS = ['all', 'links', 'images', 'url', 'inputs']
@@ -849,8 +866,6 @@ class TestIsEditable:
@pytest.mark.parametrize('setting, tagname, attributes, editable', [
(True, 'embed', {}, True),
- (True, 'embed', {}, True),
- (False, 'applet', {}, False),
(False, 'applet', {}, False),
(True, 'object', {'type': 'application/foo'}, True),
(False, 'object', {'type': 'application/foo'}, False),
diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py
index 89390cbf1..074228332 100644
--- a/tests/unit/completion/test_completionwidget.py
+++ b/tests/unit/completion/test_completionwidget.py
@@ -37,13 +37,13 @@ def completionview(qtbot, status_command_stub, config_stub, win_registry,
mocker.patch('qutebrowser.completion.completer.Completer', autospec=True)
mocker.patch(
'qutebrowser.completion.completiondelegate.CompletionItemDelegate',
- new=lambda *_: None)
+ return_value=None)
view = completionwidget.CompletionView(cmd=status_command_stub, win_id=0)
qtbot.add_widget(view)
return view
-@pytest.fixture()
+@pytest.fixture
def model():
return completionmodel.CompletionModel()
@@ -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/completion/test_models.py b/tests/unit/completion/test_models.py
index c20fe293c..9e6743083 100644
--- a/tests/unit/completion/test_models.py
+++ b/tests/unit/completion/test_models.py
@@ -76,7 +76,7 @@ def _check_completions(model, expected):
assert sum(model.column_widths) == 100
-@pytest.fixture()
+@pytest.fixture
def cmdutils_stub(monkeypatch, stubs):
"""Patch the cmdutils module to provide fake commands."""
return monkeypatch.setattr(objects, 'commands', {
@@ -93,7 +93,7 @@ def cmdutils_stub(monkeypatch, stubs):
})
-@pytest.fixture()
+@pytest.fixture
def configdata_stub(config_stub, monkeypatch, configdata_init):
"""Patch the configdata module to provide fake data."""
monkeypatch.setattr(configdata, 'DATA', collections.OrderedDict([
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/config/test_configcommands.py b/tests/unit/config/test_configcommands.py
index 0375503e8..6dd09cee3 100644
--- a/tests/unit/config/test_configcommands.py
+++ b/tests/unit/config/test_configcommands.py
@@ -27,7 +27,7 @@ from PyQt5.QtCore import QUrl
from qutebrowser.config import configcommands
from qutebrowser.api import cmdutils
-from qutebrowser.utils import usertypes, urlmatch
+from qutebrowser.utils import usertypes, urlmatch, utils
from qutebrowser.keyinput import keyutils
from qutebrowser.misc import objects
@@ -529,7 +529,7 @@ class TestSource:
pyfile = config_tmpdir / 'sourced.py'
arg = 'sourced.py'
else:
- assert False, location
+ raise utils.Unreachable(location)
pyfile.write_text('\n'.join(['config.load_autoconfig(False)',
'c.content.javascript.enabled = False']),
@@ -725,15 +725,19 @@ class TestBind:
"""Get a dict with no bindings."""
return {'normal': {}}
+ @pytest.mark.parametrize("mode, url", [
+ ("normal", QUrl("qute://bindings")),
+ ("passthrough", QUrl("qute://bindings#passthrough")),
+ ])
def test_bind_no_args(self, commands, config_stub, no_bindings,
- tabbed_browser_stubs):
+ tabbed_browser_stubs, mode, url):
"""Run ':bind'.
Should open qute://bindings."""
config_stub.val.bindings.default = no_bindings
config_stub.val.bindings.commands = no_bindings
- commands.bind(win_id=0)
- assert tabbed_browser_stubs[0].loaded_url == QUrl('qute://bindings')
+ commands.bind(win_id=0, mode=mode)
+ assert tabbed_browser_stubs[0].loaded_url == url
@pytest.mark.parametrize('command', ['nop', 'nope'])
def test_bind(self, commands, config_stub, no_bindings, key_config_stub,
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index 65952ddb4..760992e15 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -873,7 +873,7 @@ class TestConfigPyModules:
confpy.read()
expected = {'normal': {',a': 'message-info foo'}}
assert config.instance.get_obj('bindings.commands') == expected
- assert "qbmodule" not in sys.modules.keys()
+ assert "qbmodule" not in sys.modules
assert tmp_path not in sys.path
def test_restore_sys_on_err(self, confpy, qbmodulepy, tmp_path):
@@ -884,7 +884,7 @@ class TestConfigPyModules:
assert error.text == "Unhandled exception"
assert isinstance(error.exception, ZeroDivisionError)
- assert "qbmodule" not in sys.modules.keys()
+ assert "qbmodule" not in sys.modules
assert tmp_path not in sys.path
def test_fail_on_nonexistent_module(self, confpy, qbmodulepy, tmp_path):
@@ -1481,7 +1481,6 @@ class TestConfigPyWriter:
def init_patch(qapp, fake_save_manager, config_tmpdir, data_tmpdir,
config_stub, monkeypatch):
monkeypatch.setattr(configfiles, 'state', None)
- yield
def test_init(init_patch, config_tmpdir):
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 66b152937..ef3007f71 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -1876,7 +1876,6 @@ class TestShellCommand:
@pytest.mark.parametrize('kwargs, val', [
({'placeholder': True}, '[foo, bar]'),
({'placeholder': True}, '[foo, "{", "}", bar'),
- ({'placeholder': True}, '[foo, bar]'),
({'placeholder': True}, '[foo, "{fi", "le}", bar'),
# Like valid ones but with wrong placeholder
diff --git a/tests/unit/config/test_configutils.py b/tests/unit/config/test_configutils.py
index e7ce15aff..3fc5cd561 100644
--- a/tests/unit/config/test_configutils.py
+++ b/tests/unit/config/test_configutils.py
@@ -318,13 +318,11 @@ class TestFontFamilies:
@pytest.mark.parametrize('families, quote, expected', [
(['family'], True, 'family'),
(['family1', 'family2'], True, 'family1, family2'),
- (['family'], True, 'family'),
(['space family', 'alien'], True, '"space family", alien'),
(['comma,family', 'period'], True, '"comma,family", period'),
(['family'], False, 'family'),
(['family1', 'family2'], False, 'family1, family2'),
- (['family'], False, 'family'),
(['space family', 'alien'], False, 'space family, alien'),
(['comma,family', 'period'], False, 'comma,family, period'),
])
@@ -367,9 +365,13 @@ class TestFontFamilies:
# Check the requested font to make sure CSS parsing worked
assert label.font().family() == families.family
+ # Skipping the rest of the test as WORKAROUND for
+ # https://bugreports.qt.io/browse/QTBUG-94090
+ return
+
# Try to find out whether the monospace font did a fallback on a non-monospace
# font...
- fallback_label = QLabel()
+ fallback_label = QLabel() # pylint: disable=unreachable
qtbot.add_widget(label)
fallback_label.setText("fallback")
@@ -382,9 +384,5 @@ class TestFontFamilies:
if info.family() == fallback_family:
return
- if info.family() == 'Noto Sans Mono':
- # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-94090
- return
-
# If we didn't fall back, we should've gotten a fixed-pitch font.
assert info.fixedPitch(), info.family()
diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py
index 4c31c5b07..d95382624 100644
--- a/tests/unit/config/test_qtargs.py
+++ b/tests/unit/config/test_qtargs.py
@@ -324,7 +324,6 @@ class TestWebEngineArgs:
("light", "5.15.0", False),
("light", "5.15.1", False),
("light", "5.15.2", False),
- ("light", "5.15.2", False),
("light", "5.15.3", False),
("light", "6.0.0", False),
@@ -333,7 +332,6 @@ class TestWebEngineArgs:
("auto", "5.15.0", False),
("auto", "5.15.1", False),
("auto", "5.15.2", False),
- ("auto", "5.15.2", False),
("auto", "5.15.3", False),
("auto", "6.0.0", False),
])
diff --git a/tests/unit/config/test_websettings.py b/tests/unit/config/test_websettings.py
index 7601c7c24..f42563727 100644
--- a/tests/unit/config/test_websettings.py
+++ b/tests/unit/config/test_websettings.py
@@ -24,7 +24,7 @@ from qutebrowser.misc import objects
from qutebrowser.utils import usertypes
-@pytest.mark.parametrize([
+@pytest.mark.parametrize([ # noqa: PT006
'user_agent', 'os_info', 'webkit_version',
'upstream_browser_key', 'upstream_browser_version', 'qt_key'
], [
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..c664757fd 100644
--- a/tests/unit/misc/test_guiprocess.py
+++ b/tests/unit/misc/test_guiprocess.py
@@ -26,12 +26,12 @@ 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
-@pytest.fixture()
+@pytest.fixture
def proc(qtbot, caplog):
"""A fixture providing a GUIProcess and cleaning it up after the test."""
p = guiprocess.GUIProcess('testprocess')
@@ -46,7 +46,7 @@ def proc(qtbot, caplog):
p._proc.waitForFinished()
-@pytest.fixture()
+@pytest.fixture
def fake_proc(monkeypatch, stubs):
"""A fixture providing a GUIProcess with a mocked QProcess."""
p = guiprocess.GUIProcess('testprocess')
@@ -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/misc/test_miscwidgets.py b/tests/unit/misc/test_miscwidgets.py
index 6e1919ec3..3c5c1eef1 100644
--- a/tests/unit/misc/test_miscwidgets.py
+++ b/tests/unit/misc/test_miscwidgets.py
@@ -38,7 +38,7 @@ class TestCommandLineEdit:
cmd_edit.set_prompt(':')
qtbot.add_widget(cmd_edit)
assert cmd_edit.text() == ''
- yield cmd_edit
+ return cmd_edit
def test_position(self, qtbot, cmd_edit):
"""Test cursor position based on the prompt."""
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..9f5a15065 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()
@@ -349,7 +348,7 @@ class TestHideQtWarning:
"""Tests for hide_qt_warning/QtWarningFilter."""
- @pytest.fixture()
+ @pytest.fixture
def qt_logger(self):
return logging.getLogger('qt-tests')
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index 2d98feece..ad9adf032 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -51,9 +51,7 @@ else:
test_file = None
-# pylint: disable=bad-continuation
-@pytest.mark.parametrize(['qversion', 'compiled', 'pyqt', 'version', 'exact',
- 'expected'], [
+@pytest.mark.parametrize('qversion, compiled, pyqt, version, exact, expected', [
# equal versions
('5.14.0', None, None, '5.14.0', False, True),
('5.14.0', None, None, '5.14.0', True, True), # exact=True
@@ -75,7 +73,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 +556,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,
@@ -742,7 +740,7 @@ class TestPyQIODevice:
with pytest.raises(io.UnsupportedOperation):
pyqiodev.seek(0, whence)
- @pytest.mark.flaky()
+ @pytest.mark.flaky
def test_qprocess(self, py_proc):
"""Test PyQIODevice with a QProcess which is non-sequential.
diff --git a/tests/unit/utils/test_resources.py b/tests/unit/utils/test_resources.py
index 738fadd37..6a11ff588 100644
--- a/tests/unit/utils/test_resources.py
+++ b/tests/unit/utils/test_resources.py
@@ -65,7 +65,7 @@ class TestReadFile:
'qutebrowser/html/unrelatedhtml',
]
- yield zipfile.Path(zip_path) / 'qutebrowser'
+ return zipfile.Path(zip_path) / 'qutebrowser'
@pytest.fixture(params=['pathlib', 'zipfile'])
def resource_root(self, request):
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_utils.py b/tests/unit/utils/test_utils.py
index 57adc883c..330ef3b96 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -809,8 +809,11 @@ class TestYaml:
assert utils.yaml_load("[1, 2]") == [1, 2]
def test_load_float_bug(self):
- with pytest.raises(yaml.YAMLError):
+ try:
utils.yaml_load("._")
+ except yaml.YAMLError:
+ # Either no exception or YAMLError, not ValueError
+ pass
def test_load_file(self, tmp_path):
tmpfile = tmp_path / 'foo.yml'
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