Merge branch 'master' into docked-inspector
This commit is contained in:
commit
c7ab818743
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 1.10.1
|
||||
current_version = 1.11.1
|
||||
commit = True
|
||||
message = Release v{new_version}
|
||||
tag = True
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
Please report security bugs to [security@qutebrowser.org](mailto:security@qutebrowser.org).
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -108,9 +105,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 +121,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]
|
||||
|
|
@ -153,9 +151,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.
|
||||
|
|
@ -220,7 +215,7 @@ 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, various backends - 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)
|
||||
* Chrome/Chromium addons:
|
||||
https://vimium.github.io/[Vimium],
|
||||
|
|
|
|||
|
|
@ -15,10 +15,148 @@ 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.12.0 (unreleased)
|
||||
--------------------
|
||||
|
||||
No changes yet.
|
||||
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 cokies to the debug log for
|
||||
debugging.
|
||||
- New `colors.contextmenu.disabled.{fg,bg}` settings to customize colors for
|
||||
disabled items in the context menu.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- 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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
~~~~~
|
||||
|
||||
- `unsafeWindow` is now defined for Greasemonkey scripts with QtWebKit.
|
||||
- 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.
|
||||
|
||||
v1.10.1 (2020-02-15)
|
||||
--------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
+
|
||||
|
|
|
|||
|
|
@ -44,6 +44,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.
|
||||
|
|
@ -112,7 +113,7 @@ possible to run or bind multiple commands by separating them with `;;`.
|
|||
|<<set,set>>|Set an option.
|
||||
|<<set-cmd-text,set-cmd-text>>|Preset the statusbar to some text.
|
||||
|<<set-mark,set-mark>>|Set a mark at the current scroll position in the current tab.
|
||||
|<<spawn,spawn>>|Spawn a command in a shell.
|
||||
|<<spawn,spawn>>|Spawn an external command.
|
||||
|<<stop,stop>>|Stop loading in the current/[count]th tab.
|
||||
|<<tab-clone,tab-clone>>|Duplicate the current tab.
|
||||
|<<tab-close,tab-close>>|Close the current/[count]th tab.
|
||||
|
|
@ -328,6 +329,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*]+
|
||||
|
|
@ -1256,7 +1267,9 @@ Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--output-messages*
|
|||
[*--detach*]
|
||||
'cmdline'+
|
||||
|
||||
Spawn a command in a shell.
|
||||
Spawn an external command.
|
||||
|
||||
Note that the command is *not* run in a shell, so things like `$VAR` or `> output` won't have the desired effect.
|
||||
|
||||
==== positional arguments
|
||||
* +'cmdline'+: The commandline to execute.
|
||||
|
|
@ -1483,8 +1496,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.
|
||||
|
||||
|
||||
|
|
@ -1909,6 +1920,7 @@ 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.
|
||||
|
|
@ -1963,6 +1975,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'+
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -136,6 +138,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.
|
||||
|
|
@ -173,10 +177,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.
|
||||
|
|
@ -223,7 +227,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.
|
||||
|
|
@ -245,7 +251,7 @@
|
|||
|<<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.
|
||||
|
|
@ -258,6 +264,7 @@
|
|||
|<<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.
|
||||
|
|
@ -864,6 +871,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.
|
||||
|
|
@ -1735,6 +1760,9 @@ This setting is only available with the QtWebEngine backend.
|
|||
[[content.cookies.accept]]
|
||||
=== content.cookies.accept
|
||||
Which cookies to accept.
|
||||
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>>
|
||||
|
||||
|
|
@ -1809,6 +1837,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.
|
||||
|
|
@ -2283,6 +2328,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.
|
||||
|
|
@ -2322,14 +2387,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.
|
||||
|
|
@ -2764,6 +2821,19 @@ Default:
|
|||
- +pass:[\b(>>|»)\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.
|
||||
|
|
@ -2778,6 +2848,14 @@ Default:
|
|||
- +pass:[\b[<←≪]\b]+
|
||||
- +pass:[\b(<<|«)\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).
|
||||
|
|
@ -3048,7 +3126,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>>
|
||||
|
||||
|
|
@ -3209,6 +3287,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.
|
||||
|
|
@ -3754,8 +3842,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&` this placeholder expands to `slash/and%26amp`).
|
||||
* `{quoted}` quotes all characters (for `slash/and&` this placeholder
|
||||
expands to `slash%2Fand%26amp`).
|
||||
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
||||
expands to `slash/and&`).
|
||||
|
||||
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>>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@
|
|||
</content_rating>
|
||||
<releases>
|
||||
<!-- Add new releases here -->
|
||||
<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"/>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
check-manifest==0.40
|
||||
toml==0.10.0
|
||||
check-manifest==0.42
|
||||
pep517==0.8.2
|
||||
toml==0.10.1
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2019.11.28
|
||||
certifi==2020.4.5.1
|
||||
chardet==3.0.4
|
||||
codecov==2.0.15
|
||||
coverage==5.0.3
|
||||
idna==2.8
|
||||
requests==2.22.0
|
||||
urllib3==1.25.8
|
||||
codecov==2.1.0
|
||||
coverage==5.1
|
||||
idna==2.9
|
||||
requests==2.23.0
|
||||
urllib3==1.25.9
|
||||
|
|
|
|||
|
|
@ -1,22 +1,27 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
bump2version==1.0.0
|
||||
certifi==2019.11.28
|
||||
certifi==2020.4.5.1
|
||||
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.8
|
||||
jwcrypto==0.6.0
|
||||
idna==2.9
|
||||
jwcrypto==0.7
|
||||
lxml==4.5.0
|
||||
manhole==1.6.0
|
||||
pycparser==2.19
|
||||
packaging==20.3
|
||||
pycparser==2.20
|
||||
Pympler==0.8
|
||||
pyparsing==2.4.7
|
||||
PyQt-builder==1.3.2
|
||||
python-dateutil==2.8.1
|
||||
requests==2.22.0
|
||||
requests==2.23.0
|
||||
sip==5.2.0
|
||||
six==1.14.0
|
||||
toml==0.10.1
|
||||
uritemplate==3.0.1
|
||||
urllib3==1.25.8
|
||||
urllib3==1.25.9
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ github3.py
|
|||
bump2version
|
||||
requests
|
||||
lxml
|
||||
pyqt-builder
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
# 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.1
|
||||
flake8-bugbear==20.1.4
|
||||
flake8-builtins==1.4.2
|
||||
flake8-builtins==1.5.3
|
||||
flake8-comprehensions==3.2.2
|
||||
flake8-copyright==0.2.2
|
||||
flake8-debugger==3.2.1
|
||||
|
|
@ -13,13 +12,13 @@ flake8-docstrings==1.5.0
|
|||
flake8-future-import==0.4.6
|
||||
flake8-mock==0.3
|
||||
flake8-polyfill==1.0.2
|
||||
flake8-string-format==0.2.3
|
||||
flake8-tidy-imports==4.0.0
|
||||
flake8-string-format==0.3.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
|
||||
pyflakes==2.2.0
|
||||
six==1.14.0
|
||||
snowballstemmer==2.0.0
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
mypy==0.761
|
||||
mypy==0.770
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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#
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.3
|
||||
packaging==20.1
|
||||
pyparsing==2.4.6
|
||||
setuptools==45.2.0
|
||||
appdirs==1.4.4
|
||||
packaging==20.3
|
||||
pyparsing==2.4.7
|
||||
setuptools==46.4.0
|
||||
six==1.14.0
|
||||
wheel==0.34.2
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.1
|
||||
cffi==1.14.0
|
||||
chardet==3.0.4
|
||||
cryptography==2.8
|
||||
cryptography==2.9.2
|
||||
github3.py==1.3.0
|
||||
idna==2.8
|
||||
idna==2.9
|
||||
isort==4.3.21
|
||||
jwcrypto==0.6.0
|
||||
jwcrypto==0.7
|
||||
lazy-object-proxy==1.4.3
|
||||
mccabe==0.6.1
|
||||
pycparser==2.19
|
||||
pylint==2.4.4
|
||||
pycparser==2.20
|
||||
pylint==2.4.4 # rq.filter: < 2.5
|
||||
python-dateutil==2.8.1
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.22.0
|
||||
requests==2.23.0
|
||||
six==1.14.0
|
||||
typed-ast==1.4.1 ; python_version<"3.8"
|
||||
uritemplate==3.0.1
|
||||
urllib3==1.25.8
|
||||
wrapt==1.11.2
|
||||
urllib3==1.25.9
|
||||
wrapt==1.12.1
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.7.2
|
||||
PyQtWebEngine==5.12.1 # rq.filter: < 5.13
|
||||
|
|
|
|||
|
|
@ -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.7.2
|
||||
PyQtWebEngine==5.13.2 # rq.filter: < 5.14
|
||||
|
|
|
|||
|
|
@ -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.7.2
|
||||
PyQtWebEngine==5.14.0 # rq.filter: < 5.15
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.14.1
|
||||
PyQt5-sip==12.7.1
|
||||
PyQt5==5.14.2
|
||||
PyQt5-sip==12.7.2
|
||||
PyQtWebEngine==5.14.0
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.16
|
||||
Pygments==2.5.2
|
||||
Pygments==2.6.1
|
||||
pyroma==2.6
|
||||
|
|
|
|||
|
|
@ -2,25 +2,25 @@
|
|||
|
||||
alabaster==0.7.12
|
||||
Babel==2.8.0
|
||||
certifi==2019.11.28
|
||||
certifi==2020.4.5.1
|
||||
chardet==3.0.4
|
||||
docutils==0.16
|
||||
idna==2.8
|
||||
idna==2.9
|
||||
imagesize==1.2.0
|
||||
Jinja2==2.11.1
|
||||
Jinja2==2.11.2
|
||||
MarkupSafe==1.1.1
|
||||
packaging==20.1
|
||||
Pygments==2.5.2
|
||||
pyparsing==2.4.6
|
||||
pytz==2019.3
|
||||
requests==2.22.0
|
||||
packaging==20.3
|
||||
Pygments==2.6.1
|
||||
pyparsing==2.4.7
|
||||
pytz==2020.1
|
||||
requests==2.23.0
|
||||
six==1.14.0
|
||||
snowballstemmer==2.0.0
|
||||
Sphinx==2.4.0
|
||||
sphinxcontrib-applehelp==1.0.1
|
||||
sphinxcontrib-devhelp==1.0.1
|
||||
sphinxcontrib-htmlhelp==1.0.2
|
||||
Sphinx==3.0.3
|
||||
sphinxcontrib-applehelp==1.0.2
|
||||
sphinxcontrib-devhelp==1.0.2
|
||||
sphinxcontrib-htmlhelp==1.0.3
|
||||
sphinxcontrib-jsmath==1.0.1
|
||||
sphinxcontrib-qthelp==1.0.2
|
||||
sphinxcontrib-serializinghtml==1.1.3
|
||||
urllib3==1.25.8
|
||||
sphinxcontrib-qthelp==1.0.3
|
||||
sphinxcontrib-serializinghtml==1.1.4
|
||||
urllib3==1.25.9
|
||||
|
|
|
|||
|
|
@ -1,46 +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.0
|
||||
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.5.1
|
||||
hypothesis==5.14.0
|
||||
itsdangerous==1.1.0
|
||||
jaraco.functools==3.0.0 ; python_version>="3.6"
|
||||
# Jinja2==2.11.1
|
||||
Mako==1.1.1
|
||||
jaraco.functools==3.0.1 ; python_version>="3.6"
|
||||
# Jinja2==2.11.2
|
||||
Mako==1.1.2
|
||||
manhole==1.6.0
|
||||
# MarkupSafe==1.1.1
|
||||
more-itertools==8.2.0
|
||||
packaging==20.1
|
||||
parse==1.14.0
|
||||
more-itertools==8.3.0
|
||||
packaging==20.3
|
||||
parse==1.15.0
|
||||
parse-type==0.5.2
|
||||
pluggy==0.13.1
|
||||
py==1.8.1
|
||||
py-cpuinfo==5.0.0
|
||||
pyparsing==2.4.6
|
||||
pytest==5.3.5
|
||||
pytest-bdd==3.2.1
|
||||
Pygments==2.6.1
|
||||
pyparsing==2.4.7
|
||||
pytest==5.4.2
|
||||
pytest-bdd==3.3.0
|
||||
pytest-benchmark==3.2.3
|
||||
pytest-cov==2.8.1
|
||||
pytest-instafail==0.4.1.post0
|
||||
pytest-mock==2.0.0
|
||||
pytest-mock==3.1.0
|
||||
pytest-qt==3.3.0
|
||||
pytest-repeat==0.8.0
|
||||
pytest-rerunfailures==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==1.9.5
|
||||
vulture==1.3
|
||||
wcwidth==0.1.8
|
||||
Werkzeug==1.0.0
|
||||
soupsieve==2.0.1
|
||||
vulture==1.4
|
||||
wcwidth==0.1.9
|
||||
Werkzeug==1.0.1
|
||||
jaraco.functools==2.0; python_version<"3.6" # rq.filter: <= 2.0
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ beautifulsoup4
|
|||
cheroot
|
||||
coverage
|
||||
Flask
|
||||
hunter
|
||||
hypothesis
|
||||
pytest
|
||||
pytest-bdd
|
||||
|
|
@ -11,11 +10,20 @@ pytest-cov
|
|||
pytest-instafail
|
||||
pytest-mock
|
||||
pytest-qt
|
||||
pytest-repeat
|
||||
pytest-rerunfailures
|
||||
pytest-travis-fold
|
||||
pytest-xvfb
|
||||
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
appdirs==1.4.4
|
||||
distlib==0.3.0
|
||||
filelock==3.0.12
|
||||
packaging==20.1
|
||||
packaging==20.3
|
||||
pluggy==0.13.1
|
||||
py==1.8.1
|
||||
pyparsing==2.4.6
|
||||
pyparsing==2.4.7
|
||||
six==1.14.0
|
||||
toml==0.10.0
|
||||
tox==3.14.3
|
||||
tox-pip-version==0.0.6
|
||||
toml==0.10.1
|
||||
tox==3.15.0
|
||||
tox-pip-version==0.0.7
|
||||
tox-venv==0.4.0
|
||||
virtualenv==20.0.1
|
||||
virtualenv==20.0.20
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==1.3
|
||||
vulture==1.4
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
})
|
||||
});
|
||||
|
|
|
|||
43
mypy.ini
43
mypy.ini
|
|
@ -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
|
||||
|
|
@ -65,11 +65,9 @@ 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.browser.inspector]
|
||||
disallow_untyped_defs = True
|
||||
|
|
@ -85,68 +83,45 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[pytest]
|
||||
log_level = NOTSET
|
||||
addopts = --strict -rfEw --instafail --benchmark-columns=Min,Max,Median
|
||||
addopts = --strict --instafail --benchmark-columns=Min,Max,Median
|
||||
testpaths = tests
|
||||
markers =
|
||||
gui: Tests using the GUI (e.g. spawning widgets)
|
||||
|
|
@ -68,5 +68,8 @@ qt_log_ignore =
|
|||
^DirectWrite: CreateFontFaceFromHDC\(\) failed .*
|
||||
^Attribute Qt::AA_ShareOpenGLContexts must be set before QCoreApplication is created\.
|
||||
xfail_strict = true
|
||||
filterwarnings = error
|
||||
filterwarnings =
|
||||
error
|
||||
# See https://github.com/HypothesisWorks/hypothesis/issues/2370
|
||||
ignore:.*which is reset between function calls but not between test cases generated by:hypothesis.errors.HypothesisDeprecationWarning
|
||||
faulthandler_timeout = 90
|
||||
|
|
|
|||
|
|
@ -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.11.1"
|
||||
__version_info__ = tuple(int(part) for part in __version__.split('.'))
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -479,7 +482,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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -319,6 +319,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.
|
||||
|
||||
|
|
@ -326,6 +327,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
|
||||
|
|
@ -438,16 +440,15 @@ class AbstractCaret(QObject):
|
|||
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
|
||||
|
|
@ -542,7 +543,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:
|
||||
|
|
@ -689,9 +690,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,
|
||||
|
|
@ -869,8 +870,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)
|
||||
|
|
@ -886,7 +897,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)
|
||||
|
|
@ -949,7 +959,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:
|
||||
|
|
@ -974,7 +984,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()
|
||||
|
|
@ -1033,15 +1042,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:
|
||||
|
|
@ -1129,7 +1142,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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -645,12 +646,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 +663,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 +680,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 +711,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 +900,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:
|
||||
|
|
@ -1005,7 +997,10 @@ class CommandDispatcher:
|
|||
@cmdutils.argument('output_messages', flag='m')
|
||||
def spawn(self, cmdline, userscript=False, verbose=False,
|
||||
output=False, output_messages=False, detach=False, count=None):
|
||||
"""Spawn a command in a shell.
|
||||
"""Spawn an external command.
|
||||
|
||||
Note that the command is *not* run in a shell, so things like `$VAR` or
|
||||
`> output` won't have the desired effect.
|
||||
|
||||
Args:
|
||||
userscript: Run the command as a userscript. You can use an
|
||||
|
|
@ -1043,7 +1038,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))
|
||||
|
|
@ -1069,13 +1065,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 = {
|
||||
|
|
@ -1102,7 +1100,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
|
||||
|
|
@ -1525,6 +1524,7 @@ class CommandDispatcher:
|
|||
options = {
|
||||
'ignore_case': config.val.search.ignore_case,
|
||||
'reverse': reverse,
|
||||
'wrap': config.val.search.wrap,
|
||||
}
|
||||
|
||||
self._tabbed_browser.search_text = text
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -123,7 +123,14 @@ class TabEventFilter(QObject):
|
|||
self._check_insertmode_on_release = False
|
||||
|
||||
def _handle_mouse_press(self, e):
|
||||
"""Handle pressing of a mouse button."""
|
||||
"""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.rocker_gestures and
|
||||
e.buttons() == Qt.LeftButton | Qt.RightButton)
|
||||
|
||||
|
|
@ -144,7 +151,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)
|
||||
|
|
@ -155,27 +169,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:
|
||||
|
|
@ -185,16 +211,30 @@ class TabEventFilter(QObject):
|
|||
return False
|
||||
|
||||
def _handle_context_menu(self, _e):
|
||||
"""Suppress context menus if rocker gestures are turned on."""
|
||||
"""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.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):
|
||||
|
|
@ -247,6 +287,9 @@ class TabEventFilter(QObject):
|
|||
|
||||
Args:
|
||||
e: The QMouseEvent.
|
||||
|
||||
Return:
|
||||
True if the event should be filtered, False otherwise.
|
||||
"""
|
||||
if e.button() in [Qt.XButton1, Qt.LeftButton]:
|
||||
# Back button on mice which have it, or rocker gesture
|
||||
|
|
@ -262,7 +305,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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ class AbstractWebInspector(QWidget):
|
|||
|
||||
def _save_state_geometry(self) -> None:
|
||||
"""Save the geometry to the state file."""
|
||||
data = bytes(self.saveGeometry())
|
||||
data = self.saveGeometry().data()
|
||||
geom = base64.b64encode(data).decode('ASCII')
|
||||
configfiles.state['geometry']['inspector'] = geom
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -84,6 +84,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 +108,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
|
||||
|
|
@ -243,7 +246,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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -304,7 +308,7 @@ def qute_version(_url):
|
|||
|
||||
|
||||
@add_handler('plainlog')
|
||||
def qute_plainlog(url):
|
||||
def qute_plainlog(url: QUrl) -> _HandlerRet:
|
||||
"""Handler for qute://plainlog.
|
||||
|
||||
An optional query parameter specifies the minimum log level to print.
|
||||
|
|
@ -323,7 +327,7 @@ def qute_plainlog(url):
|
|||
|
||||
|
||||
@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.
|
||||
|
|
@ -343,12 +347,12 @@ def qute_log(url):
|
|||
|
||||
|
||||
@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 +362,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 +411,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 +433,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 +461,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 +479,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 +491,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 +506,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 +519,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 +570,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 +580,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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -315,7 +362,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 +373,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()
|
||||
|
||||
|
|
@ -367,9 +416,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)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import functools
|
|||
import re
|
||||
import html as html_utils
|
||||
import typing
|
||||
import textwrap
|
||||
|
||||
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QUrl,
|
||||
QTimer, QObject)
|
||||
|
|
@ -42,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
|
||||
|
||||
|
||||
|
|
@ -125,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)
|
||||
|
|
@ -157,6 +157,78 @@ 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 qtutils.version_check("5.14"):
|
||||
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.
|
||||
|
|
@ -169,8 +241,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."""
|
||||
|
|
@ -208,10 +288,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"
|
||||
|
|
@ -219,7 +299,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:
|
||||
|
|
@ -231,18 +313,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')
|
||||
|
||||
|
||||
|
|
@ -250,6 +341,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()
|
||||
|
|
@ -559,6 +657,12 @@ 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:
|
||||
self._tab.load_url(items[-1].url)
|
||||
return
|
||||
|
||||
if items:
|
||||
self._tab.before_load_started.emit(items[-1].url)
|
||||
|
||||
|
|
@ -621,6 +725,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.
|
||||
|
||||
|
|
@ -724,7 +832,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)
|
||||
|
|
@ -815,9 +923,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):
|
||||
|
|
@ -1104,25 +1214,16 @@ class _WebEngineScripts(QObject):
|
|||
if not config.val.content.site_specific_quirks:
|
||||
return
|
||||
|
||||
# WhatsApp Web, based on:
|
||||
# https://github.com/jiahaog/nativefier/issues/719#issuecomment-443809630
|
||||
script = QWebEngineScript()
|
||||
script.setName('quirk-whatsapp')
|
||||
script.setWorldId(QWebEngineScript.ApplicationWorld)
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
script.setSourceCode(textwrap.dedent(r"""
|
||||
// ==UserScript==
|
||||
// @include https://web.whatsapp.com/
|
||||
// ==/UserScript==
|
||||
if (document.body.innerText.replace(/\n/g, ' ').search(
|
||||
/whatsapp works with.*to use whatsapp.*update/i) !== -1) {
|
||||
navigator.serviceWorker.getRegistration().then(function (r) {
|
||||
r.unregister();
|
||||
document.location.reload();
|
||||
});
|
||||
}
|
||||
"""))
|
||||
self._widget.page().scripts().insert(script)
|
||||
page_scripts = self._widget.page().scripts()
|
||||
|
||||
for filename in ['whatsapp_web_quirk']:
|
||||
script = QWebEngineScript()
|
||||
script.setName(filename)
|
||||
script.setWorldId(QWebEngineScript.ApplicationWorld)
|
||||
script.setInjectionPoint(QWebEngineScript.DocumentReady)
|
||||
src = utils.read_file("javascript/{}.user.js".format(filename))
|
||||
script.setSourceCode(src)
|
||||
page_scripts.insert(script)
|
||||
|
||||
|
||||
class WebEngineTabPrivate(browsertab.AbstractTabPrivate):
|
||||
|
|
@ -1159,7 +1260,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)
|
||||
|
|
@ -1315,7 +1419,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 = ""
|
||||
|
||||
|
|
@ -1337,12 +1441,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")
|
||||
|
||||
|
|
@ -1363,7 +1470,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
|
||||
|
|
@ -1468,9 +1576,9 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
@pyqtSlot(certificateerror.CertificateErrorWrapper)
|
||||
def _on_ssl_errors(self, error):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
url = error.url()
|
||||
self._insecure_hosts.add(url.host())
|
||||
|
||||
log.webview.debug("Certificate error: {}".format(error))
|
||||
|
||||
if error.is_overridable():
|
||||
|
|
@ -1633,7 +1741,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
|||
|
||||
try:
|
||||
# pylint: disable=unused-import
|
||||
from PyQt5.QtWebEngineWidgets import ( # type: ignore
|
||||
from PyQt5.QtWebEngineWidgets import (
|
||||
QWebEngineClientCertificateSelection)
|
||||
except ImportError:
|
||||
pass
|
||||
|
|
@ -1652,10 +1760,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()
|
||||
|
|
|
|||
|
|
@ -216,8 +216,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."""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -169,14 +169,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
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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))):
|
||||
|
|
|
|||
|
|
@ -27,12 +27,14 @@ from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QPoint, QTimer, QSizeF, QSize
|
|||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWebKitWidgets import QWebPage, QWebFrame
|
||||
from PyQt5.QtWebKit import QWebSettings
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtPrintSupport import QPrinter
|
||||
|
||||
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,6 +178,13 @@ 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
|
||||
|
||||
@pyqtSlot(usertypes.KeyMode)
|
||||
def _on_mode_entered(self, mode):
|
||||
if mode != usertypes.KeyMode.caret:
|
||||
|
|
@ -391,11 +406,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 +595,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()
|
||||
|
|
@ -697,7 +716,10 @@ 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:
|
||||
|
|
@ -847,9 +869,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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -261,7 +266,7 @@ class _POSIXUserscriptRunner(_BaseUserscriptRunner):
|
|||
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 +403,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 +419,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 +457,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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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_()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,12 @@ 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.
|
||||
|
||||
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 +386,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
|
||||
|
|
@ -508,11 +558,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/80.0.3987.163 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/80.0.3987.149 Safari/537.36 "
|
||||
- Chrome 80 Linux
|
||||
supports_pattern: true
|
||||
desc: |
|
||||
User agent to send.
|
||||
|
|
@ -1091,6 +1141,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:
|
||||
|
|
@ -1394,7 +1460,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:
|
||||
|
|
@ -1883,12 +1949,24 @@ 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&` this placeholder expands to `slash/and%26amp`).
|
||||
* `{quoted}` quotes all characters (for `slash/and&` this placeholder
|
||||
expands to `slash%2Fand%26amp`).
|
||||
* `{unquoted}` quotes nothing (for `slash/and&` this placeholder
|
||||
expands to `slash/and&`).
|
||||
|
||||
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
|
||||
|
|
@ -2126,6 +2204,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
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ class YamlConfig(QObject):
|
|||
# Instead, create a config.py - see :help for details.
|
||||
|
||||
""".lstrip('\n')))
|
||||
utils.yaml_dump(data, f) # type: ignore
|
||||
utils.yaml_dump(data, f)
|
||||
|
||||
def _pop_object(self,
|
||||
yaml_data: typing.Any,
|
||||
|
|
@ -400,7 +400,7 @@ class YamlMigrations(QObject):
|
|||
except KeyError:
|
||||
continue
|
||||
|
||||
if not isinstance(opt.typ, configtypes.Font):
|
||||
if not isinstance(opt.typ, configtypes.FontBase):
|
||||
continue
|
||||
|
||||
for scope, val in self._settings[name].items():
|
||||
|
|
@ -530,14 +530,10 @@ class ConfigAPI:
|
|||
with self._handle_error('binding', key):
|
||||
seq = keyutils.KeySequence.parse(key)
|
||||
if command is None:
|
||||
text = ("Unbinding commands with config.bind('{key}', None) "
|
||||
"is deprecated. Use config.unbind('{key}') instead."
|
||||
.format(key=key))
|
||||
self.errors.append(configexc.ConfigErrorDesc(
|
||||
"While unbinding '{}'".format(key), text))
|
||||
self._keyconfig.unbind(seq, mode=mode)
|
||||
else:
|
||||
self._keyconfig.bind(seq, command, mode=mode)
|
||||
raise configexc.Error("Can't bind {key} to None (maybe you "
|
||||
"want to use config.unbind('{key}') "
|
||||
"instead?)".format(key=key))
|
||||
self._keyconfig.bind(seq, command, mode=mode)
|
||||
|
||||
def unbind(self, key: str, mode: str = 'normal') -> None:
|
||||
"""Unbind a key from a command, with an optional key mode."""
|
||||
|
|
@ -696,8 +692,8 @@ def read_config_py(filename: str, raising: bool = False) -> None:
|
|||
basename = os.path.basename(filename)
|
||||
|
||||
module = types.ModuleType('config')
|
||||
module.config = api # type: ignore
|
||||
module.c = container # type: ignore
|
||||
module.config = api # type: ignore[attr-defined]
|
||||
module.c = container # type: ignore[attr-defined]
|
||||
module.__file__ = filename
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -121,11 +121,11 @@ def _update_font_defaults(setting: str) -> None:
|
|||
if setting not in {'fonts.default_family', 'fonts.default_size'}:
|
||||
return
|
||||
|
||||
configtypes.Font.set_defaults(config.val.fonts.default_family,
|
||||
config.val.fonts.default_size)
|
||||
configtypes.FontBase.set_defaults(config.val.fonts.default_family,
|
||||
config.val.fonts.default_size)
|
||||
|
||||
for name, opt in configdata.DATA.items():
|
||||
if not isinstance(opt.typ, configtypes.Font):
|
||||
if not isinstance(opt.typ, configtypes.FontBase):
|
||||
continue
|
||||
|
||||
value = config.instance.get_obj(name)
|
||||
|
|
@ -165,8 +165,8 @@ def late_init(save_manager: savemanager.SaveManager) -> None:
|
|||
|
||||
_init_errors = None
|
||||
|
||||
configtypes.Font.set_defaults(config.val.fonts.default_family,
|
||||
config.val.fonts.default_size)
|
||||
configtypes.FontBase.set_defaults(config.val.fonts.default_family,
|
||||
config.val.fonts.default_size)
|
||||
config.instance.changed.connect(_update_font_defaults)
|
||||
|
||||
config.instance.init_save_manager(save_manager)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ from qutebrowser.config import configexc, configutils
|
|||
from qutebrowser.utils import (standarddir, utils, qtutils, urlutils, urlmatch,
|
||||
usertypes)
|
||||
from qutebrowser.keyinput import keyutils
|
||||
from qutebrowser.browser.network import pac
|
||||
|
||||
|
||||
class _SystemProxy:
|
||||
|
|
@ -702,8 +703,10 @@ class Bool(BaseType):
|
|||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues('true', 'false', generate_docs=False)
|
||||
|
||||
def to_py(self, value: typing.Optional[bool]) -> typing.Optional[bool]:
|
||||
def to_py(self,
|
||||
value: typing.Union[bool, str, None]) -> typing.Optional[bool]:
|
||||
self._basic_py_validation(value, bool)
|
||||
assert not isinstance(value, str)
|
||||
return value
|
||||
|
||||
def from_str(self, value: str) -> typing.Optional[bool]:
|
||||
|
|
@ -733,15 +736,15 @@ class BoolAsk(Bool):
|
|||
super().__init__(none_ok)
|
||||
self.valid_values = ValidValues('true', 'false', 'ask')
|
||||
|
||||
def to_py(self, # type: ignore
|
||||
def to_py(self, # type: ignore[override]
|
||||
value: typing.Union[bool, str]) -> typing.Union[bool, str, None]:
|
||||
# basic validation unneeded if it's == 'ask' and done by Bool if we
|
||||
# call super().to_py
|
||||
if isinstance(value, str) and value.lower() == 'ask':
|
||||
return 'ask'
|
||||
return super().to_py(value) # type: ignore
|
||||
return super().to_py(value)
|
||||
|
||||
def from_str(self, # type: ignore
|
||||
def from_str(self, # type: ignore[override]
|
||||
value: str) -> typing.Union[bool, str, None]:
|
||||
# basic validation unneeded if it's == 'ask' and done by Bool if we
|
||||
# call super().from_str
|
||||
|
|
@ -1146,14 +1149,9 @@ class QssColor(BaseType):
|
|||
return value
|
||||
|
||||
|
||||
class Font(BaseType):
|
||||
class FontBase(BaseType):
|
||||
|
||||
"""A font family, with optional style/weight/size.
|
||||
|
||||
* Style: `normal`/`italic`/`oblique`
|
||||
* Weight: `normal`, `bold`, `100`..`900`
|
||||
* Size: _number_ `px`/`pt`
|
||||
"""
|
||||
"""Base class for Font/QtFont/FontFamily."""
|
||||
|
||||
# Gets set when the config is initialized.
|
||||
default_family = None # type: str
|
||||
|
|
@ -1229,6 +1227,19 @@ class Font(BaseType):
|
|||
cls.default_family = families.to_str(quote=True)
|
||||
cls.default_size = default_size
|
||||
|
||||
def to_py(self, value: typing.Any) -> typing.Any:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Font(FontBase):
|
||||
|
||||
"""A font family, with optional style/weight/size.
|
||||
|
||||
* Style: `normal`/`italic`/`oblique`
|
||||
* Weight: `normal`, `bold`, `100`..`900`
|
||||
* Size: _number_ `px`/`pt`
|
||||
"""
|
||||
|
||||
def to_py(self, value: _StrUnset) -> _StrUnsetNone:
|
||||
self._basic_py_validation(value, str)
|
||||
if isinstance(value, usertypes.Unset):
|
||||
|
|
@ -1251,7 +1262,7 @@ class Font(BaseType):
|
|||
return value
|
||||
|
||||
|
||||
class FontFamily(Font):
|
||||
class FontFamily(FontBase):
|
||||
|
||||
"""A Qt font family."""
|
||||
|
||||
|
|
@ -1275,7 +1286,7 @@ class FontFamily(Font):
|
|||
return value
|
||||
|
||||
|
||||
class QtFont(Font):
|
||||
class QtFont(FontBase):
|
||||
|
||||
"""A Font which gets converted to a QFont."""
|
||||
|
||||
|
|
@ -1338,7 +1349,7 @@ class QtFont(Font):
|
|||
families = self._parse_families(family_str)
|
||||
if hasattr(font, 'setFamilies'):
|
||||
# Added in Qt 5.13
|
||||
font.setFamily(families.family) # type: ignore
|
||||
font.setFamily(families.family) # type: ignore[arg-type]
|
||||
font.setFamilies(list(families))
|
||||
else: # pragma: no cover
|
||||
font.setFamily(families.to_str(quote=False))
|
||||
|
|
@ -1713,7 +1724,8 @@ class Proxy(BaseType):
|
|||
def to_py(
|
||||
self,
|
||||
value: _StrUnset
|
||||
) -> typing.Union[usertypes.Unset, None, QNetworkProxy, _SystemProxy]:
|
||||
) -> typing.Union[usertypes.Unset, None,
|
||||
QNetworkProxy, _SystemProxy, pac.PACFetcher]:
|
||||
self._basic_py_validation(value, str)
|
||||
if isinstance(value, usertypes.Unset):
|
||||
return value
|
||||
|
|
@ -1760,22 +1772,22 @@ class SearchEngineUrl(BaseType):
|
|||
elif not value:
|
||||
return None
|
||||
|
||||
if not ('{}' in value or '{0}' in value):
|
||||
if not re.search('{(|0|semiquoted|unquoted|quoted)}', value):
|
||||
raise configexc.ValidationError(value, "must contain \"{}\"")
|
||||
|
||||
try:
|
||||
value.format("")
|
||||
format_keys = {
|
||||
'quoted': "",
|
||||
'unquoted': "",
|
||||
'semiquoted': "",
|
||||
}
|
||||
value.format("", **format_keys)
|
||||
except (KeyError, IndexError):
|
||||
raise configexc.ValidationError(
|
||||
value, "may not contain {...} (use {{ and }} for literal {/})")
|
||||
except ValueError as e:
|
||||
raise configexc.ValidationError(value, str(e))
|
||||
|
||||
url = QUrl(value.replace('{}', 'foobar'))
|
||||
if not url.isValid():
|
||||
raise configexc.ValidationError(
|
||||
value, "invalid url, {}".format(url.errorString()))
|
||||
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -1783,7 +1795,10 @@ class FuzzyUrl(BaseType):
|
|||
|
||||
"""A URL which gets interpreted as search if needed."""
|
||||
|
||||
def to_py(self, value: _StrUnset) -> _StrUnsetNone:
|
||||
def to_py(
|
||||
self,
|
||||
value: _StrUnset
|
||||
) -> typing.Union[None, QUrl, usertypes.Unset]:
|
||||
self._basic_py_validation(value, str)
|
||||
if isinstance(value, usertypes.Unset):
|
||||
return value
|
||||
|
|
@ -1819,7 +1834,7 @@ class Padding(Dict):
|
|||
fixed_keys=['top', 'bottom', 'left', 'right'],
|
||||
none_ok=none_ok)
|
||||
|
||||
def to_py( # type: ignore
|
||||
def to_py( # type: ignore[override]
|
||||
self,
|
||||
value: typing.Union[usertypes.Unset, typing.Dict, None],
|
||||
) -> typing.Union[usertypes.Unset, PaddingValues]:
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import operator
|
|||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.utils import utils, urlmatch, usertypes
|
||||
from qutebrowser.utils import utils, urlmatch, usertypes, qtutils
|
||||
from qutebrowser.config import configexc
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
|
|
@ -151,7 +151,7 @@ class Values:
|
|||
return bool(self._vmap)
|
||||
|
||||
def _check_pattern_support(
|
||||
self, arg: typing.Optional[urlmatch.UrlPattern]) -> None:
|
||||
self, arg: typing.Union[urlmatch.UrlPattern, QUrl, None]) -> None:
|
||||
"""Make sure patterns are supported if one was given."""
|
||||
if arg is not None and not self.opt.supports_pattern:
|
||||
raise configexc.NoPatternError(self.opt.name)
|
||||
|
|
@ -223,6 +223,7 @@ class Values:
|
|||
self._check_pattern_support(url)
|
||||
if url is None:
|
||||
return self._get_fallback(fallback)
|
||||
qtutils.ensure_valid(url)
|
||||
|
||||
candidates = [] # type: typing.List[ScopedValue]
|
||||
# Urls trailing with '.' are equivalent to non-trailing types.
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class _StyleSheetObserver(QObject):
|
|||
def register(self) -> None:
|
||||
"""Do a first update and listen for more."""
|
||||
qss = self._get_stylesheet()
|
||||
log.config.vdebug( # type: ignore
|
||||
log.config.vdebug( # type: ignore[attr-defined]
|
||||
"stylesheet for {}: {}".format(self._obj.__class__.__name__, qss))
|
||||
self._obj.setStyleSheet(qss)
|
||||
if self._update:
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ class AbstractSettings:
|
|||
def __init__(self, settings: typing.Any) -> None:
|
||||
self._settings = settings
|
||||
|
||||
def _assert_not_unset(self, value: typing.Any) -> None:
|
||||
assert value is not usertypes.UNSET
|
||||
|
||||
def set_attribute(self, name: str, value: typing.Any) -> bool:
|
||||
"""Set the given QWebSettings/QWebEngineSettings attribute.
|
||||
|
||||
|
|
@ -139,7 +142,7 @@ class AbstractSettings:
|
|||
Return:
|
||||
True if there was a change, False otherwise.
|
||||
"""
|
||||
assert value is not usertypes.UNSET # type: ignore
|
||||
self._assert_not_unset(value)
|
||||
family = self._FONT_SIZES[name]
|
||||
old_value = self._settings.fontSize(family)
|
||||
self._settings.setFontSize(family, value)
|
||||
|
|
@ -154,7 +157,7 @@ class AbstractSettings:
|
|||
Return:
|
||||
True if there was a change, False otherwise.
|
||||
"""
|
||||
assert value is not usertypes.UNSET # type: ignore
|
||||
self._assert_not_unset(value)
|
||||
family = self._FONT_FAMILIES[name]
|
||||
if value is None:
|
||||
font = QFont()
|
||||
|
|
@ -172,7 +175,7 @@ class AbstractSettings:
|
|||
Return:
|
||||
True if there was a change, False otherwise.
|
||||
"""
|
||||
assert encoding is not usertypes.UNSET # type: ignore
|
||||
self._assert_not_unset(encoding)
|
||||
old_value = self._settings.defaultTextEncoding()
|
||||
self._settings.setDefaultTextEncoding(encoding)
|
||||
return old_value != encoding
|
||||
|
|
@ -253,6 +256,10 @@ def _format_user_agent(template: str, backend: usertypes.Backend) -> str:
|
|||
|
||||
|
||||
def user_agent(url: QUrl = None) -> str:
|
||||
"""Get the user agent for the given URL, or the global one if URL is None.
|
||||
|
||||
Note that the given URL should always be valid.
|
||||
"""
|
||||
template = config.instance.get('content.headers.user_agent', url=url)
|
||||
return _format_user_agent(template=template, backend=objects.backend)
|
||||
|
||||
|
|
|
|||
|
|
@ -82,8 +82,8 @@ def add_module_info(module: types.ModuleType) -> ModuleInfo:
|
|||
"""Add ModuleInfo to a module (if not added yet)."""
|
||||
# pylint: disable=protected-access
|
||||
if not hasattr(module, '__qute_module_info'):
|
||||
module.__qute_module_info = ModuleInfo() # type: ignore
|
||||
return module.__qute_module_info # type: ignore
|
||||
module.__qute_module_info = ModuleInfo() # type: ignore[attr-defined]
|
||||
return module.__qute_module_info # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def load_components(*, skip_hooks: bool = False) -> None:
|
||||
|
|
@ -109,7 +109,7 @@ def _walk_normal() -> typing.Iterator[ExtensionInfo]:
|
|||
for _finder, name, ispkg in pkgutil.walk_packages(
|
||||
# Only packages have a __path__ attribute,
|
||||
# but we're sure this is one.
|
||||
path=components.__path__, # type: ignore
|
||||
path=components.__path__, # type: ignore[attr-defined]
|
||||
prefix=components.__name__ + '.',
|
||||
onerror=_on_walk_error):
|
||||
if ispkg:
|
||||
|
|
|
|||
|
|
@ -7,18 +7,26 @@ qute://warning/old-qt</span> to show it again at a later time.</span>
|
|||
|
||||
<p>You're using qutebrowser with Qt {{qt_version}}.</p>
|
||||
|
||||
{% if qt_version.startswith('5.7.') %}
|
||||
<p>Qt 5.7 was released in June 2016, with the 5.7.1 patch release in December
|
||||
2016. It is based on Chromium 49 (March 2016) with (some) security fixes up to
|
||||
Chromium 54 (October 2016). It is also
|
||||
<a href="https://www.debian.org/releases/stable/amd64/release-notes/ch-information.en.html#browser-security">not covered</a>
|
||||
by Debian security updates.</p>
|
||||
|
||||
Chromium 54 (October 2016).</p>
|
||||
{% elif qt_version.startswith('5.8.') %}
|
||||
<p>Qt 5.8 has had various bugs, and has been unsupported (but working to some
|
||||
degree) in qutebrowser for a while.</p>
|
||||
{% elif qt_version.startswith('5.9.') %}
|
||||
<p>Qt 5.9 LTS was released in May 2017 and is based on Chromium 56 (January 2017). It is a long term support release with the 5.9.9 patch release in December 2019 including (some) security fixes up to Chromium 78 (November 2019). However, its usage was found to be low, and the next LTS (Qt 5.12) was released in December 2018.</p>
|
||||
{% elif qt_version.startswith('5.10.') %}
|
||||
<p>Qt 5.10 was released in December 2017, with the 5.10.1 patch release in February
|
||||
2018. It is based on Chromium 65 (March 2018) with (some) security fixes up to
|
||||
Chromium 70 (November 2018).</p>
|
||||
{% endif %}
|
||||
|
||||
Also, note that QtWebEngine is <a href="https://www.debian.org/releases/stable/amd64/release-notes/ch-information.en.html#browser-security">not covered</a> by Debian security updates.
|
||||
|
||||
<p>Because of those security issues and the maintaince burden coming with
|
||||
supporting old versions, support for Qt < 5.9 will be dropped in a future
|
||||
qutebrowser release. You might want to check
|
||||
supporting old versions, support for Qt < 5.11 will be dropped in qutebrowser
|
||||
v2.0. You might want to check
|
||||
<a href="https://qutebrowser.org/doc/install.html">alternate installation methods</a>
|
||||
which allow you to get a newer Qt.</p>
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{% extends "styled.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
<span class="note">Note this warning will only appear once. Use <span class="mono">:open
|
||||
qute://warning/sessions</span> to show it again at a later time.</span>
|
||||
|
||||
<p>You're using qutebrowser with Qt 5.15.</p>
|
||||
|
||||
<p>Since Qt doesn't provide an API to load the history of a tab, qutebrowser relies on a reverse-engineered binary serialization format to load tab history from session files. With Qt 5.15, unfortunately that format changed (due to the underlying Chromium upgrade), in a way which makes it impossible for qutebrowser to load tab history from existing session data.</p>
|
||||
|
||||
<p>At the time of writing (April 2020), a new session format which stores part of the needed binary data in saved sessions is <a href="https://github.com/qutebrowser/qutebrowser/issues/5359">in development</a> and will be released with qutebrowser v1.12.0.</p>
|
||||
|
||||
<p>As a stop-gap measure:</p>
|
||||
|
||||
<ul>
|
||||
<li>Loading a session with this release will <b>only load the most recently opened page</b> for every tab. As a result, the back/forward-history of every tab <b>will be lost</b> as soon as the session is saved again.</li>
|
||||
<li>A one-time backup of the session folder has been created at <span class="mono">{{ datadir }}{{ sep }}sessions{{ sep }}before-qt-515</span>.</li>
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -10,12 +10,14 @@ qute://warning/webkit</span> to show it again at a later time.</span>
|
|||
<p>While QtWebKit has gained some traction again recently, its latest release
|
||||
(5.212.0 Alpha 3) is still based on an old upstream WebKit. It also lacks
|
||||
various security features (process isolation/sandboxing) present in
|
||||
QtWebEngine. From the upstream release notes:</p>
|
||||
QtWebEngine. From the
|
||||
<a href="https://github.com/qtwebkit/qtwebkit/releases">QtWebKit release
|
||||
notes</a>:</p>
|
||||
|
||||
<blockquote>WARNING: This release is based on old WebKit revision with known
|
||||
unpatched vulnerabilities. Please use it carefully and avoid visiting untrusted
|
||||
websites and using it for transmission of sensitive data. Wait for new release
|
||||
from qtwebkit-dev branch to use it with untrusted content.</blockquote>
|
||||
<blockquote><i>WARNING:</i> This release [of QtWebKit] is based on [an] <i>old
|
||||
WebKit revision</i> with known unpatched vulnerabilities. <i>Please use it
|
||||
carefully and avoid visiting untrusted websites and using it for
|
||||
transmission of sensitive data.</i></blockquote>
|
||||
|
||||
<p>It's recommended that you use QtWebEngine instead.</p>
|
||||
|
||||
|
|
@ -70,10 +72,10 @@ installed.</p>
|
|||
<p><b>QtWebEngine being unavailable on Parabola</b>: Claims of Parabola
|
||||
developers about QtWebEngine being "non-free" have repeatedly been disputed,
|
||||
and so far nobody came up with solid evidence about that being the case. Also,
|
||||
note that their qutebrowser package was often outdated in the past (even
|
||||
qutebrowser security fixes took months to arrive there). You might be better
|
||||
off chosing an <a href="https://qutebrowser.org/doc/install.html#tox">
|
||||
alternative install method</a>.</p>
|
||||
note that their qutebrowser package is usually very outdated (even qutebrowser
|
||||
security fixes took months to arrive there). You might be better off chosing an
|
||||
<a href="https://qutebrowser.org/doc/install.html#tox"> alternative install
|
||||
method</a>.</p>
|
||||
|
||||
<p><b>White flashing between loads with a custom stylesheet</b>: This doesn't
|
||||
seem to happen with <span class="mono">qt.process_model = single-process</span>
|
||||
|
|
|
|||
|
|
@ -145,47 +145,47 @@
|
|||
}
|
||||
};
|
||||
|
||||
const unsafeWindow = window;
|
||||
{% if use_proxy %}
|
||||
/*
|
||||
* Try to give userscripts an environment that they expect. Which
|
||||
* seems to be that the global window object should look the same as
|
||||
* the page's one and that if a script writes to an attribute of
|
||||
* window it should be able to access that variable in the global
|
||||
* scope.
|
||||
* Use a Proxy to stop scripts from actually changing the global
|
||||
* window (that's what unsafeWindow is for).
|
||||
* Use the "with" statement to make the proxy provide what looks
|
||||
* like global scope.
|
||||
*
|
||||
* There are other Proxy functions that we may need to override.
|
||||
* set, get and has are definitely required.
|
||||
*/
|
||||
const unsafeWindow = window;
|
||||
const qute_gm_window_shadow = {}; // stores local changes to window
|
||||
const qute_gm_windowProxyHandler = {
|
||||
get: function(target, prop) {
|
||||
if (prop in qute_gm_window_shadow)
|
||||
return qute_gm_window_shadow[prop];
|
||||
if (prop in target) {
|
||||
if (typeof target[prop] === 'function' && typeof target[prop].prototype == 'undefined')
|
||||
// Getting TypeError: Illegal Execution when callers try to execute
|
||||
// eg addEventListener from here because they were returned
|
||||
// unbound
|
||||
return target[prop].bind(target);
|
||||
return target[prop];
|
||||
}
|
||||
},
|
||||
set: function(target, prop, val) {
|
||||
return qute_gm_window_shadow[prop] = val;
|
||||
},
|
||||
has: function(target, key) {
|
||||
return key in qute_gm_window_shadow || key in target;
|
||||
}
|
||||
};
|
||||
const qute_gm_window_proxy = new Proxy(
|
||||
unsafeWindow, qute_gm_windowProxyHandler);
|
||||
/*
|
||||
* Try to give userscripts an environment that they expect. Which seems
|
||||
* to be that the global window object should look the same as the page's
|
||||
* one and that if a script writes to an attribute of window all other
|
||||
* scripts should be able to access that variable in the global scope.
|
||||
* Use a Proxy to stop scripts from actually changing the global window
|
||||
* (that's what unsafeWindow is for). Use the "with" statement to make
|
||||
* the proxy provide what looks like global scope.
|
||||
*
|
||||
* There are other Proxy functions that we may need to override. set,
|
||||
* get and has are definitely required.
|
||||
*/
|
||||
|
||||
with (qute_gm_window_proxy) {
|
||||
if (!window._qute_gm_window_proxy) {
|
||||
const qute_gm_window_shadow = {}; // stores local changes to window
|
||||
const qute_gm_windowProxyHandler = {
|
||||
get: function (target, prop) {
|
||||
if (prop in qute_gm_window_shadow)
|
||||
return qute_gm_window_shadow[prop];
|
||||
if (prop in target) {
|
||||
if (typeof target[prop] === 'function' && typeof target[prop].prototype == 'undefined')
|
||||
// Getting TypeError: Illegal Execution when callers try
|
||||
// to execute eg addEventListener from here because they
|
||||
// were returned unbound
|
||||
return target[prop].bind(target);
|
||||
return target[prop];
|
||||
}
|
||||
},
|
||||
set: function(target, prop, val) {
|
||||
return qute_gm_window_shadow[prop] = val;
|
||||
},
|
||||
has: function(target, key) {
|
||||
return key in qute_gm_window_shadow || key in target;
|
||||
}
|
||||
};
|
||||
window._qute_gm_window_proxy = new Proxy(unsafeWindow, qute_gm_windowProxyHandler);
|
||||
}
|
||||
const qute_gm_window_proxy = window._qute_gm_window_proxy;
|
||||
with (qute_gm_window_proxy) {
|
||||
// We can't return `this` or `qute_gm_window_proxy` from
|
||||
// `qute_gm_window_proxy.get('window')` because the Proxy implementation
|
||||
// does typechecking on read-only things. So we have to shadow `window`
|
||||
|
|
@ -194,10 +194,10 @@
|
|||
// ====== The actual user script source ====== //
|
||||
{{ scriptSource }}
|
||||
// ====== End User Script ====== //
|
||||
};
|
||||
};
|
||||
{% else %}
|
||||
// ====== The actual user script source ====== //
|
||||
// ====== The actual user script source ====== //
|
||||
{{ scriptSource }}
|
||||
// ====== End User Script ====== //
|
||||
// ====== End User Script ====== //
|
||||
{% endif %}
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ function timeRange() {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
throw 'timeRange: bad number of arguments'
|
||||
throw 'timeRange: bad number of arguments';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
// ==UserScript==
|
||||
// @include https://web.whatsapp.com/
|
||||
// ==/UserScript==
|
||||
|
||||
// Quirk for WhatsApp Web, based on:
|
||||
// https://github.com/jiahaog/nativefier/issues/719#issuecomment-443809630
|
||||
|
||||
"use strict";
|
||||
|
||||
if (document.querySelector("a[href='https://support.google.com/chrome/answer/95414']")) {
|
||||
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||
registration.unregister();
|
||||
document.location.reload();
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue