Merge branch 'master' into more-sophisticated-adblock

This commit is contained in:
Árni Dagur 2020-06-21 01:44:24 -04:00 committed by Árni Dagur
commit 12e1b157e7
243 changed files with 5881 additions and 2438 deletions

View File

@ -10,8 +10,8 @@ image:
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python37-x64\python.exe
TESTENV: py37-pyqt514
PYTHON: C:\Python38-x64\python.exe
TESTENV: py38-pyqt514
install:
- '%PYTHON% --version'

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.10.1
current_version = 1.12.0
commit = True
message = Release v{new_version}
tag = True

View File

@ -1,16 +0,0 @@
---
name: ❓ Support Question
about: It's okay to ask questions via GitHub, but IRC/Reddit/Mailinglist might be better.
---
<!--
While it's fine to ask questions here, check the documentation for better
ways to get help:
https://github.com/qutebrowser/qutebrowser#getting-help
-->
**Version info (see `:version`)**:
**If applicable: Does the issue happen if you start with `--temp-basedir`?**:

View File

@ -1,11 +0,0 @@
---
name: ⛔ Security Issue
about: Contact mail@qutebrowser.org for security issues.
---
⚠ PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, SEE BELOW.
If you have found a security issue in qutebrowser, please send the details to
mail [at] qutebrowser.org and don't disclose it publicly until we can provide a
fix for it

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: ❓ Support Question
url: https://github.com/qutebrowser/qutebrowser/discussions
about: Use GitHub's new discussions feature for questions

1
.github/SECURITY.md vendored Normal file
View File

@ -0,0 +1 @@
Please report security bugs to [security@qutebrowser.org](mailto:security@qutebrowser.org).

33
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: "Code scanning"
on:
push:
pull_request:
schedule:
- cron: '0 3 * * 1'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: javascript, python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

3
.gitignore vendored
View File

@ -27,7 +27,7 @@ __pycache__
/.pytest_cache
/.testmondata
/.hypothesis
/.mypy_cache
.mypy_cache
/prof
/venv
TODO
@ -44,3 +44,4 @@ TODO
/doc/extapi/_build
/misc/nsis/include
/misc/nsis/plugins
/wheels

View File

@ -4,23 +4,23 @@
python_version = 3.6
# --strict
warn_redundant_casts = True
warn_unused_ignores = True
warn_unused_configs = True
# disallow_any_generics = True
disallow_subclassing_any = True
disallow_untyped_decorators = True
## https://github.com/python/mypy/issues/5957
# warn_unused_configs = True
# disallow_untyped_calls = True
# disallow_untyped_defs = True
## https://github.com/python/mypy/issues/5954
# disallow_incomplete_defs = True
disallow_incomplete_defs = True
check_untyped_defs = True
disallow_untyped_decorators = True
# no_implicit_optional = True
warn_redundant_casts = True
warn_unused_ignores = True
# warn_return_any = True
warn_unreachable = True
# no_implicit_reexport = True
strict_equality = True
# Other strictness flags
strict_equality = True
warn_unreachable = True
# Output
show_error_codes = True
@ -69,76 +69,51 @@ disallow_subclassing_any = False
[mypy-qutebrowser.browser.browsertab]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.hints]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.objects]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.debugcachestats]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.utilcmds]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.throttle]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.backendproblem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.misc.eventfilter]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.commands.cmdutils]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.config.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.api.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.components.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.extensions.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webkit.webkitelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.browser.webengine.webengineelem]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.keyinput.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.utils.*]
disallow_untyped_defs = True
disallow_incomplete_defs = True
[mypy-qutebrowser.mainwindow.statusbar.command]
disallow_untyped_defs = True
disallow_incomplete_defs = True

View File

@ -60,7 +60,7 @@ no-docstring-rgx=(^_|^main$)
[FORMAT]
max-line-length=79
ignore-long-lines=(<?https?://|^# Copyright 201\d|link:)
ignore-long-lines=(<?https?://|file://|^# Copyright 201\d|link:)
expected-line-ending-format=LF
[VARIABLES]

View File

@ -19,8 +19,8 @@ matrix:
services: docker
### Archlinux QtWebEngine with testing/KDE-Unstable
# - env: DOCKER=archlinux-webengine-unstable QUTE_BDD_WEBENGINE=true
# services: docker
- env: DOCKER=archlinux-webengine-unstable QUTE_BDD_WEBENGINE=true
services: docker
### PyQt 5.7.1 (Python 3.5)
- python: 3.5
@ -45,7 +45,6 @@ matrix:
### PyQt 5.12 (Python 3.8)
- env: TESTENV=py38-pyqt512
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
addons:
apt:
packages:
@ -53,20 +52,31 @@ matrix:
### PyQt 5.13 (Python 3.8)
- env: TESTENV=py38-pyqt513
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
addons:
apt:
packages:
- libxkbcommon-x11-0
### PyQt 5.14 (Python 3.8, with coverage)
- env: TESTENV=py38-pyqt514-cov
# http://code.qt.io/cgit/qt/qtbase.git/commit/?id=c3a963da1f9e7b1d37e63eedded61da4fbdaaf9a
### PyQt 5.14 (Python 3.8)
- env: TESTENV=py38-pyqt514
addons:
apt:
packages:
- libxkbcommon-x11-0
### PyQt 5.15 (Python 3.8, with coverage)
- env: TESTENV=py38-pyqt515-cov
addons:
apt:
packages:
- libxkbcommon-x11-0
- libxcb-icccm4
- libxcb-image0
- libxcb-keysyms1
- libxcb-randr0
- libxcb-render-util0
- libxcb-xinerama0
### macOS Mojave (10.14)
- os: osx
env: TESTENV=py37-pyqt514 OSX=mojave

View File

@ -1,6 +1,7 @@
recursive-include qutebrowser *.py
recursive-include qutebrowser/img *.svg *.png
recursive-include qutebrowser/javascript *.js
graft tests
graft qutebrowser/html
graft qutebrowser/3rdparty
graft icons
@ -16,10 +17,10 @@ include misc/org.qutebrowser.qutebrowser.desktop
include misc/org.qutebrowser.qutebrowser.appdata.xml
include misc/Makefile
include requirements.txt
include tox.ini
include qutebrowser.py
include misc/cheatsheet.svg
include qutebrowser/config/configdata.yml
include pytest.ini
prune www
prune scripts/dev
@ -29,16 +30,17 @@ exclude scripts/asciidoc2html.py
recursive-exclude doc *.asciidoc
include doc/qutebrowser.1.asciidoc
include doc/changelog.asciidoc
prune tests
prune qutebrowser/3rdparty
exclude pytest.ini
exclude mypy.ini
exclude tox.ini
exclude qutebrowser/javascript/.eslintrc.yaml
exclude qutebrowser/javascript/.eslintignore
exclude doc/help
exclude .*
exclude misc/qutebrowser.spec
exclude misc/qutebrowser.rcc
exclude tests/unit/scripts/test_run_vulture.py
exclude tests/unit/scripts/test_check_coverage.py
prune doc/extapi
prune misc/nsis

View File

@ -28,9 +28,6 @@ time, your help is needed! See the
https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more
information. Depending on your sign-up date and how long you keep a certain
level, you can get qutebrowser t-shirts, stickers and more!
Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub
Sponsors (up to a $5000 total) will be doubled until October 2020.
// QUTE_WEB_HIDE_END
Screenshots
@ -84,6 +81,11 @@ get sent to the general qutebrowser@ list).
If you're a reddit user, there's a
https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there.
Finally, qutebrowser is participating in the Beta for GitHub's new Discussions
feature, so you can also use the
https://github.com/qutebrowser/qutebrowser/discussions[discussions tab] on
GitHub to get in touch.
Contributions / Bugs
--------------------
@ -108,9 +110,10 @@ Requirements
The following software and libraries are required to run qutebrowser:
* https://www.python.org/[Python] 3.5.2 or newer (3.6 recommended)
* https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.9
will be dropped soon) with the following modules:
* https://www.python.org/[Python] 3.5.2 or newer (3.6 - 3.8 recommended;
support for 3.5 will be dropped with qutebrowser v2.0.0)
* https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.11
will be dropped with qutebrowser v2.0.0) with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
@ -123,7 +126,7 @@ The following software and libraries are required to run qutebrowser:
avoid visiting untrusted websites and using it for transmission of
sensitive data.**
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
(5.14 recommended, support for < 5.9 will be dropped soon) for Python 3
(5.14 recommended, support for < 5.11 will be dropped soon) for Python 3
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* https://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]
@ -154,9 +157,6 @@ https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more
information. Depending on your sign-up date and how long you keep a certain
level, you can get qutebrowser t-shirts, stickers and more!
Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub
Sponsors (up to a $5000 total) will be doubled until October 2020!
Alternatively, the following donation methods are available -- note that
eligibility for swag (shirts/stickers/etc.) is handled on a case-by-case basis
for those, please mailto:mail@qutebrowser.org[get in touch] for details.
@ -212,8 +212,8 @@ link:doc/backers.asciidoc[crowdfunding campaigns]!
Similar projects
----------------
Many projects with a similar goal as qutebrowser exist.
Most of them were inspirations for qutebrowser in some way, thanks for that!
Various projects with a similar goal like qutebrowser exist.
Many of them were inspirations for qutebrowser in some way, thanks for that!
Active
~~~~~~
@ -221,8 +221,9 @@ Active
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
* https://github.com/next-browser/next/[next] (Lisp, Emacs-like but also offers Vim bindings, various backends - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
* https://next.atlas.engineer/[next] (Lisp, Emacs-like but also offers Vim bindings, QtWebKit or GTK+/WebKit2 - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly)
* https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine)
* https://vieb.dev/[Vieb] (JavaScript, Electron)
* Chrome/Chromium addons:
https://vimium.github.io/[Vimium],
* Firefox addons (based on WebExtensions):

View File

@ -15,9 +15,226 @@ 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.
v1.11.0 (unreleased)
v1.13.0 (unreleased)
--------------------
Removed
~~~~~~~
- The `:debug-log-level` command was removed as it's replaced by the new
`logging.level.console` setting.
- The `qute://plainlog` special page got replaced by `qute://log?plain` - the
names of those pages is considered an implementation detail, and
`:messages --plain` should be used instead.
Changed
~~~~~~~
- New handling of bindings in hint mode which fixes various bugs and allows for
single-letter keybindings in hint mode.
- The `tor_identity` userscript now takes the password via a `-p` flag and has
a new `-c` flag to customize the Tor control port.
- `:config-write-py` now adds a note about `config.py` files being targeted at
advanced users.
- `:report` now takes two optional arguments for bug/contact information, so
that it can be used without the report window popping up.
- New `t[Cc][Hh]` default bindings which work similarly to the `t[Ss][Hh]`
bindings for JavaScript but toggle cookie permissions.
- The `:message` command now takes a `--logfilter` / `-f` argument, which is a
list of logging categories to show.
- The `:debug-log-filter` command now understands the full logfilter syntax.
- Changes to settings:
* `fonts.tabs` has been split into `fonts.tabs.{selected,unselected}` (see
below).
* `statusbar.hide` has been renamed to `statusbar.show` with the possible
values being `always` (`hide = False`), `never` (`hide = True`) or
`in-mode` (new, only show statusbar outside of normal mode.
* The `QtFont` config type formerly used for `fonts.tabs` and
`fonts.debug_console` is now removed and entirely replaced by `Font`. The
former distinction was mainly an implementation detail, and the accepted
values shouldn't have changed.
* `input.rocker_gestures` has been renamed to `input.mouse.rocker_gestures`.
* `content.dns_prefetch` is now enabled by default again, since the crashes
it caused are now fixed (Qt 5.15) or worked around.
* `scrolling.bar` supports a new `overlay` value to show an overlay
scrollbar, which is now the default. On unsupported configurations (on Qt <
5.11, with QtWebKit or on macOS), the value falls back to `when-searching`
or `never` (QtWebKit).
- The statusbar now shows partial keychains in all modes (e.g. while hinting)
- Small performance improvements.
Added
~~~~~
- New settings:
* `logging.level.ram` and `logging.level.console` to configure the default
logging levels via the config.
* `fonts.tabs.selected` and `fonts.tabs.unselected` to set the font of the
selected tab independently from unselected tabs (e.g. to make it bold).
* `input.mouse.back_forward_buttons` which can be set to `false` to disable
back/forward mouse buttons.
Fixed
~~~~~
- Crash when `tabs.focus_stack_size` is set to -1.
- Crash when a `pdf.js` file for PDF.js exists, but `viewer.html` does not.
- Crash when `:completion-item-yank --sel` is used on a platform without
primary selection support (e.g. Windows/macOS).
- Crash when there's a feature permission request from Qt with an invalid URL
(which seems to happen with Qt 5.15 sometimes).
- Crash in rare cases where QtWebKit/QtWebEngine imports fail in unexpected
ways.
- Crash when something removed qutebrowser's IPC socket file and it's been
running for 6 hours.
- `:config-write-py` now works with paths starting with `~/...` again.
- New site-specific quirk for a missing `globalThis` in Qt <= 5.12 on Reddit
and Spotify.
- When `;` is added to `hints.chars`, using hint labels containing `;;` now
works properly.
- Hint letters outside of ASCII should now work.
- When `bindings.key_mappings` is used with hints, it now works properly with
letters outside of ASCII as well.
- With Qt 5.15, the audible/muted indicators are not updated properly due to a
Qt bug. This release adds a workaround so that at least the muted indicator
is shown properly.
- As a workaround for crashes with QtWebEngine versions between 5.12 and 5.14
(inclusive), changing the user agent (`content.headers.user_agent`) exposed
to JS now requires a restart. The corresponding HTTP header is not affected.
v1.12.0 (2020-06-01)
--------------------
Removed
~~~~~~~
- `tox -e mkvenv` which was deprecated in qutebrowser v1.10.0 is now
removed. Use the `mkvenv.py` script instead.
- Support for using `config.bind(key, None)` in `config.py` to unbind a
key was deprecated in v1.8.2 and is now removed. Use
`config.unbind(key)` instead.
- `:yank markdown` was deprecated in v1.7.0 and is now removed. Use
`:yank inline [{title}]({url})` instead.
Added
~~~~~
- New `:debug-keytester` command, which shows a "key tester" widget.
Previously, that was only available as a separate application via `python3 -m
scripts.keytester`.
- New `:config-diff` command which opens the `qute://configdiff` page.
- New `--debug-flag log-cookies` to log cookies to the debug log.
- New `colors.contextmenu.disabled.{fg,bg}` settings to customize colors for
disabled items in the context menu.
- New line selection mode (`:toggle-selection --line`), bound to `Shift-V` in caret mode.
- New `colors.webpage.darkmode.*` settings to control Chromium's dark mode.
Note that those settings only work with QtWebEngine on Qt >= 5.14 and require
a restart of qutebrowser.
Changed
~~~~~~~
- Windows and macOS releases now ship Qt 5.15, which is based on Chromium
80.0.3987.163 with security fixes up to 81.0.4044.138.
- The `content.cookies.accept` setting now accepts URL patterns.
- Tests are now included in release tarballs. Note that only running them with
the exact dependencies listed in
`misc/requirements/requirements-tests.txt{,-raw}` is supported.
- The `:tab-focus` command now has completion for tabs in the current window.
- The `bindings.key_mappings` setting now maps `<Ctrl+I>` to the tab key by default.
- `:tab-give --private` now detaches a tab into a new private window.
Fixed
~~~~~
- Using `:open -s` now only rewrites `http://` in URLs to `https://`, not other
schemes like `qute://`.
- When an unhandled exception happens in certain parts of the code (outside of
the main thread), qutebrowser did crash or freeze when trying to show its
exception handler. This is now fixed.
- `:inspector` now works correctly when cookies are disabled globally.
- Added workaround for a (Gentoo?) PyQt/packaging issue related to the
`QWebEngineFindTextResult` handling added in v1.11.0.
- When entering caret selection mode (`v, v`) very early before a page is
loaded, an error is now shown instead of a crash happening.
- The workaround for session loading with Qt 5.15 now handles
`sessions.lazy_restore` so that the saved page is loaded instead of the
"stub" page with no possibility to get to the web page.
- A site specific quirk to allow typing accented characters on Google
Docs was active for docs.google.com, but not drive.google.com. It is
now applied for both subdomains.
- With older graphics hardware (OpenGL < 4.3) with Qt 5.14 on Wayland, WebGL
causes segfaults. Now qutebrowser detects that combination and suggests to
disable WebGL or use XWayland.
v1.11.1 (2020-05-07)
--------------------
Security
~~~~~~~~
- CVE-2020-11054: After a certificate error was overridden by the user,
qutebrowser displays the URL as yellow (`colors.statusbar.url.warn.fg`).
However, when the affected website was subsequently loaded again, the URL was
mistakenly displayed as green (`colors.statusbar.url.success_https`). While
the user already has seen a certificate error prompt at this point (or set
`content.ssl_strict` to `false` which is not recommended), this could still
provide a false sense of security. This is now fixed.
v1.11.0 (2020-04-27)
--------------------
Added
~~~~~
- New settings:
* `search.wrap` which can be set to false to prevent wrapping around the page
when searching. With QtWebEngine, Qt 5.14 or newer is required.
* `content.unknown_url_scheme_policy` which allows controlling when an
external application is opened for external links (never, from user
interaction, always).
* `content.fullscreen.overlay_timeout` to configure how long the fullscreen
overlay should be displayed. If set to `0`, no overlay is displayed.
* `hints.padding` to add additional padding for hints.
* `hints.radius` to set a border radius for hints (set to `3` by default).
- New placeholders for `url.searchengines` values:
* `{unquoted}` inserts the search term without any quoting.
* `{semiquoted}` (same as `{}`) quotes most special characters, but slashes
remain unquoted.
* `{quoted}` (same as `{}` in earlier releases) also quotes slashes.
Changed
~~~~~~~
- First adaptions to Qt 5.15, including a stop-gap measure for session loading
not working properly with it.
- Searching now wraps around the page by default with QtWebKit (where it didn't
before). Set `search.wrap` to `false` to restore the old behavior.
- The `{}` placeholder for search engines (the `url.searchengines` setting) now
does not quote slashes anymore, but other characters typically encoded in
URLs still get encoded. This matches the behavior of search engines in
Chromium. To revert to the old behavior, use `{quoted}` instead.
- The `content.windowed_fullscreen` setting got renamed to
`content.fullscreen.window`.
- Mouse-wheel scrolling is now prevented while hints are active.
- Changes to userscripts:
* `qute-bitwarden` now has an optional `--totp` flag which can be used
to copy TOTP codes to clipboard (requires the `pyperclip` module).
* `readability-js` now opens readability tabs next to the original
tab (using the `:open --related` flag).
* `readability-js` now displays a favicon for readability tabs.
* `password_fill` now triggers a `change` JavaScript event after filling the
data.
- The `dictcli.py` script now shows better error messages.
- Various improvements to the `mkvenv.py` script (mainly useful for development).
- Minor performance improvements.
Deprecated
~~~~~~~~~~
- A warning about old Qt versions is now also shown with Qt 5.9 and 5.10, as
support for Qt < 5.11 will be dropped in qutebrowser v2.0.
Fixed
~~~~~
@ -25,6 +242,25 @@ Fixed
- The proxied `window` global is now shared between different
Greasemonkey scripts (but still separate from the page's `window`), to
match the original Greasemonkey implementation.
- The `--output-messages` (`-m`) flag added in v1.9.0 now also works correctly
when using `:spawn --userscript`.
- `:version` and `--version` now don't crash if there's an (invalid)
`/etc/os-release` file which has non-comment lines without a `=` character.
- Scripts in `scripts/` now report errors to `stderr` correctly, instead of
using `stdout`.
v1.10.2 (2020-04-17)
--------------------
Changed
~~~~~~~
- Windows and macOS releases now bundle Qt 5.14.2, including security fixes up
to Chromium 80.0.3987.132.
Fixed
~~~~~
- The WhatsApp workaround now also works when using WhatsApp in languages other
than English.
- The `mkvenv.py` script now also works properly on Windows.

View File

@ -707,6 +707,9 @@ qutebrowser release
* Make sure there are no unstaged changes and the tests are green.
* Make sure all issues with the related milestone are closed.
* Consider updating the completions for `content.headers.user_agent` in `configdata.yml`.
* Minor release: Consider updating some files from master:
- `misc/requirements/` and `requirements.txt`
- `scripts/`
* Make sure Python is up-to-date on build machines.
* Mark the milestone at https://github.com/qutebrowser/qutebrowser/milestones as closed.
* Update changelog in master branch

View File

@ -21,7 +21,7 @@ Why another browser?::
Read the next few questions to find out why I was unhappy with existing
software.
What's wrong with link:https://bitbucket.org/portix/dwb/[dwb]/link:https://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/jumanji/... (projects based on WebKitGTK)?::
What's wrong with link:https://bitbucket.org/portix/dwb/[dwb]/link:https://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://luakit.github.io/[luakit]/jumanji/... (projects based on WebKitGTK)?::
Most of them are based on the https://webkitgtk.org/[WebKitGTK+]
https://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
which causes a lot of crashes. As the GTK API using WebKit1 is
@ -305,13 +305,13 @@ If you ever need to renew any of these certificates, you can take a look
at the currently imported certificates using:
+
----
certutil -D "sql:${HOME}/.pki/nssdb" -L
certutil -d "sql:${HOME}/.pki/nssdb" -L
----
+
Then remove the expired certificates using:
+
----
certutil -D "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
certutil -d "sql:${HOME}/.pki/nssdb" -D -n "My Fancy Certificate Nickname"
----
+
And then import the new and valid certificates using the procedure
@ -367,14 +367,15 @@ up for a monthly donation to The-Compiler (qutebrowser's main developer),
allowing him to work part-time on qutebrowser. If you keep your donation level
for long enough, you can get some qutebrowser stickers!
Why GitHub Sponsors? What is the GitHub Matching Fund?::
Thanks to the
https://help.github.com/en/github/supporting-the-open-source-community-with-github-sponsors/about-github-sponsors#about-the-github-sponsors-matching-fund[GitHub Sponsors Matching Fund],
all donations are doubled by GitHub in the first year, up to a $5000 total limit.
Why GitHub Sponsors?::
GitHub Sponsors is a crowdfundign platform nicely integrated with
qutebrowser's existing GitHub page and a better offering than alternatives such
as Patreon or Liberapay.
+
Even outside of the matching fund, GitHub Sponsors is nicely integrated with
qutebrowser's existing GitHub page and a better offering than alternatives such
as Patreon or Liberapay.
It also offers a
https://help.github.com/en/github/supporting-the-open-source-community-with-github-sponsors/about-github-sponsors#about-the-github-sponsors-matching-fund[Matching Fund]
which matches all donations until a cap of $5000, which has already been
reached by qutebrowser.
Is it possible to contribute via a one-time donation instead?::
If you prefer a one-time donation, there are various possibilities:
@ -382,8 +383,7 @@ Is it possible to contribute via a one-time donation instead?::
- Select a tier which covers the total amount you'd like to donate (note that
payments are prorated based on the current date). After the payment is
processed, cancel your GitHub sponsors subscription again. This has a big
benefit: Thanks to GitHub's matching fund, your donation will be doubled (and
nothing will be lost to fees).
benefit: There are no fees deducted from your amount.
+
- Sign up for a lower recurring donation instead.
+

View File

@ -45,6 +45,7 @@ possible to run or bind multiple commands by separating them with `;;`.
|<<config-cycle,config-cycle>>|Cycle an option between multiple values.
|<<config-dict-add,config-dict-add>>|Add a key/value pair to a dictionary option.
|<<config-dict-remove,config-dict-remove>>|Remove a key from a dict.
|<<config-diff,config-diff>>|Show all customized options.
|<<config-edit,config-edit>>|Open the config.py file in the editor.
|<<config-list-add,config-list-add>>|Append a value to a config option that is a list.
|<<config-list-remove,config-list-remove>>|Remove a value from a list.
@ -334,6 +335,16 @@ Remove a key from a dict.
==== optional arguments
* +*-t*+, +*--temp*+: Remove value temporarily until qutebrowser is closed.
[[config-diff]]
=== config-diff
Syntax: +:config-diff [*--old*]+
Show all customized options.
==== optional arguments
* +*-o*+, +*--old*+: Show difference for the pre-v1.0 files (qutebrowser.conf/keys.conf).
[[config-edit]]
=== config-edit
Syntax: +:config-edit [*--no-source*]+
@ -805,7 +816,7 @@ Show a warning message in the statusbar.
[[messages]]
=== messages
Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] ['level']+
Syntax: +:messages [*--plain*] [*--tab*] [*--bg*] [*--window*] [*--logfilter* 'logfilter'] ['level']+
Show a log of past messages.
@ -818,6 +829,9 @@ Show a log of past messages.
* +*-t*+, +*--tab*+: Open in a new tab.
* +*-b*+, +*--bg*+: Open in a background tab.
* +*-w*+, +*--window*+: Open in a new window.
* +*-f*+, +*--logfilter*+: A comma-separated filter string of logging categories. If the filter string starts with an exclamation mark, it
is negated.
[[navigate]]
=== navigate
@ -1011,8 +1025,15 @@ Which count to pass the command.
[[report]]
=== report
Syntax: +:report ['info'] ['contact']+
Report a bug in qutebrowser.
==== positional arguments
* +'info'+: Information about the bug report. If given, no report dialog shows up.
* +'contact'+: Contact information for the report.
[[restart]]
=== restart
Restart qutebrowser while keeping existing tabs open.
@ -1337,7 +1358,7 @@ The tab index to focus, starting with 1.
[[tab-give]]
=== tab-give
Syntax: +:tab-give [*--keep*] ['win-id']+
Syntax: +:tab-give [*--keep*] [*--private*] ['win-id']+
Give the current tab to a new or existing window if win_id given.
@ -1348,6 +1369,7 @@ If no win_id is given, the tab will get detached into a new window.
==== optional arguments
* +*-k*+, +*--keep*+: If given, keep the old tab around.
* +*-p*+, +*--private*+: If the tab should be detached into a private instance.
==== count
Overrides win_id (index starts at 1 for win_id=0).
@ -1485,8 +1507,6 @@ Yank (copy) something to the clipboard or primary selection.
- `title`: The current page's title.
- `domain`: The current scheme, domain, and port number.
- `selection`: The selection under the cursor.
- `markdown`: Yank title and URL in markdown format
(deprecated, use `:yank inline [{title}]({url})` instead).
- `inline`: Yank the text contained in the 'inline' argument.
@ -1894,8 +1914,13 @@ This acts like readline's yank.
[[toggle-selection]]
=== toggle-selection
Syntax: +:toggle-selection [*--line*]+
Toggle caret selection mode.
==== optional arguments
* +*-l*+, +*--line*+: Enables line-selection.
== Debugging commands
These commands are mainly intended for debugging. They are hidden if qutebrowser was started without the `--debug`-flag.
@ -1911,9 +1936,9 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|<<debug-dump-history,debug-dump-history>>|Dump the history to a file in the old pre-SQL format.
|<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file.
|<<debug-keytester,debug-keytester>>|Show a keytester widget.
|<<debug-log-capacity,debug-log-capacity>>|Change the number of log lines to be stored in RAM.
|<<debug-log-filter,debug-log-filter>>|Change the log filter for console logging.
|<<debug-log-level,debug-log-level>>|Change the log level for console logging.
|<<debug-pyeval,debug-pyeval>>|Evaluate a python string and display the results as a web page.
|<<debug-set-fake-clipboard,debug-set-fake-clipboard>>|Put data into the fake clipboard and enable logging, used for tests.
|<<debug-trace,debug-trace>>|Trace executed code via hunter.
@ -1965,6 +1990,10 @@ Dump the current page's content to a file.
==== optional arguments
* +*-p*+, +*--plain*+: Write plain text instead of HTML.
[[debug-keytester]]
=== debug-keytester
Show a keytester widget.
[[debug-log-capacity]]
=== debug-log-capacity
Syntax: +:debug-log-capacity 'capacity'+
@ -1984,15 +2013,6 @@ Change the log filter for console logging.
* +'filters'+: A comma separated list of logger names. Can also be "none" to clear any existing filters.
[[debug-log-level]]
=== debug-log-level
Syntax: +:debug-log-level 'level'+
Change the log level for console logging.
==== positional arguments
* +'level'+: The log level to set.
[[debug-pyeval]]
=== debug-pyeval
Syntax: +:debug-pyeval [*--file*] [*--quiet*] 's'+

View File

@ -32,6 +32,8 @@
|<<colors.completion.odd.bg,colors.completion.odd.bg>>|Background color of the completion widget for odd rows.
|<<colors.completion.scrollbar.bg,colors.completion.scrollbar.bg>>|Color of the scrollbar in the completion view.
|<<colors.completion.scrollbar.fg,colors.completion.scrollbar.fg>>|Color of the scrollbar handle in the completion view.
|<<colors.contextmenu.disabled.bg,colors.contextmenu.disabled.bg>>|Background color of disabled items in the context menu.
|<<colors.contextmenu.disabled.fg,colors.contextmenu.disabled.fg>>|Foreground color of disabled items in the context menu.
|<<colors.contextmenu.menu.bg,colors.contextmenu.menu.bg>>|Background color of the context menu.
|<<colors.contextmenu.menu.fg,colors.contextmenu.menu.fg>>|Foreground color of the context menu.
|<<colors.contextmenu.selected.bg,colors.contextmenu.selected.bg>>|Background color of the context menu's selected item.
@ -109,6 +111,15 @@
|<<colors.tabs.selected.odd.bg,colors.tabs.selected.odd.bg>>|Background color of selected odd tabs.
|<<colors.tabs.selected.odd.fg,colors.tabs.selected.odd.fg>>|Foreground color of selected odd tabs.
|<<colors.webpage.bg,colors.webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color).
|<<colors.webpage.darkmode.algorithm,colors.webpage.darkmode.algorithm>>|Which algorithm to use for modifying how colors are rendered with darkmode.
|<<colors.webpage.darkmode.contrast,colors.webpage.darkmode.contrast>>|Contrast for dark mode.
|<<colors.webpage.darkmode.enabled,colors.webpage.darkmode.enabled>>|Render all web contents using a dark theme.
|<<colors.webpage.darkmode.grayscale.all,colors.webpage.darkmode.grayscale.all>>|Render all colors as grayscale.
|<<colors.webpage.darkmode.grayscale.images,colors.webpage.darkmode.grayscale.images>>|Desaturation factor for images in dark mode.
|<<colors.webpage.darkmode.policy.images,colors.webpage.darkmode.policy.images>>|Which images to apply dark mode to.
|<<colors.webpage.darkmode.policy.page,colors.webpage.darkmode.policy.page>>|Which pages to apply dark mode to.
|<<colors.webpage.darkmode.threshold.background,colors.webpage.darkmode.threshold.background>>|Threshold for inverting background elements with dark mode.
|<<colors.webpage.darkmode.threshold.text,colors.webpage.darkmode.threshold.text>>|Threshold for inverting text with dark mode.
|<<colors.webpage.prefers_color_scheme_dark,colors.webpage.prefers_color_scheme_dark>>|Force `prefers-color-scheme: dark` colors for websites.
|<<completion.cmd_history_max_items,completion.cmd_history_max_items>>|Number of commands to save in the command history.
|<<completion.delay,completion.delay>>|Delay (in milliseconds) before updating completions after typing a character.
@ -141,6 +152,8 @@
|<<content.desktop_capture,content.desktop_capture>>|Allow websites to share screen content.
|<<content.dns_prefetch,content.dns_prefetch>>|Try to pre-fetch DNS entries to speed up browsing.
|<<content.frame_flattening,content.frame_flattening>>|Expand each subframe to its contents.
|<<content.fullscreen.overlay_timeout,content.fullscreen.overlay_timeout>>|Set fullscreen notification overlay timeout in milliseconds.
|<<content.fullscreen.window,content.fullscreen.window>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|<<content.geolocation,content.geolocation>>|Allow websites to request geolocations.
|<<content.headers.accept_language,content.headers.accept_language>>|Value to send in the `Accept-Language` header.
|<<content.headers.custom,content.headers.custom>>|Custom headers for qutebrowser HTTP requests.
@ -175,10 +188,10 @@
|<<content.register_protocol_handler,content.register_protocol_handler>>|Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|<<content.site_specific_quirks,content.site_specific_quirks>>|Enable quirks (such as faked user agent headers) needed to get specific sites to work properly.
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|<<content.unknown_url_scheme_policy,content.unknown_url_scheme_policy>>|How navigation requests to URLs with unknown schemes are handled.
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|<<content.webgl,content.webgl>>|Enable WebGL.
|<<content.webrtc_ip_handling_policy,content.webrtc_ip_handling_policy>>|Which interfaces to expose via WebRTC.
|<<content.windowed_fullscreen,content.windowed_fullscreen>>|Limit fullscreen to the browser window (does not expand to fill the screen).
|<<content.xss_auditing,content.xss_auditing>>|Monitor load requests for cross-site scripting attempts.
|<<downloads.location.directory,downloads.location.directory>>|Directory to save downloads to.
|<<downloads.location.prompt,downloads.location.prompt>>|Prompt the user for the download location.
@ -203,7 +216,8 @@
|<<fonts.messages.warning,fonts.messages.warning>>|Font used for warning messages.
|<<fonts.prompts,fonts.prompts>>|Font used for prompts.
|<<fonts.statusbar,fonts.statusbar>>|Font used in the statusbar.
|<<fonts.tabs,fonts.tabs>>|Font used in the tab bar.
|<<fonts.tabs.selected,fonts.tabs.selected>>|Font used for selected tabs.
|<<fonts.tabs.unselected,fonts.tabs.unselected>>|Font used for unselected tabs.
|<<fonts.web.family.cursive,fonts.web.family.cursive>>|Font family for cursive fonts.
|<<fonts.web.family.fantasy,fonts.web.family.fantasy>>|Font family for fantasy fonts.
|<<fonts.web.family.fixed,fonts.web.family.fixed>>|Font family for fixed fonts.
@ -225,7 +239,9 @@
|<<hints.min_chars,hints.min_chars>>|Minimum number of characters used for hint strings.
|<<hints.mode,hints.mode>>|Mode to use for hints.
|<<hints.next_regexes,hints.next_regexes>>|Comma-separated list of regular expressions to use for 'next' links.
|<<hints.padding,hints.padding>>|Padding (in pixels) for hints.
|<<hints.prev_regexes,hints.prev_regexes>>|Comma-separated list of regular expressions to use for 'prev' links.
|<<hints.radius,hints.radius>>|Rounding radius (in pixels) for the edges of hints.
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|<<hints.selectors,hints.selectors>>|CSS selectors used to determine which elements on a page should have hints.
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
@ -238,16 +254,19 @@
|<<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.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.
|<<input.partial_timeout,input.partial_timeout>>|Timeout (in milliseconds) for partially typed key bindings.
|<<input.rocker_gestures,input.rocker_gestures>>|Enable Opera-like mouse rocker gestures.
|<<input.spatial_navigation,input.spatial_navigation>>|Enable spatial navigation.
|<<keyhint.blacklist,keyhint.blacklist>>|Keychains that shouldn't be shown in the keyhint dialog.
|<<keyhint.delay,keyhint.delay>>|Time (in milliseconds) from pressing a key to seeing the keyhint dialog.
|<<keyhint.radius,keyhint.radius>>|Rounding radius (in pixels) for the edges of the keyhint dialog.
|<<logging.level.console,logging.level.console>>|Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used.
|<<logging.level.ram,logging.level.ram>>|Level for in-memory logs.
|<<messages.timeout,messages.timeout>>|Duration (in milliseconds) to show messages in the statusbar for.
|<<new_instance_open_target,new_instance_open_target>>|How to open links in an existing instance if a new one is launched.
|<<new_instance_open_target_window,new_instance_open_target_window>>|Which window to choose when opening links as new tabs.
|<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in upload/download prompts.
|<<prompt.filebrowser,prompt.filebrowser>>|Show a filebrowser in download prompts.
|<<prompt.radius,prompt.radius>>|Rounding radius (in pixels) for the edges of prompts.
|<<qt.args,qt.args>>|Additional arguments to pass to Qt, without leading `--`.
|<<qt.force_platform,qt.force_platform>>|Force a Qt platform to use.
@ -256,16 +275,17 @@
|<<qt.highdpi,qt.highdpi>>|Turn on Qt HighDPI scaling.
|<<qt.low_end_device_mode,qt.low_end_device_mode>>|When to use Chromium's low-end device mode.
|<<qt.process_model,qt.process_model>>|Which Chromium process model to use.
|<<scrolling.bar,scrolling.bar>>|When to show the scrollbar.
|<<scrolling.bar,scrolling.bar>>|When/how to show the scrollbar.
|<<scrolling.smooth,scrolling.smooth>>|Enable smooth scrolling for web pages.
|<<search.ignore_case,search.ignore_case>>|When to find text on a page case-insensitively.
|<<search.incremental,search.incremental>>|Find text on a page incrementally, renewing the search for each typed character.
|<<search.wrap,search.wrap>>|Wrap around at the top and bottom of the page when advancing through text matches using `:search-next` and `:search-prev`.
|<<session.default_name,session.default_name>>|Name of the session to save by default.
|<<session.lazy_restore,session.lazy_restore>>|Load a restored tab as soon as it takes focus.
|<<spellcheck.languages,spellcheck.languages>>|Languages to use for spell checking.
|<<statusbar.hide,statusbar.hide>>|Hide the statusbar unless a message is shown.
|<<statusbar.padding,statusbar.padding>>|Padding (in pixels) for the statusbar.
|<<statusbar.position,statusbar.position>>|Position of the status bar.
|<<statusbar.show,statusbar.show>>|When to show the statusbar.
|<<statusbar.widgets,statusbar.widgets>>|List of widgets displayed in the statusbar.
|<<tabs.background,tabs.background>>|Open new tabs (middleclick/ctrl+click) in the background.
|<<tabs.close_mouse_button,tabs.close_mouse_button>>|Mouse button with which to close tabs.
@ -441,6 +461,7 @@ Default:
* +pass:[J]+: +pass:[scroll down]+
* +pass:[K]+: +pass:[scroll up]+
* +pass:[L]+: +pass:[scroll right]+
* +pass:[V]+: +pass:[toggle-selection --line]+
* +pass:[Y]+: +pass:[yank selection -s]+
* +pass:[[]+: +pass:[move-to-start-of-prev-block]+
* +pass:[]]+: +pass:[move-to-start-of-next-block]+
@ -585,6 +606,7 @@ Default:
* +pass:[Sq]+: +pass:[open qute://bookmarks]+
* +pass:[Ss]+: +pass:[open qute://settings]+
* +pass:[T]+: +pass:[tab-focus]+
* +pass:[V]+: +pass:[enter-mode caret ;; toggle-selection --line]+
* +pass:[ZQ]+: +pass:[quit]+
* +pass:[ZZ]+: +pass:[quit --save]+
* +pass:[[[]+: +pass:[navigate prev]+
@ -632,6 +654,9 @@ Default:
* +pass:[sk]+: +pass:[set-cmd-text -s :bind]+
* +pass:[sl]+: +pass:[set-cmd-text -s :set -t]+
* +pass:[ss]+: +pass:[set-cmd-text -s :set]+
* +pass:[tCH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[tCh]+: +pass:[config-cycle -p -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[tCu]+: +pass:[config-cycle -p -u {url} content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[tIH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.images ;; reload]+
* +pass:[tIh]+: +pass:[config-cycle -p -u *://{url:host}/* content.images ;; reload]+
* +pass:[tIu]+: +pass:[config-cycle -p -u {url} content.images ;; reload]+
@ -641,6 +666,9 @@ Default:
* +pass:[tSH]+: +pass:[config-cycle -p -u *://*.{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tSh]+: +pass:[config-cycle -p -u *://{url:host}/* content.javascript.enabled ;; reload]+
* +pass:[tSu]+: +pass:[config-cycle -p -u {url} content.javascript.enabled ;; reload]+
* +pass:[tcH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[tch]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[tcu]+: +pass:[config-cycle -p -t -u {url} content.cookies.accept all no-3rdparty never ;; reload]+
* +pass:[th]+: +pass:[back -t]+
* +pass:[tiH]+: +pass:[config-cycle -p -t -u *://*.{url:host}/* content.images ;; reload]+
* +pass:[tih]+: +pass:[config-cycle -p -t -u *://{url:host}/* content.images ;; reload]+
@ -734,6 +762,7 @@ Default:
- +pass:[&lt;Ctrl-6&gt;]+: +pass:[&lt;Ctrl-^&gt;]+
- +pass:[&lt;Ctrl-Enter&gt;]+: +pass:[&lt;Ctrl-Return&gt;]+
- +pass:[&lt;Ctrl-I&gt;]+: +pass:[&lt;Tab&gt;]+
- +pass:[&lt;Ctrl-J&gt;]+: +pass:[&lt;Return&gt;]+
- +pass:[&lt;Ctrl-M&gt;]+: +pass:[&lt;Return&gt;]+
- +pass:[&lt;Ctrl-[&gt;]+: +pass:[&lt;Escape&gt;]+
@ -866,6 +895,24 @@ Type: <<types,QssColor>>
Default: +pass:[white]+
[[colors.contextmenu.disabled.bg]]
=== colors.contextmenu.disabled.bg
Background color of disabled items in the context menu.
If set to null, the Qt default is used.
Type: <<types,QssColor>>
Default: empty
[[colors.contextmenu.disabled.fg]]
=== colors.contextmenu.disabled.fg
Foreground color of disabled items in the context menu.
If set to null, the Qt default is used.
Type: <<types,QssColor>>
Default: empty
[[colors.contextmenu.menu.bg]]
=== colors.contextmenu.menu.bg
Background color of the context menu.
@ -1508,6 +1555,161 @@ Type: <<types,QtColor>>
Default: +pass:[white]+
[[colors.webpage.darkmode.algorithm]]
=== colors.webpage.darkmode.algorithm
Which algorithm to use for modifying how colors are rendered with darkmode.
This setting requires a restart.
Type: <<types,String>>
Valid values:
* +lightness-cielab+: Modify colors by converting them to CIELAB color space and inverting the L value.
* +lightness-hsl+: Modify colors by converting them to the HSL color space and inverting the lightness (i.e. the "L" in HSL).
* +brightness-rgb+: Modify colors by subtracting each of r, g, and b from their maximum value.
Default: +pass:[lightness-cielab]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.contrast]]
=== colors.webpage.darkmode.contrast
Contrast for dark mode.
This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
This setting requires a restart.
Type: <<types,Float>>
Default: +pass:[0.0]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.enabled]]
=== colors.webpage.darkmode.enabled
Render all web contents using a dark theme.
Example configurations from Chromium's `chrome://flags`:
- "With simple HSL/CIELAB/RGB-based inversion": Set
`colors.webpage.darkmode.algorithm` accordingly.
- "With selective image inversion": Set
`colors.webpage.darkmode.policy.images` to `smart`.
- "With selective inversion of non-image elements": Set
`colors.webpage.darkmode.threshold.text` to 150 and
`colors.webpage.darkmode.threshold.background` to 205.
- "With selective inversion of everything": Combines the two variants
above.
This setting requires a restart.
Type: <<types,Bool>>
Default: +pass:[false]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.grayscale.all]]
=== colors.webpage.darkmode.grayscale.all
Render all colors as grayscale.
This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
This setting requires a restart.
Type: <<types,Bool>>
Default: +pass:[false]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.grayscale.images]]
=== colors.webpage.darkmode.grayscale.images
Desaturation factor for images in dark mode.
If set to 0, images are left as-is. If set to 1, images are completely grayscale. Values between 0 and 1 desaturate the colors accordingly.
This setting requires a restart.
Type: <<types,Float>>
Default: +pass:[0.0]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.policy.images]]
=== colors.webpage.darkmode.policy.images
Which images to apply dark mode to.
WARNING: On Qt 5.15.0, this setting can cause frequent renderer process crashes due to a https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt].
This setting requires a restart.
Type: <<types,String>>
Valid values:
* +always+: Apply dark mode filter to all images.
* +never+: Never apply dark mode filter to any images.
* +smart+: Apply dark mode based on image content.
Default: +pass:[never]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.policy.page]]
=== colors.webpage.darkmode.policy.page
Which pages to apply dark mode to.
This setting requires a restart.
Type: <<types,String>>
Valid values:
* +always+: Apply dark mode filter to all frames, regardless of content.
* +smart+: Apply dark mode filter to frames based on background color.
Default: +pass:[smart]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.threshold.background]]
=== colors.webpage.darkmode.threshold.background
Threshold for inverting background elements with dark mode.
Background elements with brightness above this threshold will be inverted, and below it will be left as in the original, non-dark-mode page. Set to 256 to never invert the color or to 0 to always invert it.
Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
This setting requires a restart.
Type: <<types,Int>>
Default: +pass:[0]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.darkmode.threshold.text]]
=== colors.webpage.darkmode.threshold.text
Threshold for inverting text with dark mode.
Text colors with brightness below this threshold will be inverted, and above it will be left as in the original, non-dark-mode page. Set to 256 to always invert text color or to 0 to never invert text color.
This setting requires a restart.
Type: <<types,Int>>
Default: +pass:[256]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
On QtWebKit, this setting is unavailable.
[[colors.webpage.prefers_color_scheme_dark]]
=== colors.webpage.prefers_color_scheme_dark
Force `prefers-color-scheme: dark` colors for websites.
@ -1800,6 +2002,10 @@ This setting is only available with the QtWebEngine backend.
[[content.cookies.accept]]
=== content.cookies.accept
Which cookies to accept.
With QtWebEngine, this setting also controls other features with tracking capabilities similar to those of cookies; including IndexedDB, DOM storage, filesystem API, service workers, and AppCache.
Note that with QtWebKit, only `all` and `never` are supported as per-domain values. Setting `no-3rdparty` or `no-unknown-3rdparty` per-domain on QtWebKit will have the same effect as `all`.
This setting supports URL patterns.
Type: <<types,String>>
@ -1857,7 +2063,7 @@ This setting supports URL patterns.
Type: <<types,Bool>>
Default: +pass:[false]+
Default: +pass:[true]+
On QtWebEngine, this setting requires Qt 5.12 or newer.
@ -1874,6 +2080,23 @@ Default: +pass:[false]+
This setting is only available with the QtWebKit backend.
[[content.fullscreen.overlay_timeout]]
=== content.fullscreen.overlay_timeout
Set fullscreen notification overlay timeout in milliseconds.
If set to 0, no overlay will be displayed.
Type: <<types,Int>>
Default: +pass:[3000]+
[[content.fullscreen.window]]
=== content.fullscreen.window
Limit fullscreen to the browser window (does not expand to fill the screen).
Type: <<types,Bool>>
Default: +pass:[false]+
[[content.geolocation]]
=== content.geolocation
Allow websites to request geolocations.
@ -1957,7 +2180,9 @@ The following placeholders are defined:
The default value is equal to the unchanged user agent of
QtWebKit/QtWebEngine.
Note that the value read from JavaScript is always the global value.
Note that the value read from JavaScript is always the global value. With
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
to JavaScript requires a restart.
This setting supports URL patterns.
@ -2305,6 +2530,26 @@ Valid values:
Default: +pass:[ask]+
[[content.unknown_url_scheme_policy]]
=== content.unknown_url_scheme_policy
How navigation requests to URLs with unknown schemes are handled.
This setting supports URL patterns.
Type: <<types,String>>
Valid values:
* +disallow+: Disallows all navigation requests to URLs with unknown schemes.
* +allow-from-user-interaction+: Allows navigation requests to URLs with unknown schemes that are issued from user-interaction (like a mouse-click), whereas other navigation requests (for example from JavaScript) are suppressed.
* +allow-all+: Allows all navigation requests to URLs with unknown schemes.
Default: +pass:[allow-from-user-interaction]+
On QtWebEngine, this setting requires Qt 5.11 or newer.
On QtWebKit, this setting is unavailable.
[[content.user_stylesheets]]
=== content.user_stylesheets
List of user stylesheet filenames to use.
@ -2344,14 +2589,6 @@ On QtWebEngine, this setting requires Qt 5.9.2 or newer.
On QtWebKit, this setting is unavailable.
[[content.windowed_fullscreen]]
=== content.windowed_fullscreen
Limit fullscreen to the browser window (does not expand to fill the screen).
Type: <<types,Bool>>
Default: +pass:[false]+
[[content.xss_auditing]]
=== content.xss_auditing
Monitor load requests for cross-site scripting attempts.
@ -2495,7 +2732,7 @@ Default: empty
=== fonts.debug_console
Font used for the debugging console.
Type: <<types,QtFont>>
Type: <<types,Font>>
Default: +pass:[default_size default_family]+
@ -2583,11 +2820,19 @@ Type: <<types,Font>>
Default: +pass:[default_size default_family]+
[[fonts.tabs]]
=== fonts.tabs
Font used in the tab bar.
[[fonts.tabs.selected]]
=== fonts.tabs.selected
Font used for selected tabs.
Type: <<types,QtFont>>
Type: <<types,Font>>
Default: +pass:[default_size default_family]+
[[fonts.tabs.unselected]]
=== fonts.tabs.unselected
Font used for unselected tabs.
Type: <<types,Font>>
Default: +pass:[default_size default_family]+
@ -2786,6 +3031,19 @@ Default:
- +pass:[\b(&gt;&gt;|»)\b]+
- +pass:[\bcontinue\b]+
[[hints.padding]]
=== hints.padding
Padding (in pixels) for hints.
Type: <<types,Padding>>
Default:
- +pass:[bottom]+: +pass:[0]+
- +pass:[left]+: +pass:[3]+
- +pass:[right]+: +pass:[3]+
- +pass:[top]+: +pass:[0]+
[[hints.prev_regexes]]
=== hints.prev_regexes
Comma-separated list of regular expressions to use for 'prev' links.
@ -2800,6 +3058,14 @@ Default:
- +pass:[\b[&lt;←≪]\b]+
- +pass:[\b(&lt;&lt;|«)\b]+
[[hints.radius]]
=== hints.radius
Rounding radius (in pixels) for the edges of hints.
Type: <<types,Int>>
Default: +pass:[3]+
[[hints.scatter]]
=== hints.scatter
Scatter hint key chains (like Vimium) or not (like dwb).
@ -2971,6 +3237,23 @@ Type: <<types,Bool>>
Default: +pass:[true]+
[[input.mouse.back_forward_buttons]]
=== input.mouse.back_forward_buttons
Enable back and forward buttons on the mouse.
Type: <<types,Bool>>
Default: +pass:[true]+
[[input.mouse.rocker_gestures]]
=== input.mouse.rocker_gestures
Enable Opera-like mouse rocker gestures.
This disables the context menu.
Type: <<types,Bool>>
Default: +pass:[false]+
[[input.partial_timeout]]
=== input.partial_timeout
Timeout (in milliseconds) for partially typed key bindings.
@ -2980,15 +3263,6 @@ Type: <<types,Int>>
Default: +pass:[5000]+
[[input.rocker_gestures]]
=== input.rocker_gestures
Enable Opera-like mouse rocker gestures.
This disables the context menu.
Type: <<types,Bool>>
Default: +pass:[false]+
[[input.spatial_navigation]]
=== input.spatial_navigation
Enable spatial navigation.
@ -3025,6 +3299,40 @@ Type: <<types,Int>>
Default: +pass:[6]+
[[logging.level.console]]
=== logging.level.console
Level for console (stdout/stderr) logs. Ignored if the `--loglevel` or `--debug` CLI flags are used.
Type: <<types,LogLevel>>
Valid values:
* +vdebug+
* +debug+
* +info+
* +warning+
* +error+
* +critical+
Default: +pass:[info]+
[[logging.level.ram]]
=== logging.level.ram
Level for in-memory logs.
Type: <<types,LogLevel>>
Valid values:
* +vdebug+
* +debug+
* +info+
* +warning+
* +error+
* +critical+
Default: +pass:[debug]+
[[messages.timeout]]
=== messages.timeout
Duration (in milliseconds) to show messages in the statusbar for.
@ -3070,7 +3378,7 @@ Default: +pass:[last-focused]+
[[prompt.filebrowser]]
=== prompt.filebrowser
Show a filebrowser in upload/download prompts.
Show a filebrowser in download prompts.
Type: <<types,Bool>>
@ -3186,7 +3494,7 @@ This setting is only available with the QtWebEngine backend.
[[scrolling.bar]]
=== scrolling.bar
When to show the scrollbar.
When/how to show the scrollbar.
Type: <<types,String>>
@ -3195,8 +3503,9 @@ Valid values:
* +always+: Always show the scrollbar.
* +never+: Never show the scrollbar.
* +when-searching+: Show the scrollbar when searching for text in the webpage. With the QtWebKit backend, this is equal to `never`.
* +overlay+: Show an overlay scrollbar. With Qt < 5.11, this is equal to `when-searching`; with the QtWebKit backend, this is equal to `never`. Enabling/disabling overlay scrollbars requires a restart.
Default: +pass:[when-searching]+
Default: +pass:[overlay]+
[[scrolling.smooth]]
=== scrolling.smooth
@ -3231,6 +3540,16 @@ Type: <<types,Bool>>
Default: +pass:[true]+
[[search.wrap]]
=== search.wrap
Wrap around at the top and bottom of the page when advancing through text matches using `:search-next` and `:search-prev`.
Type: <<types,Bool>>
Default: +pass:[true]+
On QtWebEngine, this setting requires Qt 5.14 or newer.
[[session.default_name]]
=== session.default_name
Name of the session to save by default.
@ -3307,14 +3626,6 @@ On QtWebEngine, this setting requires Qt 5.8 or newer.
On QtWebKit, this setting is unavailable.
[[statusbar.hide]]
=== statusbar.hide
Hide the statusbar unless a message is shown.
Type: <<types,Bool>>
Default: +pass:[false]+
[[statusbar.padding]]
=== statusbar.padding
Padding (in pixels) for the statusbar.
@ -3341,6 +3652,20 @@ Valid values:
Default: +pass:[bottom]+
[[statusbar.show]]
=== statusbar.show
When to show the statusbar.
Type: <<types,String>>
Valid values:
* +always+: Always show the statusbar.
* +never+: Always hide the statusbar.
* +in-mode+: Show the statusbar when in modes other than normal mode.
Default: +pass:[always]+
[[statusbar.widgets]]
=== statusbar.widgets
List of widgets displayed in the statusbar.
@ -3776,8 +4101,28 @@ Default: +pass:[false]+
[[url.searchengines]]
=== url.searchengines
Search engines which can be used via the address bar.
Maps a search engine name (such as `DEFAULT`, or `ddg`) to a URL with a `{}` placeholder. The placeholder will be replaced by the search term, use `{{` and `}}` for literal `{`/`}` signs.
The search engine named `DEFAULT` is used when `url.auto_search` is turned on and something else than a URL was entered to be opened. Other search engines can be used by prepending the search engine name to the search term, e.g. `:open google qutebrowser`.
Maps a search engine name (such as `DEFAULT`, or `ddg`) to a URL with a
`{}` placeholder. The placeholder will be replaced by the search term, use
`{{` and `}}` for literal `{`/`}` braces.
The following further placeholds are defined to configure how special
characters in the search terms are replaced by safe characters (called
'quoting'):
* `{}` and `{semiquoted}` quote everything except slashes; this is the most
sensible choice for almost all search engines (for the search term
`slash/and&amp` this placeholder expands to `slash/and%26amp`).
* `{quoted}` quotes all characters (for `slash/and&amp` this placeholder
expands to `slash%2Fand%26amp`).
* `{unquoted}` quotes nothing (for `slash/and&amp` this placeholder
expands to `slash/and&amp`).
The search engine named `DEFAULT` is used when `url.auto_search` is turned
on and something else than a URL was entered to be opened. Other search
engines can be used by prepending the search engine name to the search
term, e.g. `:open google qutebrowser`.
Type: <<types,Dict>>
@ -3917,6 +4262,7 @@ Lists with duplicate flags are invalid. Each item is checked against the valid v
When setting from a string, pass a json-like list, e.g. `["one", "two"]`.
|ListOrValue|A list of values, or a single value.
|LogLevel|A logging level.
|NewTabPosition|How new tabs are positioned.
|Padding|Setting for paddings around elements.
|Perc|A percentage.
@ -3929,9 +4275,6 @@ A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/
|QtColor|A color value.
A value can be in one of the following formats: * `#RGB`/`#RRGGBB`/`#RRRGGGBBB`/`#RRRRGGGGBBBB` * An SVG color name as specified in http://www.w3.org/TR/SVG/types.html#ColorKeywords[the W3C specification]. * transparent (no color) * `rgb(r, g, b)` / `rgba(r, g, b, a)` (values 0-255 or percentages) * `hsv(h, s, v)` / `hsva(h, s, v, a)` (values 0-255, hue 0-359)
|QtFont|A font family, with optional style/weight/size.
* Style: `normal`/`italic`/`oblique` * Weight: `normal`, `bold`, `100`..`900` * Size: _number_ `px`/`pt`
|Regex|A regular expression.
When setting from `config.py`, both a string or a `re.compile(...)` object are valid.

View File

@ -256,7 +256,7 @@ mailinglist] to get notified on new releases). You can install a newer version
without uninstalling the older one.
The binary release ships with a QtWebEngine built without proprietary codec
support. To get support for e.g. h264/h265 videos, you'll need to build
support. To get support for e.g. h264/mp4 videos, you'll need to build
QtWebEngine from source yourself with support for that enabled.
https://chocolatey.org/packages/qutebrowser[Chocolatey package]
@ -299,7 +299,7 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[qutebrows
mailinglist] to get notified on new releases).
The binary release ships with a QtWebEngine built without proprietary codec
support. To get support for e.g. h264/h265 videos, you'll need to build
support. To get support for e.g. h264/mp4 videos, you'll need to build
QtWebEngine from source yourself with support for that enabled.
This binary is also available through the

View File

@ -67,7 +67,7 @@ show it.
=== debug arguments
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
Set loglevel
Override the configured console loglevel
*--logfilter* 'LOGFILTER'::
Comma-separated list of things to be logged to the debug log on stdout.

View File

@ -34,6 +34,27 @@ is available in the repositories:
# apt-get install python3-pyqt5-dbg python3-pyqt5.qtwebkit-dbg python3-dbg libqt5webkit5-dbg
----
Fedora
^^^^^^
For Fedora you first need to install the dnf/yum-utils:
----
# dnf install dnf-utils
----
Or:
----
# yum install yum-utils
----
Then install the needed debuginfo packages:
----
# debuginfo-install python3 qt5-qtwebengine python3-qt5-webengine python3-qt5-base python-qt5 python3-qt5 python3-qt5-webkit
----
Archlinux
^^^^^^^^^

View File

@ -44,6 +44,10 @@
</content_rating>
<releases>
<!-- Add new releases here -->
<release version="1.12.0" date="2020-06-01"/>
<release version="1.11.1" date="2020-05-07"/>
<release version="1.11.0" date="2020-04-27"/>
<release version="1.10.2" date="2020-04-17"/>
<release version="1.10.1" date="2020-02-15"/>
<release version="1.10.0" date="2020-02-02"/>
<release version="1.9.0" date="2020-01-08"/>

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
check-manifest==0.41
pep517==0.8.1
toml==0.10.0
check-manifest==0.42
pep517==0.8.2
toml==0.10.1

View File

@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2019.11.28
certifi==2020.4.5.2
chardet==3.0.4
codecov==2.0.16
coverage==5.0.3
codecov==2.1.4
coverage==5.1
idna==2.9
requests==2.23.0
urllib3==1.25.8
urllib3==1.25.9

View File

@ -1,27 +1,26 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
bump2version==1.0.0
certifi==2019.11.28
certifi==2020.4.5.2
cffi==1.14.0
chardet==3.0.4
colorama==0.4.3
cryptography==2.8
cryptography==2.9.2
cssutils==1.0.2
github3.py==1.3.0
hunter==3.1.3
idna==2.9
jwcrypto==0.7
lxml==4.5.0
manhole==1.6.0
packaging==20.3
packaging==20.4
pycparser==2.20
Pympler==0.8
pyparsing==2.4.6
PyQt-builder==1.2.0
pyparsing==2.4.7
PyQt-builder==1.4.0
python-dateutil==2.8.1
requests==2.23.0
sip==5.1.1
six==1.14.0
toml==0.10.0
sip==5.3.0
six==1.15.0
toml==0.10.1
uritemplate==3.0.1
urllib3==1.25.8
urllib3==1.25.9

View File

@ -4,5 +4,4 @@ pympler
github3.py
bump2version
requests
lxml
pyqt-builder

View File

@ -1,11 +1,10 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==19.3.0
entrypoints==0.3
flake8==3.7.9
flake8==3.8.2
flake8-bugbear==20.1.4
flake8-builtins==1.4.2
flake8-comprehensions==3.2.2
flake8-builtins==1.5.3
flake8-comprehensions==3.2.3
flake8-copyright==0.2.2
flake8-debugger==3.2.1
flake8-deprecated==1.3
@ -14,12 +13,12 @@ flake8-future-import==0.4.6
flake8-mock==0.3
flake8-polyfill==1.0.2
flake8-string-format==0.3.0
flake8-tidy-imports==4.0.0
flake8-tidy-imports==4.1.0
flake8-tuple==0.4.1
mccabe==0.6.1
pep8-naming==0.9.1
pycodestyle==2.5.0
pep8-naming==0.10.0
pycodestyle==2.6.0
pydocstyle==5.0.2
pyflakes==2.1.1
six==1.14.0
pyflakes==2.2.0
six==1.15.0
snowballstemmer==2.0.0

View File

@ -1,9 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
mypy==0.770
mypy==0.780
mypy-extensions==0.4.3
# PyQt5==5.11.3
# PyQt5-sip==4.19.19
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5_stubs
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5_stubs
typed-ast==1.4.1
typing-extensions==3.7.4.1
typing-extensions==3.7.4.2

View File

@ -1,6 +1,5 @@
mypy
-e git+https://github.com/qutebrowser/PyQt5-stubs.git@wip#egg=PyQt5-stubs
-e git+https://github.com/stlehmann/PyQt5-stubs.git@master#egg=PyQt5-stubs
# remove @commit-id for scm installs
#@ replace: @.*# @wip#
#@ ignore: PyQt5, PyQt5-sip
#@ replace: @.*# @master#

View File

@ -1,8 +1,8 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
appdirs==1.4.3
packaging==20.3
pyparsing==2.4.6
setuptools==46.0.0
six==1.14.0
appdirs==1.4.4
packaging==20.4
pyparsing==2.4.7
setuptools==47.1.1
six==1.15.0
wheel==0.34.2

View File

@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
altgraph==0.17
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=pyinstaller

View File

@ -1,23 +1,23 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==2.3.3
certifi==2019.11.28
astroid==2.3.3 # rq.filter: < 2.4
certifi==2020.4.5.2
cffi==1.14.0
chardet==3.0.4
cryptography==2.8
cryptography==2.9.2
github3.py==1.3.0
idna==2.9
isort==4.3.21
jwcrypto==0.7
lazy-object-proxy==1.4.3
lazy-object-proxy==1.5.0
mccabe==0.6.1
pycparser==2.20
pylint==2.4.4
pylint==2.4.4 # rq.filter: < 2.5
python-dateutil==2.8.1
./scripts/dev/pylint_checkers
requests==2.23.0
six==1.14.0
six==1.15.0
typed-ast==1.4.1 ; python_version<"3.8"
uritemplate==3.0.1
urllib3==1.25.8
urllib3==1.25.9
wrapt==1.12.1

View File

@ -1,4 +1,4 @@
pylint
pylint<2.5
./scripts/dev/pylint_checkers
requests
github3.py
@ -6,3 +6,5 @@ github3.py
# 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

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.12.3 # rq.filter: < 5.13
PyQt5-sip==12.7.1
PyQt5-sip==12.8.0
PyQtWebEngine==5.12.1 # rq.filter: < 5.13

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.13.2 # rq.filter: < 5.14
PyQt5-sip==12.7.1
PyQt5-sip==12.8.0
PyQtWebEngine==5.13.2 # rq.filter: < 5.14

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.14.1 # rq.filter: < 5.15
PyQt5-sip==12.7.1
PyQt5==5.14.2 # rq.filter: < 5.15
PyQt5-sip==12.8.0
PyQtWebEngine==5.14.0 # rq.filter: < 5.15

View File

@ -0,0 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.15.0 # rq.filter: < 6
PyQt5-sip==12.8.0
PyQtWebEngine==5.15.0 # rq.filter: < 6

View File

@ -0,0 +1,4 @@
#@ filter: PyQt5 < 6
#@ filter: PyQtWebEngine < 6
PyQt5 >= 5.15, < 6
PyQtWebEngine >= 5.15, < 6

View File

@ -1,5 +1,5 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.14.1
PyQt5-sip==12.7.1
PyQtWebEngine==5.14.0
PyQt5==5.15.0
PyQt5-sip==12.8.0
PyQtWebEngine==5.15.0

View File

@ -2,25 +2,25 @@
alabaster==0.7.12
Babel==2.8.0
certifi==2019.11.28
certifi==2020.4.5.2
chardet==3.0.4
docutils==0.16
idna==2.9
imagesize==1.2.0
Jinja2==2.11.1
Jinja2==2.11.2
MarkupSafe==1.1.1
packaging==20.3
packaging==20.4
Pygments==2.6.1
pyparsing==2.4.6
pytz==2019.3
pyparsing==2.4.7
pytz==2020.1
requests==2.23.0
six==1.14.0
six==1.15.0
snowballstemmer==2.0.0
Sphinx==2.4.4
Sphinx==3.1.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==1.0.3
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.4
urllib3==1.25.8
urllib3==1.25.9

View File

@ -1,47 +1,47 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==19.3.0
beautifulsoup4==4.8.2
beautifulsoup4==4.9.1
cheroot==8.3.0
click==7.1.1
click==7.1.2
# colorama==0.4.3
coverage==5.0.3
EasyProcess==0.2.10
Flask==1.1.1
coverage==5.1
EasyProcess==0.3
Flask==1.1.2
glob2==0.7
hunter==3.1.3
hypothesis==5.6.0
hypothesis==5.16.0
itsdangerous==1.1.0
jaraco.functools==3.0.0 ; python_version>="3.6"
# Jinja2==2.11.1
Mako==1.1.2
jaraco.functools==3.0.1 ; python_version>="3.6"
# Jinja2==2.11.2
Mako==1.1.3
manhole==1.6.0
# MarkupSafe==1.1.1
more-itertools==8.2.0
packaging==20.3
more-itertools==8.3.0
packaging==20.4
parse==1.15.0
parse-type==0.5.2
pluggy==0.13.1
py==1.8.1
py-cpuinfo==5.0.0
Pygments==2.6.1
pyparsing==2.4.6
pytest==5.4.1
pytest-bdd==3.2.1
pyparsing==2.4.7
pytest==5.4.3
pytest-bdd==3.4.0
pytest-benchmark==3.2.3
pytest-cov==2.8.1
pytest-cov==2.9.0
pytest-instafail==0.4.1.post0
pytest-mock==2.0.0
pytest-mock==3.1.1
pytest-qt==3.3.0
pytest-repeat==0.8.0
pytest-rerunfailures==9.0
pytest-travis-fold==1.3.0
pytest-xvfb==1.2.0
PyVirtualDisplay==0.2.5
six==1.14.0
sortedcontainers==2.1.0
soupsieve==2.0
vulture==1.3
wcwidth==0.1.8
Werkzeug==1.0.0
PyVirtualDisplay==0.2.5 # rq.filter: < 1.0
six==1.15.0
sortedcontainers==2.2.2
soupsieve==2.0.1
vulture==1.5
wcwidth==0.2.4
Werkzeug==1.0.1
jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0

View File

@ -2,7 +2,6 @@ beautifulsoup4
cheroot
coverage
Flask
hunter
hypothesis
pytest
pytest-bdd
@ -11,13 +10,24 @@ pytest-cov
pytest-instafail
pytest-mock
pytest-qt
pytest-repeat
pytest-rerunfailures
pytest-travis-fold
pytest-xvfb
# https://github.com/The-Compiler/pytest-xvfb/issues/22
PyVirtualDisplay < 1.0
## optional:
# To test :debug-trace, gets skipped if hunter is not installed
hunter
# To test scripts/dev/run_vulture.py which is not part of the release tarball
vulture
# For colored pytest output (though also a direct qutebrowser dependency))
pygments
# Output folding on Travis
pytest-travis-fold
# --repeat switch (used to manually repeat tests)
pytest-repeat
#@ markers: jaraco.functools python_version>="3.6"
#@ add: jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
#@ ignore: Jinja2, MarkupSafe, colorama
#@ filter: PyVirtualDisplay < 1.0

View File

@ -1,15 +1,15 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
appdirs==1.4.3
appdirs==1.4.4
distlib==0.3.0
filelock==3.0.12
packaging==20.3
packaging==20.4
pluggy==0.13.1
py==1.8.1
pyparsing==2.4.6
six==1.14.0
toml==0.10.0
tox==3.14.5
pyparsing==2.4.7
six==1.15.0
toml==0.10.1
tox==3.15.2
tox-pip-version==0.0.7
tox-venv==0.4.0
virtualenv==20.0.10
virtualenv==20.0.21

View File

@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
vulture==1.3
vulture==1.5

View File

@ -14,7 +14,6 @@ https://ocefpaf.github.io/python4oceanographers/blog/2014/05/19/doi2bibtex/
import os
import sys
import shutil
import re
from collections import Counter
from urllib import parse as url_parse

View File

@ -358,11 +358,13 @@ cat <<EOF
if (isVisible(input) && (input.type == "text" || input.type == "email")) {
input.focus();
input.value = "$(javascript_escape "${username}")";
input.dispatchEvent(new Event('change'));
input.blur();
}
if (input.type == "password") {
input.focus();
input.value = "$(javascript_escape "${password}")";
input.dispatchEvent(new Event('change'));
input.blur();
}
}

View File

@ -27,6 +27,9 @@ USAGE = """The domain of the site has to be in the name of the Bitwarden entry,
"websites/github.com". The login information is inserted by emulating key events using qutebrowser's fake-key command in this manner:
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
If enabled, with the `--totp` flag, it will also move the TOTP code to the
clipboard, much like the Firefox add-on.
You must log into Bitwarden CLI using `bw login` prior to use of this script.
The session key will be stored using keyctl for the number of seconds passed to
the --auto-lock option.
@ -34,18 +37,17 @@ the --auto-lock option.
To use in qutebrowser, run: `spawn --userscript qute-bitwarden`
"""
EPILOG = """Dependencies: tldextract (Python 3 module), Bitwarden CLI (1.7.4 is
known to work but older versions may well also work)
EPILOG = """Dependencies: tldextract (Python 3 module), pyperclip (optional
Python module, used for TOTP codes), Bitwarden CLI (1.7.4 is known to work
but older versions may well also work)
WARNING: The login details are viewable as plaintext in qutebrowser's debug log
(qute://log) and might be shared if you decide to submit a crash report!"""
import argparse
import enum
import fnmatch
import functools
import os
import re
import shlex
import subprocess
import sys
@ -62,6 +64,8 @@ argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu -i
help='Invocation used to execute a dmenu-provider')
argument_parser.add_argument('--no-insert-mode', '-n', dest='insert_mode', action='store_false',
help="Don't automatically enter insert mode")
argument_parser.add_argument('--totp', '-t', action='store_true',
help="Copy TOTP key to clipboard")
argument_parser.add_argument('--io-encoding', '-i', default='UTF-8',
help='Encoding used to communicate with subprocesses')
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
@ -73,6 +77,8 @@ 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')
group.add_argument('--totp-only', '-T',
action='store_true', help='Only insert totp code')
stderr = functools.partial(print, file=sys.stderr)
@ -158,6 +164,26 @@ def pass_(domain, encoding, auto_lock):
return out
def get_totp_code(selection_id, domain_name, encoding, auto_lock):
session_key = get_session_key(auto_lock)
process = subprocess.run(
['bw', 'get', 'totp', '--session', session_key, selection_id],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
err = process.stderr.decode(encoding).strip()
if err:
# domain_name instead of selection_id to make it more user-friendly
msg = 'Bitwarden CLI returned for {:s} - {:s}'.format(domain_name, err)
stderr(msg)
return '[]'
out = process.stdout.decode(encoding).strip()
return out
def dmenu(items, invocation, encoding):
command = shlex.split(invocation)
process = subprocess.run(command, input='\n'.join(
@ -227,11 +253,22 @@ def main(arguments):
username = selection['login']['username']
password = selection['login']['password']
totp = selection['login']['totp']
if arguments.username_only:
fake_key_raw(username)
elif arguments.password_only:
fake_key_raw(password)
elif arguments.totp_only:
# No point in moving it to the clipboard in this case
fake_key_raw(
get_totp_code(
selection['id'],
selection['name'],
arguments.io_encoding,
arguments.auto_lock
)
)
else:
# Enter username and password using fake-key and <Tab> (which seems to work almost universally), then switch
# back into insert-mode, so the form can be directly submitted by
@ -243,6 +280,20 @@ def main(arguments):
if arguments.insert_mode:
qute_command('enter-mode insert')
# If it finds a TOTP code, it copies it to the clipboard,
# which is the same behaviour as the Firefox add-on.
if not arguments.totp_only and totp and arguments.totp:
# The import is done here, to make pyperclip an optional dependency
import pyperclip
pyperclip.copy(
get_totp_code(
selection['id'],
selection['name'],
arguments.io_encoding,
arguments.auto_lock
)
)
return ExitCodes.SUCCESS

View File

@ -39,10 +39,8 @@ you decide to submit a crash report!"""
import argparse
import enum
import fnmatch
import functools
import os
import re
import shlex
import subprocess
import sys

View File

@ -46,10 +46,20 @@ const HEADER = `
line-height: 1.2;
}
</style>
<!-- This icon is licensed under the Mozilla Public License 2.0 (available at: https://www.mozilla.org/en-US/MPL/2.0/).
The original icon can be found here: https://dxr.mozilla.org/mozilla-central/source/browser/themes/shared/reader/readerMode.svg -->
<link rel="shortcut icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2ZXJzaW9uPSIxLjEiI
HZpZXdCb3g9IjAgMCA2NCA2NCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgZmlsbD0iI2ZmZiI+CjxwYXRoIGQ9Im01MiAwaC00
MGMtNC40MiAwLTggMy41OC04IDh2NDhjMCA0LjQyIDMuNTggOCA4IDhoNDBjNC40MiAwIDgtMy41OCA4LTh2LTQ4YzAtNC40Mi0zLjU4LTgtOC04em0wIDU
yYzAgMi4yMS0xLjc5IDQtNCA0aC0zMmMtMi4yMSAwLTQtMS43OS00LTR2LTQwYzAtMi4yMSAxLjc5LTQgNC00aDMyYzIuMjEgMCA0IDEuNzkgNCA0em0tMT
AtMzZoLTIwYy0xLjExIDAtMiAwLjg5NS0yIDJzMC44OTUgMiAyIDJoMjBjMS4xMSAwIDItMC44OTUgMi0ycy0wLjg5NS0yLTItMnptMCA4aC0yMGMtMS4xM
SAwLTIgMC44OTUtMiAyczAuODk1IDIgMiAyaDIwYzEuMTEgMCAyLTAuODk1IDItMnMtMC44OTUtMi0yLTJ6bTAgOGgtMjBjLTEuMTEgMC0yIDAuODk1LTIg
MnMwLjg5NSAyIDIgMmgyMGMxLjExIDAgMi0wLjg5NSAyLTJzLTAuODk1LTItMi0yem0tMTIgOGgtOGMtMS4xMSAwLTIgMC44OTUtMiAyczAuODk1IDIgMiA
yaDhjMS4xMSAwIDItMC44OTUgMi0ycy0wLjg5NS0yLTItMnoiIGZpbGw9IiNmZmYiLz4KPC9nPgo8L3N2Zz4K"/>
</head>`;
const scriptsDir = path.join(process.env.QUTE_DATA_DIR, 'userscripts');
const tmpFile = path.join(scriptsDir, '/readability.html');
const domOpts = {url: process.env.QUTE_URL, contentType: "text/html; charset=utf-8"}
const domOpts = {url: process.env.QUTE_URL, contentType: "text/html; charset=utf-8"};
if (!fs.existsSync(scriptsDir)){
fs.mkdirSync(scriptsDir);
@ -66,6 +76,6 @@ JSDOM.fromFile(process.env.QUTE_HTML, domOpts).then(dom => {
return 1;
}
// Success
qute.open(['-t', tmpFile]);
qute.open(['-t', '-r', tmpFile]);
})
});

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2018 jnphilipp <mail@jnphilipp.org>
# Copyright 2018-2020 J. Nathanael Philipp (jnphilipp) <nathanael@philipp.land>
#
# This file is part of qutebrowser.
#
@ -30,6 +30,8 @@
import os
import sys
from argparse import ArgumentParser
try:
from stem import Signal
from stem.control import Controller
@ -41,12 +43,19 @@ except ImportError:
print('Failed to import stem.')
password = sys.argv[1]
with Controller.from_port(port=9051) as controller:
controller.authenticate(password)
controller.signal(Signal.NEWNYM)
if os.getenv('QUTE_FIFO'):
with open(os.environ['QUTE_FIFO'], 'w') as f:
f.write('message-info "Tor identity changed."')
else:
print('Tor identity changed.')
if __name__ == '__main__':
parser = ArgumentParser(prog='tor_identity')
parser.add_argument('-c', '--control-port', default=9051,
help='Tor control port (default 9051).')
parser.add_argument('-p', '--password', type=str, default=None,
help='Tor control port password.')
args = parser.parse_args()
with Controller.from_port(port=args.control_port) as controller:
controller.authenticate(args.password)
controller.signal(Signal.NEWNYM)
if os.getenv('QUTE_FIFO'):
with open(os.environ['QUTE_FIFO'], 'w') as f:
f.write('message-info "Tor identity changed."')
else:
print('Tor identity changed.')

View File

@ -30,6 +30,7 @@ markers =
fake_os: Fake utils.is_* to a fake operating system
unicode_locale: Tests which need an unicode locale to work
qtwebkit6021_xfail: Tests which would fail on WebKit version 602.1
js_headers: Sets JS headers dynamically on QtWebEngine (unsupported on some versions)
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*
@ -71,5 +72,5 @@ xfail_strict = true
filterwarnings =
error
# See https://github.com/HypothesisWorks/hypothesis/issues/2370
ignore:.*, but function-scoped fixtures:hypothesis.errors.HypothesisDeprecationWarning
ignore:.*which is reset between function calls but not between test cases generated by:hypothesis.errors.HypothesisDeprecationWarning
faulthandler_timeout = 90

View File

@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2020 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version__ = "1.10.1"
__version__ = "1.12.0"
__version_info__ = tuple(int(part) for part in __version__.split('.'))
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@ -208,12 +208,12 @@ class argument: # noqa: N801,N806 pylint: disable=invalid-name
raise ValueError("{} has no argument {}!".format(funcname,
self._argname))
if not hasattr(func, 'qute_args'):
func.qute_args = {} # type: ignore
elif func.qute_args is None: # type: ignore
func.qute_args = {} # type: ignore[attr-defined]
elif func.qute_args is None: # type: ignore[attr-defined]
raise ValueError("@cmdutils.argument got called above (after) "
"@cmdutils.register for {}!".format(funcname))
arginfo = command.ArgInfo(**self._kwargs)
func.qute_args[self._argname] = arginfo # type: ignore
func.qute_args[self._argname] = arginfo # type: ignore[attr-defined]
return func

View File

@ -96,7 +96,7 @@ def run(args):
q_app.setApplicationVersion(qutebrowser.__version__)
if args.version:
print(version.version())
print(version.version_info())
sys.exit(usertypes.Exit.ok)
quitter.init(args)
@ -159,13 +159,13 @@ def init(*, args: argparse.Namespace) -> None:
eventfilter.init()
log.init.debug("Connecting signals...")
q_app.focusChanged.connect(on_focus_changed) # type: ignore
q_app.focusChanged.connect(on_focus_changed)
_process_args(args)
for scheme in ['http', 'https', 'qute']:
QDesktopServices.setUrlHandler(
scheme, open_desktopservices_url) # type: ignore
scheme, open_desktopservices_url)
log.init.debug("Init done!")
crashsignal.crash_handler.raise_crashdlg()
@ -173,7 +173,6 @@ def init(*, args: argparse.Namespace) -> None:
def _init_icon():
"""Initialize the icon of qutebrowser."""
icon = QIcon()
fallback_icon = QIcon()
for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]:
filename = ':/icons/qutebrowser-{size}x{size}.png'.format(size=size)
@ -334,8 +333,12 @@ def _open_special_pages(args):
'qute://warning/webkit'),
('old-qt-warning-shown',
not qtutils.version_check('5.9'),
not qtutils.version_check('5.11'),
'qute://warning/old-qt'),
('session-warning-shown',
qtutils.version_check('5.15', compiled=False),
'qute://warning/sessions'),
]
for state, condition, url in pages:
@ -370,12 +373,23 @@ def open_desktopservices_url(url):
tabbed_browser.tabopen(url)
# This is effectively a @config.change_filter
# Howerver, logging is initialized too early to use that annotation
def _on_config_changed(name: str) -> None:
if name.startswith('logging.'):
log.init_from_config(config.val)
def _init_modules(*, args):
"""Initialize all 'modules' which need to be initialized.
Args:
args: The argparse namespace.
"""
log.init.debug("Initializing logging from config...")
log.init_from_config(config.val)
config.instance.changed.connect(_on_config_changed)
log.init.debug("Initializing save manager...")
save_manager = savemanager.SaveManager(q_app)
objreg.register('save-manager', save_manager)
@ -471,7 +485,9 @@ class Application(QApplication):
self._last_focus_object = None
qt_args = configinit.qt_args(args)
log.init.debug("Qt arguments: {}, based on {}".format(qt_args, args))
log.init.debug("Commandline args: {}".format(sys.argv[1:]))
log.init.debug("Parsed: {}".format(args))
log.init.debug("Qt arguments: {}".format(qt_args[1:]))
super().__init__(qt_args)
objects.args = args
@ -479,7 +495,7 @@ class Application(QApplication):
log.init.debug("Initializing application...")
self.launch_time = datetime.datetime.now()
self.focusObjectChanged.connect( # type: ignore
self.focusObjectChanged.connect( # type: ignore[attr-defined]
self.on_focus_object_changed)
self.setAttribute(Qt.AA_UseHighDpiPixmaps, True)

View File

@ -21,8 +21,8 @@
import enum
import itertools
import typing
import functools
import typing
import attr
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt,
@ -71,7 +71,7 @@ def create(win_id: int,
mode_manager = modeman.instance(win_id)
if objects.backend == usertypes.Backend.QtWebEngine:
from qutebrowser.browser.webengine import webenginetab
tab_class = webenginetab.WebEngineTab
tab_class = webenginetab.WebEngineTab # type: typing.Type[AbstractTab]
else:
from qutebrowser.browser.webkit import webkittab
tab_class = webkittab.WebKitTab
@ -317,6 +317,7 @@ class AbstractSearch(QObject):
def search(self, text: str, *,
ignore_case: usertypes.IgnoreCase = usertypes.IgnoreCase.never,
reverse: bool = False,
wrap: bool = True,
result_cb: _Callback = None) -> None:
"""Find the given text on the page.
@ -324,6 +325,7 @@ class AbstractSearch(QObject):
text: The text to search for.
ignore_case: Search case-insensitively.
reverse: Reverse search direction.
wrap: Allow wrapping at the top or bottom of the page.
result_cb: Called with a bool indicating whether a match was found.
"""
raise NotImplementedError
@ -425,27 +427,36 @@ class AbstractZoom(QObject):
self._set_factor_internal(self._zoom_factor)
class SelectionState(enum.Enum):
"""Possible states of selection in caret mode.
NOTE: Names need to line up with SelectionState in caret.js!
"""
none = 1
normal = 2
line = 3
class AbstractCaret(QObject):
"""Attribute ``caret`` of AbstractTab for caret browsing."""
#: Signal emitted when the selection was toggled.
#: (argument - whether the selection is now active)
selection_toggled = pyqtSignal(bool)
selection_toggled = pyqtSignal(SelectionState)
#: Emitted when a ``follow_selection`` action is done.
follow_selected_done = pyqtSignal()
def __init__(self,
tab: 'AbstractTab',
mode_manager: modeman.ModeManager,
parent: QWidget = None) -> None:
super().__init__(parent)
self._tab = tab
self._widget = typing.cast(QWidget, None)
self.selection_enabled = False
self._mode_manager = mode_manager
mode_manager.entered.connect(self._on_mode_entered)
mode_manager.left.connect(self._on_mode_left)
# self._tab is set by subclasses so mypy knows its concrete type.
def _on_mode_entered(self, mode: usertypes.KeyMode) -> None:
raise NotImplementedError
@ -498,7 +509,7 @@ class AbstractCaret(QObject):
def move_to_end_of_document(self) -> None:
raise NotImplementedError
def toggle_selection(self) -> None:
def toggle_selection(self, line: bool = False) -> None:
raise NotImplementedError
def drop_selection(self) -> None:
@ -540,7 +551,7 @@ class AbstractScroller(QObject):
@pyqtSlot()
def _log_scroll_pos_change(self) -> None:
log.webview.vdebug( # type: ignore
log.webview.vdebug( # type: ignore[attr-defined]
"Scroll position changed to {}".format(self.pos_px()))
def _init_widget(self, widget: QWidget) -> None:
@ -687,9 +698,9 @@ class AbstractElements:
[typing.Optional['webelem.AbstractWebElement']], None]
_ErrorCallback = typing.Callable[[Exception], None]
def __init__(self, tab: 'AbstractTab') -> None:
def __init__(self) -> None:
self._widget = typing.cast(QWidget, None)
self._tab = tab
# self._tab is set by subclasses so mypy knows its concrete type.
def find_css(self, selector: str,
callback: _MultiCallback,
@ -824,6 +835,15 @@ class AbstractTabPrivate:
def shutdown(self) -> None:
raise NotImplementedError
def run_js_sync(self, code: str) -> None:
"""Run javascript sync.
Result will be returned when running JS is complete.
This is only implemented for QtWebKit.
For QtWebEngine, always raises UnsupportedOperationError.
"""
raise NotImplementedError
class AbstractTab(QWidget):
@ -867,8 +887,18 @@ class AbstractTab(QWidget):
# arg 1: The exit code.
renderer_process_terminated = pyqtSignal(TerminationStatus, int)
def __init__(self, *, win_id: int, private: bool,
# Hosts for which a certificate error happened. Shared between all tabs.
#
# Note that we remember hosts here, without scheme/port:
# QtWebEngine/Chromium also only remembers hostnames, and certificates are
# for a given hostname anyways.
_insecure_hosts = set() # type: typing.Set[str]
def __init__(self, *, win_id: int,
mode_manager: modeman.ModeManager,
private: bool,
parent: QWidget = None) -> None:
utils.unused(mode_manager) # needed for mypy
self.is_private = private
self.win_id = win_id
self.tab_id = next(tab_id_gen)
@ -884,7 +914,6 @@ class AbstractTab(QWidget):
self._layout = miscwidgets.WrapperLayout(self)
self._widget = typing.cast(QWidget, None)
self._progress = 0
self._has_ssl_errors = False
self._load_status = usertypes.LoadStatus.none
self._tab_event_filter = eventfilter.TabEventFilter(
self, parent=self)
@ -946,7 +975,7 @@ class AbstractTab(QWidget):
log.webview.warning("Unable to find event target!")
return
evt.posted = True
evt.posted = True # type: ignore[attr-defined]
QApplication.postEvent(recipient, evt)
def navigation_blocked(self) -> bool:
@ -971,7 +1000,6 @@ class AbstractTab(QWidget):
@pyqtSlot()
def _on_load_started(self) -> None:
self._progress = 0
self._has_ssl_errors = False
self.data.viewing_source = False
self._set_load_status(usertypes.LoadStatus.loading)
self.load_started.emit()
@ -1030,15 +1058,19 @@ class AbstractTab(QWidget):
Needs to be called by subclasses to trigger a load status update, e.g.
as a response to a loadFinished signal.
"""
if ok and not self._has_ssl_errors:
if self.url().scheme() == 'https':
self._set_load_status(usertypes.LoadStatus.success_https)
else:
self._set_load_status(usertypes.LoadStatus.success)
elif ok:
self._set_load_status(usertypes.LoadStatus.warn)
url = self.url()
is_https = url.scheme() == 'https'
if not ok:
loadstatus = usertypes.LoadStatus.error
elif is_https and url.host() in self._insecure_hosts:
loadstatus = usertypes.LoadStatus.warn
elif is_https:
loadstatus = usertypes.LoadStatus.success_https
else:
self._set_load_status(usertypes.LoadStatus.error)
loadstatus = usertypes.LoadStatus.success
self._set_load_status(loadstatus)
@pyqtSlot()
def _on_history_trigger(self) -> None:
@ -1126,7 +1158,8 @@ class AbstractTab(QWidget):
def __repr__(self) -> str:
try:
qurl = self.url()
url = qurl.toDisplayString(QUrl.EncodeUnicode) # type: ignore
url = qurl.toDisplayString(
QUrl.EncodeUnicode) # type: ignore[arg-type]
except (AttributeError, RuntimeError) as exc:
url = '<{}>'.format(exc.__class__.__name__)
else:

View File

@ -157,7 +157,7 @@ class CommandDispatcher:
else:
return None
def _tab_focus_stack(self, mode: str, *, show_error=True):
def _tab_focus_stack(self, mode: str, *, show_error: bool = True) -> None:
"""Select the tab which was last focused."""
tab_deque = self._tabbed_browser.tab_deque
cur_tab = self._cntwidget()
@ -308,8 +308,9 @@ class CommandDispatcher:
urls = self._parse_url_input(url)
for i, cur_url in enumerate(urls):
if secure:
if secure and cur_url.scheme() == 'http':
cur_url.setScheme('https')
if not window and i > 0:
tab = False
bg = True
@ -452,7 +453,7 @@ class CommandDispatcher:
@cmdutils.argument('win_id', completion=miscmodels.window)
@cmdutils.argument('count', value=cmdutils.Value.count)
def tab_give(self, win_id: int = None, keep: bool = False,
count: int = None) -> None:
count: int = None, private: bool = False) -> None:
"""Give the current tab to a new or existing window if win_id given.
If no win_id is given, the tab will get detached into a new window.
@ -461,6 +462,7 @@ class CommandDispatcher:
win_id: The window ID of the window to give the current tab to.
keep: If given, keep the old tab around.
count: Overrides win_id (index starts at 1 for win_id=0).
private: If the tab should be detached into a private instance.
"""
if config.val.tabs.tabs_are_windows:
raise cmdutils.CommandError("Can't give tabs when using "
@ -478,7 +480,7 @@ class CommandDispatcher:
"only one tab")
tabbed_browser = self._new_tabbed_browser(
private=self._tabbed_browser.is_private)
private=private or self._tabbed_browser.is_private)
else:
if win_id not in objreg.window_registry:
raise cmdutils.CommandError(
@ -487,6 +489,10 @@ class CommandDispatcher:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if private and not tabbed_browser.is_private:
raise cmdutils.CommandError(
"The window with id {} is not private".format(win_id))
tabbed_browser.tabopen(self._current_url())
if not keep:
self._tabbed_browser.close_tab(self._current_widget(),
@ -645,12 +651,13 @@ class CommandDispatcher:
def _yank_url(self, what):
"""Helper method for yank() to get the URL to copy."""
assert what in ['url', 'pretty-url', 'markdown'], what
flags = QUrl.RemovePassword
assert what in ['url', 'pretty-url'], what
if what == 'pretty-url':
flags |= QUrl.DecodeReserved # type: ignore
flags = QUrl.RemovePassword | QUrl.DecodeReserved
else:
flags |= QUrl.FullyEncoded # type: ignore
flags = QUrl.RemovePassword | QUrl.FullyEncoded
url = QUrl(self._current_url())
url_query = QUrlQuery()
url_query_str = urlutils.query_string(url)
@ -661,12 +668,11 @@ class CommandDispatcher:
if key in config.val.url.yank_ignored_parameters:
url_query.removeQueryItem(key)
url.setQuery(url_query)
return url.toString(flags) # type: ignore
return url.toString(flags) # type: ignore[arg-type]
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('what', choices=['selection', 'url', 'pretty-url',
'title', 'domain', 'markdown',
'inline'])
'title', 'domain', 'inline'])
def yank(self, what='url', inline=None,
sel=False, keep=False, quiet=False):
"""Yank (copy) something to the clipboard or primary selection.
@ -679,8 +685,6 @@ class CommandDispatcher:
- `title`: The current page's title.
- `domain`: The current scheme, domain, and port number.
- `selection`: The selection under the cursor.
- `markdown`: Yank title and URL in markdown format
(deprecated, use `:yank inline [{title}]({url})` instead).
- `inline`: Yank the text contained in the 'inline' argument.
sel: Use the primary selection instead of the clipboard.
@ -712,14 +716,6 @@ class CommandDispatcher:
caret = self._current_widget().caret
caret.selection(callback=_selection_callback)
return
elif what == 'markdown':
message.warning(":yank markdown is deprecated, use `:yank inline "
"[{title}]({url})` instead.")
idx = self._current_index()
title = self._tabbed_browser.widget.page_title(idx)
url = self._yank_url(what)
s = '[{}]({})'.format(title, url)
what = 'markdown URL' # For printing
else: # pragma: no cover
raise ValueError("Invalid value {!r} for `what'.".format(what))
@ -909,7 +905,8 @@ class CommandDispatcher:
tabbed_browser.widget.setCurrentWidget(tab)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', choices=['last', 'stack-next', 'stack-prev'])
@cmdutils.argument('index', choices=['last', 'stack-next', 'stack-prev'],
completion=miscmodels.tab_focus)
@cmdutils.argument('count', value=cmdutils.Value.count)
def tab_focus(self, index: typing.Union[str, int] = None,
count: int = None, no_last: bool = False) -> None:
@ -1046,7 +1043,8 @@ class CommandDispatcher:
if userscript:
def _selection_callback(s):
try:
runner = self._run_userscript(s, cmd, args, verbose, count)
runner = self._run_userscript(
s, cmd, args, verbose, output_messages, count)
runner.finished.connect(_on_proc_finished)
except cmdutils.CommandError as e:
message.error(str(e))
@ -1072,13 +1070,15 @@ class CommandDispatcher:
proc.start(cmd, args)
proc.finished.connect(_on_proc_finished)
def _run_userscript(self, selection, cmd, args, verbose, count):
def _run_userscript(self, selection, cmd, args, verbose, output_messages,
count):
"""Run a userscript given as argument.
Args:
cmd: The userscript to run.
args: Arguments to pass to the userscript.
verbose: Show notifications when the command started/exited.
output_messages: Show the output as messages.
count: Exposed to the userscript.
"""
env = {
@ -1105,7 +1105,8 @@ class CommandDispatcher:
try:
runner = userscripts.run_async(
tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose)
tab, cmd, *args, win_id=self._win_id, env=env, verbose=verbose,
output_messages=output_messages)
except userscripts.Error as e:
raise cmdutils.CommandError(e)
return runner
@ -1383,24 +1384,40 @@ class CommandDispatcher:
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window')
def messages(self, level='info', plain=False, tab=False, bg=False,
window=False):
@cmdutils.argument('logfilter', flag='f')
def messages(self, level='info', *, plain=False, tab=False, bg=False,
window=False, logfilter=None):
"""Show a log of past messages.
Args:
level: Include messages with `level` or higher severity.
Valid values: vdebug, debug, info, warning, error, critical.
plain: Whether to show plaintext (as opposed to html).
logfilter: A comma-separated filter string of logging categories.
If the filter string starts with an exclamation mark, it
is negated.
tab: Open in a new tab.
bg: Open in a background tab.
window: Open in a new window.
"""
if level.upper() not in log.LOG_LEVELS:
raise cmdutils.CommandError("Invalid log level {}!".format(level))
query = QUrlQuery()
query.addQueryItem('level', level)
if plain:
url = QUrl('qute://plainlog?level={}'.format(level))
else:
url = QUrl('qute://log?level={}'.format(level))
query.addQueryItem('plain', typing.cast(str, None))
if logfilter:
try:
log.LogFilter.parse(logfilter)
except log.InvalidLogFilterError as e:
raise cmdutils.CommandError(e)
query.addQueryItem('logfilter', logfilter)
url = QUrl('qute://log')
url.setQuery(query)
self._open(url, tab, bg, window)
def _open_editor_cb(self, elem):
@ -1503,6 +1520,7 @@ class CommandDispatcher:
options = {
'ignore_case': config.val.search.ignore_case,
'reverse': reverse,
'wrap': config.val.search.wrap,
}
self._tabbed_browser.search_text = text

View File

@ -860,7 +860,7 @@ class AbstractDownloadManager(QObject):
self.data_changed.emit(-1)
@pyqtSlot(str, QUrl)
def _on_pdfjs_requested(self, filename: str, original_url: QUrl):
def _on_pdfjs_requested(self, filename: str, original_url: QUrl) -> None:
"""Open PDF.js when a download requests it."""
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')

View File

@ -105,7 +105,7 @@ class DownloadView(QListView):
def __repr__(self):
model = self.model()
if model is None:
count = 'None' # type: ignore
count = 'None' # type: ignore[unreachable]
else:
count = model.rowCount()
return utils.get_repr(self, count=count)
@ -132,7 +132,10 @@ class DownloadView(QListView):
item.open_file()
item.remove()
def _get_menu_actions(self, item) -> _ActionListType:
def _get_menu_actions(
self,
item: downloads.AbstractDownloadItem
) -> _ActionListType:
"""Get the available context menu actions for a given DownloadItem.
Args:

View File

@ -108,8 +108,15 @@ class TabEventFilter(QObject):
self._check_insertmode_on_release = False
def _handle_mouse_press(self, e):
"""Handle pressing of a mouse button."""
is_rocker_gesture = (config.val.input.rocker_gestures and
"""Handle pressing of a mouse button.
Args:
e: The QMouseEvent.
Return:
True if the event should be filtered, False otherwise.
"""
is_rocker_gesture = (config.val.input.mouse.rocker_gestures and
e.buttons() == Qt.LeftButton | Qt.RightButton)
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
@ -129,7 +136,14 @@ class TabEventFilter(QObject):
return False
def _handle_mouse_release(self, _e):
"""Handle releasing of a mouse button."""
"""Handle releasing of a mouse button.
Args:
e: The QMouseEvent.
Return:
True if the event should be filtered, False otherwise.
"""
# We want to make sure we check the focus element after the WebView is
# updated completely.
QTimer.singleShot(0, self._mouserelease_insertmode)
@ -140,27 +154,39 @@ class TabEventFilter(QObject):
Args:
e: The QWheelEvent.
Return:
True if the event should be filtered, False otherwise.
"""
if self._ignore_wheel_event:
# See https://github.com/qutebrowser/qutebrowser/issues/395
self._ignore_wheel_event = False
return True
if e.modifiers() & Qt.ControlModifier:
mode = modeman.instance(self._tab.win_id).mode
# Don't allow scrolling while hinting
mode = modeman.instance(self._tab.win_id).mode
if mode == usertypes.KeyMode.hint:
return True
elif e.modifiers() & Qt.ControlModifier:
if mode == usertypes.KeyMode.passthrough:
return False
divider = config.val.zoom.mouse_divider
if divider == 0:
return False
# Disable mouse zooming
return True
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
if factor < 0:
return False
return True
perc = int(100 * factor)
message.info("Zoom level: {}%".format(perc), replace=True)
self._tab.zoom.set_factor(factor)
elif e.modifiers() & Qt.ShiftModifier:
return True
elif (e.modifiers() & Qt.ShiftModifier and
not qtutils.version_check('5.9', compiled=False)):
if e.angleDelta().y() > 0:
self._tab.scroller.left()
else:
@ -170,16 +196,30 @@ class TabEventFilter(QObject):
return False
def _handle_context_menu(self, _e):
"""Suppress context menus if rocker gestures are turned on."""
return config.val.input.rocker_gestures
"""Suppress context menus if rocker gestures are turned on.
Args:
e: The QContextMenuEvent.
Return:
True if the event should be filtered, False otherwise.
"""
return config.val.input.mouse.rocker_gestures
def _handle_key_release(self, e):
"""Ignore repeated key release events going to the website.
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-77208
Args:
e: The QKeyEvent.
Return:
True if the event should be filtered, False otherwise.
"""
return (e.isAutoRepeat() and
qtutils.version_check('5.10') and
qtutils.version_check('5.10', compiled=False) and
not qtutils.version_check('5.14', compiled=False) and
objects.backend == usertypes.Backend.QtWebEngine)
def _mousepress_insertmode_cb(self, elem):
@ -232,7 +272,15 @@ class TabEventFilter(QObject):
Args:
e: The QMouseEvent.
Return:
True if the event should be filtered, False otherwise.
"""
if (not config.val.input.mouse.back_forward_buttons and
e.button() in [Qt.XButton1, Qt.XButton2]):
# Back and forward on mice are disabled
return
if e.button() in [Qt.XButton1, Qt.LeftButton]:
# Back button on mice which have it, or rocker gesture
if self._tab.history.can_go_back():
@ -247,7 +295,11 @@ class TabEventFilter(QObject):
message.error("At end of history.")
def eventFilter(self, obj, event):
"""Filter events going to a QWeb(Engine)View."""
"""Filter events going to a QWeb(Engine)View.
Return:
True if the event should be filtered, False otherwise.
"""
evtype = event.type()
if evtype not in self._handlers:
return False

View File

@ -235,7 +235,7 @@ class HintActions:
flags = QUrl.FullyEncoded | QUrl.RemovePassword
if url.scheme() == 'mailto':
flags |= QUrl.RemoveScheme
urlstr = url.toString(flags) # type: ignore
urlstr = url.toString(flags) # type: ignore[arg-type]
new_content = urlstr
@ -256,14 +256,15 @@ class HintActions:
def run_cmd(self, url: QUrl, context: HintContext) -> None:
"""Run the command based on a hint URL."""
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore
urlstr = url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
args = context.get_args(urlstr)
commandrunner = runners.CommandRunner(self._win_id)
commandrunner.run_safely(' '.join(args))
def preset_cmd_text(self, url: QUrl, context: HintContext) -> None:
"""Preset a commandline text based on a hint URL."""
urlstr = url.toDisplayString(QUrl.FullyEncoded) # type: ignore
flags = QUrl.FullyEncoded
urlstr = url.toDisplayString(flags) # type: ignore[arg-type]
args = context.get_args(urlstr)
text = ' '.join(args)
if text[0] not in modeparsers.STARTCHARS:
@ -308,7 +309,8 @@ class HintActions:
}
url = elem.resolve_url(context.baseurl)
if url is not None:
env['QUTE_URL'] = url.toString(QUrl.FullyEncoded) # type: ignore
flags = QUrl.FullyEncoded
env['QUTE_URL'] = url.toString(flags) # type: ignore[arg-type]
try:
userscripts.run_async(context.tab, cmd, *args, win_id=self._win_id,
@ -328,7 +330,7 @@ class HintActions:
context: The HintContext to use.
"""
urlstr = url.toString(
QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore
QUrl.FullyEncoded | QUrl.RemovePassword) # type: ignore[arg-type]
args = context.get_args(urlstr)
commandrunner = runners.CommandRunner(self._win_id)
commandrunner.run_safely('spawn ' + ' '.join(args))
@ -893,7 +895,7 @@ class HintManager(QObject):
if self._context.hint_mode == 'number':
# renumber filtered hints
strings = self._hint_strings(visible)
strings = self._hint_strings([label.elem for label in visible])
self._context.labels = {}
for label, string in zip(visible, strings):
label.update_text('', string)

View File

@ -57,7 +57,7 @@ class HistoryProgress:
self._progress.setMinimumDuration(500)
self._progress.setLabelText(text)
self._progress.setMaximum(maximum)
self._progress.setCancelButton(None) # type: ignore
self._progress.setCancelButton(None)
self._progress.show()
QApplication.processEvents()

View File

@ -83,7 +83,7 @@ class AbstractWebInspector(QWidget):
def closeEvent(self, e):
"""Save the geometry when closed."""
data = bytes(self.saveGeometry())
data = self.saveGeometry().data()
geom = base64.b64encode(data).decode('ASCII')
configfiles.state['geometry']['inspector'] = geom

View File

@ -162,8 +162,8 @@ def _find_prevnext(prev, elems):
# pylint: disable=bad-config-option
for regex in getattr(config.val.hints, option):
# pylint: enable=bad-config-option
log.hints.vdebug("== Checking regex '{}'." # type: ignore
.format(regex.pattern))
log.hints.vdebug( # type: ignore[attr-defined]
"== Checking regex '{}'.".format(regex.pattern))
for e in elems:
text = str(e)
if not text:
@ -173,8 +173,8 @@ def _find_prevnext(prev, elems):
regex.pattern, text))
return e
else:
log.hints.vdebug("No match on '{}'!" # type: ignore
.format(text))
log.hints.vdebug( # type: ignore[attr-defined]
"No match on '{}'!".format(text))
return None

View File

@ -65,7 +65,9 @@ def _js_slot(*args):
# pylint: disable=protected-access
return self._error_con.callAsConstructor([e])
# pylint: enable=protected-access
return pyqtSlot(*args, result=QJSValue)(new_method)
deco = pyqtSlot(*args, result=QJSValue) # type: ignore[arg-type]
return deco(new_method)
return _decorator
@ -215,10 +217,10 @@ class PACResolver:
if from_file:
string_flags = QUrl.PrettyDecoded
else:
string_flags = QUrl.RemoveUserInfo # type: ignore
string_flags = QUrl.RemoveUserInfo # type: ignore[assignment]
if query.url().scheme() == 'https':
string_flags |= QUrl.RemovePath # type: ignore
string_flags |= QUrl.RemoveQuery # type: ignore
string_flags |= QUrl.RemovePath # type: ignore[assignment]
string_flags |= QUrl.RemoveQuery # type: ignore[assignment]
result = self._resolver.call([query.url().toString(string_flags),
query.peerHostName()])
@ -266,7 +268,8 @@ class PACFetcher(QObject):
"""Fetch the proxy from the remote URL."""
assert self._manager is not None
self._reply = self._manager.get(QNetworkRequest(self._pac_url))
self._reply.finished.connect(self._finish) # type: ignore
self._reply.finished.connect( # type: ignore[attr-defined]
self._finish)
@pyqtSlot()
def _finish(self):

View File

@ -19,8 +19,6 @@
"""Handling of proxies."""
import typing
from PyQt5.QtCore import QUrl, pyqtSlot
from PyQt5.QtNetwork import QNetworkProxy, QNetworkProxyFactory
@ -54,7 +52,8 @@ def _warn_for_pac():
@pyqtSlot()
def shutdown():
QNetworkProxyFactory.setApplicationProxyFactory(None) # type: ignore
QNetworkProxyFactory.setApplicationProxyFactory(
None) # type: ignore[arg-type]
class ProxyFactory(QNetworkProxyFactory):
@ -73,6 +72,18 @@ class ProxyFactory(QNetworkProxyFactory):
else:
return None
def _set_capabilities(self, proxy):
if proxy.type() == QNetworkProxy.NoProxy:
return
capabilities = proxy.capabilities()
lookup_cap = QNetworkProxy.HostNameLookupCapability
if config.val.content.proxy_dns_requests:
capabilities |= lookup_cap
else:
capabilities &= ~lookup_cap
proxy.setCapabilities(capabilities)
def queryProxy(self, query):
"""Get the QNetworkProxies for a query.
@ -91,18 +102,13 @@ class ProxyFactory(QNetworkProxyFactory):
elif isinstance(proxy, pac.PACFetcher):
if objects.backend == usertypes.Backend.QtWebEngine:
# Looks like query.url() is always invalid on QtWebEngine...
proxies = [urlutils.proxy_from_url(QUrl('direct://'))]
proxy = urlutils.proxy_from_url(QUrl('direct://'))
assert not isinstance(proxy, pac.PACFetcher)
proxies = [proxy]
else:
proxies = proxy.resolve(query)
else:
proxies = [proxy]
for p in proxies:
if p.type() != QNetworkProxy.NoProxy:
capabilities = p.capabilities()
lookup_cap = QNetworkProxy.HostNameLookupCapability
if config.val.content.proxy_dns_requests:
capabilities |= lookup_cap # type: ignore
else:
capabilities &= ~lookup_cap # type: ignore
p.setCapabilities(capabilities)
for proxy in proxies:
self._set_capabilities(proxy)
return proxies

View File

@ -30,6 +30,19 @@ from qutebrowser.misc import objects
from qutebrowser.config import config
_SYSTEM_PATHS = [
# Debian pdf.js-common
# Arch Linux pdfjs (AUR)
'/usr/share/pdf.js/',
# Flatpak (Flathub)
'/app/share/pdf.js/',
# Arch Linux pdf.js (AUR)
'/usr/share/javascript/pdf.js/',
# Debian libjs-pdf
'/usr/share/javascript/pdf/',
]
class PDFJSNotFound(Exception):
"""Raised when no pdf.js installation is found.
@ -84,6 +97,9 @@ def _generate_pdfjs_script(filename):
url_query.addQueryItem('filename', filename)
url.setQuery(url_query)
js_url = javascript.to_js(
url.toString(QUrl.FullyEncoded)) # type: ignore[arg-type]
return jinja.js_environment.from_string("""
document.addEventListener("DOMContentLoaded", function() {
if (typeof window.PDFJS !== 'undefined') {
@ -105,7 +121,7 @@ def _generate_pdfjs_script(filename):
viewer.open({{ url }});
});
""").render(
url=javascript.to_js(url.toString(QUrl.FullyEncoded)), # type: ignore
url=js_url,
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70420
disable_create_object_url=(
not qtutils.version_check('5.12') and
@ -127,16 +143,7 @@ def get_pdfjs_res_and_path(path):
content = None
file_path = None
system_paths = [
# Debian pdf.js-common
# Arch Linux pdfjs (AUR)
'/usr/share/pdf.js/',
# Flatpak (Flathub)
'/app/share/pdf.js/',
# Arch Linux pdf.js (AUR)
'/usr/share/javascript/pdf.js/',
# Debian libjs-pdf
'/usr/share/javascript/pdf/',
system_paths = _SYSTEM_PATHS + [
# fallback
os.path.join(standarddir.data(), 'pdfjs'),
# hardcoded fallback for --temp-basedir
@ -221,6 +228,7 @@ def is_available():
"""Return true if a pdfjs installation is available."""
try:
get_pdfjs_res('build/pdf.js')
get_pdfjs_res('web/viewer.html')
except PDFJSNotFound:
return False
else:
@ -243,7 +251,7 @@ def get_main_url(filename: str, original_url: QUrl) -> QUrl:
query = QUrlQuery()
query.addQueryItem('filename', filename) # read from our JS
query.addQueryItem('file', '') # to avoid pdfjs opening the default PDF
urlstr = original_url.toString(QUrl.FullyEncoded) # type: ignore
urlstr = original_url.toString(QUrl.FullyEncoded) # type: ignore[arg-type]
query.addQueryItem('source', urlstr)
url.setQuery(query)
return url

View File

@ -39,7 +39,7 @@ try:
import secrets
except ImportError:
# New in Python 3.6
secrets = None # type: ignore
secrets = None # type: ignore[assignment]
from PyQt5.QtCore import QUrlQuery, QUrl, qVersion
@ -47,7 +47,7 @@ import qutebrowser
from qutebrowser.browser import pdfjs, downloads, history
from qutebrowser.config import config, configdata, configexc, configdiff
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg, urlutils)
objreg, urlutils, standarddir)
from qutebrowser.qt import sip
@ -98,8 +98,8 @@ class Redirect(Exception):
# Return value: (mimetype, data) (encoded as utf-8 if a str is returned)
_Handler = TypeVar('_Handler',
bound=Callable[[QUrl], Tuple[str, Union[str, bytes]]])
_HandlerRet = Tuple[str, Union[str, bytes]]
_Handler = TypeVar('_Handler', bound=Callable[[QUrl], _HandlerRet])
class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
@ -125,7 +125,7 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
return self._function(*args, **kwargs)
def data_for_url(url):
def data_for_url(url: QUrl) -> typing.Tuple[str, bytes]:
"""Get the data to show for the given URL.
Args:
@ -134,8 +134,9 @@ def data_for_url(url):
Return:
A (mimetype, data) tuple.
"""
norm_url = url.adjusted(QUrl.NormalizePathSegments |
QUrl.StripTrailingSlash)
norm_url = url.adjusted(
QUrl.NormalizePathSegments | # type: ignore[arg-type]
QUrl.StripTrailingSlash)
if norm_url != url:
raise Redirect(norm_url)
@ -181,7 +182,7 @@ def data_for_url(url):
@add_handler('bookmarks')
def qute_bookmarks(_url):
def qute_bookmarks(_url: QUrl) -> _HandlerRet:
"""Handler for qute://bookmarks. Display all quickmarks / bookmarks."""
bookmarks = sorted(objreg.get('bookmark-manager').marks.items(),
key=lambda x: x[1]) # Sort by title
@ -196,7 +197,7 @@ def qute_bookmarks(_url):
@add_handler('tabs')
def qute_tabs(_url):
def qute_tabs(_url: QUrl) -> _HandlerRet:
"""Handler for qute://tabs. Display information about all open tabs."""
tabs = collections.defaultdict(
list) # type: typing.Dict[str, typing.List[typing.Tuple[str, str]]]
@ -217,7 +218,10 @@ def qute_tabs(_url):
return 'text/html', src
def history_data(start_time, offset=None):
def history_data(
start_time: float,
offset: int = None
) -> typing.Sequence[typing.Dict[str, typing.Union[str, int]]]:
"""Return history data.
Arguments:
@ -240,7 +244,7 @@ def history_data(start_time, offset=None):
@add_handler('history')
def qute_history(url):
def qute_history(url: QUrl) -> _HandlerRet:
"""Handler for qute://history. Display and serve history."""
if url.path() == '/data':
q_offset = QUrlQuery(url).queryItemValue("offset")
@ -266,7 +270,7 @@ def qute_history(url):
@add_handler('javascript')
def qute_javascript(url):
def qute_javascript(url: QUrl) -> _HandlerRet:
"""Handler for qute://javascript.
Return content of file given as query parameter.
@ -280,14 +284,14 @@ def qute_javascript(url):
@add_handler('pyeval')
def qute_pyeval(_url):
def qute_pyeval(_url: QUrl) -> _HandlerRet:
"""Handler for qute://pyeval."""
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', src
@add_handler('spawn-output')
def qute_spawn_output(_url):
def qute_spawn_output(_url: QUrl) -> _HandlerRet:
"""Handler for qute://spawn-output."""
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
return 'text/html', src
@ -298,57 +302,60 @@ def qute_spawn_output(_url):
def qute_version(_url):
"""Handler for qute://version."""
src = jinja.render('version.html', title='Version info',
version=version.version(),
version=version.version_info(),
copyright=qutebrowser.__copyright__)
return 'text/html', src
@add_handler('plainlog')
def qute_plainlog(url):
"""Handler for qute://plainlog.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
"""
if log.ram_handler is None:
text = "Log output was disabled."
else:
level = QUrlQuery(url).queryItemValue('level')
if not level:
level = 'vdebug'
text = log.ram_handler.dump_log(html=False, level=level)
src = jinja.render('pre.html', title='log', content=text)
return 'text/html', src
@add_handler('log')
def qute_log(url):
def qute_log(url: QUrl) -> _HandlerRet:
"""Handler for qute://log.
An optional query parameter specifies the minimum log level to print.
There are three query parameters:
- level: The minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
Level can be one of: vdebug, debug, info, warning, error, critical.
- plain: If given (and not 'false'), plaintext is shown.
- logfilter: A filter string like the --logfilter commandline argument
accepts.
"""
query = QUrlQuery(url)
plain = (query.hasQueryItem('plain') and
query.queryItemValue('plain').lower() != 'false')
if log.ram_handler is None:
html_log = None
content = "Log output was disabled." if plain else None
else:
level = QUrlQuery(url).queryItemValue('level')
level = query.queryItemValue('level')
if not level:
level = 'vdebug'
html_log = log.ram_handler.dump_log(html=True, level=level)
src = jinja.render('log.html', title='log', content=html_log)
filter_str = query.queryItemValue('logfilter')
try:
logfilter = (log.LogFilter.parse(filter_str, only_debug=False)
if filter_str else None)
except log.InvalidLogFilterError as e:
raise UrlInvalidError(e)
content = log.ram_handler.dump_log(html=not plain,
level=level, logfilter=logfilter)
template = 'pre.html' if plain else 'log.html'
src = jinja.render(template, title='log', content=content)
return 'text/html', src
@add_handler('gpl')
def qute_gpl(_url):
def qute_gpl(_url: QUrl) -> _HandlerRet:
"""Handler for qute://gpl. Return HTML content as string."""
return 'text/html', utils.read_file('html/license.html')
def _asciidoc_fallback_path(html_path):
def _asciidoc_fallback_path(html_path: str) -> typing.Optional[str]:
"""Fall back to plaintext asciidoc if the HTML is unavailable."""
path = html_path.replace('.html', '.asciidoc')
try:
@ -358,7 +365,7 @@ def _asciidoc_fallback_path(html_path):
@add_handler('help')
def qute_help(url):
def qute_help(url: QUrl) -> _HandlerRet:
"""Handler for qute://help."""
urlpath = url.path()
if not urlpath or urlpath == '/':
@ -407,7 +414,7 @@ def qute_help(url):
return 'text/html', data
def _qute_settings_set(url):
def _qute_settings_set(url: QUrl) -> _HandlerRet:
"""Handler for qute://settings/set."""
query = QUrlQuery(url)
option = query.queryItemValue('option', QUrl.FullyDecoded)
@ -429,7 +436,7 @@ def _qute_settings_set(url):
@add_handler('settings')
def qute_settings(url):
def qute_settings(url: QUrl) -> _HandlerRet:
"""Handler for qute://settings. View/change qute configuration."""
global csrf_token
@ -457,7 +464,7 @@ def qute_settings(url):
@add_handler('bindings')
def qute_bindings(_url):
def qute_bindings(_url: QUrl) -> _HandlerRet:
"""Handler for qute://bindings. View keybindings."""
bindings = {}
defaults = config.val.bindings.default
@ -475,7 +482,7 @@ def qute_bindings(_url):
@add_handler('back')
def qute_back(url):
def qute_back(url: QUrl) -> _HandlerRet:
"""Handler for qute://back.
Simple page to free ram / lazy load a site, goes back on focusing the tab.
@ -487,7 +494,7 @@ def qute_back(url):
@add_handler('configdiff')
def qute_configdiff(url):
def qute_configdiff(url: QUrl) -> _HandlerRet:
"""Handler for qute://configdiff."""
if url.path() == '/old':
try:
@ -502,7 +509,7 @@ def qute_configdiff(url):
@add_handler('pastebin-version')
def qute_pastebin_version(_url):
def qute_pastebin_version(_url: QUrl) -> _HandlerRet:
"""Handler that pastebins the version string."""
version.pastebin_version()
return 'text/plain', b'Paste called.'
@ -515,7 +522,7 @@ def _pdf_path(filename: str) -> str:
@add_handler('pdfjs')
def qute_pdfjs(url: QUrl):
def qute_pdfjs(url: QUrl) -> _HandlerRet:
"""Handler for qute://pdfjs.
Return the pdf.js viewer or redirect to original URL if the file does not
@ -566,7 +573,7 @@ def qute_pdfjs(url: QUrl):
@add_handler('warning')
def qute_warning(url):
def qute_warning(url: QUrl) -> _HandlerRet:
"""Handler for qute://warning."""
path = url.path()
if path == '/old-qt':
@ -576,6 +583,11 @@ def qute_warning(url):
elif path == '/webkit':
src = jinja.render('warning-webkit.html',
title='QtWebKit backend warning')
elif path == '/sessions':
src = jinja.render('warning-sessions.html',
title='Qt 5.15 sessions warning',
datadir=standarddir.data(),
sep=os.sep)
else:
raise NotFoundError("Invalid warning page {}".format(path))
return 'text/html', src

View File

@ -27,7 +27,8 @@ import typing
from PyQt5.QtCore import QUrl
from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja, utils
from qutebrowser.utils import (usertypes, message, log, objreg, jinja, utils,
qtutils)
from qutebrowser.mainwindow import mainwindow
@ -160,7 +161,7 @@ def ignore_certificate_errors(url, errors, abort_on):
True if the error should be ignored, False otherwise.
"""
ssl_strict = config.instance.get('content.ssl_strict', url=url)
log.webview.debug("Certificate errors {!r}, strict {}".format(
log.network.debug("Certificate errors {!r}, strict {}".format(
errors, ssl_strict))
for error in errors:
@ -186,7 +187,7 @@ def ignore_certificate_errors(url, errors, abort_on):
ignore = False
return ignore
elif ssl_strict is False:
log.webview.debug("ssl_strict is False, only warning about errors")
log.network.debug("ssl_strict is False, only warning about errors")
for err in errors:
# FIXME we might want to use warn here (non-fatal error)
# https://github.com/qutebrowser/qutebrowser/issues/114
@ -285,8 +286,13 @@ def get_user_stylesheet(searching=False):
with open(filename, 'r', encoding='utf-8') as f:
css += f.read()
if (config.val.scrolling.bar == 'never' or
config.val.scrolling.bar == 'when-searching' and not searching):
setting = config.val.scrolling.bar
overlay_bar_available = (qtutils.version_check('5.11', compiled=False) and
not utils.is_mac)
if setting == 'overlay' and not overlay_bar_available:
setting = 'when-searching'
if setting == 'never' or setting == 'when-searching' and not searching:
css += '\nhtml > ::-webkit-scrollbar { width: 0px; height: 0px; }'
return css

View File

@ -392,7 +392,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
window.show()
# FIXME:typing Why can't mypy determine the type of
# window.tabbed_browser?
window.tabbed_browser.tabopen(url) # type: ignore
window.tabbed_browser.tabopen(url) # type: ignore[has-type]
else:
raise ValueError("Unknown ClickTarget {}".format(click_target))

View File

@ -20,21 +20,40 @@
"""Filter for QtWebEngine cookies."""
from qutebrowser.config import config
from qutebrowser.utils import utils, qtutils
from qutebrowser.utils import utils, qtutils, log
from qutebrowser.misc import objects
@utils.prevent_exceptions(False) # Runs in I/O thread
def _accept_cookie(request):
"""Check whether the given cookie should be accepted."""
accept = config.val.content.cookies.accept
url = request.firstPartyUrl
if not url.isValid():
url = None
if qtutils.version_check('5.11.3', compiled=False):
third_party = request.thirdParty
else:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-71393
third_party = (request.thirdParty and
not request.firstPartyUrl.isEmpty())
accept = config.instance.get('content.cookies.accept',
url=url)
if 'log-cookies' in objects.debug_flags:
first_party_str = ("<unknown>" if not request.firstPartyUrl.isValid()
else request.firstPartyUrl.toDisplayString())
origin_str = ("<unknown>" if not request.origin.isValid()
else request.origin.toDisplayString())
log.network.debug('Cookie from origin {} on {} (third party: {}) '
'-> applying setting {}'
.format(origin_str, first_party_str, third_party,
accept))
if accept == 'all':
return True
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
if qtutils.version_check('5.11.3', compiled=False):
third_party = request.thirdParty
else:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-71393
third_party = (request.thirdParty and
not request.firstPartyUrl.isEmpty())
return not third_party
elif accept == 'never':
return False

View File

@ -154,7 +154,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
info.resourceType())
navigation_type_str = debug.qenum_key(QWebEngineUrlRequestInfo,
info.navigationType())
log.webview.debug("{} {}, first-party {}, resource {}, "
log.network.debug("{} {}, first-party {}, resource {}, "
"navigation {}".format(
bytes(info.requestMethod()).decode('ascii'),
info.requestUrl().toDisplayString(),
@ -164,7 +164,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
url = info.requestUrl()
first_party = info.firstPartyUrl()
if not url.isValid():
log.webview.debug("Ignoring invalid intercepted URL: {}".format(
log.network.debug("Ignoring invalid intercepted URL: {}".format(
url.errorString()))
return
@ -173,7 +173,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
try:
resource_type = self._resource_types[info.resourceType()]
except KeyError:
log.webview.warning(
log.network.warning(
"Resource type {} not found in RequestInterceptor dict."
.format(debug.qenum_key(QWebEngineUrlRequestInfo,
info.resourceType())))
@ -184,7 +184,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
if (first_party != QUrl('qute://settings/') or
info.resourceType() !=
QWebEngineUrlRequestInfo.ResourceTypeXhr):
log.webview.warning("Blocking malicious request from {} to {}"
log.network.warning("Blocking malicious request from {} to {}"
.format(first_party.toDisplayString(),
url.toDisplayString()))
info.block(True)

View File

@ -26,6 +26,13 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
from qutebrowser.utils import qtutils
# kHistoryStreamVersion = 3 was originally set when history serializing was
# implemented in QtWebEngine:
# https://codereview.qt-project.org/c/qt/qtwebengine/+/81529
#
# Qt 5.14 added version 4 which also serializes favicons:
# https://codereview.qt-project.org/c/qt/qtwebengine/+/279407
# However, we don't care about those, so let's keep it at 3.
HISTORY_STREAM_VERSION = 3
@ -36,32 +43,63 @@ def _serialize_item(item, stream):
item: The WebHistoryItem to write.
stream: The QDataStream to write to.
"""
### Thanks to Otter Browser:
### https://github.com/OtterBrowser/otter-browser/blob/v0.9.10/src/modules/backends/web/qtwebengine/QtWebEngineWebWidget.cpp#L1210
### src/core/web_contents_adapter.cpp serializeNavigationHistory
# Thanks to Otter Browser:
# https://github.com/OtterBrowser/otter-browser/blob/v1.0.01/src/modules/backends/web/qtwebengine/QtWebEnginePage.cpp#L260
#
# Relevant QtWebEngine source:
# src/core/web_contents_adapter.cpp serializeNavigationHistory
#
# Sample data:
# [TabHistoryItem(active=True,
# original_url=QUrl('file:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt'),
# title='1.txt',
# url=QUrl('file:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt'),
# user_data={'zoom': 1.0, 'scroll-pos': QPoint()})]
## toQt(entry->GetVirtualURL());
# \x00\x00\x00Jfile:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt
qtutils.serialize_stream(stream, item.url)
## toQt(entry->GetTitle());
# \x00\x00\x00\n\x001\x00.\x00t\x00x\x00t
stream.writeQString(item.title)
## QByteArray(encodedPageState.data(), encodedPageState.size());
# \xff\xff\xff\xff
qtutils.serialize_stream(stream, QByteArray())
## static_cast<qint32>(entry->GetTransitionType());
# chromium/ui/base/page_transition_types.h
# \x00\x00\x00\x00
stream.writeInt32(0) # PAGE_TRANSITION_LINK
## entry->GetHasPostData();
# \x00
stream.writeBool(False)
## toQt(entry->GetReferrer().url);
# \xff\xff\xff\xff
qtutils.serialize_stream(stream, QUrl())
## static_cast<qint32>(entry->GetReferrer().policy);
# chromium/third_party/WebKit/public/platform/WebReferrerPolicy.h
# \x00\x00\x00\x00
stream.writeInt32(0) # WebReferrerPolicyAlways
## toQt(entry->GetOriginalRequestURL());
# \x00\x00\x00Jfile:///home/florian/proj/qutebrowser/git/tests/end2end/data/numbers/1.txt
qtutils.serialize_stream(stream, item.original_url)
## entry->GetIsOverridingUserAgent();
# \x00
stream.writeBool(False)
## static_cast<qint64>(entry->GetTimestamp().ToInternalValue());
# \x00\x00\x00\x00^\x97$\xe7
stream.writeInt64(int(time.time()))
## entry->GetHttpStatusCode();
# \x00\x00\x00\xc8
stream.writeInt(200)
@ -102,12 +140,13 @@ def serialize(items):
current_idx = -1
### src/core/web_contents_adapter.cpp serializeNavigationHistory
# sample data:
# kHistoryStreamVersion
stream.writeInt(HISTORY_STREAM_VERSION)
stream.writeInt(HISTORY_STREAM_VERSION) # \x00\x00\x00\x03
# count
stream.writeInt(len(items))
stream.writeInt(len(items)) # \x00\x00\x00\x01
# currentIndex
stream.writeInt(current_idx)
stream.writeInt(current_idx) # \x00\x00\x00\x00
for item in items:
_serialize_item(item, stream)

View File

@ -24,7 +24,7 @@ import os.path
import urllib
import functools
from PyQt5.QtCore import pyqtSlot, Qt, QUrl
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QObject
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
from qutebrowser.browser import downloads, pdfjs
@ -39,16 +39,18 @@ class DownloadItem(downloads.AbstractDownloadItem):
_qt_item: The wrapped item.
"""
def __init__(self, qt_item: QWebEngineDownloadItem, parent=None):
def __init__(self, qt_item: QWebEngineDownloadItem,
parent: QObject = None) -> None:
super().__init__(parent)
self._qt_item = qt_item
qt_item.downloadProgress.connect( # type: ignore
qt_item.downloadProgress.connect( # type: ignore[attr-defined]
self.stats.on_download_progress)
qt_item.stateChanged.connect(self._on_state_changed) # type: ignore
qt_item.stateChanged.connect( # type: ignore[attr-defined]
self._on_state_changed)
# Ensure wrapped qt_item is deleted manually when the wrapper object
# is deleted. See https://github.com/qutebrowser/qutebrowser/issues/3373
self.destroyed.connect(self._qt_item.deleteLater) # type: ignore
self.destroyed.connect(self._qt_item.deleteLater)
def _is_page_download(self):
"""Check if this item is a page (i.e. mhtml) download."""
@ -93,7 +95,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
"{}".format(state_name))
def _do_die(self):
self._qt_item.downloadProgress.disconnect() # type: ignore
progress_signal = self._qt_item.downloadProgress
progress_signal.disconnect() # type: ignore[attr-defined]
if self._qt_item.state() != QWebEngineDownloadItem.DownloadInterrupted:
self._qt_item.cancel()

View File

@ -248,7 +248,7 @@ class WebEngineElement(webelem.AbstractWebElement):
# (it does so with a 0ms QTimer...)
# This is also used in Qt's tests:
# https://github.com/qt/qtwebengine/commit/5e572e88efa7ba7c2b9138ec19e606d3e345ac90
QApplication.processEvents( # type: ignore
QApplication.processEvents( # type: ignore[call-overload]
QEventLoop.ExcludeSocketNotifiers |
QEventLoop.ExcludeUserInputEvents)

View File

@ -23,10 +23,10 @@ from PyQt5.QtCore import QBuffer, QIODevice, QUrl
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob)
try:
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme # type: ignore
from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
except ImportError:
# Added in Qt 5.12
QWebEngineUrlScheme = None
QWebEngineUrlScheme = None # type: ignore[misc, assignment]
from qutebrowser.browser import qutescheme
from qutebrowser.utils import log, qtutils
@ -86,9 +86,9 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
return True
if initiator.isValid() and initiator.scheme() != 'qute':
log.misc.warning("Blocking malicious request from {} to {}".format(
initiator.toDisplayString(),
request_url.toDisplayString()))
log.network.warning("Blocking malicious request from {} to {}"
.format(initiator.toDisplayString(),
request_url.toDisplayString()))
job.fail(QWebEngineUrlRequestJob.RequestDenied)
return False
@ -119,7 +119,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
assert url.scheme() == 'qute'
log.misc.debug("Got request for {}".format(url.toDisplayString()))
log.network.debug("Got request for {}".format(url.toDisplayString()))
try:
mimetype, data = qutescheme.data_for_url(url)
except qutescheme.Error as e:
@ -136,14 +136,14 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
QWebEngineUrlRequestJob.RequestFailed,
}
exctype = type(e)
log.misc.error("{} while handling qute://* URL".format(
log.network.error("{} while handling qute://* URL".format(
exctype.__name__))
job.fail(errors[exctype])
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
job.redirect(e.url)
else:
log.misc.debug("Returning {} data".format(mimetype))
log.network.debug("Returning {} data".format(mimetype))
# We can't just use the QBuffer constructor taking a QByteArray,
# because that somehow segfaults...
@ -165,6 +165,7 @@ def init():
if QWebEngineUrlScheme is not None:
assert not QWebEngineUrlScheme.schemeByName(b'qute').name()
scheme = QWebEngineUrlScheme(b'qute')
scheme.setFlags(QWebEngineUrlScheme.LocalScheme |
QWebEngineUrlScheme.LocalAccessAllowed)
scheme.setFlags(
QWebEngineUrlScheme.LocalScheme | # type: ignore[arg-type]
QWebEngineUrlScheme.LocalAccessAllowed)
QWebEngineUrlScheme.registerScheme(scheme)

View File

@ -36,7 +36,7 @@ from qutebrowser.browser.webengine import spell, webenginequtescheme
from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
from qutebrowser.utils import (utils, standarddir, qtutils, message, log,
urlmatch)
urlmatch, usertypes)
# The default QWebEngineProfile
default_profile = typing.cast(QWebEngineProfile, None)
@ -76,6 +76,10 @@ class _SettingsWrapper:
for settings in self._settings:
settings.setDefaultTextEncoding(encoding)
def setUnknownUrlSchemePolicy(self, policy):
for settings in self._settings:
settings.setUnknownUrlSchemePolicy(policy)
def testAttribute(self, attribute):
return self._settings[0].testAttribute(attribute)
@ -88,6 +92,9 @@ class _SettingsWrapper:
def defaultTextEncoding(self):
return self._settings[0].defaultTextEncoding()
def unknownUrlSchemePolicy(self):
return self._settings[0].unknownUrlSchemePolicy()
class WebEngineSettings(websettings.AbstractSettings):
@ -151,6 +158,19 @@ class WebEngineSettings(websettings.AbstractSettings):
'fonts.web.family.fantasy': QWebEngineSettings.FantasyFont,
}
# Only Qt >= 5.11 support UnknownUrlSchemePolicy
try:
_UNKNOWN_URL_SCHEME_POLICY = {
'disallow':
QWebEngineSettings.DisallowUnknownUrlSchemes,
'allow-from-user-interaction':
QWebEngineSettings.AllowUnknownUrlSchemesFromUserInteraction,
'allow-all':
QWebEngineSettings.AllowAllUnknownUrlSchemes,
}
except AttributeError:
_UNKNOWN_URL_SCHEME_POLICY = None
# Mapping from WebEngineSettings::initDefaults in
# qtwebengine/src/core/web_engine_settings.cpp
_FONT_TO_QFONT = {
@ -162,6 +182,33 @@ class WebEngineSettings(websettings.AbstractSettings):
QWebEngineSettings.FantasyFont: QFont.Fantasy,
}
def set_unknown_url_scheme_policy(
self, policy: typing.Union[str, usertypes.Unset]) -> bool:
"""Set the UnknownUrlSchemePolicy to use.
Return:
True if there was a change, False otherwise.
"""
old_value = self._settings.unknownUrlSchemePolicy()
if isinstance(policy, usertypes.Unset):
self._settings.resetUnknownUrlSchemePolicy()
new_value = self._settings.unknownUrlSchemePolicy()
else:
new_value = self._UNKNOWN_URL_SCHEME_POLICY[policy]
self._settings.setUnknownUrlSchemePolicy(new_value)
return old_value != new_value
def _update_setting(self, setting, value):
if setting == 'content.unknown_url_scheme_policy':
if self._UNKNOWN_URL_SCHEME_POLICY:
return self.set_unknown_url_scheme_policy(value)
return False
return super()._update_setting(setting, value)
def init_settings(self):
super().init_settings()
self.update_setting('content.unknown_url_scheme_policy')
def __init__(self, settings):
super().__init__(settings)
# Attributes which don't exist in all Qt versions.
@ -279,8 +326,13 @@ def _update_settings(option):
"""Update global settings when qwebsettings changed."""
global_settings.update_setting(option)
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-75884
# (note this isn't actually fixed properly before Qt 5.15)
header_bug_fixed = (not qtutils.version_check('5.12', compiled=False) or
qtutils.version_check('5.15', compiled=False))
if option in ['content.headers.user_agent',
'content.headers.accept_language']:
'content.headers.accept_language'] and header_bug_fixed:
default_profile.setter.set_http_headers()
if private_profile:
private_profile.setter.set_http_headers()
@ -315,7 +367,8 @@ def _init_profiles():
default_profile = QWebEngineProfile.defaultProfile()
init_user_agent()
default_profile.setter = ProfileSetter(default_profile)
default_profile.setter = ProfileSetter( # type: ignore[attr-defined]
default_profile)
default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine'))
default_profile.setPersistentStoragePath(
@ -325,7 +378,8 @@ def _init_profiles():
if not qtutils.is_single_process():
private_profile = QWebEngineProfile()
private_profile.setter = ProfileSetter(private_profile)
private_profile.setter = ProfileSetter( # type: ignore[attr-defined]
private_profile)
assert private_profile.isOffTheRecord()
private_profile.setter.init_profile()
@ -354,6 +408,7 @@ def _init_site_specific_quirks():
'https://accounts.google.com/*': firefox_ua,
'https://*.slack.com/*': new_chrome_ua,
'https://docs.google.com/*': firefox_ua,
'https://drive.google.com/*': firefox_ua,
}
if not qtutils.version_check('5.9'):
@ -367,9 +422,16 @@ def _init_site_specific_quirks():
def _init_devtools_settings():
"""Make sure the devtools always get images/JS permissions."""
for setting in ['content.javascript.enabled', 'content.images']:
settings = [
('content.javascript.enabled', True),
('content.images', True)
] # type: typing.List[typing.Tuple[str, typing.Any]]
if qtutils.version_check('5.11'):
settings.append(('content.cookies.accept', 'all'))
for setting, value in settings:
for pattern in ['chrome-devtools://*', 'devtools://*']:
config.instance.set_obj(setting, True,
config.instance.set_obj(setting, value,
pattern=urlmatch.UrlPattern(pattern),
hide_userconfig=True)

View File

@ -41,6 +41,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
from qutebrowser.misc import miscwidgets, objects
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
message, objreg, jinja, debug)
from qutebrowser.keyinput import modeman
from qutebrowser.qt import sip
@ -124,7 +125,7 @@ class WebEngineAction(browsertab.AbstractAction):
tb = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
urlstr = self._tab.url().toString(
QUrl.RemoveUserInfo) # type: ignore
QUrl.RemoveUserInfo) # type: ignore[arg-type]
# The original URL becomes the path of a view-source: URL
# (without a host), but query/fragment should stay.
url = QUrl('view-source:' + urlstr)
@ -156,6 +157,92 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
self._widget.page().print(printer, callback)
class _WebEngineSearchWrapHandler:
"""QtWebEngine implementations related to wrapping when searching.
Attributes:
flag_wrap: An additional flag indicating whether the last search
used wrapping.
_active_match: The 1-based index of the currently active match
on the page.
_total_matches: The total number of search matches on the page.
_nowrap_available: Whether the functionality to prevent wrapping
is available.
"""
def __init__(self):
self._active_match = 0
self._total_matches = 0
self.flag_wrap = True
self._nowrap_available = False
def connect_signal(self, page):
"""Connect to the findTextFinished signal of the page.
Args:
page: The QtWebEnginePage to connect to this handler.
"""
if not qtutils.version_check("5.14"):
return
try:
# pylint: disable=unused-import
from PyQt5.QtWebEngineCore import QWebEngineFindTextResult
except ImportError:
# WORKAROUND for some odd PyQt/packaging bug where the
# findTextResult signal is available, but QWebEngineFindTextResult
# is not. Seems to happen on e.g. Gentoo.
log.webview.warning("Could not import QWebEngineFindTextResult "
"despite running on Qt 5.14. You might need "
"to rebuild PyQtWebEngine.")
return
page.findTextFinished.connect(self._store_match_data)
self._nowrap_available = True
def _store_match_data(self, result):
"""Store information on the last match.
The information will be checked against when wrapping is turned off.
Args:
result: A FindTextResult passed by the findTextFinished signal.
"""
self._active_match = result.activeMatch()
self._total_matches = result.numberOfMatches()
log.webview.debug("Active search match: {}/{}"
.format(self._active_match, self._total_matches))
def reset_match_data(self):
"""Reset match information.
Stale information could lead to next_result or prev_result misbehaving.
"""
self._active_match = 0
self._total_matches = 0
def prevent_wrapping(self, *, going_up):
"""Prevent wrapping if possible and required.
Returns True if a wrap was prevented and False if not.
Args:
going_up: Whether the search would scroll the page up or down.
"""
if (not self._nowrap_available or
self.flag_wrap or self._total_matches == 0):
return False
elif going_up and self._active_match == 1:
message.info("Search hit TOP")
return True
elif not going_up and self._active_match == self._total_matches:
message.info("Search hit BOTTOM")
return True
else:
return False
class WebEngineSearch(browsertab.AbstractSearch):
"""QtWebEngine implementations related to searching on the page.
@ -168,8 +255,16 @@ class WebEngineSearch(browsertab.AbstractSearch):
def __init__(self, tab, parent=None):
super().__init__(tab, parent)
self._flags = QWebEnginePage.FindFlags(0) # type: ignore
self._flags = self._empty_flags()
self._pending_searches = 0
# The API necessary to stop wrapping was added in this version
self._wrap_handler = _WebEngineSearchWrapHandler()
def _empty_flags(self):
return QWebEnginePage.FindFlags(0) # type: ignore[call-overload]
def connect_signals(self):
self._wrap_handler.connect_signal(self._widget.page())
def _find(self, text, flags, callback, caller):
"""Call findText on the widget."""
@ -207,10 +302,10 @@ class WebEngineSearch(browsertab.AbstractSearch):
callback(found)
self.finished.emit(found)
self._widget.findText(text, flags, wrapped_callback)
self._widget.page().findText(text, flags, wrapped_callback)
def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
reverse=False, result_cb=None):
reverse=False, wrap=True, result_cb=None):
# Don't go to next entry on duplicate search
if self.text == text and self.search_displayed:
log.webview.debug("Ignoring duplicate search request"
@ -218,7 +313,9 @@ class WebEngineSearch(browsertab.AbstractSearch):
return
self.text = text
self._flags = QWebEnginePage.FindFlags(0) # type: ignore
self._flags = self._empty_flags()
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:
@ -230,18 +327,27 @@ class WebEngineSearch(browsertab.AbstractSearch):
if self.search_displayed:
self.cleared.emit()
self.search_displayed = False
self._widget.findText('')
self._wrap_handler.reset_match_data()
self._widget.page().findText('')
def prev_result(self, *, result_cb=None):
# The int() here makes sure we get a copy of the flags.
flags = QWebEnginePage.FindFlags(int(self._flags)) # type: ignore
flags = QWebEnginePage.FindFlags(
int(self._flags)) # type: ignore[call-overload]
if flags & QWebEnginePage.FindBackward:
if self._wrap_handler.prevent_wrapping(going_up=False):
return
flags &= ~QWebEnginePage.FindBackward
else:
if self._wrap_handler.prevent_wrapping(going_up=True):
return
flags |= QWebEnginePage.FindBackward
self._find(self.text, flags, result_cb, 'prev_result')
def next_result(self, *, result_cb=None):
going_up = self._flags & QWebEnginePage.FindBackward
if self._wrap_handler.prevent_wrapping(going_up=going_up):
return
self._find(self.text, self._flags, result_cb, 'next_result')
@ -249,6 +355,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
"""QtWebEngine implementations related to moving the cursor/selection."""
def __init__(self,
tab: 'WebEngineTab',
mode_manager: modeman.ModeManager,
parent: QWidget = None) -> None:
super().__init__(mode_manager, parent)
self._tab = tab
def _flags(self):
"""Get flags to pass to JS."""
flags = set()
@ -282,7 +395,10 @@ class WebEngineCaret(browsertab.AbstractCaret):
if enabled is None:
log.webview.debug("Ignoring selection status None")
return
self.selection_toggled.emit(enabled)
if enabled:
self.selection_toggled.emit(browsertab.SelectionState.normal)
else:
self.selection_toggled.emit(browsertab.SelectionState.none)
@pyqtSlot(usertypes.KeyMode)
def _on_mode_left(self, mode):
@ -337,8 +453,9 @@ class WebEngineCaret(browsertab.AbstractCaret):
def move_to_end_of_document(self):
self._js_call('moveToEndOfDocument')
def toggle_selection(self):
self._js_call('toggleSelection', callback=self.selection_toggled.emit)
def toggle_selection(self, line=False):
self._js_call('toggleSelection', line,
callback=self._toggle_sel_translate)
def drop_selection(self):
self._js_call('dropSelection')
@ -413,6 +530,13 @@ class WebEngineCaret(browsertab.AbstractCaret):
code = javascript.assemble('caret', command, *args)
self._tab.run_js_async(code, callback)
def _toggle_sel_translate(self, state_str):
if state_str is None:
message.error("Error toggling caret selection")
return
state = browsertab.SelectionState[state_str]
self.selection_toggled.emit(state)
class WebEngineScroller(browsertab.AbstractScroller):
@ -558,6 +682,16 @@ class WebEngineHistoryPrivate(browsertab.AbstractHistoryPrivate):
qtutils.deserialize(data, self._history)
def load_items(self, items):
if qtutils.version_check('5.15', compiled=False):
# WORKAROUND for https://github.com/qutebrowser/qutebrowser/issues/5359
if items:
url = items[-1].url
if ((url.scheme(), url.host()) == ('qute', 'back') and
len(items) >= 2):
url = items[-2].url
self._tab.load_url(url)
return
if items:
self._tab.before_load_started.emit(items[-1].url)
@ -620,6 +754,10 @@ class WebEngineElements(browsertab.AbstractElements):
"""QtWebEngine implemementations related to elements on the page."""
def __init__(self, tab: 'WebEngineTab') -> None:
super().__init__()
self._tab = tab
def _js_cb_multiple(self, callback, error_cb, js_elems):
"""Handle found elements coming from JS and call the real callback.
@ -708,10 +846,15 @@ class WebEngineAudio(browsertab.AbstractAudio):
config.instance.changed.connect(self._on_config_changed)
def set_muted(self, muted: bool, override: bool = False) -> None:
was_muted = self.is_muted()
self._overridden = override
assert self._widget is not None
page = self._widget.page()
page.setAudioMuted(muted)
if was_muted != muted and qtutils.version_check('5.15'):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85118
# so that the tab title at least updates the muted indicator
self.muted_changed.emit(muted)
def is_muted(self):
page = self._widget.page()
@ -723,7 +866,7 @@ class WebEngineAudio(browsertab.AbstractAudio):
@pyqtSlot(QUrl)
def _on_url_changed(self, url):
if self._overridden:
if self._overridden or not url.isValid():
return
mute = config.instance.get('content.mute', url=url)
self.set_muted(mute)
@ -814,9 +957,11 @@ class _WebEnginePermissions(QObject):
self._tab.data.fullscreen = on
self._tab.fullscreen_requested.emit(on)
if on:
notification = miscwidgets.FullscreenNotification(self._widget)
notification.show()
notification.set_timeout(3000)
timeout = config.val.content.fullscreen.overlay_timeout
if timeout != 0:
notification = miscwidgets.FullscreenNotification(self._widget)
notification.set_timeout(timeout)
notification.show()
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
def _on_feature_permission_requested(self, url, feature):
@ -829,9 +974,18 @@ class _WebEnginePermissions(QObject):
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionDeniedByUser)
permission_str = debug.qenum_key(QWebEnginePage, feature)
if not url.isValid():
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-85116
log.webview.warning("Ignoring feature permission {} for invalid "
"URL {}".format(permission_str, url))
deny_permission()
return
if feature not in self._options:
log.webview.error("Unhandled feature permission {}".format(
debug.qenum_key(QWebEnginePage, feature)))
permission_str))
deny_permission()
return
@ -1097,19 +1251,31 @@ class _WebEngineScripts(QObject):
"""Add site-specific quirk scripts.
NOTE: This isn't implemented for Qt 5.7 because of different UserScript
semantics there. We only have a quirk for WhatsApp Web right now. It
looks like that quirk isn't needed for Qt < 5.13.
semantics there. The WhatsApp Web quirk isn't needed for Qt < 5.13.
The globalthis_quirk would be, but let's not keep such old QtWebEngine
versions on life support.
"""
if not config.val.content.site_specific_quirks:
return
page_scripts = self._widget.page().scripts()
quirks = [
(
'whatsapp_web_quirk',
QWebEngineScript.DocumentReady,
QWebEngineScript.ApplicationWorld,
),
]
if not qtutils.version_check('5.13'):
quirks.append(('globalthis_quirk',
QWebEngineScript.DocumentCreation,
QWebEngineScript.MainWorld))
for filename in ['whatsapp_web_quirk']:
for filename, injection_point, world in quirks:
script = QWebEngineScript()
script.setName(filename)
script.setWorldId(QWebEngineScript.ApplicationWorld)
script.setInjectionPoint(QWebEngineScript.DocumentReady)
script.setWorldId(world)
script.setInjectionPoint(injection_point)
src = utils.read_file("javascript/{}.user.js".format(filename))
script.setSourceCode(src)
page_scripts.insert(script)
@ -1136,6 +1302,9 @@ class WebEngineTabPrivate(browsertab.AbstractTabPrivate):
self._tab.action.exit_fullscreen()
self._widget.shutdown()
def run_js_sync(self, code):
raise browsertab.UnsupportedOperationError
class WebEngineTab(browsertab.AbstractTab):
@ -1149,7 +1318,10 @@ class WebEngineTab(browsertab.AbstractTab):
abort_questions = pyqtSignal()
def __init__(self, *, win_id, mode_manager, private, parent=None):
super().__init__(win_id=win_id, private=private, parent=parent)
super().__init__(win_id=win_id,
mode_manager=mode_manager,
private=private,
parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
private=private)
self.history = WebEngineHistory(tab=self)
@ -1301,7 +1473,7 @@ class WebEngineTab(browsertab.AbstractTab):
title_url = QUrl(url)
title_url.setScheme('')
title_url_str = title_url.toDisplayString(
QUrl.RemoveScheme) # type: ignore
QUrl.RemoveScheme) # type: ignore[arg-type]
if title == title_url_str.strip('/'):
title = ""
@ -1323,12 +1495,15 @@ class WebEngineTab(browsertab.AbstractTab):
title="Proxy authentication required", text=msg,
mode=usertypes.PromptMode.user_pwd,
abort_on=[self.abort_questions], url=urlstr)
if answer is not None:
authenticator.setUser(answer.user)
authenticator.setPassword(answer.password)
else:
try:
sip.assign(authenticator, QAuthenticator()) # type: ignore
sip.assign( # type: ignore[attr-defined]
authenticator,
QAuthenticator())
except AttributeError:
self._show_error_page(url, "Proxy authentication required")
@ -1349,7 +1524,8 @@ class WebEngineTab(browsertab.AbstractTab):
if not netrc_success and answer is None:
log.network.debug("Aborting auth")
try:
sip.assign(authenticator, QAuthenticator()) # type: ignore
sip.assign( # type: ignore[attr-defined]
authenticator, QAuthenticator())
except AttributeError:
# WORKAROUND for
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
@ -1454,19 +1630,19 @@ class WebEngineTab(browsertab.AbstractTab):
@pyqtSlot(certificateerror.CertificateErrorWrapper)
def _on_ssl_errors(self, error):
self._has_ssl_errors = True
url = error.url()
log.webview.debug("Certificate error: {}".format(error))
self._insecure_hosts.add(url.host())
log.network.debug("Certificate error: {}".format(error))
if error.is_overridable():
error.ignore = shared.ignore_certificate_errors(
url, [error], abort_on=[self.abort_questions])
else:
log.webview.error("Non-overridable certificate error: "
log.network.error("Non-overridable certificate error: "
"{}".format(error))
log.webview.debug("ignore {}, URL {}, requested {}".format(
log.network.debug("ignore {}, URL {}, requested {}".format(
error.ignore, url, self.url(requested=True)))
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
@ -1619,7 +1795,7 @@ class WebEngineTab(browsertab.AbstractTab):
try:
# pylint: disable=unused-import
from PyQt5.QtWebEngineWidgets import ( # type: ignore
from PyQt5.QtWebEngineWidgets import (
QWebEngineClientCertificateSelection)
except ImportError:
pass
@ -1638,10 +1814,13 @@ class WebEngineTab(browsertab.AbstractTab):
page.loadFinished.connect(self._on_load_finished)
self.before_load_started.connect(self._on_before_load_started)
self.shutting_down.connect(self.abort_questions) # type: ignore
self.load_started.connect(self.abort_questions) # type: ignore
self.shutting_down.connect(
self.abort_questions) # type: ignore[arg-type]
self.load_started.connect(
self.abort_questions) # type: ignore[arg-type]
# pylint: disable=protected-access
self.audio._connect_signals()
self.search.connect_signals()
self._permissions.connect_signals()
self._scripts.connect_signals()

View File

@ -19,6 +19,8 @@
"""The main browser widget for QtWebEngine."""
import typing
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QWidget
@ -66,20 +68,26 @@ class WebEngineView(QWebEngineView):
However, it sometimes isn't, so we use this as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-68727
This got introduced in Qt 5.11.0 and fixed in 5.12.0.
The above bug got introduced in Qt 5.11.0 and fixed in 5.12.0.
"""
if 'lost-focusproxy' not in objects.debug_flags:
proxy = self.focusProxy()
if proxy is not None:
return proxy
proxy = self.focusProxy() # type: typing.Optional[QWidget]
if 'lost-focusproxy' in objects.debug_flags:
proxy = None
if (proxy is not None or
not qtutils.version_check('5.11', compiled=False) or
qtutils.version_check('5.12', compiled=False)):
return proxy
# We don't want e.g. a QMenu.
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
children = [c for c in self.findChildren(QWidget)
if c.isVisible() and c.inherits(rwhv_class)]
log.webview.debug("Found possibly lost focusProxy: {}"
.format(children))
if children:
log.webview.debug("Found possibly lost focusProxy: {}"
.format(children))
return children[-1] if children else None
@ -216,8 +224,7 @@ class WebEnginePage(QWebEnginePage):
self.shutting_down],
escape_msg=escape_msg)
except shared.CallSuper:
return super().javaScriptPrompt( # type: ignore
url, js_msg, default)
return super().javaScriptPrompt(url, js_msg, default)
def javaScriptAlert(self, url, js_msg):
"""Override javaScriptAlert to use qutebrowser prompts."""

View File

@ -25,8 +25,8 @@ from PyQt5.QtNetwork import QNetworkCookie, QNetworkCookieJar
from PyQt5.QtCore import pyqtSignal, QDateTime
from qutebrowser.config import config
from qutebrowser.utils import utils, standarddir, objreg
from qutebrowser.misc import lineparser
from qutebrowser.utils import utils, standarddir, objreg, log
from qutebrowser.misc import lineparser, objects
cookie_jar = None
@ -56,7 +56,13 @@ class RAMCookieJar(QNetworkCookieJar):
Return:
True if one or more cookies are set for 'url', otherwise False.
"""
if config.val.content.cookies.accept == 'never':
accept = config.instance.get('content.cookies.accept', url=url)
if 'log-cookies' in objects.debug_flags:
log.network.debug('Cookie on {} -> applying setting {}'
.format(url.toDisplayString(), accept))
if accept == 'never':
return False
else:
self.changed.emit()
@ -89,7 +95,8 @@ class CookieJar(RAMCookieJar):
"""Parse cookies from lineparser and store them."""
cookies = [] # type: typing.Sequence[QNetworkCookie]
for line in self._lineparser:
cookies += QNetworkCookie.parseCookies(line)
line_cookies = QNetworkCookie.parseCookies(line)
cookies += line_cookies # type: ignore[operator]
self.setAllCookies(cookies)
def purge_old_cookies(self):
@ -98,7 +105,8 @@ class CookieJar(RAMCookieJar):
# http://doc.qt.io/qt-5/qtwebkitexamples-webkitwidgets-browser-cookiejar-cpp.html
now = QDateTime.currentDateTime()
cookies = [c for c in self.allCookies()
if c.isSessionCookie() or c.expirationDate() >= now]
if c.isSessionCookie() or
c.expirationDate() >= now] # type: ignore[operator]
self.setAllCookies(cookies)
def save(self):

View File

@ -105,8 +105,9 @@ def _is_secure_cipher(cipher):
def init():
"""Disable insecure SSL ciphers on old Qt versions."""
default_ciphers = QSslSocket.defaultCiphers()
log.init.debug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
log.init.vdebug( # type: ignore[attr-defined]
"Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
good_ciphers = []
bad_ciphers = []
@ -116,9 +117,10 @@ def init():
else:
bad_ciphers.append(cipher)
log.init.debug("Disabling bad ciphers: {}".format(
', '.join(c.name() for c in bad_ciphers)))
QSslSocket.setDefaultCiphers(good_ciphers)
if bad_ciphers:
log.init.debug("Disabling bad ciphers: {}".format(
', '.join(c.name() for c in bad_ciphers)))
QSslSocket.setDefaultCiphers(good_ciphers)
_SavedErrorsType = typing.MutableMapping[urlutils.HostTupleType,
@ -169,14 +171,15 @@ class NetworkManager(QNetworkAccessManager):
}
self._set_cookiejar()
self._set_cache()
self.sslErrors.connect(self.on_ssl_errors) # type: ignore
self.sslErrors.connect( # type: ignore[attr-defined]
self.on_ssl_errors)
self._rejected_ssl_errors = collections.defaultdict(
list) # type: _SavedErrorsType
self._accepted_ssl_errors = collections.defaultdict(
list) # type: _SavedErrorsType
self.authenticationRequired.connect( # type: ignore
self.authenticationRequired.connect( # type: ignore[attr-defined]
self.on_authentication_required)
self.proxyAuthenticationRequired.connect( # type: ignore
self.proxyAuthenticationRequired.connect( # type: ignore[attr-defined]
self.on_proxy_authentication_required)
self.netrc_used = False
@ -235,7 +238,7 @@ class NetworkManager(QNetworkAccessManager):
errors: A list of errors.
"""
errors = [certificateerror.CertificateErrorWrapper(e) for e in errors]
log.webview.debug("Certificate errors: {!r}".format(
log.network.debug("Certificate errors: {!r}".format(
' / '.join(str(err) for err in errors)))
try:
host_tpl = urlutils.host_tuple(
@ -251,7 +254,7 @@ class NetworkManager(QNetworkAccessManager):
is_rejected = set(errors).issubset(
self._rejected_ssl_errors[host_tpl])
log.webview.debug("Already accepted: {} / "
log.network.debug("Already accepted: {} / "
"rejected {}".format(is_accepted, is_rejected))
if is_rejected:
@ -424,7 +427,7 @@ class NetworkManager(QNetworkAccessManager):
if 'log-requests' in objects.debug_flags:
operation = debug.qenum_key(QNetworkAccessManager, op)
operation = operation.replace('Operation', '').upper()
log.webview.debug("{} {}, first-party {}".format(
log.network.debug("{} {}, first-party {}".format(
operation,
req.url().toDisplayString(),
current_url.toDisplayString()))

View File

@ -59,12 +59,15 @@ class FixedDataNetworkReply(QNetworkReply):
# For some reason, a segfault will be triggered if these lambdas aren't
# there.
# pylint: disable=unnecessary-lambda
QTimer.singleShot(0, lambda:
self.metaDataChanged.emit()) # type: ignore
QTimer.singleShot(0, lambda:
self.readyRead.emit()) # type: ignore
QTimer.singleShot(0, lambda:
self.finished.emit()) # type: ignore
QTimer.singleShot(
0,
lambda: self.metaDataChanged.emit()) # type: ignore[attr-defined]
QTimer.singleShot(
0,
lambda: self.readyRead.emit()) # type: ignore[attr-defined]
QTimer.singleShot(
0,
lambda: self.finished.emit()) # type: ignore[attr-defined]
@pyqtSlot()
def abort(self):
@ -120,9 +123,9 @@ class ErrorNetworkReply(QNetworkReply):
self.setOpenMode(QIODevice.ReadOnly)
self.setError(error, errorstring)
QTimer.singleShot(0, lambda:
self.error.emit(error)) # type: ignore
self.error.emit(error)) # type: ignore[attr-defined]
QTimer.singleShot(0, lambda:
self.finished.emit()) # type: ignore
self.finished.emit()) # type: ignore[attr-defined]
def abort(self):
"""Do nothing since it's a fake reply."""
@ -150,7 +153,7 @@ class RedirectNetworkReply(QNetworkReply):
super().__init__(parent)
self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url)
QTimer.singleShot(0, lambda:
self.finished.emit()) # type: ignore
self.finished.emit()) # type: ignore[attr-defined]
def abort(self):
"""Called when there's e.g. a redirection limit."""

View File

@ -48,7 +48,7 @@ def handler(request, operation, current_url):
if ((url.scheme(), url.host(), url.path()) ==
('qute', 'settings', '/set')):
if current_url != QUrl('qute://settings/'):
log.webview.warning("Blocking malicious request from {} to {}"
log.network.warning("Blocking malicious request from {} to {}"
.format(current_url.toDisplayString(),
url.toDisplayString()))
return networkreply.ErrorNetworkReply(

View File

@ -175,6 +175,11 @@ class WebKitElement(webelem.AbstractWebElement):
self._elem.parent())
if elem is None or elem.isNull():
return None
if typing.TYPE_CHECKING:
# pylint: disable=used-before-assignment
assert isinstance(self._tab, webkittab.WebKitTab)
return WebKitElement(elem, tab=self._tab)
def _rect_on_view_js(self) -> typing.Optional[QRect]:
@ -189,7 +194,7 @@ class WebKitElement(webelem.AbstractWebElement):
return None
text = utils.compact_text(self._elem.toOuterXml(), 500)
log.webelem.vdebug( # type: ignore
log.webelem.vdebug( # type: ignore[attr-defined]
"Client rectangles of element '{}': {}".format(text, rects))
for i in range(int(rects.get("length", 0))):

View File

@ -25,6 +25,7 @@ import xml.etree.ElementTree
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtPrintSupport import QPrinter
@ -33,6 +34,7 @@ from qutebrowser.browser import browsertab, shared
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
webkitsettings)
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
from qutebrowser.keyinput import modeman
from qutebrowser.qt import sip
@ -85,7 +87,10 @@ class WebKitSearch(browsertab.AbstractSearch):
def __init__(self, tab, parent=None):
super().__init__(tab, parent)
self._flags = QWebPage.FindFlags(0) # type: ignore
self._flags = self._empty_flags()
def _empty_flags(self):
return QWebPage.FindFlags(0) # type: ignore[call-overload]
def _call_cb(self, callback, found, text, flags, caller):
"""Call the given callback if it's non-None.
@ -125,7 +130,7 @@ class WebKitSearch(browsertab.AbstractSearch):
self._widget.findText('', QWebPage.HighlightAllOccurrences)
def search(self, text, *, ignore_case=usertypes.IgnoreCase.never,
reverse=False, result_cb=None):
reverse=False, wrap=True, result_cb=None):
# Don't go to next entry on duplicate search
if self.text == text and self.search_displayed:
log.webview.debug("Ignoring duplicate search request"
@ -137,11 +142,13 @@ class WebKitSearch(browsertab.AbstractSearch):
self.text = text
self.search_displayed = True
self._flags = QWebPage.FindWrapsAroundDocument
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
# 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)
@ -157,7 +164,8 @@ class WebKitSearch(browsertab.AbstractSearch):
def prev_result(self, *, result_cb=None):
self.search_displayed = True
# The int() here makes sure we get a copy of the flags.
flags = QWebPage.FindFlags(int(self._flags)) # type: ignore
flags = QWebPage.FindFlags(
int(self._flags)) # type: ignore[call-overload]
if flags & QWebPage.FindBackward:
flags &= ~QWebPage.FindBackward
else:
@ -170,13 +178,24 @@ class WebKitCaret(browsertab.AbstractCaret):
"""QtWebKit implementations related to moving the cursor/selection."""
def __init__(self,
tab: 'WebKitTab',
mode_manager: modeman.ModeManager,
parent: QWidget = None) -> None:
super().__init__(mode_manager, parent)
self._tab = tab
self._selection_state = browsertab.SelectionState.none
@pyqtSlot(usertypes.KeyMode)
def _on_mode_entered(self, mode):
if mode != usertypes.KeyMode.caret:
return
self.selection_enabled = self._widget.hasSelection()
self.selection_toggled.emit(self.selection_enabled)
if self._widget.hasSelection():
self._selection_state = browsertab.SelectionState.normal
else:
self._selection_state = browsertab.SelectionState.none
self.selection_toggled.emit(self._selection_state)
settings = self._widget.settings()
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, True)
@ -191,7 +210,7 @@ class WebKitCaret(browsertab.AbstractCaret):
#
# Note: We can't use hasSelection() here, as that's always
# true in caret mode.
if not self.selection_enabled:
if self._selection_state is browsertab.SelectionState.none:
self._widget.page().currentFrame().evaluateJavaScript(
utils.read_file('javascript/position_caret.js'))
@ -199,151 +218,189 @@ class WebKitCaret(browsertab.AbstractCaret):
def _on_mode_left(self, _mode):
settings = self._widget.settings()
if settings.testAttribute(QWebSettings.CaretBrowsingEnabled):
if self.selection_enabled and self._widget.hasSelection():
if (self._selection_state is not browsertab.SelectionState.none and
self._widget.hasSelection()):
# Remove selection if it exists
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
settings.setAttribute(QWebSettings.CaretBrowsingEnabled, False)
self.selection_enabled = False
self._selection_state = browsertab.SelectionState.none
def move_to_next_line(self, count=1):
if not self.selection_enabled:
act = QWebPage.MoveToNextLine
else:
if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectNextLine
else:
act = QWebPage.MoveToNextLine
for _ in range(count):
self._widget.triggerPageAction(act)
if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_end()
def move_to_prev_line(self, count=1):
if not self.selection_enabled:
act = QWebPage.MoveToPreviousLine
else:
if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectPreviousLine
else:
act = QWebPage.MoveToPreviousLine
for _ in range(count):
self._widget.triggerPageAction(act)
if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_start()
def move_to_next_char(self, count=1):
if not self.selection_enabled:
act = QWebPage.MoveToNextChar
else:
if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectNextChar
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToNextChar
for _ in range(count):
self._widget.triggerPageAction(act)
def move_to_prev_char(self, count=1):
if not self.selection_enabled:
act = QWebPage.MoveToPreviousChar
else:
if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectPreviousChar
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToPreviousChar
for _ in range(count):
self._widget.triggerPageAction(act)
def move_to_end_of_word(self, count=1):
if not self.selection_enabled:
act = [QWebPage.MoveToNextWord]
if utils.is_windows: # pragma: no cover
act.append(QWebPage.MoveToPreviousChar)
else:
if self._selection_state is browsertab.SelectionState.normal:
act = [QWebPage.SelectNextWord]
if utils.is_windows: # pragma: no cover
act.append(QWebPage.SelectPreviousChar)
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = [QWebPage.MoveToNextWord]
if utils.is_windows: # pragma: no cover
act.append(QWebPage.MoveToPreviousChar)
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
def move_to_next_word(self, count=1):
if not self.selection_enabled:
act = [QWebPage.MoveToNextWord]
if not utils.is_windows: # pragma: no branch
act.append(QWebPage.MoveToNextChar)
else:
if self._selection_state is browsertab.SelectionState.normal:
act = [QWebPage.SelectNextWord]
if not utils.is_windows: # pragma: no branch
act.append(QWebPage.SelectNextChar)
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = [QWebPage.MoveToNextWord]
if not utils.is_windows: # pragma: no branch
act.append(QWebPage.MoveToNextChar)
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
def move_to_prev_word(self, count=1):
if not self.selection_enabled:
act = QWebPage.MoveToPreviousWord
else:
if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectPreviousWord
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToPreviousWord
for _ in range(count):
self._widget.triggerPageAction(act)
def move_to_start_of_line(self):
if not self.selection_enabled:
act = QWebPage.MoveToStartOfLine
else:
if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectStartOfLine
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToStartOfLine
self._widget.triggerPageAction(act)
def move_to_end_of_line(self):
if not self.selection_enabled:
act = QWebPage.MoveToEndOfLine
else:
if self._selection_state is browsertab.SelectionState.normal:
act = QWebPage.SelectEndOfLine
elif self._selection_state is browsertab.SelectionState.line:
return
else:
act = QWebPage.MoveToEndOfLine
self._widget.triggerPageAction(act)
def move_to_start_of_next_block(self, count=1):
if not self.selection_enabled:
act = [QWebPage.MoveToNextLine,
QWebPage.MoveToStartOfBlock]
else:
if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectNextLine,
QWebPage.SelectStartOfBlock]
else:
act = [QWebPage.MoveToNextLine,
QWebPage.MoveToStartOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_end()
def move_to_start_of_prev_block(self, count=1):
if not self.selection_enabled:
act = [QWebPage.MoveToPreviousLine,
QWebPage.MoveToStartOfBlock]
else:
if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectPreviousLine,
QWebPage.SelectStartOfBlock]
else:
act = [QWebPage.MoveToPreviousLine,
QWebPage.MoveToStartOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_start()
def move_to_end_of_next_block(self, count=1):
if not self.selection_enabled:
act = [QWebPage.MoveToNextLine,
QWebPage.MoveToEndOfBlock]
else:
if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectNextLine,
QWebPage.SelectEndOfBlock]
else:
act = [QWebPage.MoveToNextLine,
QWebPage.MoveToEndOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_end()
def move_to_end_of_prev_block(self, count=1):
if not self.selection_enabled:
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
else:
if self._selection_state is not browsertab.SelectionState.none:
act = [QWebPage.SelectPreviousLine, QWebPage.SelectEndOfBlock]
else:
act = [QWebPage.MoveToPreviousLine, QWebPage.MoveToEndOfBlock]
for _ in range(count):
for a in act:
self._widget.triggerPageAction(a)
if self._selection_state is browsertab.SelectionState.line:
self._select_line_to_start()
def move_to_start_of_document(self):
if not self.selection_enabled:
act = QWebPage.MoveToStartOfDocument
else:
if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectStartOfDocument
else:
act = QWebPage.MoveToStartOfDocument
self._widget.triggerPageAction(act)
if self._selection_state is browsertab.SelectionState.line:
self._select_line()
def move_to_end_of_document(self):
if not self.selection_enabled:
act = QWebPage.MoveToEndOfDocument
else:
if self._selection_state is not browsertab.SelectionState.none:
act = QWebPage.SelectEndOfDocument
else:
act = QWebPage.MoveToEndOfDocument
self._widget.triggerPageAction(act)
def toggle_selection(self):
self.selection_enabled = not self.selection_enabled
self.selection_toggled.emit(self.selection_enabled)
def toggle_selection(self, line=False):
if line:
self._selection_state = browsertab.SelectionState.line
self._select_line()
self.reverse_selection()
self._select_line()
self.reverse_selection()
elif self._selection_state is not browsertab.SelectionState.normal:
self._selection_state = browsertab.SelectionState.normal
else:
self._selection_state = browsertab.SelectionState.none
self.selection_toggled.emit(self._selection_state)
def drop_selection(self):
self._widget.triggerPageAction(QWebPage.MoveToNextChar)
@ -360,6 +417,32 @@ class WebKitCaret(browsertab.AbstractCaret):
);
}""")
def _select_line(self):
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
self.reverse_selection()
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
self.reverse_selection()
def _select_line_to_end(self):
# direction of selection (if anchor is to the left or right
# of focus) has to be checked before moving selection
# to the end of line
if self._js_selection_left_to_right():
self._widget.triggerPageAction(QWebPage.SelectEndOfLine)
def _select_line_to_start(self):
if not self._js_selection_left_to_right():
self._widget.triggerPageAction(QWebPage.SelectStartOfLine)
def _js_selection_left_to_right(self):
"""Return True iff the selection's direction is left to right."""
return self._tab.private_api.run_js_sync("""
var sel = window.getSelection();
var position = sel.anchorNode.compareDocumentPosition(sel.focusNode);
(!position && sel.anchorOffset < sel.focusOffset ||
position === Node.DOCUMENT_POSITION_FOLLOWING);
""")
def _follow_selected(self, *, tab=False):
if QWebSettings.globalSettings().testAttribute(
QWebSettings.JavascriptEnabled):
@ -391,11 +474,11 @@ class WebKitCaret(browsertab.AbstractCaret):
if selected_element is not None:
try:
url = selected_element.attrib['href']
href = selected_element.attrib['href']
except KeyError:
raise browsertab.WebTabError('Anchor element without '
'href!')
url = self._tab.url().resolved(QUrl(url))
url = self._tab.url().resolved(QUrl(href))
if tab:
self._tab.new_tab_requested.emit(url)
else:
@ -580,6 +663,10 @@ class WebKitElements(browsertab.AbstractElements):
"""QtWebKit implemementations related to elements on the page."""
def __init__(self, tab: 'WebKitTab') -> None:
super().__init__()
self._tab = tab
def find_css(self, selector, callback, error_cb, *, only_visible=False):
utils.unused(error_cb)
mainframe = self._widget.page().mainFrame()
@ -691,13 +778,21 @@ class WebKitTabPrivate(browsertab.AbstractTabPrivate):
def shutdown(self):
self._widget.shutdown()
def run_js_sync(self, code):
document_element = self._widget.page().mainFrame().documentElement()
result = document_element.evaluateJavaScript(code)
return result
class WebKitTab(browsertab.AbstractTab):
"""A QtWebKit tab in the browser."""
def __init__(self, *, win_id, mode_manager, private, parent=None):
super().__init__(win_id=win_id, private=private, parent=parent)
super().__init__(win_id=win_id,
mode_manager=mode_manager,
private=private,
parent=parent)
widget = webview.WebView(win_id=win_id, tab_id=self.tab_id,
private=private, tab=self)
if private:
@ -749,8 +844,7 @@ class WebKitTab(browsertab.AbstractTab):
def run_js_async(self, code, callback=None, *, world=None):
if world is not None and world != usertypes.JsWorld.jseval:
log.webview.warning("Ignoring world ID {}".format(world))
document_element = self._widget.page().mainFrame().documentElement()
result = document_element.evaluateJavaScript(code)
result = self.private_api.run_js_sync(code)
if callback is not None:
callback(result)
@ -847,9 +941,9 @@ class WebKitTab(browsertab.AbstractTab):
if navigation.is_main_frame:
self.settings.update_for_url(navigation.url)
@pyqtSlot()
def _on_ssl_errors(self):
self._has_ssl_errors = True
@pyqtSlot('QNetworkReply*')
def _on_ssl_errors(self, reply):
self._insecure_hosts.add(reply.url().host())
def _connect_signals(self):
view = self._widget

View File

@ -21,6 +21,7 @@
import html
import functools
import typing
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
from PyQt5.QtGui import QDesktopServices
@ -77,22 +78,24 @@ class BrowserPage(QWebPage):
self.setNetworkAccessManager(self._networkmanager)
self.setForwardUnsupportedContent(True)
self.reloading.connect(self._networkmanager.clear_rejected_ssl_errors)
self.printRequested.connect( # type: ignore
self.printRequested.connect( # type: ignore[attr-defined]
self.on_print_requested)
self.downloadRequested.connect( # type: ignore
self.downloadRequested.connect( # type: ignore[attr-defined]
self.on_download_requested)
self.unsupportedContent.connect( # type: ignore
self.unsupportedContent.connect( # type: ignore[attr-defined]
self.on_unsupported_content)
self.loadStarted.connect(self.on_load_started) # type: ignore
self.featurePermissionRequested.connect( # type: ignore
self.loadStarted.connect( # type: ignore[attr-defined]
self.on_load_started)
self.featurePermissionRequested.connect( # type: ignore[attr-defined]
self._on_feature_permission_requested)
self.saveFrameStateRequested.connect( # type: ignore
self.saveFrameStateRequested.connect( # type: ignore[attr-defined]
self.on_save_frame_state_requested)
self.restoreFrameStateRequested.connect( # type: ignore
self.restoreFrameStateRequested.connect( # type: ignore[attr-defined]
self.on_restore_frame_state_requested)
self.loadFinished.connect( # type: ignore
self.loadFinished.connect( # type: ignore[attr-defined]
functools.partial(self._inject_userjs, self.mainFrame()))
self.frameCreated.connect(self._connect_userjs_signals) # type: ignore
self.frameCreated.connect( # type: ignore[attr-defined]
self._connect_userjs_signals)
@pyqtSlot('QWebFrame*')
def _connect_userjs_signals(self, frame):
@ -205,8 +208,10 @@ class BrowserPage(QWebPage):
suggested_file = ""
if info.suggestedFileNames:
suggested_file = info.suggestedFileNames[0]
files.fileNames, _ = QFileDialog.getOpenFileNames(
None, None, suggested_file) # type: ignore
None, None, suggested_file) # type: ignore[arg-type]
return True
def shutdown(self):
@ -348,11 +353,11 @@ class BrowserPage(QWebPage):
self.setFeaturePermission, frame, feature,
QWebPage.PermissionDeniedByUser)
url = frame.url().adjusted(QUrl.RemoveUserInfo |
QUrl.RemovePath |
QUrl.RemoveQuery |
QUrl.RemoveFragment)
url = frame.url().adjusted(typing.cast(QUrl.FormattingOptions,
QUrl.RemoveUserInfo |
QUrl.RemovePath |
QUrl.RemoveQuery |
QUrl.RemoveFragment))
question = shared.feature_permission(
url=url,
option=options[feature], msg=messages[feature],
@ -411,6 +416,8 @@ class BrowserPage(QWebPage):
def userAgentForUrl(self, url):
"""Override QWebPage::userAgentForUrl to customize the user agent."""
if not url.isValid():
url = None
return websettings.user_agent(url)
def supportsExtension(self, ext):

View File

@ -87,7 +87,8 @@ class WebView(QWebView):
stylesheet.set_register(self)
def __repr__(self):
urlstr = self.url().toDisplayString(QUrl.EncodeUnicode) # type: ignore
flags = QUrl.EncodeUnicode
urlstr = self.url().toDisplayString(flags) # type: ignore[arg-type]
url = utils.elide(urlstr, 100)
return utils.get_repr(self, tab_id=self._tab_id, url=url)
@ -97,7 +98,7 @@ class WebView(QWebView):
# Copied from:
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
try:
self.setPage(None) # type: ignore
self.setPage(None) # type: ignore[arg-type]
except RuntimeError:
# It seems sometimes Qt has already deleted the QWebView and we
# get: RuntimeError: wrapped C/C++ object of type WebView has been
@ -180,9 +181,9 @@ class WebView(QWebView):
This is not needed for QtWebEngine, so it's in here.
"""
menu = self.page().createStandardContextMenu()
self.shutting_down.connect(menu.close) # type: ignore
self.shutting_down.connect(menu.close) # type: ignore[arg-type]
mm = modeman.instance(self.win_id)
mm.entered.connect(menu.close) # type: ignore
mm.entered.connect(menu.close) # type: ignore[arg-type]
menu.exec_(e.globalPos())
def showEvent(self, e):

View File

@ -244,7 +244,7 @@ class Command:
args = self._param_to_argparse_args(param, is_bool)
callsig = debug_utils.format_call(self.parser.add_argument, args,
kwargs, full=False)
log.commands.vdebug( # type: ignore
log.commands.vdebug( # type: ignore[attr-defined]
'Adding arg {} of type {} -> {}'
.format(param.name, typ, callsig))
self.parser.add_argument(*args, **kwargs)
@ -334,8 +334,8 @@ class Command:
Args:
param: The inspect.Parameter to look at.
"""
arginfo = self.get_arg_info(param)
if arginfo.value:
arg_info = self.get_arg_info(param)
if arg_info.value:
# Filled values are passed 1:1
return None
elif param.kind in [inspect.Parameter.VAR_POSITIONAL,
@ -409,7 +409,8 @@ class Command:
if hasattr(typing, 'UnionMeta'):
# Python 3.5.2
# pylint: disable=no-member,useless-suppression
is_union = isinstance(typ, typing.UnionMeta) # type: ignore
is_union = isinstance(
typ, typing.UnionMeta) # type: ignore[attr-defined]
else:
is_union = getattr(typ, '__origin__', None) is typing.Union
@ -575,7 +576,7 @@ class Command:
def register(self):
"""Register this command in objects.commands."""
log.commands.vdebug( # type: ignore
log.commands.vdebug( # type: ignore[attr-defined]
"Registering command {} (from {}:{})".format(
self.name, self.handler.__module__, self.handler.__qualname__))
if self.name in objects.commands:

View File

@ -94,7 +94,7 @@ def _init_variable_replacements() -> typing.Mapping[str, _ReplacementFunction]:
modified_key = '{' + key + '}'
# x = modified_key is to avoid binding x as a closure
replacements[modified_key] = (
lambda _, x=modified_key: x) # type: ignore
lambda _, x=modified_key: x) # type: ignore[misc]
return replacements
@ -332,7 +332,7 @@ class CommandRunner(AbstractCommandRunner):
self._win_id = win_id
@contextlib.contextmanager
def _handle_error(self, safely) -> typing.Iterator[None]:
def _handle_error(self, safely: bool) -> typing.Iterator[None]:
"""Show exceptions as errors if safely=True is given."""
try:
yield

View File

@ -31,6 +31,7 @@ from qutebrowser.commands import runners
from qutebrowser.config import websettings
from qutebrowser.misc import guiprocess
from qutebrowser.browser import downloads
from qutebrowser.qt import sip
class _QtFIFOReader(QObject):
@ -59,8 +60,10 @@ class _QtFIFOReader(QObject):
fd = os.open(filepath, os.O_RDWR | os.O_NONBLOCK)
# pylint: enable=no-member,useless-suppression
self._fifo = os.fdopen(fd, 'r')
self._notifier = QSocketNotifier(fd, QSocketNotifier.Read, self)
self._notifier.activated.connect(self.read_line) # type: ignore
self._notifier = QSocketNotifier(typing.cast(sip.voidptr, fd),
QSocketNotifier.Read, self)
self._notifier.activated.connect( # type: ignore[attr-defined]
self.read_line)
@pyqtSlot()
def read_line(self):
@ -148,7 +151,8 @@ class _BaseUserscriptRunner(QObject):
log.procs.debug("Both text/HTML stored, kicking off userscript!")
self._run_process(*self._args, **self._kwargs)
def _run_process(self, cmd, *args, env=None, verbose=False):
def _run_process(self, cmd, *args, env=None, verbose=False,
output_messages=False):
"""Start the given command.
Args:
@ -156,15 +160,16 @@ class _BaseUserscriptRunner(QObject):
*args: The arguments to hand to the command
env: A dictionary of environment variables to add.
verbose: Show notifications when the command started/exited.
output_messages: Show the output as messages.
"""
assert self._filepath is not None
self._env['QUTE_FIFO'] = self._filepath
if env is not None:
self._env.update(env)
self._proc = guiprocess.GUIProcess('userscript',
additional_env=self._env,
verbose=verbose, parent=self)
self._proc = guiprocess.GUIProcess(
'userscript', additional_env=self._env,
output_messages=output_messages, verbose=verbose, parent=self)
self._proc.finished.connect(self.on_proc_finished)
self._proc.error.connect(self.on_proc_error)
self._proc.start(cmd, args)
@ -254,14 +259,15 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
self._filepath = tempfile.mktemp(prefix='qutebrowser-userscript-',
dir=standarddir.runtime())
# pylint: disable=no-member,useless-suppression
os.mkfifo(self._filepath)
os.mkfifo(self._filepath, mode=0o600)
# pylint: enable=no-member,useless-suppression
except OSError as e:
self._filepath = None # Make sure it's not used
message.error("Error while creating FIFO: {}".format(e))
return
self._reader = _QtFIFOReader(self._filepath)
self._reader.got_line.connect(self.got_cmd) # type: ignore
self._reader.got_line.connect(self.got_cmd) # type: ignore[arg-type]
@pyqtSlot()
def on_proc_finished(self):
@ -398,7 +404,8 @@ def _lookup_path(cmd):
raise NotFoundError(cmd, directories)
def run_async(tab, cmd, *args, win_id, env, verbose=False):
def run_async(tab, cmd, *args, win_id, env, verbose=False,
output_messages=False):
"""Run a userscript after dumping page html/source.
Raises:
@ -413,15 +420,15 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
win_id: The window id the userscript is executed in.
env: A dictionary of variables to add to the process environment.
verbose: Show notifications when the command started/exited.
output_messages: Show the output as messages.
"""
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
commandrunner = runners.CommandRunner(win_id, parent=tabbed_browser)
tb = objreg.get('tabbed-browser', scope='window', window=win_id)
commandrunner = runners.CommandRunner(win_id, parent=tb)
if utils.is_posix:
runner = _POSIXUserscriptRunner(tabbed_browser)
runner = _POSIXUserscriptRunner(tb) # type: _BaseUserscriptRunner
elif utils.is_windows: # pragma: no cover
runner = _WindowsUserscriptRunner(tabbed_browser)
runner = _WindowsUserscriptRunner(tb)
else: # pragma: no cover
raise UnsupportedError
@ -451,7 +458,8 @@ def run_async(tab, cmd, *args, win_id, env, verbose=False):
runner.finished.connect(commandrunner.deleteLater)
runner.finished.connect(runner.deleteLater)
runner.prepare_run(cmd_path, *args, env=env, verbose=verbose)
runner.prepare_run(cmd_path, *args, env=env, verbose=verbose,
output_messages=output_messages)
tab.dump_async(runner.store_html)
tab.dump_async(runner.store_text, plain=True)
return runner

View File

@ -290,7 +290,7 @@ class CompletionItemDelegate(QStyledItemDelegate):
size = self._style.sizeFromContents(QStyle.CT_ItemViewItem, self._opt,
docsize, self._opt.widget)
qtutils.ensure_valid(size)
return size + QSize(10, 3)
return size + QSize(10, 3) # type: ignore[operator]
def paint(self, painter, option, index):
"""Override the QStyledItemDelegate paint function.

View File

@ -278,7 +278,7 @@ class CompletionView(QTreeView):
selmodel.setCurrentIndex(
idx,
QItemSelectionModel.ClearAndSelect | # type: ignore
QItemSelectionModel.ClearAndSelect | # type: ignore[arg-type]
QItemSelectionModel.Rows)
# if the last item is focused, try to fetch more
@ -424,4 +424,8 @@ class CompletionView(QTreeView):
if not index.isValid():
raise cmdutils.CommandError("No item selected!")
text = self.model().data(index)
if not utils.supports_selection():
sel = False
utils.set_clipboard(text, selection=sel)

View File

@ -183,11 +183,13 @@ class CompletionModel(QAbstractItemModel):
# WORKAROUND:
# layoutChanged is broken in PyQt 5.7.1, so we must use metaObject
# https://www.riverbankcomputing.com/pipermail/pyqt/2017-January/038483.html
self.metaObject().invokeMethod(self, # type: ignore
"layoutAboutToBeChanged")
meta = self.metaObject()
meta.invokeMethod(self, # type: ignore[misc, call-overload]
"layoutAboutToBeChanged")
for cat in self._categories:
cat.set_pattern(pattern)
self.metaObject().invokeMethod(self, "layoutChanged") # type: ignore
meta.invokeMethod(self, # type: ignore[misc, call-overload]
"layoutChanged")
def first_item(self):
"""Return the index of the first child (non-category) in the model."""

View File

@ -97,11 +97,13 @@ def session(*, info=None): # pylint: disable=unused-argument
return model
def _buffer(skip_win_id=None):
def _buffer(*, win_id_filter=lambda _win_id: True, add_win_id=True):
"""Helper to get the completion model for buffer/other_buffer.
Args:
skip_win_id: The id of the window to skip, or None to include all.
win_id_filter: A filter function for window IDs to include.
Should return True for all included windows.
add_win_id: Whether to add the window ID to the completion items.
"""
def delete_buffer(data):
"""Close the selected tab."""
@ -117,8 +119,9 @@ def _buffer(skip_win_id=None):
windows = [] # type: typing.List[typing.Tuple[str, str, str]]
for win_id in objreg.window_registry:
if skip_win_id is not None and win_id == skip_win_id:
if not win_id_filter(win_id):
continue
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if tabbed_browser.shutting_down:
@ -126,14 +129,18 @@ def _buffer(skip_win_id=None):
tabs = [] # type: typing.List[typing.Tuple[str, str, str]]
for idx in range(tabbed_browser.widget.count()):
tab = tabbed_browser.widget.widget(idx)
tabs.append(("{}/{}".format(win_id, idx + 1),
tab_str = ("{}/{}".format(win_id, idx + 1) if add_win_id
else str(idx + 1))
tabs.append((tab_str,
tab.url().toDisplayString(),
tabbed_browser.widget.page_title(idx)))
if tabs_are_windows:
windows += tabs
else:
title = str(win_id) if add_win_id else "Tabs"
cat = listcategory.ListCategory(
str(win_id), tabs, delete_func=delete_buffer, sort=False)
title, tabs, delete_func=delete_buffer, sort=False)
model.add_category(cat)
if tabs_are_windows:
@ -157,7 +164,22 @@ def other_buffer(*, info):
Used for the tab-take command.
"""
return _buffer(skip_win_id=info.win_id)
return _buffer(win_id_filter=lambda win_id: win_id != info.win_id)
def tab_focus(*, info):
"""A model to complete on open tabs in the current window."""
model = _buffer(win_id_filter=lambda win_id: win_id == info.win_id,
add_win_id=False)
special = [
("last", "Focus the last-focused tab"),
("stack-next", "Go forward through a stack of focused tabs"),
("stack-prev", "Go backward through a stack of focused tabs"),
]
model.add_category(listcategory.ListCategory("Special", special))
return model
def window(*, info):

View File

@ -21,6 +21,9 @@
import typing
if typing.TYPE_CHECKING:
from PyQt5.QtCore import QAbstractItemModel
from qutebrowser.completion.models import (completionmodel, listcategory,
histcategory)
from qutebrowser.browser import history
@ -74,7 +77,7 @@ def url(*, info):
if k != 'DEFAULT']
# pylint: enable=bad-config-option
categories = config.val.completion.open_categories
models = {}
models = {} # type: typing.Dict[str, QAbstractItemModel]
if searchengines and 'searchengines' in categories:
models['searchengines'] = listcategory.ListCategory(

View File

@ -33,7 +33,7 @@ from qutebrowser.api import (cmdutils, hook, config, message, downloads,
interceptor, apitypes, qtutils)
logger = logging.getLogger('misc')
logger = logging.getLogger('network')
_host_blocker = typing.cast('HostBlocker', None)
@ -139,8 +139,8 @@ class HostBlocker:
"""Block the given request if necessary."""
if self._is_blocked(request_url=info.request_url,
first_party_url=info.first_party_url):
logger.info("Request to {} blocked by host blocker."
.format(info.request_url.host()))
logger.debug("Request to {} blocked by host blocker."
.format(info.request_url.host()))
info.block()
def _read_hosts_line(self, raw_line: bytes) -> typing.Set[str]:

View File

@ -185,9 +185,13 @@ def move_to_end_of_document(tab: apitypes.Tab) -> None:
@cmdutils.register(modes=[cmdutils.KeyMode.caret])
@cmdutils.argument('tab', value=cmdutils.Value.cur_tab)
def toggle_selection(tab: apitypes.Tab) -> None:
"""Toggle caret selection mode."""
tab.caret.toggle_selection()
def toggle_selection(tab: apitypes.Tab, line: bool = False) -> None:
"""Toggle caret selection mode.
Args:
line: Enables line-selection.
"""
tab.caret.toggle_selection(line)
@cmdutils.register(modes=[cmdutils.KeyMode.caret])

View File

@ -71,8 +71,10 @@ def _print_preview(tab: apitypes.Tab) -> None:
tab.printing.check_preview_support()
diag = QPrintPreviewDialog(tab)
diag.setAttribute(Qt.WA_DeleteOnClose)
diag.setWindowFlags(diag.windowFlags() | Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint)
diag.setWindowFlags(
diag.windowFlags() | # type: ignore[operator, arg-type]
Qt.WindowMaximizeButtonHint |
Qt.WindowMinimizeButtonHint)
diag.paintRequested.connect(functools.partial(
tab.printing.to_printer, callback=print_callback))
diag.exec_()

View File

@ -206,7 +206,7 @@ class KeyConfig:
'mode'.format(key, mode))
self._validate(key, mode)
log.keyboard.vdebug( # type: ignore
log.keyboard.vdebug( # type: ignore[attr-defined]
"Adding binding {} -> {} in mode {}.".format(key, command, mode))
bindings = self._config.get_mutable_obj('bindings.commands')

View File

@ -261,6 +261,23 @@ class ConfigCommands:
with self._handle_config_error():
self._config.unset(option, save_yaml=not temp)
@cmdutils.register(instance='config-commands')
@cmdutils.argument('win_id', value=cmdutils.Value.win_id)
def config_diff(self, win_id: int, old: bool = False) -> None:
"""Show all customized options.
Args:
old: Show difference for the pre-v1.0 files
(qutebrowser.conf/keys.conf).
"""
url = QUrl('qute://configdiff')
if old:
url.setPath('/old')
tabbed_browser = objreg.get('tabbed-browser',
scope='window', window=win_id)
tabbed_browser.load_url(url, newtab=False)
@cmdutils.register(instance='config-commands')
@cmdutils.argument('option', completion=configmodel.list_option)
def config_list_add(self, option: str, value: str,
@ -443,9 +460,9 @@ class ConfigCommands:
if filename is None:
filename = standarddir.config_py()
else:
filename = os.path.expanduser(filename)
if not os.path.isabs(filename):
filename = os.path.join(standarddir.config(), filename)
filename = os.path.expanduser(filename)
if os.path.exists(filename) and not force:
raise cmdutils.CommandError("{} already exists - use --force to "

View File

@ -48,6 +48,16 @@ search.incremental:
default: True
desc: Find text on a page incrementally, renewing the search for each typed character.
search.wrap:
type: Bool
default: True
backend:
QtWebEngine: Qt 5.14
QtWebKit: true
desc: >-
Wrap around at the top and bottom of the page when advancing through text matches
using `:search-next` and `:search-prev`.
new_instance_open_target:
type:
name: String
@ -341,6 +351,7 @@ content.cookies.accept:
backend:
QtWebKit: true
QtWebEngine: Qt 5.11
supports_pattern: true
type:
name: String
valid_values:
@ -351,7 +362,16 @@ content.cookies.accept:
a cookie is already set for the domain. On QtWebEngine, this is the
same as no-3rdparty."
- never: "Don't accept cookies at all."
desc: Which cookies to accept.
desc: >-
Which cookies to accept.
With QtWebEngine, this setting also controls other features with tracking
capabilities similar to those of cookies; including IndexedDB, DOM storage,
filesystem API, service workers, and AppCache.
Note that with QtWebKit, only `all` and `never` are supported as per-domain
values. Setting `no-3rdparty` or `no-unknown-3rdparty` per-domain on
QtWebKit will have the same effect as `all`.
content.cookies.store:
default: true
@ -370,12 +390,46 @@ content.default_encoding:
The encoding must be a string describing an encoding such as _utf-8_,
_iso-8859-1_, etc.
content.unknown_url_scheme_policy:
type:
name: String
valid_values:
- disallow: "Disallows all navigation requests to URLs with unknown
schemes."
- allow-from-user-interaction: "Allows navigation requests to URLs with
unknown schemes that are issued from user-interaction (like a
mouse-click), whereas other navigation requests (for example from
JavaScript) are suppressed."
- allow-all: "Allows all navigation requests to URLs with unknown
schemes."
default: allow-from-user-interaction
backend:
QtWebEngine: Qt 5.11
QtWebKit: false
supports_pattern: true
desc: >-
How navigation requests to URLs with unknown schemes are handled.
content.windowed_fullscreen:
renamed: content.fullscreen.window
content.fullscreen.window:
type: Bool
default: false
desc: >-
Limit fullscreen to the browser window (does not expand to fill the screen).
content.fullscreen.overlay_timeout:
type:
name: Int
minval: 0
maxval: maxint
default: 3000
desc: >-
Set fullscreen notification overlay timeout in milliseconds.
If set to 0, no overlay will be displayed.
content.desktop_capture:
type: BoolAsk
default: ask
@ -390,7 +444,7 @@ content.developer_extras:
deleted: true
content.dns_prefetch:
default: false
default: true
type: Bool
backend:
QtWebKit: true
@ -508,11 +562,11 @@ content.headers.user_agent:
completions:
# See https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
- - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
like Gecko) Chrome/79.0.3945.117 Safari/537.36"
- Chrome 79 Win10
like Gecko) Chrome/81.0.4044.129 Safari/537.36"
- Chrome 80 Win10
- - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like
Gecko) Chrome/79.0.3945.117 Safari/537.36"
- Chrome 79 Linux
Gecko) Chrome/81.0.4044.138 Safari/537.36"
- Chrome 80 Linux
supports_pattern: true
desc: |
User agent to send.
@ -531,7 +585,9 @@ content.headers.user_agent:
The default value is equal to the unchanged user agent of
QtWebKit/QtWebEngine.
Note that the value read from JavaScript is always the global value.
Note that the value read from JavaScript is always the global value. With
QtWebEngine between 5.12 and 5.14 (inclusive), changing the value exposed
to JavaScript requires a restart.
content.host_blocking.enabled:
renamed: content.blocking.hosts.enabled
@ -1120,6 +1176,22 @@ hints.border:
type: String
desc: CSS border value for hints.
hints.padding:
default:
top: 0
bottom: 0
left: 3
right: 3
type: Padding
desc: Padding (in pixels) for hints.
hints.radius:
default: 3
type:
name: Int
minval: 0
desc: Rounding radius (in pixels) for the edges of hints.
hints.chars:
default: asdfghjkl
type:
@ -1338,6 +1410,19 @@ input.links_included_in_focus_chain:
supports_pattern: true
desc: Include hyperlinks in the keyboard focus chain when tabbing.
input.mouse.back_forward_buttons:
default: true
type: Bool
desc: Enable back and forward buttons on the mouse.
input.mouse.rocker_gestures:
default: false
type: Bool
desc: >-
Enable Opera-like mouse rocker gestures.
This disables the context menu.
input.partial_timeout:
default: 5000
type:
@ -1351,12 +1436,7 @@ input.partial_timeout:
cleared after this time.
input.rocker_gestures:
default: false
type: Bool
desc: >-
Enable Opera-like mouse rocker gestures.
This disables the context menu.
renamed: input.mouse.rocker_gestures
input.spatial_navigation:
default: false
@ -1423,7 +1503,7 @@ messages.unfocused:
prompt.filebrowser:
type: Bool
default: true
desc: Show a filebrowser in upload/download prompts.
desc: Show a filebrowser in download prompts.
prompt.radius:
type:
@ -1442,8 +1522,12 @@ scrolling.bar:
- never: Never show the scrollbar.
- when-searching: Show the scrollbar when searching for text in the
webpage. With the QtWebKit backend, this is equal to `never`.
default: when-searching
desc: When to show the scrollbar.
- overlay: Show an overlay scrollbar. With Qt < 5.11 or on macOS, this is
unavailable and equal to `when-searching`; with the QtWebKit
backend, this is equal to `never`. Enabling/disabling overlay
scrollbars requires a restart.
default: overlay
desc: When/how to show the scrollbar.
scrolling.smooth:
type: Bool
@ -1518,10 +1602,15 @@ spellcheck.languages:
## statusbar
statusbar.hide:
type: Bool
default: false
desc: Hide the statusbar unless a message is shown.
statusbar.show:
default: always
type:
name: String
valid_values:
- always: Always show the statusbar.
- never: Always hide the statusbar.
- in-mode: Show the statusbar when in modes other than normal mode.
desc: When to show the statusbar.
statusbar.padding:
type: Padding
@ -1912,17 +2001,29 @@ url.searchengines:
name: String
forbidden: ' '
valtype: SearchEngineUrl
desc: >-
desc: |
Search engines which can be used via the address bar.
Maps a search engine name (such as `DEFAULT`, or `ddg`) to a URL with a
`{}` placeholder. The placeholder will be replaced by the search term, use
`{{` and `}}` for literal `{`/`}` signs.
`{{` and `}}` for literal `{`/`}` braces.
The following further placeholds are defined to configure how special
characters in the search terms are replaced by safe characters (called
'quoting'):
* `{}` and `{semiquoted}` quote everything except slashes; this is the most
sensible choice for almost all search engines (for the search term
`slash/and&amp` this placeholder expands to `slash/and%26amp`).
* `{quoted}` quotes all characters (for `slash/and&amp` this placeholder
expands to `slash%2Fand%26amp`).
* `{unquoted}` quotes nothing (for `slash/and&amp` this placeholder
expands to `slash/and&amp`).
The search engine named `DEFAULT` is used when `url.auto_search` is turned
on and something else than a URL was entered to be opened. Other search
engines can be used by prepending the search engine name to the search
term, e.g. `:open google qutebrowser`.
term, e.g. `:open google qutebrowser`.
url.start_pages:
type:
@ -2155,6 +2256,26 @@ colors.contextmenu.selected.fg:
If set to null, the Qt default is used.
colors.contextmenu.disabled.bg:
type:
name: QssColor
none_ok: true
default: null
desc: >-
Background color of disabled items in the context menu.
If set to null, the Qt default is used.
colors.contextmenu.disabled.fg:
type:
name: QssColor
none_ok: true
default: null
desc: >-
Foreground color of disabled items in the context menu.
If set to null, the Qt default is used.
colors.downloads.bar.bg:
default: black
type: QssColor
@ -2539,6 +2660,169 @@ colors.webpage.prefers_color_scheme_dark:
QtWebEngine: Qt 5.14
QtWebKit: false
## dark mode
colors.webpage.darkmode.enabled:
default: false
type: Bool
desc: >-
Render all web contents using a dark theme.
Example configurations from Chromium's `chrome://flags`:
- "With simple HSL/CIELAB/RGB-based inversion": Set
`colors.webpage.darkmode.algorithm` accordingly.
- "With selective image inversion": Set
`colors.webpage.darkmode.policy.images` to `smart`.
- "With selective inversion of non-image elements": Set
`colors.webpage.darkmode.threshold.text` to 150 and
`colors.webpage.darkmode.threshold.background` to 205.
- "With selective inversion of everything": Combines the two variants
above.
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.algorithm:
default: lightness-cielab
desc: "Which algorithm to use for modifying how colors are rendered with
darkmode."
type:
name: String
valid_values:
- lightness-cielab: Modify colors by converting them to CIELAB color
space and inverting the L value.
- lightness-hsl: Modify colors by converting them to the HSL color space
and inverting the lightness (i.e. the "L" in HSL).
- brightness-rgb: Modify colors by subtracting each of r, g, and b from
their maximum value.
# kSimpleInvertForTesting is not exposed, as it's equivalent to
# kInvertBrightness without gamma correction, and only available for
# Chromium's automated tests
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.contrast:
default: 0.0
type:
name: Float
minval: -1.0
maxval: 1.0
desc: >-
Contrast for dark mode.
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
`lightness-hsl` or `brightness-rgb`.
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.policy.images:
default: never
type:
name: String
valid_values:
- always: Apply dark mode filter to all images.
- never: Never apply dark mode filter to any images.
- smart: Apply dark mode based on image content.
desc: >-
Which images to apply dark mode to.
WARNING: On Qt 5.15.0, this setting can cause frequent renderer process
crashes due to a
https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/304211[bug in Qt].
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.policy.page:
default: smart
type:
name: String
valid_values:
- always: Apply dark mode filter to all frames, regardless of content.
- smart: Apply dark mode filter to frames based on background color.
desc: Which pages to apply dark mode to.
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.threshold.text:
default: 256
type:
name: Int
minval: 0
maxval: 256
desc: >-
Threshold for inverting text with dark mode.
Text colors with brightness below this threshold will be inverted, and
above it will be left as in the original, non-dark-mode page. Set to 256
to always invert text color or to 0 to never invert text color.
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.threshold.background:
default: 0
type:
name: Int
minval: 0
maxval: 256
desc: >-
Threshold for inverting background elements with dark mode.
Background elements with brightness above this threshold will be inverted,
and below it will be left as in the original, non-dark-mode page. Set to
256 to never invert the color or to 0 to always invert it.
Note: This behavior is the opposite of
`colors.webpage.darkmode.threshold.text`!
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.grayscale.all:
default: false
type: Bool
desc: >-
Render all colors as grayscale.
This only has an effect when `colors.webpage.darkmode.algorithm` is set to
`lightness-hsl` or `brightness-rgb`.
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
colors.webpage.darkmode.grayscale.images:
default: 0.0
type:
name: Float
minval: 0.0
maxval: 1.0
desc: >-
Desaturation factor for images in dark mode.
If set to 0, images are left as-is. If set to 1, images are completely
grayscale. Values between 0 and 1 desaturate the colors accordingly.
restart: true
backend:
QtWebEngine: Qt 5.14
QtWebKit: false
# emacs: '
## fonts
@ -2593,7 +2877,7 @@ fonts.contextmenu:
fonts.debug_console:
default: default_size default_family
type: QtFont
type: Font
desc: Font used for the debugging console.
fonts.downloads:
@ -2636,10 +2920,15 @@ fonts.statusbar:
type: Font
desc: Font used in the statusbar.
fonts.tabs:
fonts.tabs.selected:
default: default_size default_family
type: QtFont
desc: Font used in the tab bar.
type: Font
desc: Font used for selected tabs.
fonts.tabs.unselected:
default: default_size default_family
type: Font
desc: Font used for unselected tabs.
fonts.web.family.standard:
default: ''
@ -2730,6 +3019,7 @@ bindings.key_mappings:
<Ctrl-6>: <Ctrl-^>
<Ctrl-M>: <Return>
<Ctrl-J>: <Return>
<Ctrl-I>: <Tab>
<Shift-Return>: <Return>
<Enter>: <Return>
<Shift-Enter>: <Return>
@ -2827,6 +3117,7 @@ bindings.default:
N: search-prev
i: enter-mode insert
v: enter-mode caret
V: enter-mode caret ;; toggle-selection --line
"`": enter-mode set_mark
"'": enter-mode jump_mark
yy: yank
@ -2930,6 +3221,12 @@ bindings.default:
tIH: config-cycle -p -u *://*.{url:host}/* content.images ;; reload
tiu: config-cycle -p -t -u {url} content.images ;; reload
tIu: config-cycle -p -u {url} content.images ;; reload
tch: config-cycle -p -t -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
tCh: config-cycle -p -u *://{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
tcH: config-cycle -p -t -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
tCH: config-cycle -p -u *://*.{url:host}/* content.cookies.accept all no-3rdparty never ;; reload
tcu: config-cycle -p -t -u {url} content.cookies.accept all no-3rdparty never ;; reload
tCu: config-cycle -p -u {url} content.cookies.accept all no-3rdparty never ;; reload
insert:
<Ctrl-E>: open-editor
<Shift-Ins>: insert-text -- {primary}
@ -3008,6 +3305,7 @@ bindings.default:
<Escape>: leave-mode
caret:
v: toggle-selection
V: toggle-selection --line
<Space>: toggle-selection
<Ctrl-Space>: drop-selection
c: enter-mode normal
@ -3145,3 +3443,18 @@ bindings.commands:
* register: Entered when qutebrowser is waiting for a register name/key for
commands like `:set-mark`.
## logging
logging.level.ram:
default: debug
type: LogLevel
desc:
Level for in-memory logs.
logging.level.console:
default: info
type: LogLevel
desc: >-
Level for console (stdout/stderr) logs.
Ignored if the `--loglevel` or `--debug` CLI flags are used.

Some files were not shown because too many files have changed in this diff Show More