Compare commits

...

436 Commits
v1.3.x ... nsis

Author SHA1 Message Date
Florian Bruhin
179e583af5 NSIS: Remove installation dir if it exists
If we don't do this, when a newer version is installed on top of an older one,
old files which have been deleted persist. In the case of v1.3.3 -> v1.4.0,
this means qutebrowser won't start because of a leftover sip installation which
confuses PyQt 5.11.

This is still potentially dangerous as the user could use e.g. C:\ as
installation path, but we have that issue when uninstalling anyways.
See http://nsis.sourceforge.net/Uninstall_only_installed_files
2018-07-10 14:57:40 +02:00
Florian Bruhin
b9e3d3cab9 Add workaround for chrome-extension:// URLs
Fixes #4049
2018-07-09 12:29:35 +02:00
Florian Bruhin
274b66ec46 Strip trailing newlines from pastebin URL 2018-07-08 22:09:56 +02:00
Florian Bruhin
ae32b79d54 Add exam comments to contributing docs 2018-07-04 15:37:50 +02:00
Florian Bruhin
e80e695a56 Add a mkvenv-pypi-old environment
Fixes #4038
See #3662
2018-07-04 14:08:04 +02:00
Florian Bruhin
0a31e19eda Handle download errors when the reply is already gone
Fixes #1270
2018-07-03 17:16:02 +02:00
Florian Bruhin
0f037fb415 Release v1.4.0 2018-07-03 15:44:44 +02:00
Florian Bruhin
85cc1e4f84 Update changelog for v1.4.0 2018-07-03 15:44:25 +02:00
Florian Bruhin
42a3622906 Ignore a new Qt 5.11 lowlevel message 2018-07-03 15:38:29 +02:00
Florian Bruhin
a0f36c5cbf Skip JS test which is too flaky 2018-07-03 14:15:08 +02:00
Florian Bruhin
dfafab4cff Update changelog 2018-07-03 13:52:50 +02:00
Florian Bruhin
8cf22c85e0 Merge remote-tracking branch 'origin/pr/4019' 2018-07-03 13:52:24 +02:00
Florian Bruhin
4d1e56a8c6 Ignore "Lost UI shared context" error happening on AppVeyor 2018-07-03 13:39:34 +02:00
Florian Bruhin
8c11c516b4 tox: Always allow setting python via envvar 2018-07-03 13:32:53 +02:00
Florian Bruhin
857288b283 Try importing QtWebEngine from AppVeyor 2018-07-03 13:28:18 +02:00
Florian Bruhin
24e93fe023 Remove AppVeyor debugging 2018-07-03 13:27:31 +02:00
Florian Bruhin
8115e109db Update default env in tox.ini 2018-07-03 13:27:18 +02:00
Florian Bruhin
6abe8f2c97 Ignore QtNetwork warning on macOS 2018-07-03 13:22:05 +02:00
Florian Bruhin
7a9183d0b7 Remove 32-bit makensis call
I forgot to remove this in 0af8eec73a
2018-07-03 13:17:15 +02:00
Florian Bruhin
ba362de2c0 Turn on AppVeyor debugging 2018-07-03 13:15:07 +02:00
Florian Bruhin
d03b03f7cb Use Python 3.7 on macOS 2018-07-03 13:13:58 +02:00
Florian Bruhin
7e8c741937 Set compiled=False for BDD Qt comparisons
We often check for bugs and not APIs there.
2018-07-03 13:10:15 +02:00
Florian Bruhin
641f7eb3c5 Don't import test_file on Windows
See https://github.com/pytest-dev/pytest/issues/3650
2018-07-03 13:08:54 +02:00
Florian Bruhin
38791a2386 Only import gen_versioninfo on Windows 2018-07-03 11:38:48 +02:00
Florian Bruhin
b8fb5d4590 Make sure we're using Python 3.6 in release instructions 2018-07-03 11:37:26 +02:00
Florian Bruhin
f65f3db747 appveyor: Make sure we get the Python we want 2018-07-03 11:17:27 +02:00
Florian Bruhin
5add2cd930 Update changelog 2018-07-03 11:15:59 +02:00
Florian Bruhin
0af8eec73a build_release: Remove 32-bit support for Windows
QtWebEngine isn't available for 32-bit anymore:
https://blog.qt.io/blog/2018/05/22/qt-5-11-released/ (comments)
2018-07-03 11:08:56 +02:00
Florian Bruhin
49be92e047 Use 64-bit Python on AppVeyor
QtWebEngine isn't available in the 32-bit build.
2018-07-03 11:06:31 +02:00
Florian Bruhin
ec0bbe67f8 travis: Test with Python 3.7
See https://github.com/travis-ci/travis-ci/issues/9069
2018-07-03 11:00:33 +02:00
Florian Bruhin
e6e28c846f Use PyYAML 3.13b1
It seems to be finished except for a missing wheel we don't need, and it makes
things work on Python 3.7
2018-07-03 10:30:59 +02:00
Florian Bruhin
efc4eb9069 Use PyYAML from git for requirements-tests-git 2018-07-03 10:30:30 +02:00
Florian Bruhin
26d6cf8ef6 Stabilize URL escaping test 2018-07-03 10:29:28 +02:00
Florian Bruhin
05531ddcf0 brew: Update instead of install libyaml 2018-07-03 10:27:38 +02:00
Florian Bruhin
1c8917b10e Remove old brew_install line 2018-07-03 10:27:10 +02:00
pyup-bot
c33c019075 Update py from 1.5.3 to 1.5.4
(cherry picked from commit fa9698564af184e6e7cdddc96c9906442031197e)
2018-07-02 23:26:14 +02:00
pyup-bot
564cd3732b Update py from 1.5.3 to 1.5.4
(cherry picked from commit 2006ad70e88cf5fffec75aa465f968ebba722758)
2018-07-02 23:26:12 +02:00
pyup-bot
1d91a3ac66 Update hypothesis from 3.61.0 to 3.65.0
(cherry picked from commit 98ecc7e77b7dd21037224a4f3b51d0cb654610e6)
2018-07-02 23:26:09 +02:00
Florian Bruhin
f6f713bbfe Skip key forwarding tests on Qt 5.11.1
See #4036
2018-07-02 23:19:57 +02:00
Florian Bruhin
6ca11ed95b Adjust SSL test for Qt 5.11 2018-07-02 23:15:04 +02:00
Florian Bruhin
41303ecfcf Make sure temporary dir exists
This seems to be enforced with Qt 5.12
See #4025
2018-07-02 22:32:59 +02:00
Florian Bruhin
9a14574c9f Skip invalid links on any Qt 5.11 version
See #3661
2018-07-02 22:32:59 +02:00
Florian Bruhin
d861c097b1 Support new dead keys added in Qt 5.11 properly
See https://codereview.qt-project.org/#/c/207231/
2018-07-02 22:32:59 +02:00
Florian Bruhin
77fe2e1c85 Fix test_set_wrong_backend 2018-07-02 22:32:59 +02:00
Florian Bruhin
ad19833e34 Revert "Add workaround for PyQt 5.11 headerDataChanged bug"
PyQt 5.11.1 has already been released, with the bug fixed.

This reverts commit 291763a55643342a6f977ce2a12dcc6f4badbe8a.
2018-07-02 22:32:59 +02:00
Florian Bruhin
eca08f064b Add workaround for PyQt 5.11 headerDataChanged bug
https://www.riverbankcomputing.com/pipermail/pyqt/2018-June/040445.html
2018-07-02 22:32:59 +02:00
Florian Bruhin
c3455d9082 Add a wrapper around sip
Starting with PyQt 5.11, the sip module now is bundled with PyQt as PyQt.sip.
Having a qutebrowser.qt also helps with #3625, see #995
2018-07-02 22:32:59 +02:00
Florian Bruhin
f7ae7e7d40 Update for PyQt 5.11 2018-07-02 22:32:59 +02:00
Florian Bruhin
e857400c2c Add missing str() 2018-06-28 13:26:45 +02:00
Florian Bruhin
dbd4ce48e6 Downgrade and filter PyYAML 4.1
This reverts commit 96defc5dc2.

With PyYAML 4.1 we don't have C extensions on Travis CI:
https://github.com/yaml/pyyaml/issues/179
https://github.com/yaml/pyyaml/issues/182

Unfortunately, cython isn't in the APT whitelist either:
https://github.com/travis-ci/apt-source-whitelist/issues/37
2018-06-28 11:46:03 +02:00
Florian Bruhin
a7af5195d1 Set title when showing PDF.js error page
Fixes #3894
2018-06-28 11:22:44 +02:00
Florian Bruhin
e9c78b29ed Ignore Python 3.7 collections.abc warning
Related issues/PRs:
https://github.com/yaml/pyyaml/pull/181
https://github.com/pypa/setuptools/issues/1401
https://github.com/pallets/markupsafe/pull/98
https://github.com/yaml/pyyaml/pull/181
https://github.com/pallets/jinja/pull/867
2018-06-27 16:01:21 +02:00
Florian Bruhin
96defc5dc2 Update PyYAML to 4.1 2018-06-27 15:53:58 +02:00
Florian Bruhin
a804300dc0 Add missing tests for spell.init() 2018-06-26 16:56:22 +02:00
Florian Bruhin
2f612aa6df Update comment 2018-06-26 15:54:56 +02:00
Florian Bruhin
aed964d9f5 Update changelog 2018-06-26 10:41:21 +02:00
Florian Bruhin
8519aa940f Decorate slots properly 2018-06-26 10:40:13 +02:00
Florian Bruhin
ea4ee6f00b Use the url_changed signal in the tab API 2018-06-26 10:39:33 +02:00
Florian Bruhin
1536843f33 Only get greasemonkey object once 2018-06-26 10:39:04 +02:00
Florian Bruhin
61da5d0c7c Merge remote-tracking branch 'origin/pr/4017' 2018-06-26 10:37:17 +02:00
Florian Bruhin
3312c221c4 Stabilize ssl_strict test 2018-06-26 10:26:17 +02:00
Florian Bruhin
85a9f6a08a Fix lint 2018-06-26 10:23:48 +02:00
Jimmy
c43d173197 greasemonkey: s/userscripts/greasemonkey_scripts/
No need to confuse developers as well as users.
2018-06-26 16:42:31 +12:00
Jimmy
ee2c765859 greasemonkey: check _widget is not deleted
Just for good luck.

No crash has been reported here but it is a common pattern for functions
called from signals.
2018-06-26 16:42:31 +12:00
Jimmy
6f1232e621 greasemonkey: move 5.7.1 injection method into _WebEngineScripts
Moves the 5.8 check to `_WebEngineScripts.init()`.

Changes `_inject_userscripts` to allow for the two code paths. With
5.7.1 we need to specify the injection point and not clear all scripts
for each call, since we have to call it three times.

Change the 5.8+ hook to call a new method which passes all the scripts
into `_inject_userscripts` so that doesn't have to have a fallback
conditional inside it because thats an inversion of responsibility!

Pulling the remove scripts part into a seperate function and making it
the callers responsibilty to call that first would tidy it up a little
more but meh.

I was worried about just doing `_widget.page().urlChanged.connect()`
once at tab init, where before it was connected at page init, because I
was under the impression that the child page can be replaced at any
time, eg when navigating to a new origin. But under manual testing I
didn't see that at all. Maybe I was mistaken or maybe that only started
in a later Qt version.
2018-06-26 16:42:31 +12:00
Jimmy
324966cfe7 greasemonkey: also support qute-js-world on 5.7.1
A straight copy from webengintab.

Yes I know I shouldn't be importing a private thing from webenginetab,
I'm working on refactoring now.
2018-06-26 15:00:35 +12:00
Jimmy
521268a1f7 Update comment. 2018-06-26 15:00:35 +12:00
Jimmy
54ca9b34e5 greasemonkey: enable running in isolated js worlds
QtWebEngine (via chromium) has the ability to run injected scripts in
isolated "worlds". What is isolated is just the javascript environment,
so variables and functions defined by the page and the script won't
clobber each other, or be able to interact (including variables saved to
the global `window` object). The DOM is still accessible from "isolated"
scripts.

This is NOT a security measure. You cannot put untrusted scripts in one
of these isolated worlds and expect it to not be able to do whatever
page js can do, it is just for namespacing convenience. See
https://stackoverflow.com/questions/9515704/insert-code-into-the-page-context-using-a-content-script
for some examples of how to inject scripts into the page scope using DOM
elements.

Now you can specify the world ID in a `@qute-js-world` directive like:

```
// ==UserScript==
// @name         Do thing
// @match        *://some.site/*
// @qute-js-world 1234
// ==/UserScript==
document.body.innerHTML = "<strong>overwritten</strong>"
```

The QtWebEngine docs say worldid is a `quint32` so you can put whatever
number (positive, whole, real) you want there. I have chosen to allow
the `qutebrowser.utils.usertypes` enum as aliases for IDs that are
predefined in
`qutebrowser.browser.webengine.webenginetab._JS_WORLD_MAP`. So you can
pass `main`, `application`, `user` or `jseval` in there too. `main` (0)
is the default one and is the only one in which JS disabled when
`content.javascript.enabled` is set to `false`. All others are still
enabled.

I'm not sure whether using any of those already-named worlds makes
sense, apart from `main`. We could stop people from using them I
suppose. Another option is to allow people to pass in `*` as a value to
have scripts put into their own little worlds, probably backed by a
counter in the GreaseMonkeyManager class.

Chrome docs: https://developer.chrome.com/extensions/content_scripts#execution-environment
Webengine docs: https://doc.qt.io/qt-5/qwebenginescript.html#details
2018-06-26 15:00:35 +12:00
Florian Bruhin
1bc3d444b6 Stabilize escaping URLs test 2018-06-25 23:02:50 +02:00
Florian Bruhin
876aa5a9b1 Fix lint 2018-06-25 22:51:55 +02:00
Florian Bruhin
87778277e0 Fix SSL error page tests 2018-06-25 22:51:48 +02:00
Florian Bruhin
81b3ef937e Move handling of certificate errors to webenginetab 2018-06-25 21:04:32 +02:00
Florian Bruhin
8a4bba11ed Disable certificate workaround on Qt >= 5.9
Fixes #4020
2018-06-25 20:35:48 +02:00
Florian Bruhin
3f923b41e0 Revert "Update pyqt5 from 5.10.1 to 5.11.1"
This reverts commit 1f19db0785.
2018-06-25 20:08:50 +02:00
Florian Bruhin
158ccd7d54 Revert "Update pyqt5 from 5.10 to 5.11.1"
This reverts commit e1bc5389a2.
2018-06-25 20:08:42 +02:00
Florian Bruhin
228ca732d5 Merge remote-tracking branch 'origin/pyup-scheduled-update-2018-06-25' 2018-06-25 20:08:24 +02:00
pyup-bot
b3790f7a7e Update pytest from 3.6.1 to 3.6.2 2018-06-25 19:21:17 +02:00
pyup-bot
d6554a131e Update hypothesis from 3.59.1 to 3.61.0 2018-06-25 19:21:15 +02:00
pyup-bot
1f19db0785 Update pyqt5 from 5.10.1 to 5.11.1 2018-06-25 19:21:14 +02:00
pyup-bot
e1bc5389a2 Update pyqt5 from 5.10 to 5.11.1 2018-06-25 19:21:12 +02:00
Jay Kamat
da0a6305df Fix crash when tab is closed after a per-domain forced reload 2018-06-25 12:45:17 -04:00
Florian Bruhin
6c9e23af4a eslint: Turn off max-lines-per-function 2018-06-25 08:14:02 +02:00
Florian Bruhin
13f765a000 Fix changelog formatting 2018-06-24 22:33:46 +02:00
Florian Bruhin
fc19262eaa Fix test_shared.py 2018-06-24 22:31:27 +02:00
Florian Bruhin
f2f481d991 Support URL patterns for permissions and ssl_strict
See #3636
2018-06-24 21:38:37 +02:00
Florian Bruhin
f5e69b2174 Show inspector after creating it 2018-06-24 19:57:52 +02:00
Florian Bruhin
e6e844b039 Support URL patterns for content.headers settings
See #3636
2018-06-24 19:54:24 +02:00
Florian Bruhin
a02c25dfb1 Don't escape URLs for qute://history
We only use the URL to set a 'href' attribute, which does not need escaping.

See #4011
Fixes #4012
2018-06-23 14:27:07 +02:00
Florian Bruhin
d2254ca48b Release v1.3.3
(cherry picked from commit ad9b50601c)
2018-06-21 23:32:56 +02:00
Florian Bruhin
66fc3a30dd Update changelog 2018-06-21 23:30:27 +02:00
Florian Bruhin
0864ad4069 Fix shadowing of 'html' name 2018-06-21 22:28:27 +02:00
Florian Bruhin
9a5439e5d0 Re-add waiting for QQuickWidget
Apparently this is still needed on some PyQt versions.
2018-06-21 22:22:04 +02:00
Florian Bruhin
7a7e04a054 Move fix to v1.3.3 in changelog 2018-06-21 21:42:44 +02:00
Florian Bruhin
d961eab1d2 Update changelog for v1.3.3 2018-06-21 21:42:08 +02:00
Florian Bruhin
5a7869f2fe Fix XSS issue on qute://history
Fixes #4011
2018-06-21 21:20:19 +02:00
Florian Bruhin
62d8b5b574 Don't depend on PyQt5.QtQuickWidgets to get RWHV
Some distributions (at least FreeBSD) don't package that module, so let's not
rely on it.
2018-06-21 17:14:29 +02:00
Florian Bruhin
c87757a913 Revert "Properly add QtQuickWidgets dependency"
Looks like FreeBSD doesn't have QtQuickWidgets packaged at all, so let's do the
same without requiring it...

This reverts commit e5405f0ae9.
2018-06-21 16:35:29 +02:00
Florian Bruhin
9f5ca475c9 Don't try to set focus if prev_focus is None 2018-06-21 01:44:15 +02:00
Florian Bruhin
e7a300865c Fix lint 2018-06-21 01:43:09 +02:00
Florian Bruhin
4887385bdd Fix test_dictionary_dir 2018-06-21 01:40:36 +02:00
Florian Bruhin
1000a1eac2 Merge remote-tracking branch 'origin/pr/4005' 2018-06-21 01:01:27 +02:00
Florian Bruhin
e5405f0ae9 Properly add QtQuickWidgets dependency 2018-06-21 00:21:52 +02:00
pyup-bot
a73a778b9d Update pytest-qt from 2.4.0 to 2.4.1 2018-06-18 19:11:20 +02:00
pyup-bot
7c4eaa80b0 Update hypothesis from 3.57.0 to 3.59.1 2018-06-18 19:11:19 +02:00
pyup-bot
c3b76d1d01 Update cheroot from 6.3.1 to 6.3.2 2018-06-18 19:11:17 +02:00
pyup-bot
fa0e8c1b51 Update requests from 2.18.4 to 2.19.1 2018-06-18 19:11:16 +02:00
pyup-bot
da8f76d082 Update requests from 2.18.4 to 2.19.1 2018-06-18 19:11:14 +02:00
pyup-bot
91c0aae05b Update requests from 2.18.4 to 2.19.1 2018-06-18 19:11:13 +02:00
Florian Bruhin
3399f2df96 Always clear searches between page loads
Looks like this wasn't properly fixed in Qt for some reason.
Fixes #3693
See #2728 and bef372e5f5
2018-06-17 21:03:44 +02:00
Florian Bruhin
2029f52fdc Show cause when ~/.netrc can't be read 2018-06-17 20:53:29 +02:00
Florian Bruhin
663d1a4d2f Read dictionaries from /usr/share/qt on Qt >= 5.10
Fixes #3759
Supersedes #3762
See #2939, #4003
2018-06-17 20:27:52 +02:00
Florian Bruhin
7b7e0c93f5 Update Chromium version in changelog 2018-06-16 11:54:53 +02:00
Florian Bruhin
e2ef39e872 Add Comment to .desktop file 2018-06-14 23:29:16 +02:00
Florian Bruhin
7654467f36 Remove unused import 2018-06-14 17:43:20 +02:00
Florian Bruhin
b1b06fcb43 Fix restore test 2018-06-14 17:42:33 +02:00
Florian Bruhin
746c2986f0 Fix test_stylesheet on Qt 5.11 2018-06-14 16:42:24 +02:00
Florian Bruhin
f4386fa9ea Update changelog 2018-06-14 16:35:16 +02:00
Florian Bruhin
07cf2f5b60 Unconditionally restore mode after prompt 2018-06-14 16:09:30 +02:00
Florian Bruhin
4dddc07753 Make sure modeman.enter(KeyMode.normal) does something sensible 2018-06-14 16:09:26 +02:00
Florian Bruhin
11fce30ed0 Stabilize mode_on_change tests 2018-06-14 15:56:48 +02:00
Florian Bruhin
1335fccba1 Merge remote-tracking branch 'origin/pr/3590' into tab-mode 2018-06-14 15:40:58 +02:00
Florian Bruhin
e4e982c0a7 Remove unused variable 2018-06-14 14:58:07 +02:00
Florian Bruhin
7592186181 Update changelog 2018-06-14 14:58:07 +02:00
Florian Bruhin
868cd115be Remove old focus handling code 2018-06-14 14:58:07 +02:00
Florian Bruhin
cec63ea449 Merge remote-tracking branch 'origin/pr/3906' 2018-06-14 14:49:30 +02:00
Jay Kamat
e5b6552568 Clean up and simplify some logic 2018-06-13 15:37:32 -07:00
Florian Bruhin
a0adee55c9 Quit hard on ignored exceptions
We can't realistically shut down cleanly because we most likely haven't init'ed
properly yet.

Fixes #3993
2018-06-13 21:13:39 +02:00
Florian Bruhin
389c1d11a0 Merge remote-tracking branch 'origin/pr/3990' 2018-06-13 20:28:08 +02:00
Florian Bruhin
b67733b781 Use ImportError for winreg import
This also satisfies pylint.
2018-06-13 20:15:16 +02:00
Florian Bruhin
bcc4bd22ee Merge remote-tracking branch 'origin/pr/3991' 2018-06-13 20:15:06 +02:00
Florian Bruhin
cb2881e0d7 Use info loglevel for :debug-cache-stats
Closes #3994
2018-06-13 19:10:01 +02:00
Florian Bruhin
1541088e76 Use -webkit-filter on old Qt 2018-06-12 17:01:34 +02:00
Florian Bruhin
5435609552 Update changelog 2018-06-12 16:50:32 +02:00
Florian Bruhin
c37134861e Use ES6 template strings 2018-06-12 16:49:54 +02:00
Florian Bruhin
772654bcae Use calculated background color for newer Qt versions 2018-06-12 16:46:03 +02:00
Florian Bruhin
68a7387b6b Merge remote-tracking branch 'origin/pr/3940' 2018-06-12 16:32:05 +02:00
Florian Bruhin
fd4ae2fabe Fix test_set_wrong_backend 2018-06-12 14:17:50 +02:00
Florian Bruhin
a1a5885367 Set parents for tab sub-objects 2018-06-12 14:09:06 +02:00
Florian Bruhin
a6db700886 Fix HTML5 fullscreen 2018-06-12 13:44:47 +02:00
Florian Bruhin
d5dcec4320 Add changelog entry for Qt update 2018-06-12 13:44:41 +02:00
Slackhead
088c7b235d fix for qt 5.7.1 2018-06-12 11:22:41 +01:00
Florian Bruhin
6faff11243 Improve error messages with backend conditionals 2018-06-12 11:10:03 +02:00
Florian Bruhin
2f97a597a4 travis: Run coverage with PyQt 5.10 2018-06-12 10:53:34 +02:00
Florian Bruhin
e066f83a7c Make configinit tests run with all setups 2018-06-12 10:52:22 +02:00
Florian Bruhin
5b5657b0d5 Make sure debug_flags is available for unit tests 2018-06-12 10:38:19 +02:00
Florian Bruhin
3b0c8e46a3 Add an initial cookie filter for Qt 5.11
See #3010
2018-06-12 09:36:05 +02:00
Slackhead
35a1e118f8 combine style nodes 2018-06-12 04:25:13 +01:00
Florian Bruhin
093f07f552 Add content.canvas_reading setting
See #2377
Closes #2235
2018-06-11 23:28:04 +02:00
bitraid
b61c99687d build_release.py: reformat 2018-06-12 00:27:43 +03:00
Florian Bruhin
ad7e080827 Fix lint 2018-06-11 21:47:38 +02:00
Florian Bruhin
67c67db230 Handle multiple visible children when finding lost focusProxy
When we click a QTBUG link (to open in a new tab) from Qt's codereview, we get
two RWHV objects which both are visible.

Experimenting with .setEnabled(False) it looks like it's (hopefully always...)
the last one which is the one to use.
2018-06-11 21:43:27 +02:00
Florian Bruhin
c328d54ebe Add a lost-focusproxy debug flag 2018-06-11 21:27:08 +02:00
Florian Bruhin
a6b314ae91 Don't connect Qt 5.11 signals on PyQt 5.10
Apparently the signal attributes already exist with PyQt 5.10 (*sigh*) but PyQt
doesn't know what to do with the arguments, causing this to happen:

TypeError: unable to convert a C++ 'QWebEngineRegisterProtocolHandlerRequest'
instance to a Python object
2018-06-11 21:21:41 +02:00
Florian Bruhin
d42934af08 Turn off FocusOnNavigationEnabled on Qt 5.9
This way we get the same behavior with Qt 5.9 and 5.10 at least, leaving only
5.7 if we pretend that 5.8 never existed.
2018-06-11 20:28:00 +02:00
Florian Bruhin
e7659cea63 Update changelog 2018-06-11 20:24:00 +02:00
Florian Bruhin
141afff0c6 Add a content.desktop_capture setting
See #2939
2018-06-11 20:18:57 +02:00
bitraid
ddfbe255e7 build_release.py: Get python path from registry on Windows 2018-06-11 20:45:35 +03:00
Florian Bruhin
69abc9a1a1 Add a content.webrtc_public_interfaces_only option
See #3010
Fixes #2163
2018-06-11 19:44:45 +02:00
Florian Bruhin
3bf89bcea4 Add content.autoplay option
See #3010
Closes #1643
2018-06-11 19:32:34 +02:00
Florian Bruhin
c5b7ed350e Make it possible to provide a converter for websettings 2018-06-11 19:14:31 +02:00
pyup-bot
a5447e0e94 Update vulture from 0.26 to 0.27 2018-06-11 19:06:46 +02:00
pyup-bot
4d026bde6f Update vulture from 0.26 to 0.27 2018-06-11 19:06:39 +02:00
pyup-bot
6943677a90 Update pytest from 3.6.0 to 3.6.1 2018-06-11 19:06:37 +02:00
pyup-bot
37d3cf1e9c Update pylint from 1.9.1 to 1.9.2 2018-06-11 19:06:36 +02:00
pyup-bot
21dba1ffda Update astroid from 1.6.4 to 1.6.5 2018-06-11 19:06:34 +02:00
pyup-bot
82a0c94573 Update idna from 2.6 to 2.7 2018-06-11 19:06:33 +02:00
pyup-bot
72f0820204 Update idna from 2.6 to 2.7 2018-06-11 19:06:31 +02:00
pyup-bot
33ba47797d Update idna from 2.6 to 2.7 2018-06-11 19:06:30 +02:00
Florian Bruhin
b7c1d7fe37 Fix broken tests 2018-06-11 18:38:22 +02:00
Florian Bruhin
263d298449 Remove the content.developer_extras setting 2018-06-11 18:09:24 +02:00
Florian Bruhin
e5fbb9f68a Remove pyqtSlot annotations for new types 2018-06-11 18:09:24 +02:00
Florian Bruhin
54011782c8 webenginetab: Move scripts to separate object 2018-06-11 18:09:24 +02:00
Florian Bruhin
b3749df009 webenginetab: Move permissions to separate object 2018-06-11 18:09:18 +02:00
Florian Bruhin
4186577928 Add support for navigator.registerProtocolHandler
See #3010
2018-06-11 18:09:18 +02:00
Florian Bruhin
c020160f75 Add support for navigator.webkitPersistentStorage.requestQuota
See #3010
2018-06-11 18:09:18 +02:00
Florian Bruhin
4ea957b68b Allow Qt 5.11 for backends in configdata.yml 2018-06-11 18:09:18 +02:00
Florian Bruhin
05e73872b6 Add blocking=True to shared.feature_permission 2018-06-11 16:37:53 +02:00
Florian Bruhin
be95d6f505 Remove moved audio API 2018-06-11 16:07:32 +02:00
Florian Bruhin
b954fd4b15 Move _on_feature_permission_requested to WebEngineTab 2018-06-11 16:06:58 +02:00
Florian Bruhin
6e90465cdd Update docs 2018-06-11 15:38:01 +02:00
Florian Bruhin
98c82859b5 Skip "History with view-source URL" on QtWebKit 2018-06-11 15:36:48 +02:00
Florian Bruhin
8964845c18 Remove unused import 2018-06-11 15:30:01 +02:00
Florian Bruhin
c7f57bc111 Tell pylint to shut up about a shadowed argument 2018-06-11 15:29:35 +02:00
Florian Bruhin
09a5ef8140 pylint: Ignore ImportError for windows-specific import 2018-06-11 15:22:34 +02:00
Florian Bruhin
9f5a5a12ba Break long line 2018-06-11 15:22:12 +02:00
Florian Bruhin
f052eff038 Stop using view-source: scheme for Pygments-highlighted URLs
Doing so causes QtWebEngine to load its own view-source: page even if we supply
custom data.

Instead we pass the original page's URL (to not regress #2948).

This partially reverts #3521 and reintroduces TabData.viewing_source.

However, on QtWebEngine we can still ":view-source --pygments" and then
":view-source" (with or without "--pygments") again, because the bit gets
cleaned in _on_load_started.

See #3654.
2018-06-11 15:18:00 +02:00
Florian Bruhin
6e23a6b958 Merge remote-tracking branch 'origin/pr/3654' 2018-06-11 14:56:32 +02:00
Florian Bruhin
cbd9e36e0f Fix typo 2018-06-11 14:13:11 +02:00
Florian Bruhin
12a7aaed95 flake8: Don't require docstrings for stuff under tests/ 2018-06-11 14:12:47 +02:00
Florian Bruhin
2b0e89ab14 Update changelog 2018-06-11 14:01:42 +02:00
Florian Bruhin
b63e06561d Only consider visible render widgets for lost focusProxy
Otherwise, when commenting out the focusProxy way above, and using "foo !npm"
with DuckDuckGo, we get two children (one visible, one invisible).
2018-06-11 14:00:02 +02:00
Florian Bruhin
49b6a512c2 Add missing docstring 2018-06-11 13:17:14 +02:00
Florian Bruhin
8949afc2bf Reformat and document gen_versioninfo 2018-06-11 13:13:58 +02:00
Florian Bruhin
e6dd05a2c7 Merge remote-tracking branch 'origin/pr/3857' 2018-06-11 13:08:29 +02:00
Florian Bruhin
b5d1614c48 Add audio attribute to FakeWebTab 2018-06-11 12:41:55 +02:00
Florian Bruhin
bfae6b357a Update changelog 2018-06-11 12:39:47 +02:00
Florian Bruhin
4dcba2343d Add expected_names to test arguments 2018-06-11 12:37:48 +02:00
Florian Bruhin
e36b6b13e0 Edit docstring :D 2018-06-11 12:35:49 +02:00
Florian Bruhin
2934f4a1ca Merge remote-tracking branch 'origin/pr/3973' 2018-06-11 12:34:18 +02:00
Florian Bruhin
8376278961 Update docs 2018-06-11 12:14:24 +02:00
Florian Bruhin
a1fcdbcfd1 Move muted/audible API to own ".audio" object 2018-06-11 12:12:37 +02:00
Florian Bruhin
1c8d470bd7 Merge remote-tracking branch 'origin/pr/3908' 2018-06-11 12:06:09 +02:00
Jimmy
b7929ef747 Add tests for suppressing debug log records.
For the LogFilter tests I just copied some of the examples that were
already being used and reversed them. To do that without even more
duplication I had to add another parameter to the test.
2018-06-11 21:56:59 +12:00
Florian Bruhin
c08c740793 Update changelog 2018-06-11 11:45:31 +02:00
Florian Bruhin
649c9f37cd Fix import 2018-06-11 11:35:37 +02:00
Florian Bruhin
3999802c71 Add missing 'return' 2018-06-11 11:35:35 +02:00
Florian Bruhin
cf8dbd8bfd Move pressing Enter into a method 2018-06-11 11:35:16 +02:00
Florian Bruhin
7f69920158 Merge remote-tracking branch 'origin/pr/3947' 2018-06-11 11:29:14 +02:00
Jimmy
addd2e74ce Allow negating categories in --logfilter
Sometimes I want to see all the logs _except_ for the sql stuff and
"marked cookies as dirty". with this you should be able to pass
`--logfilter \!sql,save`.
2018-06-11 21:26:54 +12:00
Florian Bruhin
f034abe6a1 Handle showing/hiding of the inspector properly 2018-06-11 11:07:14 +02:00
Florian Bruhin
1ba2e3e24b Implement Qt 5.11 devtools support
See #3010
2018-06-11 11:07:14 +02:00
Jay Kamat
315ed519ee Use ctrl-enter in all cases 2018-06-10 16:27:56 -07:00
Florian Bruhin
20763a87c0 Update changelog 2018-06-10 17:34:17 +02:00
Florian Bruhin
c4add62301 Merge remote-tracking branch 'origin/pr/3825' 2018-06-10 17:30:44 +02:00
Florian Bruhin
50fa7743ba Only use OS-specific line separator for hints 2018-06-10 17:21:31 +02:00
Florian Bruhin
3bffe1ccf8 Release v1.3.2 2018-06-10 15:59:50 +02:00
Florian Bruhin
bd26498f6e Update changelog for v1.3.2
[ci skip]
2018-06-10 15:57:10 +02:00
Florian Bruhin
829a6855a3 Make sure test_env_vars restores variables correctly
Otherwise, we'd get a segfault inside Qt in test_stylesheet, since
88f2873a79.
2018-06-10 15:49:24 +02:00
Florian Bruhin
ca8d3903e3 Improve configinit tests 2018-06-10 10:49:26 +02:00
Jay Kamat
11d8df0e3e Simplify logic and resolve style issues 2018-06-09 16:45:42 -07:00
bitraid
00199ae60e Corrections to gen_versioninfo.py script
- Use main() function
- Call utils.change_cwd()
- Import info text
- Don't use fstrings
2018-06-10 02:24:47 +03:00
Jay Kamat
de127497a2 Press enter to follow links instead of using js
This codepath may trigger a crash which was fixed by
0e75f3272d.
However, this commit does not make it more likely to happen, and this
patch was backported into arch (at least).

In the future, we may be able to use <enter> on qtwebkit with js,
without triggering this crash
2018-06-09 15:42:44 -07:00
Florian Bruhin
292d92d02e Update changelog 2018-06-09 23:01:29 +02:00
Florian Bruhin
c9fddfe458 Fix lint 2018-06-09 23:00:57 +02:00
Florian Bruhin
7fdeeb25b7 Merge remote-tracking branch 'origin/pr/3793' 2018-06-09 22:59:52 +02:00
Florian Bruhin
6022fd2b26 Document how to run a qutebrowser development build
Fixes #3795
Supersedes #3798
2018-06-09 21:56:56 +02:00
Florian Bruhin
1d19a2ebbb Fix typo 2018-06-09 21:54:32 +02:00
Florian Bruhin
7d11c3aa43 Update changelog 2018-06-09 21:32:09 +02:00
Florian Bruhin
96739d0013 Merge remote-tracking branch 'origin/pr/3864' 2018-06-09 21:29:15 +02:00
Florian Bruhin
dcb1191f93 Remove the deprecated :tab-detach 2018-06-09 21:25:09 +02:00
Florian Bruhin
cf3c200fcb Update changelog 2018-06-09 20:50:35 +02:00
Florian Bruhin
a663c857cc Merge remote-tracking branch 'origin/pr/3926' 2018-06-09 20:50:17 +02:00
Slackhead
c3771ca2b2 double quoted and className corrected 2018-06-09 19:45:48 +01:00
Florian Bruhin
c73cb7efad Update changelog 2018-06-09 20:40:57 +02:00
Florian Bruhin
22f05e3ca5 Merge remote-tracking branch 'origin/pr/3929' 2018-06-09 20:40:32 +02:00
Florian Bruhin
9161aa6e17 Merge remote-tracking branch 'origin/pr/3930' 2018-06-09 20:36:28 +02:00
Florian Bruhin
313cc42d8a Link to code.qt.io instead of GitHub for _chromium_version 2018-06-09 20:26:12 +02:00
Florian Bruhin
d486a056e7 Update docs 2018-06-09 20:20:46 +02:00
Florian Bruhin
7ce7176475 Fix lint 2018-06-09 20:20:08 +02:00
Florian Bruhin
1b48b67443 Rename reporter.escape_quits to input.escape_quits_reporter 2018-06-09 20:19:32 +02:00
Florian Bruhin
486a92a710 Merge remote-tracking branch 'origin/pr/3944' 2018-06-09 20:18:25 +02:00
Florian Bruhin
ec88c15390 Fix waiting for initial focus object with Qt 5.11 workarounds
This was broken in d32d541ac0 because now
apparently PyQt knows it's a QQuickWidget.
2018-06-09 20:05:26 +02:00
Florian Bruhin
40b1be6925 Fix test_disable_gpu with QtWebKit 2018-06-09 19:59:11 +02:00
Florian Bruhin
41b5fca9b7 Add additional test 2018-06-09 19:57:22 +02:00
Florian Bruhin
b0325e17b7 Add a YamlConfig._migrate_bool helper 2018-06-09 19:57:11 +02:00
Florian Bruhin
7949335a2b Remove unused import 2018-06-09 19:51:05 +02:00
Florian Bruhin
6e954a1596 Allow to force software rendering with wayland on Qt 5.11
Closes #2932 (hopefully for the last time)
2018-06-09 16:50:48 +02:00
Florian Bruhin
88f2873a79 Allow more values for the qt.force_software_rendering setting 2018-06-09 16:21:10 +02:00
Jay Kamat
5d38d28fee Fix incorrect usage of tab_id 2018-06-08 20:59:25 -07:00
Florian Bruhin
d32d541ac0 Further simplify getting focusProxy children 2018-06-08 17:10:47 +02:00
Florian Bruhin
cc497bf2ea Improve RWHV typecheck for focusProxy 2018-06-08 15:13:48 +02:00
Florian Bruhin
9725d9ce33 Exclude QMenu when trying to find the missing focusProxy 2018-06-08 14:55:34 +02:00
Florian Bruhin
1531961aeb Show children in focusProxy workaround 2018-06-08 14:40:59 +02:00
Florian Bruhin
4e6fede40d Update changelog 2018-06-08 08:56:17 +02:00
Florian Bruhin
91b4106dcf Fix check for reloads on Qt < 5.11
This was broken in 6ccd69dad2
2018-06-08 08:53:10 +02:00
Florian Bruhin
a13618fe2a Merge branch 'pyup-scheduled-update-2018-06-04' 2018-06-07 22:57:48 +02:00
Florian Bruhin
8bf7cb539a Mark modal window test as flaky 2018-06-07 22:57:16 +02:00
Florian Bruhin
d96a98a058 Mention other colorschemes as well 2018-06-07 20:31:27 +02:00
Florian Bruhin
5e6002fcdc Merge remote-tracking branch 'origin/pr/3963' 2018-06-07 20:25:24 +02:00
Florian Bruhin
11b957f24b tests: Adjust getting markers for pytest 3.6 2018-06-07 19:29:01 +02:00
Florian Bruhin
4614ad5063 Remove unused import 2018-06-07 18:01:29 +02:00
Florian Bruhin
6e48648033 Update changelog 2018-06-07 17:52:46 +02:00
Florian Bruhin
d541634a7c Avoid hacks for changing per-domain settings on Qt 5.11.1 2018-06-07 17:51:21 +02:00
Florian Bruhin
b1506274c5 Implement a better workaround for chrome-error:// URLs
It looks like chrome-error://chromewebdata/ triggers another invalid scheme
load which is why the endless loop happens. When we install a custom scheme
handler for chrome-error:// we can at least show an error page.
2018-06-07 16:03:25 +02:00
Florian Bruhin
c6a1d729f4 Update the list of chrome:// URLs
See #3010
2018-06-07 15:42:58 +02:00
Florian Bruhin
596041c40e Go back to using an invalid scheme for invalid_link.html
Otherwise, this breaks the tests on Qt 5.10
2018-06-07 15:30:28 +02:00
Florian Bruhin
9b795c5257 Update changelog 2018-06-07 14:35:09 +02:00
Florian Bruhin
89f4333df1 Make sure external schemes are clickable via hints
This issue was probably introduced in 545539f28d
- with JavaScript, we can't "click" on an external link.

There might be a better solution using
QWebEngineSettings::setUnknownUrlSchemePolicy(QWebEngineSettings::AllowAllUnknownUrlSchemes)
temporarily when using hints with PyQt 5.11.

Fixes #2833
2018-06-07 14:33:49 +02:00
Florian Bruhin
59e5a2a6f1 Update changelog
See #3661
2018-06-07 13:49:39 +02:00
Florian Bruhin
0c0d204fd4 Add a workaround for chrome-error:// loops on Qt 5.11
See #3661
2018-06-07 13:49:33 +02:00
Florian Bruhin
999513d5d8 Skip invalid link tests on Qt 5.11
Qt 5.11 just loads about:blank and doesn't let us catch this in
acceptNavigationRequest, but the same happens in Chromium as well.

See #3661
2018-06-07 13:49:28 +02:00
Florian Bruhin
d059197bc9 Use a valid scheme in invalid_link.html
This is to avoid triggering QTBUG-63378 which fails differently with a custom
scheme.

See #3661
2018-06-07 13:49:22 +02:00
Florian Bruhin
0e9159e8e8 Revert "Fix Qt 5.11 issues with clicking invalid links"
This reverts commit 1956590df84a72c7f9a516e805d01529291fccf8.

Turns out the actual issue wasn't due to *invalid* links - it's with links
which have an unknown scheme.

There's still a change in behavior between Qt 5.10 and 5.11 though: Invalid
links are apparently not passed to acceptNavigationRequest (sometimes?) so we
don't show an error message. Instead, we just load about:blank. However,
Chromium does that too and we can't handle a real click easily, so let's just
ignore that one.

See #3661
2018-06-07 13:49:13 +02:00
Florian Bruhin
3d53d0d2c5 Fix Qt 5.11 issues with clicking invalid links
See #3661
2018-06-07 13:48:04 +02:00
Florian Bruhin
eb6478dd3e Reenable Qt 5.11 tests on Travis
Fixes #3661
2018-06-07 13:48:04 +02:00
Florian Bruhin
8cc3804119 Don't run test with failed download on Qt 5.11
Looks like we can't use an <a> tag with download-attribute to trigger a failed
download in the test on Qt 5.11...

See #2298, #3661
2018-06-06 21:12:23 +02:00
Florian Bruhin
513591348c Update changelog 2018-06-06 20:26:12 +02:00
Florian Bruhin
456fdc55cc Only set PseudoLayout with Qt 5.11 2018-06-06 20:26:12 +02:00
Florian Bruhin
7e31897dcc Fix lint 2018-06-06 20:26:12 +02:00
Florian Bruhin
5147fc832c Handle resizing via PseudoLayout
This fixes the scenario where we just get a grey view when opening a link in a
tab from DuckDuckGo.
2018-06-06 20:26:12 +02:00
Florian Bruhin
ec6c5ebb69 Try harder to get the RenderWidgetHostViewQt 2018-06-06 20:26:12 +02:00
Florian Bruhin
34d054e8a5 Revert "Use WrapperLayout instead of PseudoLayout"
This reverts commit 6cc920472ee4170b257a0b588687b175162e83df.

Since self._widget can go stale in the layout, we need to somehow solve this differently...
2018-06-06 20:26:12 +02:00
Florian Bruhin
e0213e7447 Use WrapperLayout instead of PseudoLayout 2018-06-06 20:26:12 +02:00
Florian Bruhin
cee88cd7ca Initial proof of concept for pseudo layout
Fixes #3920 - hopefully properly this time...
2018-06-06 20:26:12 +02:00
Florian Bruhin
5635639ed4 view-in-mpv: Remove --yes-playlist argument for youtube-dl
Reverts #3057
Fixes #3977
2018-06-06 09:59:03 +02:00
Florian Bruhin
982a42d453 Unbind Ctrl-Shift-Tab by default 2018-06-05 11:33:34 +02:00
pyup-bot
dc203d46de Update pytest-rerunfailures from 4.0 to 4.1 2018-06-04 19:00:21 +02:00
pyup-bot
62e86e1482 Update pytest-qt from 2.3.2 to 2.4.0 2018-06-04 19:00:19 +02:00
pyup-bot
9f9a5d576b Update pytest from 3.5.1 to 3.6.0 2018-06-04 19:00:18 +02:00
pyup-bot
46329b0f03 Update parse from 1.8.2 to 1.8.4 2018-06-04 19:00:16 +02:00
pyup-bot
454d026fdc Update more-itertools from 4.1.0 to 4.2.0 2018-06-04 19:00:14 +02:00
pyup-bot
e30e08a04e Update pyroma from 2.3 to 2.3.1 2018-06-04 19:00:13 +02:00
Slackhead
7858bb97d1 Change default bg colour to #000 2018-05-31 06:33:48 +01:00
Slackhead
8c1080de97 Change filter value to 85% to really darken it 2018-05-31 05:58:05 +01:00
Slackhead
d37b2713cf Change min-width to 0.2em 2018-05-31 05:45:43 +01:00
Slackhead
41a092cd80 Tweak style/animation 2018-05-31 04:50:41 +01:00
Slackhead
0fcbc209bb Add animation stopper/starter 2018-05-31 04:50:41 +01:00
Slackhead
1578a4836c Add fallback colours if inherit fails 2018-05-31 04:50:41 +01:00
Slackhead
f34d1b6ce1 Remove inherit styles 2018-05-31 04:50:41 +01:00
Slackhead
4cc2b919fa Change caret style 2018-05-31 04:50:41 +01:00
Florian Bruhin
5e99da5459 Remove broken shields.io badges
[ci skip]
2018-05-30 11:59:34 +02:00
theova
5543e75abd Add base16-qutebrowser 2018-05-29 20:00:49 +02:00
Florian Bruhin
6fc3546923 travis: Allow Archlinux to fail for now
See #3661
2018-05-29 13:19:05 +02:00
Florian Bruhin
52c44d3da6 setup.py: Set long_description_content_type
Otherwise, Warehouse (new PyPI) refuses the upload...
2018-05-29 11:23:10 +02:00
Florian Bruhin
b3c95c1668 Release v1.3.1
(cherry picked from commit ac29c579ff)
2018-05-29 11:15:15 +02:00
Florian Bruhin
f78410ef2b Update changelog for v1.3.1 2018-05-29 11:09:06 +02:00
Jay Kamat
28fce9a7cb Add support for opening background tabs in 5.11
Adding more workarounds to 442bdd4a4f, *sigh*
2018-05-26 13:52:57 -07:00
Jay Kamat
3392d82f50 Merge branch 'master' of https://github.com/qutebrowser/qutebrowser into jay/tab-bg-focus 2018-05-26 13:52:12 -07:00
Jay Kamat
c33a887b2d Add support for following tab selected elements to :follow-selected 2018-05-25 12:39:36 -07:00
Jay Kamat
cd56b97e7d Add an option to disable escape in the report dialog 2018-05-24 11:50:27 -07:00
Florian Bruhin
12e0edbcd0 Fix lint 2018-05-24 08:47:31 +02:00
Jay Kamat
40e391e199 Prevent closing :report dialog when pressing <escape> 2018-05-23 21:25:43 -07:00
Florian Bruhin
17cfb0d39c Add some more logging for #3920 2018-05-23 21:31:18 +02:00
Florian Bruhin
7162f15348 Use functools instead of a lambda for QTimer
It reads nicer, and this is also speculative fix for #3896 as PyQt5 is
hopefully better at disconnecting partial-objects from dead objects than it is
with lambdas.
2018-05-23 09:44:44 +02:00
Jay Kamat
74ea696a5c Merge branch 'master' of https://github.com/qutebrowser/qutebrowser into jay/tab-bg-focus 2018-05-22 23:17:52 -07:00
Jay Kamat
2f76ef1e53 Revert "Only apply workaround for QTBUG-68076 on non-background tabs"
This reverts commit 77c8575a88.
2018-05-22 23:17:43 -07:00
Florian Bruhin
26e37739e2 Fix typo 2018-05-22 23:07:31 +02:00
Florian Bruhin
e895ee8bdc Update changelog 2018-05-22 12:28:55 +02:00
Florian Bruhin
29ad252278 Handle ² keypress correctly
Turns out str.isdigit() also handles ² as a digit, but int('²') causes a
ValueError.

Here we use `string.digits` instead, which is '0123456789'.

Fixes #3743
2018-05-22 12:25:45 +02:00
Florian Bruhin
db1287cb82 Update changelog 2018-05-22 09:40:54 +02:00
Florian Bruhin
71ad8bdb47 Properly work around Qt 5.11 keyboard focus issues
Please let this be the last attempt... :D

Fixes #3939
Supersedes #3921
Reverts ae295a7f65
See #3661

This should not regress #3872. Might affect #3834 in some way.
2018-05-22 09:36:14 +02:00
Florian Bruhin
bc37af7c95 Document QtWebEngine internals
[ci skip]
2018-05-22 07:42:23 +02:00
Jay Kamat
3bf02aa62f Clarify install command for Ubuntu 17.04 2018-05-21 17:29:23 -07:00
Florian Bruhin
ae06c2cdb7 Merge pull request #3938 from qutebrowser/pyup-scheduled-update-2018-05-21
Scheduled weekly dependency update for week 20
2018-05-21 21:31:03 +02:00
Florian Bruhin
89f019b710 Remove unneeded "except ... as e:" assignments 2018-05-21 20:03:18 +02:00
pyup-bot
228c86c799 Update virtualenv from 15.2.0 to 16.0.0 2018-05-21 18:53:26 +02:00
pyup-bot
c551e29f4c Update pytest-qt from 2.3.1 to 2.3.2 2018-05-21 18:53:25 +02:00
pyup-bot
7596dbdd43 Update pytest-instafail from 0.3.0 to 0.4.0 2018-05-21 18:53:23 +02:00
pyup-bot
616371a3c1 Update hypothesis from 3.56.9 to 3.57.0 2018-05-21 18:53:22 +02:00
pyup-bot
725a133b9a Update cheroot from 6.2.4 to 6.3.1 2018-05-21 18:53:20 +02:00
pyup-bot
f60c42b7fb Update pylint from 1.8.4 to 1.9.1 2018-05-21 18:53:19 +02:00
pyup-bot
9dc9ec3435 Update astroid from 1.6.3 to 1.6.4 2018-05-21 18:53:17 +02:00
pyup-bot
e7a779ef90 Update setuptools from 39.1.0 to 39.2.0 2018-05-21 18:53:16 +02:00
pyup-bot
5ec3f1949a Update pyflakes from 1.6.0 to 2.0.0 2018-05-21 18:53:14 +02:00
pyup-bot
5df4128e8d Update pep8-naming from 0.6.1 to 0.7.0 2018-05-21 18:53:13 +02:00
Jimmy
47446baf29 s/obj/target/
Would a bit of consistency in variable names be too much to ask?
2018-05-21 21:23:38 +12:00
Jimmy
749b29e599 tests: Don't pretend to be using webkit if unavailable
Since `objects.backend` was being set to usertypes.Backend.QtWebKit by
default some feature detection code was calling
`version.qWebKitVersion()` which was failing because the import of
`PyQt5.QtWebKit` was failing in version.

This should not change behavior where both backends are available on if
any tests fail because they are expecting their environment to say they
are on webkit when they either aren't actually using any webkit features
or all mocked webkit features then they should probably be changed to
patch `objects.backend` or not depend on it.
2018-05-21 20:59:27 +12:00
Wayne Cheng
9a85796ac3 CR changes 2018-05-20 20:30:24 -04:00
Florian Bruhin
a31542269a Remove old files from MANIFEST.in 2018-05-20 16:12:33 +02:00
Wayne Cheng
36ddf3a328 Code review changes 2018-05-20 05:35:48 -04:00
Jimmy
d162e01422 Greasemonkey: remove extra window scope IIFE
Also change the block scoped `window` declaration to be const because it
seemed like a good idea. The real window seems to just silently ignore
attempts to overwrite it.
2018-05-20 18:42:40 +12:00
Jimmy
6573a4d616 Tell pylint to shut its fat mouth.
I just want to return something I can refer to the attributes of via dot
syntax without having to pointlessly write the names both when I declare
the data class and when I assign the variables.

Such a stupid warning.
2018-05-20 18:42:40 +12:00
Jimmy
b0d1a137da Greasemonkey: Don't attempt scope isolation on webkit
Since the JSCore used by WebKit 602.1 doesn't fully support Proxy and I
can't think of a way to provide isolation otherwise just revert to the
old behaviour in that case. I am checking for the specific WebKit
version because I'm pretty sure that version just happened to be
released when Proxy support was only partially done, any later release
will presumably have a newer JSCore where it works.

There I changed the indentation of a block in the jinja template which
will have inflated the diff.

I added mocking of `objects.backend` to the `webview` and
`webenginewebview` fixtures, I am pretty sure they are mutually
exclusive so don't expect any issues from that.

Because of the feature detection being at template compile time I had to
tweak the test setup to be done via a fixture instead of the setupClass
functionality that I was using before.
2018-05-20 18:42:40 +12:00
Jimmy
13249329f7 Greasemonkey: skip window scoping test on webkit
The implementation of Proxy in JSCore used by current QtWebkit (webkit
602.1) doesn't support the `set()` handler for whatever reason. So
instead of testing for a specific behaviour that we can't ensure on that
version let's just skip the tests and handle user complaints with
sympathy.
2018-05-20 18:42:40 +12:00
Jimmy
19242b6cb2 Greasemonkey: fix scoping function decleration.
Apparently making a function like `function foo() {};` in block scope is
illegal. It should be named via assignment.

Switched to an IIFE anyway because it doesn't need a name.
2018-05-20 18:42:40 +12:00
Jimmy
c7a9792b67 Greasemonkey: Add test for window scoping refinements.
Adds a test to codify what I think greasemonkey scripts expect from
their scope chains. Particularly that they can:

1. access the global `window` object
2. access all of the attributes of the global window object as global
   objects themselves
3. see any changes the page made to the global scope
4. write to attributes of `window` and have those attributes, and changes
   to existing attributes, accessable via global scope
5. do number 4 without breaking the pages expectations, that is what
   `unsafeWindow` is for

There are some other points about greasemonkey scripts' environment that
I believe to be true but am not testing in this change:

* changes a page makes to `window` _after_ a greasemonkey script is
  injected will still be visible to the script if it cares to check and
  it hasn't already shadowed them
* said changes will not overwrite changes that the greasemonkey script
  has made.
2018-05-20 18:42:40 +12:00
Jimmy
23bfe6daa2 Greasemonkey: fix window proxy for new attributes
Previously scripts were failing to find attributes that they assigned to
window and then tried to use from the global scope. Eg

    window.newthing = function() {...};
    newthing(...);  // newthing is not defined error

This wasn't the case for things that already existed in the global scope
and were just being overwritten.

This change just overrides the `Proxy.has()` function which seems to fix
it. Probably the `while` implementation was failing to pick up new
attributes because of the lack.

I also tweaked some comments and variable names and const-ness to be a
little more production ready.
2018-05-20 18:42:40 +12:00
Jimmy
ab50ad735b Greasemonkey: isolates scripts' writes to window
Since the global namespace of javascript in the browser is accessible
there where issues with scripts clobbering things that the page expected
to be attributes of window pages clobbering things that userscripts
saved to `window`. The latter was occuring with OneeChan. OneeChan was
setting `window.$` to a function that took a css selector and the 4chan
catalog script was setting `window.$` to some object. Since OneeChan was
set to run at document-start this broke OneeChan, switching it to
document-end broke scripts on 4chan.

I used OneeChan and 4chan-X on 4chan as the test case for this and
TamperMonkey as a guide for what is the correct way to handle scoping. I
didn't manage to pick apart just how TamperMonkey does what it does (I
think it might just be the environment that Chrome provides extensions
actually) but I got close to the same behaviour.

TamperMonkey provides a `window` object that appears to be what the
global window looked like before the webpage modified it. The global
scope though does have the pages modifications accessible. If the script
assigns something to an attribute `window` it can see that attribute in
the global scope. This implementation differs from that one in that, to
the scipt, `window` and the global scope always look the same, and that
is the same as the global scope looks in the environment provided by
TamperMonkey.

I am using the ES6 `Proxy` feature to allow the `window` object to look
like the actual (unsafe) one while allowing writing to it that doesn't
clobber the unsafe one. I am then using the ES4 `with` function to make
attributes of that window (proxy) object visible in the scope chain.
There may be other ways to do this without using `with` by using nested
functions and setting `this` creatively. There are notes around
alleging `with` to be various states of uncool[1].

I also ran into an issue where a userscript calling
`window.addEventListener(...)` would fail with `TypeError: Illegal
Execution` which is apparently due to `this` not being set correctly. I
looked at the functions which threw that error and those that didn't and
am using whether they have a `prototype` attribute or not to tell
whether I need to bind them with `window` as `this`. I am not sure how
correct that is but it worked for all the cases I ran into.

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with
2018-05-20 18:42:40 +12:00
Wayne Cheng
7130f863cb Create lastpass implementation based off qute-pass code 2018-05-19 15:53:08 -04:00
Sebastian Heinlein
547edb3c4e appdata: install the metainfo using the Makefile
Furthermore add some information about the latest releases
and remove a few trailing whitespaces.
2018-05-19 13:57:26 +02:00
Jay Kamat
77c8575a88 Only apply workaround for QTBUG-68076 on non-background tabs
Previously, we would focus webviews even if they were in the
background to work around https://bugreports.qt.io/browse/QTBUG-68076.
This adjusts that to only occur when needed.
2018-05-18 10:46:53 -07:00
Joakim Särehag
4966a70709 Merge from main repo master, resolved conflict in webenginetab.py 2018-05-18 13:59:25 +02:00
Florian Bruhin
046c0a7ea2 Merge remote-tracking branch 'origin/pr/3914' 2018-05-17 17:53:47 +02:00
Florian Bruhin
d208c48532 Update changelog 2018-05-17 17:53:15 +02:00
Florian Bruhin
dacca0d2ed Remove double settings 2018-05-17 15:54:26 +02:00
Florian Bruhin
6ccd69dad2 Fix reload for JavaScript support on Qt 5.11 2018-05-17 15:54:17 +02:00
Florian Bruhin
d8569a36f9 Update changelog 2018-05-17 14:24:29 +02:00
Florian Bruhin
44d26f77a5 Add workaround for the "split page" Qt bug (QTBUG-68224)
Fixes #3920
2018-05-17 14:21:14 +02:00
Florian Bruhin
6eadff4a10 Add Python executable to :version 2018-05-14 22:13:15 +02:00
pyup-bot
6784356d9d Update hypothesis from 3.56.5 to 3.56.9 2018-05-14 18:48:20 +02:00
pyup-bot
b0105b0157 Update python-dateutil from 2.7.2 to 2.7.3 2018-05-14 18:48:18 +02:00
pyup-bot
553a91c012 Update python-dateutil from 2.7.2 to 2.7.3 2018-05-14 18:48:17 +02:00
pyup-bot
b0f83615b6 Update wheel from 0.31.0 to 0.31.1 2018-05-14 18:48:15 +02:00
pyup-bot
0c674c7110 Update flake8-builtins from 1.3.1 to 1.4.1 2018-05-14 18:48:14 +02:00
Jay Kamat
71d55e9213 Refocus command prompt after a new tab is opened when in command mode 2018-05-11 08:49:13 -07:00
Jay Kamat
95093b82c9 Refocus webview after spawning a background tab 2018-05-10 10:15:01 -07:00
Jay Kamat
538b4fafdb Revert "Restore focus to webview after clicking tab-bg"
This reverts commit 262cea8e75.
2018-05-10 09:30:28 -07:00
Jay Kamat
563afb277d Fix style issues 2018-05-10 08:04:48 -07:00
Jay Kamat
4bb048cb6a Fix mocked tests 2018-05-09 21:41:59 -07:00
Jay Kamat
ae04da7b78 Upgrade indicator to show recentlyAudible status 2018-05-09 18:57:53 -07:00
Jay Kamat
0642695c7d Update docs 2018-05-09 18:33:52 -07:00
Jay Kamat
7a297e2e3f Add support for muting tabs 2018-05-09 18:28:05 -07:00
Jay Kamat
bc9f178a08 Add test for tab-bg focus 2018-05-09 15:34:01 -07:00
Jay Kamat
262cea8e75 Restore focus to webview after clicking tab-bg 2018-05-09 11:05:07 -07:00
Joakim Särehag
dc7f39514d pull request #3864, reverted webview changes, fixed regression 2018-05-09 18:28:52 +02:00
Joakim Särehag
72c2962908 removed trailing whitespace 2018-05-09 15:52:22 +02:00
Joakim Särehag
cbf95d76bd Merge branch 'master' of https://github.com/qutebrowser/qutebrowser 2018-05-09 14:37:27 +02:00
Joakim Särehag
0e756d2f68 changes to pull request 3864, window.print for webengine 2018-05-09 14:36:03 +02:00
Florian Bruhin
48c44e1b4d Fix and update docs 2018-05-08 15:22:49 +02:00
Florian Bruhin
20efaeff19 Improve configuration docs 2018-05-08 14:57:56 +02:00
Florian Bruhin
d194a8ddb0 Merge pull request #3898 from qutebrowser/pyup-scheduled-update-2018-05-07
Scheduled weekly dependency update for week 18
2018-05-08 14:21:23 +02:00
Florian Bruhin
c8d539a4c6 Update changelog 2018-05-08 11:46:07 +02:00
Florian Bruhin
8531f89ca3 Merge remote-tracking branch 'origin/pr/3789' 2018-05-08 11:45:20 +02:00
Florian Bruhin
4fee9b9637 Update changelog 2018-05-08 11:44:06 +02:00
Florian Bruhin
89a1c43b4c Merge remote-tracking branch 'origin/pr/3790' 2018-05-08 11:43:30 +02:00
Florian Bruhin
a39858c436 Update changelog 2018-05-08 11:41:14 +02:00
Florian Bruhin
b718c8b43a Merge remote-tracking branch 'origin/pr/3899' 2018-05-08 11:40:43 +02:00
Florian Bruhin
b28bfbe8f5 Update docs 2018-05-08 11:39:35 +02:00
Florian Bruhin
70c7a42a63 Merge remote-tracking branch 'origin/pr/3900' 2018-05-08 11:37:52 +02:00
Florian Bruhin
648b1219a2 Filter flake8-builtins 1.4.0
See https://github.com/gforcada/flake8-builtins/issues/36
2018-05-08 11:34:17 +02:00
Florian Bruhin
d005088b6b Add missing fake_args fixture 2018-05-08 11:26:45 +02:00
Jay Kamat
906da44d70 Simplify visited link clearing by looping over connections 2018-05-07 14:10:20 -07:00
Jay Kamat
ca48f9f100 Clear url from visitedLinks if a single url is deleted 2018-05-07 13:48:38 -07:00
Jay Kamat
d0b2360745 Clear visited links db when running :history-clear on webengine 2018-05-07 13:35:41 -07:00
Pol Van Aubel
f1b481dcc7 Short options for --debug and --debug-flags 2018-05-07 22:35:32 +02:00
Pol Van Aubel
431a52da6c Short options for --basedir and --temp-basedir 2018-05-07 22:10:08 +02:00
pyup-bot
b109fc569f Update pytest-mock from 1.9.0 to 1.10.0 2018-05-07 18:39:22 +02:00
pyup-bot
fcd98443f9 Update flask from 1.0.1 to 1.0.2 2018-05-07 18:39:21 +02:00
pyup-bot
e455d8c13a Update pep8-naming from 0.5.0 to 0.6.1 2018-05-07 18:39:19 +02:00
pyup-bot
d4e5a2eb7f Update flake8-builtins from 1.3.1 to 1.4.0 2018-05-07 18:39:18 +02:00
pyup-bot
a4424f2863 Update attrs from 17.4.0 to 18.1.0 2018-05-07 18:39:17 +02:00
pyup-bot
9e33f1a275 Update attrs from 17.4.0 to 18.1.0 2018-05-07 18:39:15 +02:00
pyup-bot
470c392ee0 Update attrs from 17.4.0 to 18.1.0 2018-05-07 18:39:13 +02:00
Florian Bruhin
b9fc068af5 Add a log-requests debug-flag 2018-05-07 10:23:56 +02:00
Jay Kamat
2663feea2f Add :hint inputs --first as a default binding to gi 2018-05-03 11:37:31 -04:00
bitraid
1e5b325ea5 Move file_version_info.txt to misc/ and add it to .gitignore 2018-05-03 15:50:18 +03:00
Joakim Särehag
199eac2db8 window.print() support for WebEngine 2018-04-24 14:05:53 +02:00
bitraid
3f1041eb0a gen_versioninfo.py: remove unused import, rename most variables and reformat 2018-04-22 20:35:34 +03:00
bitraid
8c16d81d89 Pyinstaller: add VERSIONINFO resource to Windows executables. 2018-04-20 17:48:51 +03:00
rr-
537aa22d64 Change clipboard mocking 2018-04-18 11:00:05 +02:00
rr-
30d3612a17 Add test for rapid yanking 2018-04-18 10:59:54 +02:00
rr-
563d9bd097 Fix crash in non-rapid link yanking 2018-04-16 08:52:06 +02:00
rr-
d705e600e2 Simplify num in hinting to first_run 2018-04-13 11:03:39 +02:00
rr-
46e4aeb3e9 Don't hardcode newline 2018-04-10 08:45:23 +02:00
rr-
945bc44550 Support rapid hinting mode in yanking 2018-04-09 17:20:35 +02:00
Ryan Roden-Corrent
bca07eebba Unit-test configmodel arg filtering.
Test that values the user has already input are omitted from the
suggestions. This brings configmodel coverage back to 100%.
2018-03-31 18:55:54 -04:00
Ryan Roden-Corrent
707fc1176d Regen completion if args change.
Allow completion functions to react dynamically to args as the user
inputs them. This allows config-cycle to filter out values that were
already provided.

Args provided after the maxsplit do not cause the completion to regen.
For example, successive words typed after `:open` just set the filter
pattern and do not spuriously regenerate the completion model.
2018-03-31 11:45:50 -04:00
Jay Kamat
cb8a75577e Add tests for hinting with --first 2018-03-30 15:03:08 -04:00
Jay Kamat
3d5d679561 Add basic implementation for clicking first hinted element 2018-03-30 15:03:08 -04:00
Ryan Roden-Corrent
f237a87ad0 Completion for varargs.
When a command has positional varargs, keep offering the configured
completion for each successive argument.

Right now this only influences `config-cycle`.

Previously, `config-cycle <option> ` would offer a value completion for
only the first argument after the option. Now it will keep offering
value completion for each successive argument.

This will be useful for passing multiple tags to the new bookmark
commands that will be added for #882.
2018-03-25 21:59:30 -04:00
Marc Jauvin
b7159d780a Merge 'origin/master' into tab-input-mode 2018-03-16 14:28:36 -04:00
Marc Jauvin
c9f6cd507b address requested changes
- add INPUT_MODES & PROMPT_MODES constants in modeman
- use those in tabbedbrowser and modeman
- fix debug logs format to be more human readable
- fix associated tests for new debug logs
2018-03-13 23:31:48 -04:00
Markus Ongyerth
40364ce774 view-source pygments feedback pass 2018-03-13 12:40:51 +01:00
Marc Jauvin
7c2802e843 beautify code as requested 2018-03-07 11:46:14 -05:00
Marc Jauvin
5992688926 Save input modes when mode_on_change=='restore' 2018-03-07 11:43:17 -05:00
Markus Ongyerth
f7bcdfc818 Add --pygment argument to view-source
The --pygment argument allows to use the pygment version of view-source
over the qtwebengine internal one.
This version is slightly different in what's processed before the site
is generated, so some javascript created texts can be available.
2018-02-28 16:53:28 +01:00
Marc Jauvin
872cff2ae1 make sure tab is not None either, had a crash because of this 2018-02-16 14:03:11 -05:00
Marc Jauvin
620a966d1e add debug logs and adjust tests to use them 2018-02-14 09:58:23 -05:00
Marc Jauvin
b791dd3eb4 no restore while in prompt modes on tab change 2018-02-14 09:44:40 -05:00
Marc Jauvin
8a3049f09b make sure there IS a current widget before using it 2018-02-14 08:33:32 -05:00
Marc Jauvin
9b8a182a78 history-clear does nothing to help here 2018-02-13 17:03:01 -05:00
Marc Jauvin
f94e12008a fix the tests by clearing history 2018-02-13 16:23:56 -05:00
Marc Jauvin
e38df261cb skip this test for qt>=5.10 until the log problem gets resolved 2018-02-13 13:00:44 -05:00
Marc Jauvin
fbb78f1133 hook into modeman's entered and left signals
- save previous mode when a prompt is shown
- restore previous mode when finished prompting
2018-02-12 21:57:53 -05:00
Marc Jauvin
6214c38d7e add input_mode tests for tabs.mode_on_change 2018-02-12 18:11:32 -05:00
Marc Jauvin
d04fc29163 save input_mode when entering/leaving mode
instead of when changing tab
2018-02-12 18:10:22 -05:00
157 changed files with 3190 additions and 962 deletions

View File

@@ -5,15 +5,15 @@ cache:
build: off
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python36\python.exe
PYTHON: C:\Python36-x64\python.exe
matrix:
- TESTENV: py36-pyqt510
- TESTENV: py36-pyqt511
- TESTENV: pylint
install:
- '%PYTHON% -m pip install -U pip'
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
- 'set PATH=%PATH%;C:\Python36'
- 'set PATH=C:\Python36-x64;%PATH'
test_script:
- '%PYTHON% -m tox -e %TESTENV%'

View File

@@ -44,7 +44,7 @@ ignore =
min-version = 3.4.0
max-complexity = 12
per-file-ignores =
/tests/**/test_*.py : D100,D101,D401
/tests/**/*.py : D100,D101,D401
/tests/unit/browser/test_history.py : N806
/tests/helpers/fixtures.py : N806
/tests/unit/browser/webkit/http/test_content_disposition.py : D400

View File

@@ -1,3 +1,8 @@
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
because of exams coming up. Review of non-trivial pull requests will thus be
delayed until then. If you're reading this note after mid-September, please
open an issue.
- Before you start to work on something, please leave a comment on the relevant
issue (or open one). This makes sure there is no duplicate work done.

1
.gitignore vendored
View File

@@ -41,3 +41,4 @@ TODO
/scripts/testbrowser/cpp/webengine/testbrowser
/scripts/testbrowser/cpp/webengine/.qmake.stash
/scripts/dev/pylint_checkers/qute_pylint.egg-info
/misc/file_version_info.txt

View File

@@ -18,20 +18,19 @@ matrix:
python: 3.5
env: TESTENV=py35-pyqt571
- os: linux
env: TESTENV=py36-pyqt59-cov
env: TESTENV=py36-pyqt59
- os: linux
env: TESTENV=py36-pyqt510
# We need a newer Xvfb as a WORKAROUND for:
# https://bugreports.qt.io/browse/QTBUG-64928
- os: linux
env: TESTENV=py36-pyqt511-cov
# https://github.com/travis-ci/travis-ci/issues/9069
- os: linux
python: 3.7
sudo: required
addons:
apt:
sources:
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
packages:
- xvfb
dist: xenial
env: TESTENV=py37-pyqt511
- os: osx
env: TESTENV=py36 OSX=sierra
env: TESTENV=py37 OSX=sierra
osx_image: xcode9.2
language: generic
# https://github.com/qutebrowser/qutebrowser/issues/2013

View File

@@ -1,6 +1,5 @@
recursive-include qutebrowser *.py
recursive-include qutebrowser/img *.svg *.png
recursive-include qutebrowser/test *.py
recursive-include qutebrowser/javascript *.js
graft qutebrowser/html
graft qutebrowser/3rdparty
@@ -27,20 +26,18 @@ prune scripts/dev
prune scripts/testbrowser/cpp
prune .github
exclude scripts/asciidoc2html.py
exclude doc/notes
recursive-exclude doc *.asciidoc
include doc/qutebrowser.1.asciidoc
include doc/changelog.asciidoc
prune tests
prune qutebrowser/3rdparty
exclude pytest.ini
exclude qutebrowser.rcc
exclude qutebrowser/javascript/.eslintrc.yaml
exclude qutebrowser/javascript/.eslintignore
exclude doc/help
exclude .*
exclude misc/appveyor_install.py
exclude misc/qutebrowser.spec
exclude misc/qutebrowser.nsi
exclude misc/qutebrowser.rcc
global-exclude __pycache__ *.pyc *.pyo

View File

@@ -9,8 +9,6 @@ qutebrowser
// QUTE_WEB_HIDE
image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.*
image:https://img.shields.io/pypi/l/qutebrowser.svg?style=flat["license badge",link="https://github.com/qutebrowser/qutebrowser/blob/master/LICENSE"]
image:https://img.shields.io/pypi/v/qutebrowser.svg?style=flat["version badge",link="https://pypi.python.org/pypi/qutebrowser/"]
image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"]
image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"]
image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"]
@@ -99,7 +97,7 @@ Requirements
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules:
* http://qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
@@ -109,7 +107,7 @@ The following software and libraries are required to run qutebrowser:
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
supported
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
(5.10 recommended) for Python 3
(5.11.2 recommended) for Python 3
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]

View File

@@ -15,6 +15,183 @@ 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.5.0 (unreleased)
-------------------
Fixed
~~~~~
- Rare crash when an error occurs in downloads.
v1.4.0
------
Added
~~~~~
- Support for the bundled `sip` module in PyQt 5.11 and other changes in
Qt/PyQt 5.11.x.
- New `--debug-flag log-requests` to log requests to the debug log for
debugging.
- New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically
selects the first hint.
- New `input.escape_quits_reporter` setting which can be used to avoid
accidentally quitting the crash reporter when pressing escape.
- New `qute-lastpass` userscript which uses the LastPass CLI to fill passwords.
- The Makefile now installs a `/usr/share/metainfo/qutebrowser.appdata.xml` file.
- QtWebEngine: Support for printing from webpages via `window.print`.
- QtWebEngine: Support for muting tabs:
* New `{audio}` field for `window.title_format` and `tabs.title.format` which
displays `[M]`/`[A]` for muted/recently audible tabs.
* New `:tab-mute` command (bound to `<Alt-m>`) to mute/unmute a tab.
- QtWebEngine: Support for `content.cookies.accept` with third-party cookies
blocked by default (requires Qt 5.11).
- QtWebEngine: New settings:
* Support for requesting persistent storage via
`navigator.webkitPersistentStorage.requestQuota` with a new
`content.persistent_storage` setting (requires Qt 5.11).
This setting also supports URL patterns.
* Support for registering custom protocol handlers via
`navigator.registerProtocolHandler` with a new
`content.register_protocol_handler` setting (requires Qt 5.11).
This setting also supports URL patterns.
* Support for WebRTC screen sharing with a new `content.desktop_capture`
setting (requires Qt 5.10).
This setting also supports URL patterns.
* New `content.autoplay` setting to enable/disable automatic video playback
(requires Qt 5.10).
* New `content.webrtc_public_interfaces_only` setting to only expose public
interfaces over WebRTC (requires Qt 5.9.2 or 5.11).
* New `content.canvas_reading` setting to disable reading from canvas
elements.
Changed
~~~~~~~
- The following settings now support URL patterns:
* `content.headers.do_not_track`
* `content.headers.custom`
* `content.headers.accept_language`
* `content.headers.user_agent`
* `content.ssl_strict`
* `content.geolocation`
* `content.notifications`
* `content.media_capture`
- The Windows/macOS releases now bundle Qt 5.11.1 which is based on
Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87.
- New short flags for commandline arguments: `-B` and `-T` for `--basedir` and
`--temp-basedir`; `-d` and `-D` for `--debug` and `--debug-flag`.
- Deleting history items via `:history-clear` or `:completion-item-del` now
also removes that URL from QtWebEngine's visited links.
- There's now completion for commands taking a variable count of arguments
(like `:config-cycle`).
- QtWebEngine: On Qt 5.11.1, no reloads are needed anymore when switching
between pages with changed settings (e.g. `content.javascript.enabled`).
- The `qt.force_software_rendering` setting changed from a boolean to taking
different values (`software-opengl`, `qt-quick` and `chromium`) for different
kinds of software rendering workarounds.
- On Qt 5.11, using wayland with QtWebEngine is now possible when using
software rendering.
- GreaseMonkey scripts now get their own global scope (based on the page's
one), which allows scripts like OneeChan to work.
- Rapid hinting is now supported with the `yank` and `yank-primary` targets,
copying newline-separated links.
- QtWebEngine: On Qt 5.11, the developer tools (inspector) can now be used
securely and without requiring the `--enable-webengine-inspector` option.
- The `<Enter>` key (`:follow-selected`) now follows the currently focused
element if there's no selection.
- The `--logfilter` argument now can be prepended with an exclamation mark
(e.g. `--logfilter '!init,destroy'`) to invert the filter.
- `:view-source` now has a `--pygments` flag which uses the "old" way of
rendering sources even with QtWebEngine.
- Improved error messages when a setting needs a newer Qt version.
- QtWebEngine: Various improvements to make the cursor more visible in caret
browsing.
- When a prompt is opened in insert/passthrough mode, the mode is restored
after closing the prompt.
- On Qt 5.10 or newer, dictionaries are now read from the qutebrowser data
directory (e.g. `~/.local/share/qutebrowser`) instead of `/usr/share/qt`.
Existing dictionaries are copied over.
- If an error while parsing `~/.netrc` occurs, the cause of the error is now
logged.
- On Qt 5.9 or newer, certificate errors now show Chromium's detailed error
page.
- Greasemonkey scripts now support a "@qute-js-world" tag to run them in a
different JavaScript context.
Fixed
~~~~~
- Various subtle keyboard focus issues.
- The security fix in v1.3.3 caused URLs with ampersands
(`www.example.com?one=1&two=2`) to send the wrong arguments when clicked on
the `qute://history` page.
- Crash when opening a PDF page with PDF.js enabled (on QtWebKit), but no
PDF.js installed.
- Crash when closing a tab shortly after opening it.
Removed
~~~~~~~
- No prebuilt binaries for 32-bit Windows are supplied anymore. This is due to
Qt removing QtWebEngine support for those upstream. It might be possible to
distribute 32-bit binaries again with Qt 5.12 in December, but that will only
happen if it turns out enough people actually need 32-bit support.
- `:tab-detach` which has been deprecated in v1.1.0 has been removed.
- The `content.developer_extras` setting got removed. On QtWebKit, developer
extras are now automatically enabled when opening the inspector.
v1.3.3
------
Security
~~~~~~~~
- An XSS vulnerability on the `qute://history` page allowed websites to inject
HTML into the page via a crafted title tag. This could allow them to steal
your browsing history. If you're currently unable to upgrade, avoid using
`:history`. A CVE request for this issue is pending, see
https://github.com/qutebrowser/qutebrowser/issues/4011[#4011] for updates.
Fixed
~~~~~
- Crash in a workaround for a Qt 5.11 bug in rare circumstances.
- Workaround for a Qt bug which preserves searches between page loads.
- In v1.3.2 a dependency on the `PyQt5.QtQuickWidgets` module was accidentally
introduced. Since that module isn't packaged everywhere, it's been removed
again.
v1.3.2
------
Fixed
~~~~~
- QtWebEngine: Improved workaround for a bug in Qt 5.11 where only the
top/bottom half of the window is used.
- QtWebEngine: Work around a bug in Qt 5.11 where an endless loading-loop is
triggered when clicking a link with an unknown scheme.
- QtWebEngine: When switching between pages with changed settings, less
unnecessary reloads are done now.
- QtWebEngine: It's now possible to open external links such as `magnet://` or
`mailto:` via hints.
v1.3.1
------
Fixed
~~~~~
- Work around a bug in Qt 5.11 where only the top/bottom half of the window is used.
This workaround is incomplete, but fixes the majority of the cases where this happens.
- Work around keyboard focus issues with Qt 5.11.
- Work around an issue in Qt 5.11 where e.g. activating JavaScript per-domain
needed a manual reload in some cases.
- Don't crash when a ² key is pressed (e.g. on AZERTY keyboards).
- Don't crash when a tab is opened and quickly closed again.
v1.3.0
------

View File

@@ -5,6 +5,11 @@ The Compiler <mail@qutebrowser.org>
:data-uri:
:toc:
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
because of exams coming up. Review of non-trivial pull requests will thus be
delayed until then. If you're reading this note after mid-September, please
open an issue.
I `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
This document contains guidelines for contributing to qutebrowser, as well as
@@ -85,6 +90,16 @@ git format-patch origin/master <1>
<1> Replace `master` by the branch your work was based on, e.g.,
`origin/develop`.
Running qutebrowser
-------------------
After link:install.asciidoc#tox[installing qutebrowser via tox], you can run
`.venv/bin/qutebrowser --debug --temp-basedir` to test your changes with debug
logging enabled and without affecting existing running instances.
Alternatively, you can install qutebrowser's dependencies system-wide and run
`python3 -m qutebrowser --debug --temp-basedir`.
Useful utilities
----------------
@@ -188,8 +203,8 @@ There are some useful functions for debugging in the `qutebrowser.utils.debug`
module.
When starting qutebrowser with the `--debug` flag, you also get useful debug
logs. You can add +--logfilter _category[,category,...]_+ to restrict logging
to the given categories.
logs. You can add +--logfilter _[!]category[,category,...]_+ to restrict
logging to the given categories.
With `--debug` there are also some additional +debug-_*_+ commands available,
for example `:debug-all-objects` and `:debug-all-widgets` which print a list of
@@ -566,6 +581,23 @@ can be useful for debugging:
- chrome://gpuclean/ (crashes the current renderer process!)
- chrome://ppapiflashcrash/
- chrome://ppapiflashhang/
- chrome://quota-internals/ (Qt 5.11)
- chrome://taskscheduler-internals/ (Qt 5.11)
- chrome://sandbox/ (Qt 5.11, Linux only)
QtWebEngine internals
~~~~~~~~~~~~~~~~~~~~~
This is mostly useful for qutebrowser maintainers to work around issues in Qt - if you don't understand it, don't worry, just ignore it.
The hierarchy of widgets when QtWebEngine is involved looks like this:
- qutebrowser has a `WebEngineTab` object, which is its abstraction over QtWebKit/QtWebEngine.
- The `WebEngineTab` has a `_widget` attribute, which is the https://doc.qt.io/qt-5/qwebengineview.html[QWebEngineView]
- That view has a https://doc.qt.io/qt-5/qwebenginepage.html[QWebEnginePage] for everything which doesn't require rendering.
- The view also has a layout with exactly one element (which also is its `focusProxy()`)
- That element is the http://code.qt.io/cgit/qt/qtwebengine.git/tree/src/webenginewidgets/render_widget_host_view_qt_delegate_widget.cpp[RenderWidgetHostViewQtDelegateWidget] (it inherits https://doc.qt.io/qt-5/qquickwidget.html[QQuickWidget]) - also often referred to as RWHV or RWHVQDW. It can be obtained via `sip.cast(tab._widget.focusProxy(), QQuickWidget)`.
- Calling `rootObject()` on that gives us the https://doc.qt.io/qt-5/qquickitem.html[QQuickItem] where Chromium renders into (?). With it, we can do things like `.setRotation(20)`.
Style conventions
-----------------
@@ -662,8 +694,6 @@ New PyQt release
~~~~~~~~~~~~~~~~
* See above.
* Install new PyQt in Windows VM (32- and 64-bit).
* Download new installer and update PyQt installer path in `ci_install.py`.
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
qutebrowser release
@@ -685,8 +715,8 @@ qutebrowser release
as closed.
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
* Windows: Run `git checkout v1.X.Y; py -3.6 scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
* On server:
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`

View File

@@ -111,6 +111,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|<<tab-focus,tab-focus>>|Select the tab given as argument/[count].
|<<tab-give,tab-give>>|Give the current tab to a new or existing window if win_id given.
|<<tab-move,tab-move>>|Move the current tab according to the argument and [count].
|<<tab-mute,tab-mute>>|Mute/Unmute the current/[count]th tab.
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|<<tab-pin,tab-pin>>|Pin/Unpin the current/[count]th tab.
@@ -530,7 +531,7 @@ Show help about a command or setting.
[[hint]]
=== hint
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*]
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*]
['group'] ['target'] ['args' ['args' ...]]+
Start hinting.
@@ -600,6 +601,7 @@ Start hinting.
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
`window`, `run`, `hover`, `userscript` and `spawn`.
* +*-f*+, +*--first*+: Click the first hinted element without prompting.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
@@ -1283,6 +1285,13 @@ If moving relatively: Offset. If moving absolutely: New position (default: 0). T
overrides the index argument, if given.
[[tab-mute]]
=== tab-mute
Mute/Unmute the current/[count]th tab.
==== count
The tab index to mute or unmute
[[tab-next]]
=== tab-next
Switch to the next tab, or switch [count] tabs forward.
@@ -1356,12 +1365,16 @@ Show version information.
[[view-source]]
=== view-source
Syntax: +:view-source [*--edit*]+
Syntax: +:view-source [*--edit*] [*--pygments*]+
Show the source of the current page in a new tab.
==== optional arguments
* +*-e*+, +*--edit*+: Edit the source in the editor instead of opening a tab.
* +*-p*+, +*--pygments*+: Use pygments to generate the view. This is always the case for QtWebKit. For QtWebEngine it may display
slightly different source.
Some JavaScript processing may be applied.
[[window-only]]
=== window-only

View File

@@ -3,44 +3,28 @@ Configuring qutebrowser
IMPORTANT: qutebrowser's configuration system was completely rewritten in
September 2017. This information is not applicable to older releases, and older
information elsewhere might be outdated. **If you had an old configuration
around and upgraded, this page will automatically open once**. To view it at a
later time, use the `:help` command.
information elsewhere might be outdated.
Migrating older configurations
------------------------------
qutebrowser's config files
--------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff/old[configdiff] page
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
did in your old configuration, compared to the old defaults.
qutebrowser releases before v1.0.0 had a `qutebrowser.conf` and `keys.conf`
file. Those are not used anymore since that release - see
<<migrating,"Migrating older configurations">> for information on how to
migrate to the new config.
Other changes in default settings:
When using `:set` and `:bind`, changes are saved to an `autoconfig.yml` file
automatically. If you don't want to have a config file which is curated by
hand, you can simply use those - see
<<autoconfig,"Configuring qutebrowser via the user interface">> for details.
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
if no text was entered yet.
With v1.0.x, they always navigate through command history instead of selecting
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
instead.
You can get back the old behavior by doing:
+
----
:bind -m command <Up> completion-item-focus prev
:bind -m command <Down> completion-item-focus next
----
+
or always navigate through command history with
+
----
:bind -m command <Up> command-history-prev
:bind -m command <Down> command-history-next
----
- The default for `completion.web_history_max_items` is now set to `-1`, showing
an unlimited number of items in the completion for `:open` as the new
sqlite-based completion is much faster. If the `:open` completion is too slow
on your machine, set an appropriate limit again.
For more advanced configuration, you can write a `config.py` file - see
<<configpy,"Configuring qutebrowser via config.py">>. As soon as a `config.py`
exists, the `autoconfig.yml` file **is not read anymore** by default. You need
to <<configpy-autoconfig,load it by hand>> if you want settings done via
`:set`/`:bind` to still persist.
[[autoconfig]]
Configuring qutebrowser via the user interface
----------------------------------------------
@@ -88,6 +72,7 @@ link:commands.html#config-clear[`:config-clear`] to reset the entire configurati
and link:commands.html#config-cycle[`:config-cycle`] to cycle a setting between
different values.
[[configpy]]
Configuring qutebrowser via config.py
-------------------------------------
@@ -239,6 +224,7 @@ config.bind(',v', 'spawn mpv {url}')
To suppress loading of any default keybindings, you can set
`c.bindings.default = {}`.
[[configpy-autoconfig]]
Loading `autoconfig.yml`
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -404,6 +390,12 @@ xresources = read_xresources('*')
c.colors.statusbar.normal.bg = xresources['*.background']
----
Pre-built colorschemes
^^^^^^^^^^^^^^^^^^^^^^
- A collection of https://github.com/chriskempson/base16[base16] color-schemes can be found in https://github.com/theova/base16-qutebrowser[base16-qutebrowser] and used with https://github.com/AuditeMarlow/base16-manager[base16-manager].
- Two implementations of the https://github.com/arcticicestudio/nord[Nord] colorscheme for qutebrowser exist: https://github.com/Linuus/nord-qutebrowser[Linuus], https://github.com/KnownAsDon/QuteBrowser-Nord-Theme[KnownAsDon]
Avoiding flake8 errors
^^^^^^^^^^^^^^^^^^^^^^
@@ -429,3 +421,38 @@ from qutebrowser.config.config import ConfigContainer # noqa: F401
config = config # type: ConfigAPI # noqa: F821 pylint: disable=E0602,C0103
c = c # type: ConfigContainer # noqa: F821 pylint: disable=E0602,C0103
----
[[migrating]]
Migrating older configurations
------------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff/old[configdiff] page
(`qute://configdiff/old`) in qutebrowser, which will show you the changes you
did in your old configuration, compared to the old defaults.
Other changes in default settings:
- In v1.1.x and newer, `<Up>` and `<Down>` navigate through command history
if no text was entered yet.
With v1.0.x, they always navigate through command history instead of selecting
completion items. Use `<Tab>`/`<Shift-Tab>` to cycle through the completion
instead.
You can get back the old behavior by doing:
+
----
:bind -m command <Up> completion-item-focus prev
:bind -m command <Down> completion-item-focus next
----
+
or always navigate through command history with
+
----
:bind -m command <Up> command-history-prev
:bind -m command <Down> command-history-next
----
- The default for `completion.web_history_max_items` is now set to `-1`, showing
an unlimited number of items in the completion for `:open` as the new
sqlite-based completion is much faster. If the `:open` completion is too slow
on your machine, set an appropriate limit again.

View File

@@ -109,13 +109,15 @@
|<<completion.use_best_match,completion.use_best_match>>|Execute the best-matching command on a partial match.
|<<completion.web_history_max_items,completion.web_history_max_items>>|Number of URLs to show in the web history.
|<<confirm_quit,confirm_quit>>|Require a confirmation before quitting the application.
|<<content.autoplay,content.autoplay>>|Automatically start playing `<video>` elements.
|<<content.cache.appcache,content.cache.appcache>>|Enable support for the HTML 5 web application cache feature.
|<<content.cache.maximum_pages,content.cache.maximum_pages>>|Maximum number of pages to hold in the global memory page cache.
|<<content.cache.size,content.cache.size>>|Size (in bytes) of the HTTP network cache. Null to use the default value.
|<<content.canvas_reading,content.canvas_reading>>|Allow websites to read canvas elements.
|<<content.cookies.accept,content.cookies.accept>>|Which cookies to accept.
|<<content.cookies.store,content.cookies.store>>|Store cookies.
|<<content.default_encoding,content.default_encoding>>|Default encoding to use for websites.
|<<content.developer_extras,content.developer_extras>>|Enable extra tools for Web developers.
|<<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.geolocation,content.geolocation>>|Allow websites to request geolocations.
@@ -144,14 +146,17 @@
|<<content.netrc_file,content.netrc_file>>|Netrc-file for HTTP authentication.
|<<content.notifications,content.notifications>>|Allow websites to show notifications.
|<<content.pdfjs,content.pdfjs>>|Allow pdf.js to view PDF files in the browser.
|<<content.persistent_storage,content.persistent_storage>>|Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|<<content.plugins,content.plugins>>|Enable plugins in Web pages.
|<<content.print_element_backgrounds,content.print_element_backgrounds>>|Draw the background color and images also when the page is printed.
|<<content.private_browsing,content.private_browsing>>|Open new windows in private browsing mode which does not record visited pages.
|<<content.proxy,content.proxy>>|Proxy to use.
|<<content.proxy_dns_requests,content.proxy_dns_requests>>|Send DNS requests over the configured proxy.
|<<content.register_protocol_handler,content.register_protocol_handler>>|Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|<<content.ssl_strict,content.ssl_strict>>|Validate SSL handshakes.
|<<content.user_stylesheets,content.user_stylesheets>>|List of user stylesheet filenames to use.
|<<content.webgl,content.webgl>>|Enable WebGL.
|<<content.webrtc_public_interfaces_only,content.webrtc_public_interfaces_only>>|Only expose public interfaces 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.
@@ -200,6 +205,7 @@
|<<hints.scatter,hints.scatter>>|Scatter hint key chains (like Vimium) or not (like dwb).
|<<hints.uppercase,hints.uppercase>>|Make characters in hint strings uppercase.
|<<history_gap_interval,history_gap_interval>>|Maximum time (in minutes) between two history items for them to be considered being from the same browsing session.
|<<input.escape_quits_reporter,input.escape_quits_reporter>>|Allow Escape to quit the crash reporter.
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Which unbound keys to forward to the webview in normal mode.
|<<input.insert_mode.auto_enter,input.insert_mode.auto_enter>>|Enter insert mode if an editable element is clicked.
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
@@ -487,6 +493,7 @@ Default:
* +pass:[&lt;Alt-7&gt;]+: +pass:[tab-focus 7]+
* +pass:[&lt;Alt-8&gt;]+: +pass:[tab-focus 8]+
* +pass:[&lt;Alt-9&gt;]+: +pass:[tab-focus -1]+
* +pass:[&lt;Alt-m&gt;]+: +pass:[tab-mute]+
* +pass:[&lt;Ctrl-A&gt;]+: +pass:[navigate increment]+
* +pass:[&lt;Ctrl-Alt-p&gt;]+: +pass:[print]+
* +pass:[&lt;Ctrl-B&gt;]+: +pass:[scroll-page 0 -1]+
@@ -500,6 +507,7 @@ Default:
* +pass:[&lt;Ctrl-Return&gt;]+: +pass:[follow-selected -t]+
* +pass:[&lt;Ctrl-Shift-N&gt;]+: +pass:[open -p]+
* +pass:[&lt;Ctrl-Shift-T&gt;]+: +pass:[undo]+
* +pass:[&lt;Ctrl-Shift-Tab&gt;]+: +pass:[nop]+
* +pass:[&lt;Ctrl-Shift-W&gt;]+: +pass:[close]+
* +pass:[&lt;Ctrl-T&gt;]+: +pass:[open -t]+
* +pass:[&lt;Ctrl-Tab&gt;]+: +pass:[tab-focus last]+
@@ -562,6 +570,7 @@ Default:
* +pass:[gd]+: +pass:[download]+
* +pass:[gf]+: +pass:[view-source]+
* +pass:[gg]+: +pass:[scroll-to-perc 0]+
* +pass:[gi]+: +pass:[hint inputs --first]+
* +pass:[gl]+: +pass:[tab-move -]+
* +pass:[gm]+: +pass:[tab-move]+
* +pass:[go]+: +pass:[set-cmd-text :open {url:pretty}]+
@@ -1462,6 +1471,19 @@ Default:
- +pass:[never]+
[[content.autoplay]]
=== content.autoplay
Automatically start playing `<video>` elements.
Note this option needs a restart with QtWebEngine on Qt < 5.11.
Type: <<types,Bool>>
Default: +pass:[true]+
On QtWebEngine, this setting requires Qt 5.10 or newer.
On QtWebKit, this setting is unavailable.
[[content.cache.appcache]]
=== content.cache.appcache
Enable support for the HTML 5 web application cache feature.
@@ -1496,6 +1518,18 @@ Type: <<types,Int>>
Default: empty
[[content.canvas_reading]]
=== content.canvas_reading
Allow websites to read canvas elements.
Note this is needed for some websites to work properly.
This setting requires a restart.
Type: <<types,Bool>>
Default: +pass:[true]+
This setting is only available with the QtWebEngine backend.
[[content.cookies.accept]]
=== content.cookies.accept
Which cookies to accept.
@@ -1506,12 +1540,12 @@ Valid values:
* +all+: Accept all cookies.
* +no-3rdparty+: Accept cookies from the same origin only.
* +no-unknown-3rdparty+: Accept cookies from the same origin only, unless a cookie is already set for the domain.
* +no-unknown-3rdparty+: Accept cookies from the same origin only, unless a cookie is already set for the domain. On QtWebEngine, this is the same as no-3rdparty.
* +never+: Don't accept cookies at all.
Default: +pass:[no-3rdparty]+
This setting is only available with the QtWebKit backend.
On QtWebEngine, this setting requires Qt 5.11 or newer.
[[content.cookies.store]]
=== content.cookies.store
@@ -1531,16 +1565,22 @@ Type: <<types,String>>
Default: +pass:[iso-8859-1]+
[[content.developer_extras]]
=== content.developer_extras
Enable extra tools for Web developers.
This needs to be enabled for `:inspector` to work and also adds an _Inspect_ entry to the context menu. For QtWebEngine, see `--enable-webengine-inspector` in `qutebrowser --help` instead.
[[content.desktop_capture]]
=== content.desktop_capture
Allow websites to share screen content.
On Qt < 5.10, a dialog box is always displayed, even if this is set to "true".
Type: <<types,Bool>>
This setting supports URL patterns.
Default: +pass:[false]+
Type: <<types,BoolAsk>>
This setting is only available with the QtWebKit backend.
Valid values:
* +true+
* +false+
* +ask+
Default: +pass:[ask]+
[[content.dns_prefetch]]
=== content.dns_prefetch
@@ -1571,6 +1611,8 @@ This setting is only available with the QtWebKit backend.
=== content.geolocation
Allow websites to request geolocations.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
@@ -1584,6 +1626,9 @@ Default: +pass:[ask]+
[[content.headers.accept_language]]
=== content.headers.accept_language
Value to send in the `Accept-Language` header.
Note that the value read from JavaScript is always the global value.
This setting supports URL patterns.
Type: <<types,String>>
@@ -1593,6 +1638,8 @@ Default: +pass:[en-US,en]+
=== content.headers.custom
Custom headers for qutebrowser HTTP requests.
This setting supports URL patterns.
Type: <<types,Dict>>
Default: empty
@@ -1602,6 +1649,8 @@ Default: empty
Value to send in the `DNT` header.
When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all.
This setting supports URL patterns.
Type: <<types,Bool>>
Default: +pass:[true]+
@@ -1626,6 +1675,9 @@ This setting is only available with the QtWebKit backend.
[[content.headers.user_agent]]
=== content.headers.user_agent
User agent to send. Unset to send the default.
Note that the value read from JavaScript is always the global value.
This setting supports URL patterns.
Type: <<types,String>>
@@ -1805,6 +1857,8 @@ Default: +pass:[true]+
=== content.media_capture
Allow websites to record audio/video.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
@@ -1830,6 +1884,8 @@ Default: empty
=== content.notifications
Allow websites to show notifications.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
@@ -1853,6 +1909,26 @@ Default: +pass:[false]+
This setting is only available with the QtWebKit backend.
[[content.persistent_storage]]
=== content.persistent_storage
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
* +true+
* +false+
* +ask+
Default: +pass:[ask]+
On QtWebEngine, this setting requires Qt 5.11 or newer.
On QtWebKit, this setting is unavailable.
[[content.plugins]]
=== content.plugins
Enable plugins in Web pages.
@@ -1907,10 +1983,32 @@ Default: +pass:[true]+
This setting is only available with the QtWebKit backend.
[[content.register_protocol_handler]]
=== content.register_protocol_handler
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
* +true+
* +false+
* +ask+
Default: +pass:[ask]+
On QtWebEngine, this setting requires Qt 5.11 or newer.
On QtWebKit, this setting is unavailable.
[[content.ssl_strict]]
=== content.ssl_strict
Validate SSL handshakes.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
@@ -1939,6 +2037,19 @@ Type: <<types,Bool>>
Default: +pass:[true]+
[[content.webrtc_public_interfaces_only]]
=== content.webrtc_public_interfaces_only
Only expose public interfaces via WebRTC.
On Qt 5.9, this option requires a restart. On Qt 5.10, this option doesn't work at all because of a Qt bug. On Qt >= 5.11, no restart is required.
Type: <<types,Bool>>
Default: +pass:[false]+
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).
@@ -2385,6 +2496,14 @@ Type: <<types,Int>>
Default: +pass:[30]+
[[input.escape_quits_reporter]]
=== input.escape_quits_reporter
Allow Escape to quit the crash reporter.
Type: <<types,Bool>>
Default: +pass:[true]+
[[input.forward_unbound_keys]]
=== input.forward_unbound_keys
Which unbound keys to forward to the webview in normal mode.
@@ -2577,12 +2696,19 @@ Default: empty
[[qt.force_software_rendering]]
=== qt.force_software_rendering
Force software rendering for QtWebEngine.
This is needed for QtWebEngine to work with Nouveau drivers.
This is needed for QtWebEngine to work with Nouveau drivers and can be useful in other scenarios related to graphic issues.
This setting requires a restart.
Type: <<types,Bool>>
Type: <<types,String>>
Default: +pass:[false]+
Valid values:
* +software-opengl+: Tell LibGL to use a software implementation of GL (`LIBGL_ALWAYS_SOFTWARE` / `QT_XCB_FORCE_SOFTWARE_OPENGL`)
* +qt-quick+: Tell Qt Quick to use a software renderer instead of OpenGL. (`QT_QUICK_BACKEND=software`)
* +chromium+: Tell Chromium to disable GPU support and use Skia software rendering instead. (`--disable-gpu`)
* +none+: Don't force software rendering.
Default: +pass:[none]+
This setting is only available with the QtWebEngine backend.
@@ -3044,11 +3170,12 @@ The following placeholders are defined:
* `{private}`: Indicates when private mode is enabled.
* `{current_url}`: URL of the current web page.
* `{protocol}`: Protocol (http/https/...) of the current web page.
* `{audio}`: Indicator for audio/mute status.
Type: <<types,FormatString>>
Default: +pass:[{index}: {title}]+
Default: +pass:[{audio}{index}: {title}]+
[[tabs.title.format_pinned]]
=== tabs.title.format_pinned

View File

@@ -57,7 +57,8 @@ You'll need to download three packages:
(or both) depending on the backend you want to use. QtWebEngine is the
default/recommended choice.
After downloading, install the packages:
After downloading, install the packages (make sure to install all the
downloaded qutebrowser deb files in one apt command):
----
# apt install ./python3-pypeg2_*_all.deb
@@ -381,8 +382,8 @@ $ git clone https://github.com/qutebrowser/qutebrowser.git
$ cd qutebrowser
----
Installing depdendencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Installing dependencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
@@ -391,6 +392,10 @@ https://docs.python.org/3/library/venv.html[virtual environment]:
$ tox -e mkvenv-pypi
----
If your system comes with Python 3.5.3 or older (such as Ubuntu 16.04 LTS), use
`tox -e mkvenv-pypi-old` instead. This installs an older Qt version (5.10) due
to bugs in newer versions.
This installs all needed Python dependencies in a `.venv` subfolder.
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few

View File

@@ -38,7 +38,7 @@ show it.
*-h*, *--help*::
show this help message and exit
*--basedir* 'BASEDIR'::
*-B* 'BASEDIR', *--basedir* 'BASEDIR'::
Base directory for all storage.
*-V*, *--version*::
@@ -60,7 +60,7 @@ show it.
Which backend to use.
*--enable-webengine-inspector*::
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. This is not needed anymore since Qt 5.11 where the inspector is always enabled and secure.
=== debug arguments
*-l* '{critical,error,warning,info,debug,vdebug}', *--loglevel* '{critical,error,warning,info,debug,vdebug}'::
@@ -72,7 +72,7 @@ show it.
*--loglines* 'LOGLINES'::
How many lines of the debug log to keep in RAM (-1: unlimited).
*--debug*::
*-d*, *--debug*::
Turn on debugging options.
*--json-logging*::
@@ -87,7 +87,7 @@ show it.
*--nowindow*::
Don't show the main window.
*--temp-basedir*::
*-T*, *--temp-basedir*::
Use a temporary basedir.
*--no-err-windows*::
@@ -99,7 +99,7 @@ show it.
*--qt-flag* 'QT_FLAG'::
Pass an argument to Qt as flag.
*--debug-flag* 'DEBUG_FLAGS'::
*-D* 'DEBUG_FLAGS', *--debug-flag* 'DEBUG_FLAGS'::
Pass name of debugging feature to be turned on.
// QUTE_OPTIONS_END

View File

@@ -15,6 +15,8 @@ doc/qutebrowser.1.html:
install: doc/qutebrowser.1.html
$(PYTHON) setup.py install --prefix="$(PREFIX)" --optimize=1 $(SETUPTOOLSOPTS)
install -Dm644 misc/qutebrowser.appdata.xml \
"$(DESTDIR)$(PREFIX)/share/metainfo/qutebrowser.appdata.xml"
install -Dm644 doc/qutebrowser.1 \
"$(DESTDIR)$(PREFIX)/share/man/man1/qutebrowser.1"
install -Dm644 misc/qutebrowser.desktop \

View File

@@ -9,9 +9,9 @@
<description>
<p>
qutebrowser is a keyboard-focused browser with a minimal GUI.
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl,
and is based on Python and PyQt5.
</p>
</p>
</description>
<categories>
<category>Network</category>
@@ -40,4 +40,9 @@
<url type="help">https://qutebrowser.org/doc/help/</url>
<url type="bugtracker">https://github.com/qutebrowser/qutebrowser/issues/</url>
<url type="donation">https://github.com/qutebrowser/qutebrowser#donating</url>
<releases>
<release version="1.3.0" date="2018-05-04"/>
<release version="1.2.1" date="2018-03-14"/>
<release version="1.2.0" date="2018-03-09"/>
</releases>
</component>

View File

@@ -1,6 +1,7 @@
[Desktop Entry]
Name=qutebrowser
GenericName=Web Browser
Comment=A keyboard-driven, vim-like browser based on PyQt5
Icon=qutebrowser
Type=Application
Categories=Network;WebBrowser;

View File

@@ -40,6 +40,8 @@ Section "Install"
; Uninstall old versions
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{633F41F9-FE9B-42D1-9CC4-718CBD01EE11}'
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{9331D947-AC86-4542-A755-A833429C6E69}'
RMDir /r "$INSTDIR\*.*"
CreateDirectory "$INSTDIR"
SetOutPath "$INSTDIR"

View File

@@ -59,7 +59,8 @@ exe = EXE(pyz,
debug=False,
strip=False,
upx=False,
console=False )
console=False,
version='misc/file_version_info.txt')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,

View File

@@ -4,6 +4,6 @@ certifi==2018.4.16
chardet==3.0.4
codecov==2.0.15
coverage==4.5.1
idna==2.6
requests==2.18.4
idna==2.7
requests==2.19.1
urllib3==1.22

View File

@@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==17.4.0
attrs==18.1.0
flake8==3.5.0
flake8-bugbear==18.2.0
flake8-builtins==1.3.1
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
flake8-comprehensions==1.4.1
flake8-copyright==0.2.0
flake8-debugger==3.1.0
@@ -18,10 +18,10 @@ flake8-tidy-imports==1.1.0
flake8-tuple==0.2.13
mccabe==0.6.1
pathmatch==0.2.1
pep8-naming==0.5.0
pep8-naming==0.7.0
pycodestyle==2.3.1 # rq.filter: < 2.4.0
pydocstyle==2.1.1
pyflakes==1.6.0
pyflakes==2.0.0
six==1.11.0
snowballstemmer==1.2.1
typing==3.6.4

View File

@@ -1,6 +1,6 @@
flake8
flake8-bugbear
flake8-builtins
flake8-builtins!=1.4.0
flake8-comprehensions
flake8-copyright
flake8-debugger
@@ -18,3 +18,6 @@ pyflakes
# https://github.com/PyCQA/pycodestyle/issues/741
#@ filter: pycodestyle < 2.4.0
# https://github.com/gforcada/flake8-builtins/issues/36
#@ filter: flake8-builtins != 1.4.0

View File

@@ -3,6 +3,6 @@
appdirs==1.4.3
packaging==17.1
pyparsing==2.2.0
setuptools==39.1.0
setuptools==39.2.0
six==1.11.0
wheel==0.31.0
wheel==0.31.1

View File

@@ -4,14 +4,14 @@
certifi==2018.4.16
chardet==3.0.4
github3.py==1.1.0
idna==2.6
idna==2.7
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
python-dateutil==2.7.2
python-dateutil==2.7.3
./scripts/dev/pylint_checkers
requests==2.18.4
requests==2.19.1
six==1.11.0
uritemplate==3.0.0
urllib3==1.22

View File

@@ -1,17 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==1.6.3
astroid==1.6.5
certifi==2018.4.16
chardet==3.0.4
github3.py==1.1.0
idna==2.6
idna==2.7
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
pylint==1.8.4
python-dateutil==2.7.2
pylint==1.9.2
python-dateutil==2.7.3
./scripts/dev/pylint_checkers
requests==2.18.4
requests==2.19.1
six==1.11.0
uritemplate==3.0.0
urllib3==1.22

View File

@@ -1,4 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.10 # rq.filter: != 5.10.1
sip==4.19.8

View File

@@ -1,2 +0,0 @@
PyQt5==5.10.0
#@ filter: PyQt5 != 5.10.1

View File

@@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.10.1
sip==4.19.8
PyQt5==5.11.2
PyQt5-sip==4.19.11

View File

@@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
docutils==0.14
pyroma==2.3
pyroma==2.3.1

View File

@@ -1,7 +1,9 @@
Jinja2
Pygments
pyPEG2
PyYAML
PyYAML!=4.1
colorama
cssutils
attrs
#@ filter: PyYAML != 4.1

View File

@@ -35,8 +35,4 @@ git+https://github.com/pallets/markupsafe.git
hg+http://bitbucket.org/birkenfeld/pygments-main
hg+https://bitbucket.org/fdik/pypeg
git+https://github.com/python-attrs/attrs.git
# Fails to build:
# gcc: error: ext/_yaml.c: No such file or directory
# hg+https://bitbucket.org/xi/pyyaml
PyYAML==3.12
git+https://github.com/yaml/pyyaml.git

View File

@@ -1,40 +1,40 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==17.4.0
attrs==18.1.0
beautifulsoup4==4.6.0
cheroot==6.2.4
cheroot==6.3.2
click==6.7
# colorama==0.3.9
coverage==4.5.1
EasyProcess==0.2.3
fields==5.0.0
Flask==1.0.1
Flask==1.0.2
glob2==0.6
hunter==2.0.2
hypothesis==3.56.5
hypothesis==3.65.0
itsdangerous==0.24
# Jinja2==2.10
Mako==1.0.7
# MarkupSafe==1.0
more-itertools==4.1.0
parse==1.8.2
more-itertools==4.2.0
parse==1.8.4
parse-type==0.4.2
pluggy==0.6.0
py==1.5.3
py==1.5.4
py-cpuinfo==4.0.0
pytest==3.5.1
pytest==3.6.2
pytest-bdd==2.21.0
pytest-benchmark==3.1.1
pytest-cov==2.5.1
pytest-faulthandler==1.5.0
pytest-instafail==0.3.0
pytest-mock==1.9.0
pytest-qt==2.3.1
pytest-instafail==0.4.0
pytest-mock==1.10.0
pytest-qt==2.4.1
pytest-repeat==0.4.1
pytest-rerunfailures==4.0
pytest-rerunfailures==4.1
pytest-travis-fold==1.3.0
pytest-xvfb==1.1.0
PyVirtualDisplay==0.2.1
six==1.11.0
vulture==0.26
vulture==0.27
Werkzeug==0.14.1

View File

@@ -1,7 +1,7 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
pluggy==0.6.0
py==1.5.3
py==1.5.4
six==1.11.0
tox==3.0.0
virtualenv==15.2.0
virtualenv==16.0.0

View File

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

172
misc/userscripts/qute-lastpass Executable file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env python3
# Copyright 2017 Chris Braun (cryzed) <cryzed@googlemail.com>
# Adapted for LastPass by Wayne Cheng (welps) <waynethecheng@gmail.com>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published bjy
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""
Insert login information using lastpass CLI and a dmenu-compatible application (e.g. dmenu, rofi -dmenu, ...).
A short demonstration can be seen here: https://i.imgur.com/zA61NrF.gifv.
"""
USAGE = """The domain of the site has to be in the name of the LastPass entry, for example: "github.com/cryzed" or
"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.
You must log into LastPass CLI using `lpass login <email>` prior to use of this script. The LastPass CLI agent only holds your master password for an hour by default. If you wish to change this, please see `man lpass`.
To use in qutebrowser, run: `spawn --userscript qute-lastpass`
"""
EPILOG = """Dependencies: tldextract (Python 3 module), LastPass CLI (1.3 or newer)
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
import json
import tldextract
argument_parser = argparse.ArgumentParser(
description=__doc__, usage=USAGE, epilog=EPILOG)
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
argument_parser.add_argument('--dmenu-invocation', '-d', default='rofi -dmenu',
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('--io-encoding', '-i', default='UTF-8',
help='Encoding used to communicate with subprocesses')
argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
help='Merge pass candidates for fully-qualified and registered domain name')
group = argument_parser.add_mutually_exclusive_group()
group.add_argument('--username-only', '-e',
action='store_true', help='Only insert username')
group.add_argument('--password-only', '-w',
action='store_true', help='Only insert password')
stderr = functools.partial(print, file=sys.stderr)
class ExitCodes(enum.IntEnum):
SUCCESS = 0
FAILURE = 1
# 1 is automatically used if Python throws an exception
NO_PASS_CANDIDATES = 2
COULD_NOT_MATCH_USERNAME = 3
COULD_NOT_MATCH_PASSWORD = 4
def qute_command(command):
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
fifo.write(command + '\n')
fifo.flush()
def pass_(domain, encoding):
args = ['lpass', 'show', '-x', '-j', '-G', '.*{:s}.*'.format(domain)]
process = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
err = process.stderr.decode(encoding).strip()
if err:
msg = "LastPass CLI returned for {:s} - {:s}".format(domain, 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(
items).encode(encoding), stdout=subprocess.PIPE)
return process.stdout.decode(encoding).strip()
def fake_key_raw(text):
for character in text:
# Escape all characters by default, space requires special handling
sequence = '" "' if character == ' ' else '\{}'.format(character)
qute_command('fake-key {}'.format(sequence))
def main(arguments):
if not arguments.url:
argument_parser.print_help()
return ExitCodes.FAILURE
extract_result = tldextract.extract(arguments.url)
# Try to find candidates using targets in the following order: fully-qualified domain name (includes subdomains),
# the registered domain name and finally: the IPv4 address if that's what
# the URL represents
candidates = []
for target in filter(None, [extract_result.fqdn, extract_result.registered_domain, extract_result.subdomain + extract_result.domain, extract_result.domain, extract_result.ipv4]):
target_candidates = json.loads(pass_(target, arguments.io_encoding))
if not target_candidates:
continue
candidates = candidates + target_candidates
if not arguments.merge_candidates:
break
else:
if not candidates:
stderr('No pass candidates for URL {!r} found!'.format(
arguments.url))
return ExitCodes.NO_PASS_CANDIDATES
if len(candidates) == 1:
selection = candidates.pop()
else:
choices = ["{:s} | {:s} | {:s} | {:s}".format(c["id"], c["name"], c["url"], c["username"]) for c in candidates]
choice = dmenu(choices, arguments.dmenu_invocation, arguments.io_encoding)
choiceId = choice.split("|")[0].strip()
selection = next((c for (i, c) in enumerate(candidates) if c["id"] == choiceId), None)
# Nothing was selected, simply return
if not selection:
return ExitCodes.SUCCESS
username = selection["username"]
password = selection["password"]
if arguments.username_only:
fake_key_raw(username)
elif arguments.password_only:
fake_key_raw(password)
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
# hitting enter afterwards
fake_key_raw(username)
qute_command('fake-key <Tab>')
fake_key_raw(password)
if arguments.insert_mode:
qute_command('enter-mode insert')
return ExitCodes.SUCCESS
if __name__ == '__main__':
arguments = argument_parser.parse_args()
sys.exit(main(arguments))

View File

@@ -49,7 +49,7 @@ msg() {
MPV_COMMAND=${MPV_COMMAND:-mpv}
# Warning: spaces in single flags are not supported
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl}
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
js() {

View File

@@ -30,6 +30,7 @@ markers =
qtbug60673: Tests which are broken if the conversion from orange selection to real selection is flaky
fake_os: Fake utils.is_* to a fake operating system
unicode_locale: Tests which need an unicode locale to work
qtwebkit6021_skip: Tests which would fail on WebKit version 602.1
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*
@@ -62,4 +63,8 @@ qt_log_ignore =
^inotify_add_watch\(".*"\) failed: "No space left on device"
^QSettings::value: Empty key passed
^Icon theme ".*" not found
^Error receiving trust for a CA certificate
xfail_strict = true
filterwarnings =
# This happens in many qutebrowser dependencies...
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning

View File

@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (1, 3, 0)
__version_info__ = (1, 4, 0)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@@ -22,18 +22,22 @@
import enum
import itertools
import sip
import attr
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QApplication
import pygments
import pygments.lexers
import pygments.formatters
from qutebrowser.keyinput import modeman
from qutebrowser.config import config
from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils,
urlutils, message)
from qutebrowser.misc import miscwidgets, objects
from qutebrowser.browser import mouse, hints
from qutebrowser.qt import sip
tab_id_gen = itertools.count(0)
@@ -95,6 +99,8 @@ class TabData:
keep_icon: Whether the (e.g. cloned) icon should not be cleared on page
load.
inspector: The QWebInspector used for this webview.
viewing_source: Set if we're currently showing a source view.
Only used when sources are shown via pygments.
open_target: Where to open the next link.
Only used for QtWebKit.
override_target: Override for open_target for fake clicks (like hints).
@@ -106,6 +112,7 @@ class TabData:
"""
keep_icon = attr.ib(False)
viewing_source = attr.ib(False)
inspector = attr.ib(None)
open_target = attr.ib(usertypes.ClickTarget.normal)
override_target = attr.ib(None)
@@ -150,10 +157,31 @@ class AbstractAction:
raise WebTabError("{} is not a valid web action!".format(name))
self._widget.triggerPageAction(member)
def show_source(self):
def show_source(self,
pygments=False): # pylint: disable=redefined-outer-name
"""Show the source of the current page in a new tab."""
raise NotImplementedError
def _show_source_pygments(self):
def show_source_cb(source):
"""Show source as soon as it's ready."""
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
# pylint: disable=no-member
lexer = pygments.lexers.HtmlLexer()
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table')
# pylint: enable=no-member
highlighted = pygments.highlight(source, lexer, formatter)
tb = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
new_tab = tb.tabopen(background=False, related=True)
new_tab.set_html(highlighted, self._tab.url())
new_tab.data.viewing_source = True
self._tab.dump_async(show_source_cb)
class AbstractPrinting:
@@ -414,6 +442,13 @@ class AbstractCaret(QObject):
def selection(self, callback):
raise NotImplementedError
def _follow_enter(self, tab):
"""Follow a link by faking an enter press."""
if tab:
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
else:
self._tab.key_press(Qt.Key_Enter)
def follow_selected(self, *, tab=False):
raise NotImplementedError
@@ -599,6 +634,33 @@ class AbstractElements:
raise NotImplementedError
class AbstractAudio(QObject):
"""Handling of audio/muting for this tab."""
muted_changed = pyqtSignal(bool)
recently_audible_changed = pyqtSignal(bool)
def __init__(self, parent=None):
super().__init__(parent)
self._widget = None
def set_muted(self, muted: bool):
"""Set this tab as muted or not."""
raise NotImplementedError
def is_muted(self):
"""Whether this tab is muted."""
raise NotImplementedError
def toggle_muted(self):
self.set_muted(not self.is_muted())
def is_recently_audible(self):
"""Whether this tab has had audio playing recently."""
raise NotImplementedError
class AbstractTab(QWidget):
"""A wrapper over the given widget to hide its API and expose another one.
@@ -693,6 +755,7 @@ class AbstractTab(QWidget):
self.printing._widget = widget
self.action._widget = widget
self.elements._widget = widget
self.audio._widget = widget
self.settings._settings = widget.settings()
self._install_event_filter()
@@ -753,6 +816,7 @@ class AbstractTab(QWidget):
def _on_load_started(self):
self._progress = 0
self._has_ssl_errors = False
self.data.viewing_source = False
self._set_load_status(usertypes.LoadStatus.loading)
self.load_started.emit()
@@ -829,10 +893,6 @@ class AbstractTab(QWidget):
self._progress = perc
self.load_progress.emit(perc)
@pyqtSlot()
def _on_ssl_errors(self):
self._has_ssl_errors = True
def url(self, requested=False):
raise NotImplementedError

View File

@@ -566,12 +566,6 @@ class CommandDispatcher:
tabbed_browser.tabopen(self._current_url())
self._tabbed_browser.close_tab(self._current_widget(), add_undo=False)
@cmdutils.register(instance='command-dispatcher', scope='window',
deprecated='Use :tab-give instead!')
def tab_detach(self):
"""Deprecated way to detach a tab."""
self.tab_give()
def _back_forward(self, tab, bg, window, count, forward):
"""Helper function for :back/:forward."""
history = self._current_widget().history
@@ -1461,6 +1455,7 @@ class CommandDispatcher:
if tab.data.inspector is None:
tab.data.inspector = inspector.create()
tab.data.inspector.inspect(page)
tab.data.inspector.show()
else:
tab.data.inspector.toggle(page)
except inspector.WebInspectorError as e:
@@ -1521,11 +1516,15 @@ class CommandDispatcher:
)
@cmdutils.register(instance='command-dispatcher', scope='window')
def view_source(self, edit=False):
def view_source(self, edit=False, pygments=False):
"""Show the source of the current page in a new tab.
Args:
edit: Edit the source in the editor instead of opening a tab.
pygments: Use pygments to generate the view. This is always
the case for QtWebKit. For QtWebEngine it may display
slightly different source.
Some JavaScript processing may be applied.
"""
tab = self._current_widget()
try:
@@ -1533,14 +1532,15 @@ class CommandDispatcher:
except cmdexc.CommandError as e:
message.error(str(e))
return
if current_url.scheme() == 'view-source':
if current_url.scheme() == 'view-source' or tab.data.viewing_source:
raise cmdexc.CommandError("Already viewing source!")
if edit:
ed = editor.ExternalEditor(self._tabbed_browser)
tab.dump_async(ed.edit)
else:
tab.action.show_source()
tab.action.show_source(pygments)
@cmdutils.register(instance='command-dispatcher', scope='window',
debug=True)
@@ -1674,7 +1674,7 @@ class CommandDispatcher:
"""
try:
elem.set_value(text)
except webelem.OrphanedError as e:
except webelem.OrphanedError:
message.error('Edited element vanished')
ed.backup()
except webelem.Error as e:
@@ -2230,3 +2230,20 @@ class CommandDispatcher:
window = self._tabbed_browser.widget.window()
window.setWindowState(window.windowState() ^ Qt.WindowFullScreen)
@cmdutils.register(instance='command-dispatcher', scope='window',
name='tab-mute')
@cmdutils.argument('count', count=True)
def tab_mute(self, count=None):
"""Mute/Unmute the current/[count]th tab.
Args:
count: The tab index to mute or unmute, or None
"""
tab = self._cntwidget(count)
if tab is None:
return
try:
tab.audio.toggle_muted()
except browsertab.WebTabError as e:
raise cmdexc.CommandError(e)

View File

@@ -29,7 +29,6 @@ import pathlib
import tempfile
import enum
import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel, QUrl)
@@ -37,6 +36,7 @@ from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import config
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
qtutils)
from qutebrowser.qt import sip
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)

View File

@@ -21,13 +21,13 @@
import functools
import sip
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
from qutebrowser.browser import downloads
from qutebrowser.config import config
from qutebrowser.utils import qtutils, utils, objreg
from qutebrowser.qt import sip
def update_geometry(obj):

View File

@@ -31,9 +31,10 @@ import attr
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
javascript, urlmatch)
javascript, urlmatch, version, usertypes)
from qutebrowser.commands import cmdutils
from qutebrowser.browser import downloads
from qutebrowser.misc import objects
def _scripts_dir():
@@ -57,6 +58,7 @@ class GreasemonkeyScript:
self.run_at = None
self.script_meta = None
self.runs_on_sub_frames = True
self.jsworld = "main"
for name, value in properties:
if name == 'name':
self.name = value
@@ -76,6 +78,8 @@ class GreasemonkeyScript:
self.runs_on_sub_frames = False
elif name == 'require':
self.requires.append(value)
elif name == 'qute-js-world':
self.jsworld = value
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
@@ -108,13 +112,18 @@ class GreasemonkeyScript:
browser's debugger/inspector will not match up to the line
numbers in the source script directly.
"""
# Don't use Proxy on this webkit version, the support isn't there.
use_proxy = not (
objects.backend == usertypes.Backend.QtWebKit and
version.qWebKitVersion() == '602.1')
template = jinja.js_environment.get_template('greasemonkey_wrapper.js')
return template.render(
scriptName=javascript.string_escape(
"/".join([self.namespace or '', self.name])),
scriptInfo=self._meta_json(),
scriptMeta=javascript.string_escape(self.script_meta),
scriptSource=self._code)
scriptSource=self._code,
use_proxy=use_proxy)
def _meta_json(self):
return json.dumps({

View File

@@ -22,6 +22,7 @@
import collections
import functools
import math
import os
import re
import html
import enum
@@ -154,6 +155,7 @@ class HintContext:
to_follow: The link to follow when enter is pressed.
args: Custom arguments for userscript/spawn
rapid: Whether to do rapid hinting.
first_run: Whether the action is run for the 1st time in rapid hinting.
add_history: Whether to add yanked or spawned link to the history.
filterstr: Used to save the filter string for restoring in rapid mode.
tab: The WebTab object we started hinting in.
@@ -166,12 +168,14 @@ class HintContext:
baseurl = attr.ib(None)
to_follow = attr.ib(None)
rapid = attr.ib(False)
first_run = attr.ib(True)
add_history = attr.ib(False)
filterstr = attr.ib(None)
args = attr.ib(attr.Factory(list))
tab = attr.ib(None)
group = attr.ib(None)
hint_mode = attr.ib(None)
first = attr.ib(False)
def get_args(self, urlstr):
"""Get the arguments, with {hint-url} replaced by the given URL."""
@@ -240,7 +244,18 @@ class HintActions:
if url.scheme() == 'mailto':
flags |= QUrl.RemoveScheme
urlstr = url.toString(flags)
utils.set_clipboard(urlstr, selection=sel)
new_content = urlstr
# only second and consecutive yanks are to append to the clipboard
if context.rapid and not context.first_run:
try:
old_content = utils.get_clipboard(selection=sel)
except utils.ClipboardEmptyError:
pass
else:
new_content = os.linesep.join([old_content, new_content])
utils.set_clipboard(new_content, selection=sel)
msg = "Yanked URL to {}: {}".format(
"primary selection" if sel else "clipboard",
@@ -612,6 +627,9 @@ class HintManager(QObject):
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
if self._context.first:
self._fire(strings[0])
return
# to make auto_follow == 'always' work
self._handle_auto_follow()
@@ -620,7 +638,8 @@ class HintManager(QObject):
@cmdutils.argument('win_id', win_id=True)
def start(self, # pylint: disable=keyword-arg-before-vararg
group=webelem.Group.all, target=Target.normal,
*args, win_id, mode=None, add_history=False, rapid=False):
*args, win_id, mode=None, add_history=False, rapid=False,
first=False):
"""Start hinting.
Args:
@@ -631,6 +650,7 @@ class HintManager(QObject):
`window`, `run`, `hover`, `userscript` and `spawn`.
add_history: Whether to add the spawned or yanked link to the
browsing history.
first: Click the first hinted element without prompting.
group: The element types to hint.
- `all`: All clickable elements.
@@ -694,7 +714,8 @@ class HintManager(QObject):
if rapid:
if target in [Target.tab_bg, Target.window, Target.run,
Target.hover, Target.userscript, Target.spawn,
Target.download, Target.normal, Target.current]:
Target.download, Target.normal, Target.current,
Target.yank, Target.yank_primary]:
pass
elif target == Target.tab and config.val.tabs.background:
pass
@@ -713,6 +734,7 @@ class HintManager(QObject):
self._context.rapid = rapid
self._context.hint_mode = mode
self._context.add_history = add_history
self._context.first = first
try:
self._context.baseurl = tabbed_browser.current_url()
except qtutils.QtValueError:
@@ -907,6 +929,9 @@ class HintManager(QObject):
except HintingError as e:
message.error(str(e))
if self._context is not None:
self._context.first_run = False
@cmdutils.register(instance='hintmanager', scope='tab',
modes=[usertypes.KeyMode.hint])
def follow_hint(self, select=False, keystring=None):

View File

@@ -23,7 +23,7 @@ import os
import time
import contextlib
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer, pyqtSignal
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
@@ -52,6 +52,11 @@ class WebHistory(sql.SqlTable):
"""The global history of visited pages."""
# All web history cleared
history_cleared = pyqtSignal()
# one url cleared
url_cleared = pyqtSignal(QUrl)
def __init__(self, parent=None):
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
constraints={'url': 'NOT NULL',
@@ -157,6 +162,7 @@ class WebHistory(sql.SqlTable):
with self._handle_sql_errors():
self.delete_all()
self.completion.delete_all()
self.history_cleared.emit()
def delete_url(self, url):
"""Remove all history entries with the given url.
@@ -168,6 +174,7 @@ class WebHistory(sql.SqlTable):
qtutils.ensure_valid(qurl)
self.delete('url', self._format_url(qurl))
self.completion.delete('url', self._format_completion_url(qurl))
self.url_cleared.emit(qurl)
@pyqtSlot(QUrl, QUrl, str)
def add_from_tab(self, url, requested_url, title):

View File

@@ -87,6 +87,8 @@ class AbstractWebInspector(QWidget):
data = bytes(self.saveGeometry())
geom = base64.b64encode(data).decode('ASCII')
configfiles.state['geometry']['inspector'] = geom
self.inspect(None)
super().closeEvent(e)
def inspect(self, page):
@@ -99,3 +101,4 @@ class AbstractWebInspector(QWidget):
self.hide()
else:
self.inspect(page)
self.show()

View File

@@ -22,7 +22,7 @@
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes
from qutebrowser.utils import message, log, usertypes, qtutils, objreg
from qutebrowser.keyinput import modeman
@@ -40,11 +40,12 @@ class ChildEventFilter(QObject):
_widget: The widget expected to send out childEvents.
"""
def __init__(self, eventfilter, widget, parent=None):
def __init__(self, eventfilter, widget, win_id, parent=None):
super().__init__(parent)
self._filter = eventfilter
assert widget is not None
self._widget = widget
self._win_id = win_id
def eventFilter(self, obj, event):
"""Act on ChildAdded events."""
@@ -54,6 +55,29 @@ class ChildEventFilter(QObject):
obj, child))
assert obj is self._widget
child.installEventFilter(self._filter)
if qtutils.version_check('5.11', compiled=False, exact=True):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
pass_modes = [usertypes.KeyMode.command,
usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]
if modeman.instance(self._win_id).mode not in pass_modes:
tabbed_browser = objreg.get('tabbed-browser',
scope='window',
window=self._win_id)
current_index = tabbed_browser.widget.currentIndex()
try:
widget_index = tabbed_browser.widget.indexOf(
self._widget.parent())
except RuntimeError:
widget_index = -1
if current_index == widget_index:
QTimer.singleShot(0, self._widget.setFocus)
elif event.type() == QEvent.ChildRemoved:
child = event.child()
log.mouse.debug("{}: removed child {}".format(obj, child))
return False

View File

@@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from qutebrowser.config import config
from qutebrowser.utils import message, usertypes, log, urlutils, utils
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug
from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
@@ -307,7 +307,14 @@ class DownloadItem(downloads.AbstractDownloadItem):
"""Handle QNetworkReply errors."""
if code == QNetworkReply.OperationCanceledError:
return
self._die(self._reply.errorString())
if self._reply is None:
error = "Unknown error: {}".format(
debug.qenum_key(QNetworkReply, code))
else:
error = self._reply.errorString()
self._die(error)
@pyqtSlot()
def _on_read_timer_timeout(self):

View File

@@ -24,6 +24,7 @@ Module attributes:
_HANDLERS: The handlers registered via decorators.
"""
import html
import json
import os
import time
@@ -33,7 +34,6 @@ import urllib
import collections
import pkg_resources
import sip
from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
@@ -41,6 +41,7 @@ from qutebrowser.config import config, configdata, configexc, configdiff
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg, urlutils)
from qutebrowser.misc import objects
from qutebrowser.qt import sip
pyeval_output = ":pyeval was never called"
@@ -123,12 +124,12 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
def wrong_backend_handler(self, url):
"""Show an error page about using the invalid backend."""
html = jinja.render('error.html',
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()))
return 'text/html', html
src = jinja.render('error.html',
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()))
return 'text/html', src
def data_for_url(url):
@@ -177,7 +178,7 @@ def data_for_url(url):
except OSError as e:
# FIXME:qtwebengine how to handle this?
raise QuteSchemeOSError(e)
except QuteSchemeError as e:
except QuteSchemeError:
raise
assert mimetype is not None, url
@@ -196,11 +197,11 @@ def qute_bookmarks(_url):
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
key=lambda x: x[0]) # Sort by name
html = jinja.render('bookmarks.html',
title='Bookmarks',
bookmarks=bookmarks,
quickmarks=quickmarks)
return 'text/html', html
src = jinja.render('bookmarks.html',
title='Bookmarks',
bookmarks=bookmarks,
quickmarks=quickmarks)
return 'text/html', src
@add_handler('tabs')
@@ -218,10 +219,10 @@ def qute_tabs(_url):
urlstr = tab.url().toDisplayString()
tabs[str(win_id)].append((tab.title(), urlstr))
html = jinja.render('tabs.html',
title='Tabs',
tab_list_by_window=tabs)
return 'text/html', html
src = jinja.render('tabs.html',
title='Tabs',
tab_list_by_window=tabs)
return 'text/html', src
def history_data(start_time, offset=None):
@@ -241,8 +242,9 @@ def history_data(start_time, offset=None):
end_time = start_time - 24*60*60
entries = hist.entries_between(end_time, start_time)
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
for e in entries]
return [{"url": e.url,
"title": html.escape(e.title) or html.escape(e.url),
"time": e.atime} for e in entries]
@add_handler('history')
@@ -287,25 +289,25 @@ def qute_javascript(url):
@add_handler('pyeval')
def qute_pyeval(_url):
"""Handler for qute://pyeval."""
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', html
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', src
@add_handler('spawn-output')
def qute_spawn_output(_url):
"""Handler for qute://spawn-output."""
html = jinja.render('pre.html', title='spawn output', content=spawn_output)
return 'text/html', html
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
return 'text/html', src
@add_handler('version')
@add_handler('verizon')
def qute_version(_url):
"""Handler for qute://version."""
html = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
return 'text/html', html
src = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
return 'text/html', src
@add_handler('plainlog')
@@ -323,8 +325,8 @@ def qute_plainlog(url):
if not level:
level = 'vdebug'
text = log.ram_handler.dump_log(html=False, level=level)
html = jinja.render('pre.html', title='log', content=text)
return 'text/html', html
src = jinja.render('pre.html', title='log', content=text)
return 'text/html', src
@add_handler('log')
@@ -343,8 +345,8 @@ def qute_log(url):
level = 'vdebug'
html_log = log.ram_handler.dump_log(html=True, level=level)
html = jinja.render('log.html', title='log', content=html_log)
return 'text/html', html
src = jinja.render('log.html', title='log', content=html_log)
return 'text/html', src
@add_handler('gpl')
@@ -415,12 +417,12 @@ def qute_help(url):
@add_handler('backend-warning')
def qute_backend_warning(_url):
"""Handler for qute://backend-warning."""
html = jinja.render('backend-warning.html',
distribution=version.distribution(),
Distribution=version.Distribution,
version=pkg_resources.parse_version,
title="Legacy backend warning")
return 'text/html', html
src = jinja.render('backend-warning.html',
distribution=version.distribution(),
Distribution=version.Distribution,
version=pkg_resources.parse_version,
title="Legacy backend warning")
return 'text/html', src
def _qute_settings_set(url):
@@ -450,10 +452,10 @@ def qute_settings(url):
if url.path() == '/set':
return _qute_settings_set(url)
html = jinja.render('settings.html', title='settings',
configdata=configdata,
confget=config.instance.get_str)
return 'text/html', html
src = jinja.render('settings.html', title='settings',
configdata=configdata,
confget=config.instance.get_str)
return 'text/html', src
@add_handler('bindings')
@@ -467,9 +469,9 @@ def qute_bindings(_url):
for mode in modes:
bindings[mode] = config.key_instance.get_bindings_for(mode)
html = jinja.render('bindings.html', title='Bindings',
bindings=bindings)
return 'text/html', html
src = jinja.render('bindings.html', title='Bindings',
bindings=bindings)
return 'text/html', src
@add_handler('back')
@@ -478,10 +480,10 @@ def qute_back(url):
Simple page to free ram / lazy load a site, goes back on focusing the tab.
"""
html = jinja.render(
src = jinja.render(
'back.html',
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
return 'text/html', html
return 'text/html', src
@add_handler('configdiff')

View File

@@ -34,21 +34,22 @@ class CallSuper(Exception):
"""Raised when the caller should call the superclass instead."""
def custom_headers():
def custom_headers(url):
"""Get the combined custom headers."""
headers = {}
dnt_config = config.val.content.headers.do_not_track
dnt_config = config.instance.get('content.headers.do_not_track', url=url)
if dnt_config is not None:
dnt = b'1' if dnt_config else b'0'
headers[b'DNT'] = dnt
headers[b'X-Do-Not-Track'] = dnt
conf_headers = config.val.content.headers.custom
conf_headers = config.instance.get('content.headers.custom', url=url)
for header, value in conf_headers.items():
headers[header.encode('ascii')] = value.encode('ascii')
accept_language = config.val.content.headers.accept_language
accept_language = config.instance.get('content.headers.accept_language',
url=url)
if accept_language is not None:
headers[b'Accept-Language'] = accept_language.encode('ascii')
@@ -156,7 +157,7 @@ def ignore_certificate_errors(url, errors, abort_on):
Return:
True if the error should be ignored, False otherwise.
"""
ssl_strict = config.val.content.ssl_strict
ssl_strict = config.instance.get('content.ssl_strict', url=url)
log.webview.debug("Certificate errors {!r}, strict {}".format(
errors, ssl_strict))
@@ -196,7 +197,8 @@ def ignore_certificate_errors(url, errors, abort_on):
raise utils.Unreachable
def feature_permission(url, option, msg, yes_action, no_action, abort_on):
def feature_permission(url, option, msg, yes_action, no_action, abort_on,
blocking=False):
"""Handle a feature permission request.
Args:
@@ -206,11 +208,13 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
yes_action: A callable to call if the request was approved
no_action: A callable to call if the request was denied
abort_on: A list of signals which interrupt the question.
blocking: If True, ask a blocking question.
Return:
The Question object if a question was asked, None otherwise.
The Question object if a question was asked (and blocking=False),
None otherwise.
"""
config_val = config.instance.get(option)
config_val = config.instance.get(option, url=url)
if config_val == 'ask':
if url.isValid():
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
@@ -220,10 +224,20 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on):
urlstr = None
text = "Allow the website to {}?".format(msg)
return message.confirm_async(
yes_action=yes_action, no_action=no_action,
cancel_action=no_action, abort_on=abort_on,
title='Permission request', text=text, url=urlstr)
if blocking:
answer = message.ask(abort_on=abort_on, title='Permission request',
text=text, url=urlstr,
mode=usertypes.PromptMode.yesno)
if answer:
yes_action()
else:
no_action()
return None
else:
return message.confirm_async(
yes_action=yes_action, no_action=no_action,
cancel_action=no_action, abort_on=abort_on,
title='Permission request', text=text, url=urlstr)
elif config_val:
yes_action()
return None
@@ -299,10 +313,10 @@ def netrc_authentication(url, authenticator):
(user, _account, password) = authenticators
except FileNotFoundError:
log.misc.debug("No .netrc file found")
except OSError:
log.misc.exception("Unable to read the netrc file")
except netrc.NetrcParseError:
log.misc.exception("Error when parsing the netrc file")
except OSError as e:
log.misc.exception("Unable to read the netrc file: {}".format(e))
except netrc.NetrcParseError as e:
log.misc.exception("Error when parsing the netrc file: {}".format(e))
if user is None:
return False

View File

@@ -307,6 +307,10 @@ class AbstractWebElement(collections.abc.MutableMapping):
href_tags = ['a', 'area', 'link']
return self.tag_name() in href_tags and 'href' in self
def _requires_user_interaction(self):
"""Return True if clicking this element needs user interaction."""
raise NotImplementedError
def _mouse_pos(self):
"""Get the position to click/hover."""
# Click the center of the largest square fitting into the top/left
@@ -405,7 +409,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
return
if click_target == usertypes.ClickTarget.normal:
if self.is_link():
if self.is_link() and not self._requires_user_interaction():
log.webelem.debug("Clicking via JS click()")
self._click_js(click_target)
elif self.is_editable(strict=True):

View File

@@ -28,6 +28,10 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
"""A wrapper over a QWebEngineCertificateError."""
def __init__(self, error):
super().__init__(error)
self.ignore = False
def __str__(self):
return self._error.errorDescription()
@@ -37,5 +41,8 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
self._error.error()),
string=str(self))
def url(self):
return self._error.url()
def is_overridable(self):
return self._error.isOverridable()

View File

@@ -0,0 +1,48 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Filter for QtWebEngine cookies."""
from qutebrowser.config import config
from qutebrowser.utils import utils
def _accept_cookie(request):
"""Check whether the given cookie should be accepted."""
accept = config.val.content.cookies.accept
if accept == 'all':
return True
elif accept in ['no-3rdparty', 'no-unknown-3rdparty']:
return not request.thirdParty
elif accept == 'never':
return False
else:
raise utils.Unreachable
def install_filter(profile):
"""Install the cookie filter on the given profile.
On Qt < 5.11, the filter isn't installed.
"""
store = profile.cookieStore()
try:
store.setCookieFilter(_accept_cookie)
except AttributeError:
pass

View File

@@ -19,20 +19,22 @@
"""A request interceptor taking care of adblocking and custom headers."""
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
QWebEngineUrlRequestInfo)
from qutebrowser.config import config
from qutebrowser.browser import shared
from qutebrowser.utils import utils, log
from qutebrowser.utils import utils, log, debug
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
"""Handle ad blocking and custom headers."""
def __init__(self, host_blocker, parent=None):
def __init__(self, host_blocker, args, parent=None):
super().__init__(parent)
self._host_blocker = host_blocker
self._args = args
def install(self, profile):
"""Install the interceptor on the given QWebEngineProfile."""
@@ -54,15 +56,29 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
Args:
info: QWebEngineUrlRequestInfo &info
"""
if 'log-requests' in self._args.debug_flags:
resource_type = debug.qenum_key(QWebEngineUrlRequestInfo,
info.resourceType())
navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo,
info.navigationType())
log.webview.debug("{} {}, first-party {}, resource {}, "
"navigation {}".format(
bytes(info.requestMethod()).decode('ascii'),
info.requestUrl().toDisplayString(),
info.firstPartyUrl().toDisplayString(),
resource_type, navigation_type))
url = info.requestUrl()
# FIXME:qtwebengine only block ads for NavigationTypeOther?
if self._host_blocker.is_blocked(info.requestUrl()):
if self._host_blocker.is_blocked(url):
log.webview.info("Request to {} blocked by host blocker.".format(
info.requestUrl().host()))
url.host()))
info.block(True)
for header, value in shared.custom_headers():
for header, value in shared.custom_headers(url=url):
info.setHttpHeader(header, value)
user_agent = config.val.content.headers.user_agent
user_agent = config.instance.get('content.headers.user_agent', url=url)
if user_agent is not None:
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))

View File

@@ -21,10 +21,12 @@
import glob
import os
import os.path
import re
import shutil
from PyQt5.QtCore import QLibraryInfo
from qutebrowser.utils import log, message
from qutebrowser.utils import log, message, standarddir, qtutils
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
@@ -39,9 +41,12 @@ def version(filename):
return tuple(int(n) for n in match.group('version').split('-'))
def dictionary_dir():
def dictionary_dir(old=False):
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
if qtutils.version_check('5.10', compiled=False) and not old:
datapath = standarddir.data()
else:
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
return os.path.join(datapath, 'qtwebengine_dictionaries')
@@ -73,3 +78,16 @@ def local_filename(code):
"""
all_installed = local_files(code)
return os.path.splitext(all_installed[0])[0] if all_installed else None
def init():
"""Initialize the dictionary path if supported."""
if qtutils.version_check('5.10', compiled=False):
new_dir = dictionary_dir()
old_dir = dictionary_dir(old=True)
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir
try:
if os.path.exists(old_dir) and not os.path.exists(new_dir):
shutil.copytree(old_dir, new_dir)
except OSError:
log.misc.exception("Failed to copy old dictionaries")

View File

@@ -27,7 +27,7 @@ from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
from qutebrowser.utils import log, javascript
from qutebrowser.utils import log, javascript, urlutils
from qutebrowser.browser import webelem
@@ -198,6 +198,13 @@ class WebEngineElement(webelem.AbstractWebElement):
if self.is_text_input() and self.is_editable():
self._js_call('move_cursor_to_end')
def _requires_user_interaction(self):
baseurl = self._tab.url()
url = self.resolve_url(baseurl)
if url is None:
return True
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
def _click_editable(self, click_target):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),

View File

@@ -39,8 +39,8 @@ class WebEngineInspector(inspector.AbstractWebInspector):
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
self._set_widget(view)
def inspect(self, _page):
"""Set up the inspector."""
def _inspect_old(self, page):
"""Set up the inspector for Qt < 5.11."""
try:
port = int(os.environ['QTWEBENGINE_REMOTE_DEBUGGING'])
except KeyError:
@@ -48,5 +48,18 @@ class WebEngineInspector(inspector.AbstractWebInspector):
"QtWebEngine inspector is not enabled. See "
"'qutebrowser --help' for details.")
url = QUrl('http://localhost:{}/'.format(port))
self._widget.load(url)
self.show()
if page is None:
self._widget.load(QUrl('about:blank'))
else:
self._widget.load(url)
def _inspect_new(self, page):
"""Set up the inspector for Qt >= 5.11."""
self._widget.page().setInspectedPage(page)
def inspect(self, page):
try:
self._inspect_new(page)
except AttributeError:
self._inspect_old(page)

View File

@@ -34,6 +34,10 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
def install(self, profile):
"""Install the handler for qute:// URLs on the given profile."""
profile.installUrlSchemeHandler(b'qute', self)
if qtutils.version_check('5.11', compiled=False):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
profile.installUrlSchemeHandler(b'chrome-error', self)
profile.installUrlSchemeHandler(b'chrome-extension', self)
def requestStarted(self, job):
"""Handle a request for a qute: scheme.
@@ -45,6 +49,12 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
job: QWebEngineUrlRequestJob
"""
url = job.requestUrl()
if url.scheme() in ['chrome-error', 'chrome-extension']:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
return
assert job.requestMethod() == b'GET'
assert url.scheme() == 'qute'
log.misc.debug("Got request for {}".format(url.toDisplayString()))

View File

@@ -27,10 +27,12 @@ Module attributes:
import os
from PyQt5.QtGui import QFont
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
QWebEnginePage)
from qutebrowser.browser.webengine import spell
from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
from qutebrowser.utils import utils, standarddir, qtutils, message, log
# The default QWebEngineProfile
@@ -87,35 +89,40 @@ class WebEngineSettings(websettings.AbstractSettings):
_ATTRIBUTES = {
'content.xss_auditing':
[QWebEngineSettings.XSSAuditingEnabled],
Attr(QWebEngineSettings.XSSAuditingEnabled),
'content.images':
[QWebEngineSettings.AutoLoadImages],
Attr(QWebEngineSettings.AutoLoadImages),
'content.javascript.enabled':
[QWebEngineSettings.JavascriptEnabled],
Attr(QWebEngineSettings.JavascriptEnabled),
'content.javascript.can_open_tabs_automatically':
[QWebEngineSettings.JavascriptCanOpenWindows],
Attr(QWebEngineSettings.JavascriptCanOpenWindows),
'content.javascript.can_access_clipboard':
[QWebEngineSettings.JavascriptCanAccessClipboard],
Attr(QWebEngineSettings.JavascriptCanAccessClipboard),
'content.plugins':
[QWebEngineSettings.PluginsEnabled],
Attr(QWebEngineSettings.PluginsEnabled),
'content.hyperlink_auditing':
[QWebEngineSettings.HyperlinkAuditingEnabled],
Attr(QWebEngineSettings.HyperlinkAuditingEnabled),
'content.local_content_can_access_remote_urls':
[QWebEngineSettings.LocalContentCanAccessRemoteUrls],
Attr(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
'content.local_content_can_access_file_urls':
[QWebEngineSettings.LocalContentCanAccessFileUrls],
Attr(QWebEngineSettings.LocalContentCanAccessFileUrls),
'content.webgl':
[QWebEngineSettings.WebGLEnabled],
Attr(QWebEngineSettings.WebGLEnabled),
'content.local_storage':
[QWebEngineSettings.LocalStorageEnabled],
Attr(QWebEngineSettings.LocalStorageEnabled),
'content.desktop_capture':
Attr(QWebEngineSettings.ScreenCaptureEnabled,
converter=lambda val: True if val == 'ask' else val),
# 'ask' is handled via the permission system,
# or a hardcoded dialog on Qt < 5.10
'input.spatial_navigation':
[QWebEngineSettings.SpatialNavigationEnabled],
Attr(QWebEngineSettings.SpatialNavigationEnabled),
'input.links_included_in_focus_chain':
[QWebEngineSettings.LinksIncludedInFocusChain],
Attr(QWebEngineSettings.LinksIncludedInFocusChain),
'scrolling.smooth':
[QWebEngineSettings.ScrollAnimatorEnabled],
Attr(QWebEngineSettings.ScrollAnimatorEnabled),
}
_FONT_SIZES = {
@@ -154,15 +161,19 @@ class WebEngineSettings(websettings.AbstractSettings):
# Attributes which don't exist in all Qt versions.
new_attributes = {
# Qt 5.8
'content.print_element_backgrounds': 'PrintElementBackgrounds',
'content.print_element_backgrounds':
('PrintElementBackgrounds', None),
# Qt 5.11
'content.autoplay':
('PlaybackRequiresUserGesture', lambda val: not val),
}
for name, attribute in new_attributes.items():
for name, (attribute, converter) in new_attributes.items():
try:
value = getattr(QWebEngineSettings, attribute)
except AttributeError:
continue
self._ATTRIBUTES[name] = [value]
self._ATTRIBUTES[name] = Attr(value, converter=converter)
class ProfileSetter:
@@ -176,8 +187,17 @@ class ProfileSetter:
"""Initialize settings on the given profile."""
self.set_http_headers()
self.set_http_cache_size()
self._profile.settings().setAttribute(
settings = self._profile.settings()
settings.setAttribute(
QWebEngineSettings.FullScreenSupportEnabled, True)
try:
settings.setAttribute(
QWebEngineSettings.FocusOnNavigationEnabled, False)
except AttributeError:
# Added in Qt 5.8
pass
if qtutils.version_check('5.8'):
self.set_dictionary_language()
@@ -274,9 +294,12 @@ def _init_profiles():
def init(args):
"""Initialize the global QWebSettings."""
if args.enable_webengine_inspector:
if (args.enable_webengine_inspector and
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
spell.init()
_init_profiles()
config.instance.changed.connect(_update_settings)

View File

@@ -25,9 +25,8 @@ import sys
import re
import html as html_utils
import sip
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
QUrl, QTimer)
QUrl, QTimer, QObject, qVersion)
from PyQt5.QtGui import QKeyEvent, QIcon
from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QApplication
@@ -37,11 +36,12 @@ from qutebrowser.config import configdata, config
from qutebrowser.browser import browsertab, mouse, shared
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
interceptor, webenginequtescheme,
webenginedownloads,
webenginesettings)
cookies, webenginedownloads,
webenginesettings, certificateerror)
from qutebrowser.misc import miscwidgets
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
message, objreg, jinja, debug)
from qutebrowser.qt import sip
_qute_scheme_handler = None
@@ -62,8 +62,9 @@ def init():
log.init.debug("Initializing request interceptor...")
host_blocker = objreg.get('host-blocker')
args = objreg.get('args')
req_interceptor = interceptor.RequestInterceptor(
host_blocker, parent=app)
host_blocker, args=args, parent=app)
req_interceptor.install(webenginesettings.default_profile)
req_interceptor.install(webenginesettings.private_profile)
@@ -73,6 +74,18 @@ def init():
download_manager.install(webenginesettings.private_profile)
objreg.register('webengine-download-manager', download_manager)
log.init.debug("Initializing cookie filter...")
cookies.install_filter(webenginesettings.default_profile)
cookies.install_filter(webenginesettings.private_profile)
# Clear visited links on web history clear
hist = objreg.get('web-history')
for p in [webenginesettings.default_profile,
webenginesettings.private_profile]:
hist.history_cleared.connect(p.clearAllVisitedLinks)
hist.url_cleared.connect(lambda url, profile=p:
profile.clearVisitedLinks([url]))
# Mapping worlds from usertypes.JsWorld to QWebEngineScript world IDs.
_JS_WORLD_MAP = {
@@ -97,7 +110,11 @@ class WebEngineAction(browsertab.AbstractAction):
"""Save the current page."""
self._widget.triggerPageAction(QWebEnginePage.SavePage)
def show_source(self):
def show_source(self, pygments=False):
if pygments:
self._show_source_pygments()
return
try:
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
except AttributeError:
@@ -229,7 +246,8 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.search.clear()
self._tab.run_js_async(
javascript.assemble('caret', 'setPlatform', sys.platform))
javascript.assemble('caret',
'setPlatform', sys.platform, qVersion()))
self._js_call('setInitialCursor', self._selection_cb)
def _selection_cb(self, enabled):
@@ -326,6 +344,11 @@ class WebEngineCaret(browsertab.AbstractCaret):
"""
if js_elem is None:
return
if js_elem == "focused":
# we had a focused element, not a selected one. Just send <enter>
self._follow_enter(tab)
return
assert isinstance(js_elem, dict), js_elem
elem = webengineelem.WebEngineElement(js_elem, tab=self._tab)
if tab:
@@ -348,14 +371,11 @@ class WebEngineCaret(browsertab.AbstractCaret):
log.webview.debug("Clicking a searched link via fake key press.")
# send a fake enter, clicking the orange selection box
if tab:
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
else:
self._tab.key_press(Qt.Key_Enter)
self._follow_enter(tab)
else:
# click an existing blue selection
js_code = javascript.assemble('webelem', 'find_selected_link')
js_code = javascript.assemble('webelem',
'find_selected_focused_link')
self._tab.run_js_async(js_code, lambda jsret:
self._follow_selected_cb(jsret, tab))
@@ -606,42 +626,172 @@ class WebEngineElements(browsertab.AbstractElements):
self._tab.run_js_async(js_code, js_cb)
class WebEngineTab(browsertab.AbstractTab):
class WebEngineAudio(browsertab.AbstractAudio):
"""A QtWebEngine tab in the browser.
"""QtWebEngine implemementations related to audio/muting."""
Signals:
_load_finished_fake:
Used in place of unreliable loadFinished
"""
def _connect_signals(self):
page = self._widget.page()
page.audioMutedChanged.connect(self.muted_changed)
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
_load_finished_fake = pyqtSignal(bool)
def set_muted(self, muted: bool):
page = self._widget.page()
page.setAudioMuted(muted)
def __init__(self, *, win_id, mode_manager, private, parent=None):
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(self)
self.scroller = WebEngineScroller(self, parent=self)
self.caret = WebEngineCaret(mode_manager=mode_manager,
tab=self, parent=self)
self.zoom = WebEngineZoom(tab=self, parent=self)
self.search = WebEngineSearch(parent=self)
self.printing = WebEnginePrinting()
self.elements = WebEngineElements(tab=self)
self.action = WebEngineAction(tab=self)
# We're assigning settings in _set_widget
self.settings = webenginesettings.WebEngineSettings(settings=None)
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine
self._child_event_filter = None
self._saved_zoom = None
self._reload_url = None
def is_muted(self):
page = self._widget.page()
return page.isAudioMuted()
def is_recently_audible(self):
page = self._widget.page()
return page.recentlyAudible()
class _WebEnginePermissions(QObject):
"""Handling of various permission-related signals."""
_abort_questions = pyqtSignal()
def __init__(self, tab, parent=None):
super().__init__(parent)
self._tab = tab
self._widget = None
def connect_signals(self):
"""Connect related signals from the QWebEnginePage."""
page = self._widget.page()
page.fullScreenRequested.connect(
self._on_fullscreen_requested)
page.featurePermissionRequested.connect(
self._on_feature_permission_requested)
if qtutils.version_check('5.11'):
page.quotaRequested.connect(
self._on_quota_requested)
page.registerProtocolHandlerRequested.connect(
self._on_register_protocol_handler_requested)
self._tab.shutting_down.connect(self._abort_questions)
self._tab.load_started.connect(self._abort_questions)
@pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request):
request.accept()
on = request.toggleOn()
self._tab.data.fullscreen = on
self._tab.fullscreen_requested.emit(on)
if on:
notification = miscwidgets.FullscreenNotification(self._widget)
notification.show()
notification.set_timeout(3000)
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
def _on_feature_permission_requested(self, url, feature):
"""Ask the user for approval for geolocation/media/etc.."""
options = {
QWebEnginePage.Geolocation: 'content.geolocation',
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
}
messages = {
QWebEnginePage.Geolocation: 'access your location',
QWebEnginePage.MediaAudioCapture: 'record audio',
QWebEnginePage.MediaVideoCapture: 'record video',
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
}
try:
options.update({
QWebEnginePage.DesktopVideoCapture:
'content.desktop_capture',
QWebEnginePage.DesktopAudioVideoCapture:
'content.desktop_capture',
})
messages.update({
QWebEnginePage.DesktopVideoCapture:
'capture your desktop',
QWebEnginePage.DesktopAudioVideoCapture:
'capture your desktop and audio',
})
except AttributeError:
# Added in Qt 5.10
pass
assert options.keys() == messages.keys()
page = self._widget.page()
if feature not in options:
log.webview.error("Unhandled feature permission {}".format(
debug.qenum_key(QWebEnginePage, feature)))
page.setFeaturePermission(url, feature,
QWebEnginePage.PermissionDeniedByUser)
return
yes_action = functools.partial(
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionGrantedByUser)
no_action = functools.partial(
page.setFeaturePermission, url, feature,
QWebEnginePage.PermissionDeniedByUser)
question = shared.feature_permission(
url=url, option=options[feature], msg=messages[feature],
yes_action=yes_action, no_action=no_action,
abort_on=[self._abort_questions])
if question is not None:
page.featurePermissionRequestCanceled.connect(
functools.partial(self._on_feature_permission_cancelled,
question, url, feature))
def _on_feature_permission_cancelled(self, question, url, feature,
cancelled_url, cancelled_feature):
"""Slot invoked when a feature permission request was cancelled.
To be used with functools.partial.
"""
if url == cancelled_url and feature == cancelled_feature:
try:
question.abort()
except RuntimeError:
# The question could already be deleted, e.g. because it was
# aborted after a loadStarted signal.
pass
def _on_quota_requested(self, request):
size = utils.format_size(request.requestedSize())
shared.feature_permission(
url=request.origin(),
option='content.persistent_storage',
msg='use {} of persistent storage'.format(size),
yes_action=request.accept, no_action=request.reject,
abort_on=[self._abort_questions],
blocking=True)
def _on_register_protocol_handler_requested(self, request):
shared.feature_permission(
url=request.origin(),
option='content.register_protocol_handler',
msg='open all {} links'.format(request.scheme()),
yes_action=request.accept, no_action=request.reject,
abort_on=[self._abort_questions],
blocking=True)
class _WebEngineScripts(QObject):
def __init__(self, tab, parent=None):
super().__init__(parent)
self._tab = tab
self._widget = None
self._greasemonkey = objreg.get('greasemonkey')
def connect_signals(self):
config.instance.changed.connect(self._on_config_changed)
self._init_js()
@pyqtSlot(str)
def _on_config_changed(self, option):
@@ -653,7 +803,7 @@ class WebEngineTab(browsertab.AbstractTab):
"""Update the custom stylesheet in existing tabs."""
css = shared.get_user_stylesheet()
code = javascript.assemble('stylesheet', 'set_css', css)
self.run_js_async(code)
self._tab.run_js_async(code)
def _inject_early_js(self, name, js_code, *,
world=QWebEngineScript.ApplicationWorld,
@@ -688,7 +838,7 @@ class WebEngineTab(browsertab.AbstractTab):
if not script.isNull():
scripts.remove(script)
def _init_js(self):
def init(self):
"""Initialize global qutebrowser JavaScript."""
js_code = javascript.wrap_global(
'scripts',
@@ -696,13 +846,24 @@ class WebEngineTab(browsertab.AbstractTab):
utils.read_file('javascript/webelem.js'),
utils.read_file('javascript/caret.js'),
)
self._inject_early_js('js',
utils.read_file('javascript/print.js'),
subframes=True,
world=QWebEngineScript.MainWorld)
# FIXME:qtwebengine what about subframes=True?
self._inject_early_js('js', js_code, subframes=True)
self._init_stylesheet()
greasemonkey = objreg.get('greasemonkey')
greasemonkey.scripts_reloaded.connect(self._inject_userscripts)
self._inject_userscripts()
# The Greasemonkey metadata block support in QtWebEngine only starts at
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
# response to urlChanged.
if not qtutils.version_check('5.8'):
self._tab.url_changed.connect(
self._inject_greasemonkey_scripts_for_url)
else:
self._greasemonkey.scripts_reloaded.connect(
self._inject_all_greasemonkey_scripts)
self._inject_all_greasemonkey_scripts()
def _init_stylesheet(self):
"""Initialize custom stylesheets.
@@ -719,40 +880,123 @@ class WebEngineTab(browsertab.AbstractTab):
)
self._inject_early_js('stylesheet', js_code, subframes=True)
def _inject_userscripts(self):
"""Register user JavaScript files with the global profiles."""
# The Greasemonkey metadata block support in QtWebEngine only starts at
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
# response to urlChanged.
if not qtutils.version_check('5.8'):
@pyqtSlot(QUrl)
def _inject_greasemonkey_scripts_for_url(self, url):
matching_scripts = self._greasemonkey.scripts_for(url)
self._inject_greasemonkey_scripts(
matching_scripts.start, QWebEngineScript.DocumentCreation, True)
self._inject_greasemonkey_scripts(
matching_scripts.end, QWebEngineScript.DocumentReady, False)
self._inject_greasemonkey_scripts(
matching_scripts.idle, QWebEngineScript.Deferred, False)
@pyqtSlot()
def _inject_all_greasemonkey_scripts(self):
scripts = self._greasemonkey.all_scripts()
self._inject_greasemonkey_scripts(scripts)
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
remove_first=True):
"""Register user JavaScript files with the current tab.
Args:
scripts: A list of GreasemonkeyScripts, or None to add all
known by the Greasemonkey subsystem.
injection_point: The QWebEngineScript::InjectionPoint stage
to inject the script into, None to use
auto-detection.
remove_first: Whether to remove all previously injected
scripts before adding these ones.
"""
if sip.isdeleted(self._widget):
return
# Since we are inserting scripts into profile.scripts they won't
# just get replaced by new gm scripts like if we were injecting them
# ourselves so we need to remove all gm scripts, while not removing
# any other stuff that might have been added. Like the one for
# stylesheets.
greasemonkey = objreg.get('greasemonkey')
scripts = self._widget.page().scripts()
for script in scripts.toList():
if script.name().startswith("GM-"):
log.greasemonkey.debug('Removing script: {}'
.format(script.name()))
removed = scripts.remove(script)
assert removed, script.name()
# Since we are inserting scripts into a per-tab collection,
# rather than just injecting scripts on page load, we need to
# make sure we replace existing scripts, not just add new ones.
# While, taking care not to remove any other scripts that might
# have been added elsewhere, like the one for stylesheets.
page_scripts = self._widget.page().scripts()
if remove_first:
for script in page_scripts.toList():
if script.name().startswith("GM-"):
log.greasemonkey.debug('Removing script: {}'
.format(script.name()))
removed = page_scripts.remove(script)
assert removed, script.name()
# Then add the new scripts.
for script in greasemonkey.all_scripts():
# @run-at (and @include/@exclude/@match) is parsed by
# QWebEngineScript.
if not scripts:
return
for script in scripts:
new_script = QWebEngineScript()
new_script.setWorldId(QWebEngineScript.MainWorld)
try:
world = int(script.jsworld)
except ValueError:
try:
world = _JS_WORLD_MAP[usertypes.JsWorld[
script.jsworld.lower()]]
except KeyError:
log.greasemonkey.error(
"script {} has invalid value for '@qute-js-world'"
": {}".format(script.name, script.jsworld))
continue
new_script.setWorldId(world)
new_script.setSourceCode(script.code())
new_script.setName("GM-{}".format(script.name))
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
# Override the @run-at value parsed by QWebEngineScript if desired.
if injection_point:
new_script.setInjectionPoint(injection_point)
log.greasemonkey.debug('adding script: {}'
.format(new_script.name()))
scripts.insert(new_script)
page_scripts.insert(new_script)
class WebEngineTab(browsertab.AbstractTab):
"""A QtWebEngine tab in the browser.
Signals:
_load_finished_fake:
Used in place of unreliable loadFinished
"""
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
_load_finished_fake = pyqtSignal(bool)
def __init__(self, *, win_id, mode_manager, private, parent=None):
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(self)
self.scroller = WebEngineScroller(self, parent=self)
self.caret = WebEngineCaret(mode_manager=mode_manager,
tab=self, parent=self)
self.zoom = WebEngineZoom(tab=self, parent=self)
self.search = WebEngineSearch(parent=self)
self.printing = WebEnginePrinting()
self.elements = WebEngineElements(tab=self)
self.action = WebEngineAction(tab=self)
self.audio = WebEngineAudio(parent=self)
self._permissions = _WebEnginePermissions(tab=self, parent=self)
self._scripts = _WebEngineScripts(tab=self, parent=self)
# We're assigning settings in _set_widget
self.settings = webenginesettings.WebEngineSettings(settings=None)
self._set_widget(widget)
self._connect_signals()
self.backend = usertypes.Backend.QtWebEngine
self._child_event_filter = None
self._saved_zoom = None
self._reload_url = None
self._scripts.init()
def _set_widget(self, widget):
# pylint: disable=protected-access
super()._set_widget(widget)
self._permissions._widget = widget
self._scripts._widget = widget
def _install_event_filter(self):
fp = self._widget.focusProxy()
@@ -760,7 +1004,7 @@ class WebEngineTab(browsertab.AbstractTab):
fp.installEventFilter(self._mouse_event_filter)
self._child_event_filter = mouse.ChildEventFilter(
eventfilter=self._mouse_event_filter, widget=self._widget,
parent=self)
win_id=self.win_id, parent=self)
self._widget.installEventFilter(self._child_event_filter)
@pyqtSlot()
@@ -780,8 +1024,9 @@ class WebEngineTab(browsertab.AbstractTab):
url: The QUrl to open.
predict: If set to False, predicted_navigation is not emitted.
"""
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
self._widget.setFocus()
if sip.isdeleted(self._widget):
# https://github.com/qutebrowser/qutebrowser/issues/3896
return
self._saved_zoom = self.zoom.factor()
self._openurl_prepare(url, predict=predict)
self._widget.load(url)
@@ -937,26 +1182,13 @@ class WebEngineTab(browsertab.AbstractTab):
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-December/038400.html
self._show_error_page(url, "Authentication required")
@pyqtSlot('QWebEngineFullScreenRequest')
def _on_fullscreen_requested(self, request):
request.accept()
on = request.toggleOn()
self.data.fullscreen = on
self.fullscreen_requested.emit(on)
if on:
notification = miscwidgets.FullscreenNotification(self)
notification.show()
notification.set_timeout(3000)
@pyqtSlot()
def _on_load_started(self):
"""Clear search when a new load is started if needed."""
if (qtutils.version_check('5.9', compiled=False) and
not qtutils.version_check('5.9.2', compiled=False)):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
self.search.clear()
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
# (seems to be back in later Qt versions as well)
self.search.clear()
super()._on_load_started()
self.data.netrc_used = False
@@ -1028,8 +1260,9 @@ class WebEngineTab(browsertab.AbstractTab):
log.config.debug(
"Loading {} again because of config change".format(
self._reload_url.toDisplayString()))
QTimer.singleShot(100, lambda url=self._reload_url:
self.openurl(url, predict=False))
QTimer.singleShot(100, functools.partial(self.openurl,
self._reload_url,
predict=False))
self._reload_url = None
if not qtutils.version_check('5.10', compiled=False):
@@ -1039,33 +1272,82 @@ class WebEngineTab(browsertab.AbstractTab):
# the old icon is still displayed.
self.icon_changed.emit(QIcon())
@pyqtSlot(certificateerror.CertificateErrorWrapper)
def _on_ssl_errors(self, error):
self._has_ssl_errors = True
url = error.url()
log.webview.debug("Certificate error: {}".format(error))
if error.is_overridable():
error.ignore = shared.ignore_certificate_errors(
url, [error], abort_on=[self.shutting_down, self.load_started])
else:
log.webview.error("Non-overridable certificate error: "
"{}".format(error))
log.webview.debug("ignore {}, URL {}, requested {}".format(
error.ignore, url, self.url(requested=True)))
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207
# We can't really know when to show an error page, as the error might
# have happened when loading some resource.
# However, self.url() is not available yet and the requested URL
# might not match the URL we get from the error - so we just apply a
# heuristic here.
if (not qtutils.version_check('5.9') and
not error.ignore and
url.matches(self.url(requested=True), QUrl.RemoveScheme)):
self._show_error_page(url, str(error))
@pyqtSlot(QUrl)
def _on_predicted_navigation(self, url):
"""If we know we're going to visit an URL soon, change the settings."""
"""If we know we're going to visit an URL soon, change the settings.
This is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
"""
super()._on_predicted_navigation(url)
self.settings.update_for_url(url)
if not qtutils.version_check('5.11.1', compiled=False):
self.settings.update_for_url(url)
@pyqtSlot(usertypes.NavigationRequest)
def _on_navigation_request(self, navigation):
super()._on_navigation_request(navigation)
if navigation.url == QUrl('qute://print'):
command_dispatcher = objreg.get('command-dispatcher',
scope='window',
window=self.win_id)
command_dispatcher.printpage()
navigation.accepted = False
if not navigation.accepted or not navigation.is_main_frame:
return
needs_reload = {
settings_needing_reload = {
'content.plugins',
'content.javascript.enabled',
'content.javascript.can_access_clipboard',
'content.javascript.can_access_clipboard',
'content.print_element_backgrounds',
'input.spatial_navigation',
'input.spatial_navigation',
}
assert needs_reload.issubset(configdata.DATA)
assert settings_needing_reload.issubset(configdata.DATA)
changed = self.settings.update_for_url(navigation.url)
if (changed & needs_reload and navigation.navigation_type !=
navigation.Type.link_clicked):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
reload_needed = changed & settings_needing_reload
# On Qt < 5.11, we don't don't need a reload when type == link_clicked.
# On Qt 5.11.0, we always need a reload.
# On Qt > 5.11.0, we never need a reload:
# https://codereview.qt-project.org/#/c/229525/1
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
if qtutils.version_check('5.11.1', compiled=False):
reload_needed = False
elif not qtutils.version_check('5.11.0', exact=True, compiled=False):
if navigation.navigation_type == navigation.Type.link_clicked:
reload_needed = False
if reload_needed:
self._reload_url = navigation.url
def _connect_signals(self):
@@ -1080,7 +1362,6 @@ class WebEngineTab(browsertab.AbstractTab):
page.authenticationRequired.connect(self._on_authentication_required)
page.proxyAuthenticationRequired.connect(
self._on_proxy_authentication_required)
page.fullScreenRequested.connect(self._on_fullscreen_requested)
page.contentsSizeChanged.connect(self.contents_size_changed)
page.navigation_request.connect(self._on_navigation_request)
@@ -1105,5 +1386,10 @@ class WebEngineTab(browsertab.AbstractTab):
self.predicted_navigation.connect(self._on_predicted_navigation)
# pylint: disable=protected-access
self.audio._connect_signals()
self._permissions.connect_signals()
self._scripts.connect_signals()
def event_target(self):
return self._widget.focusProxy()
return self._widget.render_widget()

View File

@@ -19,17 +19,17 @@
"""The main browser widget for QtWebEngine."""
import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
QWebEngineScript)
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from qutebrowser.browser import shared
from qutebrowser.browser.webengine import certificateerror, webenginesettings
from qutebrowser.browser.webengine import webenginesettings, certificateerror
from qutebrowser.config import config
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
from qutebrowser.utils import log, debug, usertypes, objreg, qtutils
from qutebrowser.misc import miscwidgets
from qutebrowser.qt import sip
class WebEngineView(QWebEngineView):
@@ -51,6 +51,35 @@ class WebEngineView(QWebEngineView):
parent=self)
self.setPage(page)
if qtutils.version_check('5.11', compiled=False):
# Set a PseudoLayout as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-68224
# and other related issues.
sip.delete(self.layout())
self._layout = miscwidgets.PseudoLayout(self)
def render_widget(self):
"""Get the RenderWidgetHostViewQt for this view.
Normally, this would always be the focusProxy().
However, it sometimes isn't, so we use this as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-68727
"""
if 'lost-focusproxy' not in objreg.get('args').debug_flags:
proxy = self.focusProxy()
if proxy is not None:
return proxy
# We don't want e.g. a QMenu.
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
children = [c for c in self.findChildren(QWidget)
if c.isVisible() and c.inherits(rwhv_class)]
log.webview.debug("Found possibly lost focusProxy: {}"
.format(children))
return children[-1] if children else None
def shutdown(self):
self.page().shutdown()
@@ -122,23 +151,22 @@ class WebEnginePage(QWebEnginePage):
Signals:
certificate_error: Emitted on certificate errors.
Needs to be directly connected to a slot setting the
'ignore' attribute.
shutting_down: Emitted when the page is shutting down.
navigation_request: Emitted on acceptNavigationRequest.
"""
certificate_error = pyqtSignal()
certificate_error = pyqtSignal(certificateerror.CertificateErrorWrapper)
shutting_down = pyqtSignal()
navigation_request = pyqtSignal(usertypes.NavigationRequest)
def __init__(self, *, theme_color, profile, parent=None):
super().__init__(profile, parent)
self._is_shutting_down = False
self.featurePermissionRequested.connect(
self._on_feature_permission_requested)
self._theme_color = theme_color
self._set_bg_color()
config.instance.changed.connect(self._set_bg_color)
self.urlChanged.connect(self._inject_userjs)
@config.change_filter('colors.webpage.bg')
def _set_bg_color(self):
@@ -147,97 +175,15 @@ class WebEnginePage(QWebEnginePage):
col = self._theme_color
self.setBackgroundColor(col)
@pyqtSlot(QUrl, 'QWebEnginePage::Feature')
def _on_feature_permission_requested(self, url, feature):
"""Ask the user for approval for geolocation/media/etc.."""
options = {
QWebEnginePage.Geolocation: 'content.geolocation',
QWebEnginePage.MediaAudioCapture: 'content.media_capture',
QWebEnginePage.MediaVideoCapture: 'content.media_capture',
QWebEnginePage.MediaAudioVideoCapture: 'content.media_capture',
}
messages = {
QWebEnginePage.Geolocation: 'access your location',
QWebEnginePage.MediaAudioCapture: 'record audio',
QWebEnginePage.MediaVideoCapture: 'record video',
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
}
assert options.keys() == messages.keys()
if feature not in options:
log.webview.error("Unhandled feature permission {}".format(
debug.qenum_key(QWebEnginePage, feature)))
self.setFeaturePermission(url, feature,
QWebEnginePage.PermissionDeniedByUser)
return
yes_action = functools.partial(
self.setFeaturePermission, url, feature,
QWebEnginePage.PermissionGrantedByUser)
no_action = functools.partial(
self.setFeaturePermission, url, feature,
QWebEnginePage.PermissionDeniedByUser)
question = shared.feature_permission(
url=url, option=options[feature], msg=messages[feature],
yes_action=yes_action, no_action=no_action,
abort_on=[self.shutting_down, self.loadStarted])
if question is not None:
self.featurePermissionRequestCanceled.connect(
functools.partial(self._on_feature_permission_cancelled,
question, url, feature))
def _on_feature_permission_cancelled(self, question, url, feature,
cancelled_url, cancelled_feature):
"""Slot invoked when a feature permission request was cancelled.
To be used with functools.partial.
"""
if url == cancelled_url and feature == cancelled_feature:
try:
question.abort()
except RuntimeError:
# The question could already be deleted, e.g. because it was
# aborted after a loadStarted signal.
pass
def shutdown(self):
self._is_shutting_down = True
self.shutting_down.emit()
def certificateError(self, error):
"""Handle certificate errors coming from Qt."""
self.certificate_error.emit()
url = error.url()
error = certificateerror.CertificateErrorWrapper(error)
log.webview.debug("Certificate error: {}".format(error))
url_string = url.toDisplayString()
error_page = jinja.render(
'error.html', title="Error loading page: {}".format(url_string),
url=url_string, error=str(error))
if error.is_overridable():
ignore = shared.ignore_certificate_errors(
url, [error], abort_on=[self.loadStarted, self.shutting_down])
else:
log.webview.error("Non-overridable certificate error: "
"{}".format(error))
ignore = False
# We can't really know when to show an error page, as the error might
# have happened when loading some resource.
# However, self.url() is not available yet and self.requestedUrl()
# might not match the URL we get from the error - so we just apply a
# heuristic here.
# See https://bugreports.qt.io/browse/QTBUG-56207
log.webview.debug("ignore {}, URL {}, requested {}".format(
ignore, url, self.requestedUrl()))
if not ignore and url.matches(self.requestedUrl(), QUrl.RemoveScheme):
self.setHtml(error_page)
return ignore
self.certificate_error.emit(error)
return error.ignore
def javaScriptConfirm(self, url, js_msg):
"""Override javaScriptConfirm to use qutebrowser prompts."""
@@ -315,43 +261,3 @@ class WebEnginePage(QWebEnginePage):
is_main_frame=is_main_frame)
self.navigation_request.emit(navigation)
return navigation.accepted
@pyqtSlot('QUrl')
def _inject_userjs(self, url):
"""Inject userscripts registered for `url` into the current page."""
if qtutils.version_check('5.8'):
# Handled in webenginetab with the builtin Greasemonkey
# support.
return
# Using QWebEnginePage.scripts() to hold the user scripts means
# we don't have to worry ourselves about where to inject the
# page but also means scripts hang around for the tab lifecycle.
# So clear them here.
scripts = self.scripts()
for script in scripts.toList():
if script.name().startswith("GM-"):
log.greasemonkey.debug("Removing script: {}"
.format(script.name()))
removed = scripts.remove(script)
assert removed, script.name()
def _add_script(script, injection_point):
new_script = QWebEngineScript()
new_script.setInjectionPoint(injection_point)
new_script.setWorldId(QWebEngineScript.MainWorld)
new_script.setSourceCode(script.code())
new_script.setName("GM-{}".format(script.name))
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
log.greasemonkey.debug("Adding script: {}"
.format(new_script.name()))
scripts.insert(new_script)
greasemonkey = objreg.get('greasemonkey')
matching_scripts = greasemonkey.scripts_for(url)
for script in matching_scripts.start:
_add_script(script, QWebEngineScript.DocumentCreation)
for script in matching_scripts.end:
_add_script(script, QWebEngineScript.DocumentReady)
for script in matching_scripts.idle:
_add_script(script, QWebEngineScript.Deferred)

View File

@@ -28,7 +28,8 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
from qutebrowser.utils import (message, log, usertypes, utils, objreg,
urlutils, debug)
from qutebrowser.browser import shared
from qutebrowser.browser.webkit import certificateerror
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
@@ -147,6 +148,7 @@ class NetworkManager(QNetworkAccessManager):
super().__init__(parent)
log.init.debug("NetworkManager init done")
self.adopted_downloads = 0
self._args = objreg.get('args')
self._win_id = win_id
self._tab_id = tab_id
self._private = private
@@ -378,7 +380,7 @@ class NetworkManager(QNetworkAccessManager):
result.setParent(self)
return result
for header, value in shared.custom_headers():
for header, value in shared.custom_headers(url=req.url()):
req.setRawHeader(header, value)
host_blocker = objreg.get('host-blocker')
@@ -406,5 +408,13 @@ class NetworkManager(QNetworkAccessManager):
# the webpage shutdown here.
current_url = QUrl()
if 'log-requests' in self._args.debug_flags:
operation = debug.qenum_key(QNetworkAccessManager, op)
operation = operation.replace('Operation', '').upper()
log.webview.debug("{} {}, first-party {}".format(
operation,
req.url().toDisplayString(),
current_url.toDisplayString()))
self.set_referer(req, current_url)
return super().createRequest(op, req, outgoing_data)

View File

@@ -305,6 +305,9 @@ class WebKitElement(webelem.AbstractWebElement):
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()
def _requires_user_interaction(self):
return False
def _click_editable(self, click_target):
ok = self._elem.evaluateJavaScript('this.focus(); true;')
if ok:

View File

@@ -19,11 +19,10 @@
"""Customized QWebInspector for QtWebKit."""
from PyQt5.QtWebKit import QWebSettings
from PyQt5.QtWebKitWidgets import QWebInspector
from qutebrowser.browser import inspector
from qutebrowser.config import config
class WebKitInspector(inspector.AbstractWebInspector):
@@ -36,9 +35,6 @@ class WebKitInspector(inspector.AbstractWebInspector):
self._set_widget(qwebinspector)
def inspect(self, page):
if not config.val.content.developer_extras:
raise inspector.WebInspectorError(
"Please enable content.developer_extras before using the "
"webinspector!")
settings = QWebSettings.globalSettings()
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
self._widget.setPage(page)
self.show()

View File

@@ -30,6 +30,7 @@ from PyQt5.QtGui import QFont
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.config import config, websettings
from qutebrowser.config.websettings import AttributeInfo as Attr
from qutebrowser.utils import standarddir, urlutils
from qutebrowser.browser import shared
@@ -44,50 +45,48 @@ class WebKitSettings(websettings.AbstractSettings):
_ATTRIBUTES = {
'content.images':
[QWebSettings.AutoLoadImages],
Attr(QWebSettings.AutoLoadImages),
'content.javascript.enabled':
[QWebSettings.JavascriptEnabled],
Attr(QWebSettings.JavascriptEnabled),
'content.javascript.can_open_tabs_automatically':
[QWebSettings.JavascriptCanOpenWindows],
Attr(QWebSettings.JavascriptCanOpenWindows),
'content.javascript.can_close_tabs':
[QWebSettings.JavascriptCanCloseWindows],
Attr(QWebSettings.JavascriptCanCloseWindows),
'content.javascript.can_access_clipboard':
[QWebSettings.JavascriptCanAccessClipboard],
Attr(QWebSettings.JavascriptCanAccessClipboard),
'content.plugins':
[QWebSettings.PluginsEnabled],
Attr(QWebSettings.PluginsEnabled),
'content.webgl':
[QWebSettings.WebGLEnabled],
Attr(QWebSettings.WebGLEnabled),
'content.hyperlink_auditing':
[QWebSettings.HyperlinkAuditingEnabled],
Attr(QWebSettings.HyperlinkAuditingEnabled),
'content.local_content_can_access_remote_urls':
[QWebSettings.LocalContentCanAccessRemoteUrls],
Attr(QWebSettings.LocalContentCanAccessRemoteUrls),
'content.local_content_can_access_file_urls':
[QWebSettings.LocalContentCanAccessFileUrls],
Attr(QWebSettings.LocalContentCanAccessFileUrls),
'content.dns_prefetch':
[QWebSettings.DnsPrefetchEnabled],
Attr(QWebSettings.DnsPrefetchEnabled),
'content.frame_flattening':
[QWebSettings.FrameFlatteningEnabled],
Attr(QWebSettings.FrameFlatteningEnabled),
'content.cache.appcache':
[QWebSettings.OfflineWebApplicationCacheEnabled],
Attr(QWebSettings.OfflineWebApplicationCacheEnabled),
'content.local_storage':
[QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled],
'content.developer_extras':
[QWebSettings.DeveloperExtrasEnabled],
Attr(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
'content.print_element_backgrounds':
[QWebSettings.PrintElementBackgrounds],
Attr(QWebSettings.PrintElementBackgrounds),
'content.xss_auditing':
[QWebSettings.XSSAuditingEnabled],
Attr(QWebSettings.XSSAuditingEnabled),
'input.spatial_navigation':
[QWebSettings.SpatialNavigationEnabled],
Attr(QWebSettings.SpatialNavigationEnabled),
'input.links_included_in_focus_chain':
[QWebSettings.LinksIncludedInFocusChain],
Attr(QWebSettings.LinksIncludedInFocusChain),
'zoom.text_only':
[QWebSettings.ZoomTextOnly],
Attr(QWebSettings.ZoomTextOnly),
'scrolling.smooth':
[QWebSettings.ScrollAnimatorEnabled],
Attr(QWebSettings.ScrollAnimatorEnabled),
}
_FONT_SIZES = {

View File

@@ -23,11 +23,6 @@ import re
import functools
import xml.etree.ElementTree
import pygments
import pygments.lexers
import pygments.formatters
import sip
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
QSize)
from PyQt5.QtGui import QKeyEvent, QIcon
@@ -38,7 +33,8 @@ 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, objreg, usertypes, utils, log, debug
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
from qutebrowser.qt import sip
class WebKitAction(browsertab.AbstractAction):
@@ -55,28 +51,8 @@ class WebKitAction(browsertab.AbstractAction):
"""Save the current page."""
raise browsertab.UnsupportedOperationError
def show_source(self):
def show_source_cb(source):
"""Show source as soon as it's ready."""
# WORKAROUND for https://github.com/PyCQA/pylint/issues/491
# pylint: disable=no-member
lexer = pygments.lexers.HtmlLexer()
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table')
# pylint: enable=no-member
highlighted = pygments.highlight(source, lexer, formatter)
tb = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
new_tab = tb.tabopen(background=False, related=True)
# The original URL becomes the path of a view-source: URL
# (without a host), but query/fragment should stay.
url = QUrl('view-source:' + urlstr)
new_tab.set_html(highlighted, url)
urlstr = self._tab.url().toString(QUrl.RemoveUserInfo)
self._tab.dump_async(show_source_cb)
def show_source(self, pygments=False):
self._show_source_pygments()
class WebKitPrinting(browsertab.AbstractPrinting):
@@ -377,11 +353,22 @@ class WebKitCaret(browsertab.AbstractCaret):
QWebSettings.JavascriptEnabled):
if tab:
self._tab.data.override_target = usertypes.ClickTarget.tab
self._tab.run_js_async(
'window.getSelection().anchorNode.parentNode.click()')
self._tab.run_js_async("""
const aElm = document.activeElement;
if (window.getSelection().anchorNode) {
window.getSelection().anchorNode.parentNode.click();
} else if (aElm && aElm !== document.body) {
aElm.click();
}
""")
else:
selection = self._widget.selectedHtml()
if not selection:
# Getting here may mean we crashed, but we can't do anything
# about that until this commit is released:
# https://github.com/annulen/webkit/commit/0e75f3272d149bc64899c161f150eb341a2417af
# TODO find a way to check if something is focused
self._follow_enter(tab)
return
try:
selected_element = xml.etree.ElementTree.fromstring(
@@ -640,6 +627,20 @@ class WebKitElements(browsertab.AbstractElements):
callback(elem)
class WebKitAudio(browsertab.AbstractAudio):
"""Dummy handling of audio status for QtWebKit."""
def set_muted(self, muted: bool):
raise browsertab.WebTabError('Muting is not supported on QtWebKit!')
def is_muted(self):
return False
def is_recently_audible(self):
return False
class WebKitTab(browsertab.AbstractTab):
"""A QtWebKit tab in the browser."""
@@ -660,6 +661,7 @@ class WebKitTab(browsertab.AbstractTab):
self.printing = WebKitPrinting()
self.elements = WebKitElements(tab=self)
self.action = WebKitAction(tab=self)
self.audio = WebKitAudio(parent=self)
# We're assigning settings in _set_widget
self.settings = webkitsettings.WebKitSettings(settings=None)
self._set_widget(widget)
@@ -806,6 +808,10 @@ 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
def _connect_signals(self):
view = self._widget
page = view.page()

View File

@@ -22,7 +22,6 @@
import html
import functools
import sip
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
@@ -35,6 +34,7 @@ from qutebrowser.browser import pdfjs, shared
from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.utils import message, usertypes, log, jinja, objreg
from qutebrowser.qt import sip
class BrowserPage(QWebPage):
@@ -212,7 +212,8 @@ class BrowserPage(QWebPage):
page = pdfjs.generate_pdfjs_page(reply.url())
except pdfjs.PDFJSNotFound:
page = jinja.render('no_pdfjs.html',
url=reply.url().toDisplayString())
url=reply.url().toDisplayString(),
title="PDF.js not found")
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
reply.url())
reply.deleteLater()
@@ -415,7 +416,7 @@ class BrowserPage(QWebPage):
def userAgentForUrl(self, url):
"""Override QWebPage::userAgentForUrl to customize the user agent."""
ua = config.val.content.headers.user_agent
ua = config.instance.get('content.headers.user_agent', url=url)
if ua is None:
return super().userAgentForUrl(url)
else:

View File

@@ -19,7 +19,7 @@
"""The main browser widgets."""
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QStyleFactory
from PyQt5.QtWebKit import QWebSettings
@@ -78,10 +78,6 @@ class WebView(QWebView):
self.setPage(page)
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
mode_manager.entered.connect(self.on_mode_entered)
mode_manager.left.connect(self.on_mode_left)
config.instance.changed.connect(self._set_bg_color)
def __repr__(self):
@@ -130,32 +126,6 @@ class WebView(QWebView):
"""
self.load(url)
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode):
"""Ignore attempts to focus the widget if in any status-input mode.
FIXME:qtwebengine
For QtWebEngine, doing the same has no effect, so we do it in here.
"""
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]:
log.webview.debug("Ignoring focus because mode {} was "
"entered.".format(mode))
self.setFocusPolicy(Qt.NoFocus)
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Restore focus policy if status-input modes were left.
FIXME:qtwebengine
For QtWebEngine, doing the same has no effect, so we do it in here.
"""
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]:
log.webview.debug("Restoring focus policy because mode {} was "
"left.".format(mode))
self.setFocusPolicy(Qt.WheelFocus)
def createWindow(self, wintype):
"""Called by Qt when a page wants to create a new window.

View File

@@ -123,6 +123,7 @@ class Command:
self.pos_args = []
self.desc = None
self.flags_with_args = []
self._has_vararg = False
# This is checked by future @cmdutils.argument calls so they fail
# (as they'd be silently ignored otherwise)
@@ -170,6 +171,8 @@ class Command:
def get_pos_arg_info(self, pos):
"""Get an ArgInfo tuple for the given positional parameter."""
if pos >= len(self.pos_args) and self._has_vararg:
pos = len(self.pos_args) - 1
name = self.pos_args[pos][0]
return self._qute_args.get(name, ArgInfo())
@@ -233,6 +236,8 @@ class Command:
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
param.name, typ, callsig))
self.parser.add_argument(*args, **kwargs)
if param.kind == inspect.Parameter.VAR_POSITIONAL:
self._has_vararg = True
return signature.parameters.values()
def _param_to_argparse_kwargs(self, param, is_bool):

View File

@@ -49,7 +49,7 @@ class Completer(QObject):
_last_cursor_pos: The old cursor position so we avoid double completion
updates.
_last_text: The old command text so we avoid double completion updates.
_last_completion_func: The completion function used for the last text.
_last_before_cursor: The prior value of before_cursor.
"""
def __init__(self, *, cmd, win_id, parent=None):
@@ -62,7 +62,7 @@ class Completer(QObject):
self._timer.timeout.connect(self._update_completion)
self._last_cursor_pos = -1
self._last_text = None
self._last_completion_func = None
self._last_before_cursor = None
self._cmd.update_completion.connect(self.schedule_completion_update)
def __repr__(self):
@@ -228,7 +228,7 @@ class Completer(QObject):
# FIXME complete searches
# https://github.com/qutebrowser/qutebrowser/issues/32
completion.set_model(None)
self._last_completion_func = None
self._last_before_cursor = None
return
before_cursor, pattern, after_cursor = self._partition()
@@ -242,11 +242,11 @@ class Completer(QObject):
if func is None:
log.completion.debug('Clearing completion')
completion.set_model(None)
self._last_completion_func = None
self._last_before_cursor = None
return
if func != self._last_completion_func:
self._last_completion_func = func
if before_cursor != self._last_before_cursor:
self._last_before_cursor = before_cursor
args = (x for x in before_cursor[1:] if not x.startswith('-'))
with debug.log_time(log.completion, 'Starting {} completion'
.format(func.__name__)):

View File

@@ -47,12 +47,12 @@ def customized_option(*, info):
return model
def value(optname, *_values, info):
def value(optname, *values, info):
"""A CompletionModel filled with setting values.
Args:
optname: The name of the config option this model shows.
_values: The values already provided on the command line.
values: The values already provided on the command line.
info: A CompletionInfo instance.
"""
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
@@ -64,13 +64,18 @@ def value(optname, *_values, info):
opt = info.config.get_opt(optname)
default = opt.typ.to_str(opt.default)
cur_cat = listcategory.ListCategory(
"Current/Default",
[(current, "Current value"), (default, "Default value")])
model.add_category(cur_cat)
cur_def = []
if current not in values:
cur_def.append((current, "Current value"))
if default not in values:
cur_def.append((default, "Default value"))
if cur_def:
cur_cat = listcategory.ListCategory("Current/Default", cur_def)
model.add_category(cur_cat)
vals = opt.typ.complete()
if vals is not None:
vals = opt.typ.complete() or []
vals = [x for x in vals if x[0] not in values]
if vals:
model.add_category(listcategory.ListCategory("Completions", vals))
return model

View File

@@ -276,7 +276,8 @@ class Config(QObject):
"""Set the given option to the given value."""
if not isinstance(objects.backend, objects.NoBackend):
if objects.backend not in opt.backends:
raise configexc.BackendError(opt.name, objects.backend)
raise configexc.BackendError(opt.name, objects.backend,
opt.raw_backends)
opt.typ.to_py(value) # for validation

View File

@@ -102,7 +102,7 @@ def _parse_yaml_type(name, node):
try:
typ = getattr(configtypes, type_name)
except AttributeError as e:
except AttributeError:
raise AttributeError("Did not find type {} for {}".format(
type_name, name))
@@ -149,6 +149,9 @@ def _parse_yaml_backends_dict(name, node):
False: False,
'Qt 5.8': qtutils.version_check('5.8'),
'Qt 5.9': qtutils.version_check('5.9'),
'Qt 5.9.2': qtutils.version_check('5.9.2'),
'Qt 5.10': qtutils.version_check('5.10'),
'Qt 5.11': qtutils.version_check('5.11'),
}
for key in sorted(node.keys()):
if conditionals[node[key]]:

View File

@@ -150,14 +150,24 @@ force_software_rendering:
renamed: qt.force_software_rendering
qt.force_software_rendering:
type: Bool
default: false
type:
name: String
valid_values:
- software-opengl: Tell LibGL to use a software implementation of GL
(`LIBGL_ALWAYS_SOFTWARE` / `QT_XCB_FORCE_SOFTWARE_OPENGL`)
- qt-quick: Tell Qt Quick to use a software renderer instead of OpenGL.
(`QT_QUICK_BACKEND=software`)
- chromium: Tell Chromium to disable GPU support and use Skia software
rendering instead. (`--disable-gpu`)
- none: Don't force software rendering.
default: none
backend: QtWebEngine
restart: true
desc: >-
Force software rendering for QtWebEngine.
This is needed for QtWebEngine to work with Nouveau drivers.
This is needed for QtWebEngine to work with Nouveau drivers and can be
useful in other scenarios related to graphic issues.
qt.force_platform:
type:
@@ -204,6 +214,17 @@ auto_save.session:
## content
content.autoplay:
default: true
type: Bool
backend:
QtWebEngine: Qt 5.10
QtWebKit: false
desc: >-
Automatically start playing `<video>` elements.
Note this option needs a restart with QtWebEngine on Qt < 5.11.
content.cache.size:
default: null
type:
@@ -216,6 +237,16 @@ content.cache.size:
With QtWebEngine, the maximum supported value is 2147483647 (~2 GB).
content.canvas_reading:
default: true
type: Bool
backend: QtWebEngine
restart: true
desc: >-
Allow websites to read canvas elements.
Note this is needed for some websites to work properly.
# Defaults from QWebSettings::QWebSettings() in
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
@@ -250,14 +281,17 @@ content.cache.appcache:
content.cookies.accept:
default: no-3rdparty
backend: QtWebKit
backend:
QtWebKit: true
QtWebEngine: Qt 5.11
type:
name: String
valid_values:
- all: "Accept all cookies."
- no-3rdparty: "Accept cookies from the same origin only."
- no-unknown-3rdparty: "Accept cookies from the same origin only, unless
a cookie is already set for the domain."
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.
@@ -284,16 +318,18 @@ content.windowed_fullscreen:
desc: >-
Limit fullscreen to the browser window (does not expand to fill the screen).
content.developer_extras:
type: Bool
default: false
backend: QtWebKit
content.desktop_capture:
type: BoolAsk
default: ask
supports_pattern: true
desc: >-
Enable extra tools for Web developers.
Allow websites to share screen content.
This needs to be enabled for `:inspector` to work and also adds an
_Inspect_ entry to the context menu. For QtWebEngine, see
`--enable-webengine-inspector` in `qutebrowser --help` instead.
On Qt < 5.10, a dialog box is always displayed, even if this is set to
"true".
content.developer_extras:
deleted: true
content.dns_prefetch:
default: true
@@ -315,14 +351,19 @@ content.frame_flattening:
content.geolocation:
default: ask
type: BoolAsk
supports_pattern: true
desc: Allow websites to request geolocations.
content.headers.accept_language:
type:
name: String
none_ok: true
supports_pattern: true
default: en-US,en
desc: Value to send in the `Accept-Language` header.
desc: >-
Value to send in the `Accept-Language` header.
Note that the value read from JavaScript is always the global value.
content.headers.custom:
default: {}
@@ -335,6 +376,7 @@ content.headers.custom:
name: String
encoding: ascii
none_ok: true
supports_pattern: true
desc: Custom headers for qutebrowser HTTP requests.
content.headers.do_not_track:
@@ -342,6 +384,7 @@ content.headers.do_not_track:
name: Bool
none_ok: true
default: true
supports_pattern: true
desc: >-
Value to send in the `DNT` header.
@@ -416,7 +459,11 @@ content.headers.user_agent:
Gecko"
- IE 11.0 for Desktop Win7 64-bit
desc: User agent to send. Unset to send the default.
supports_pattern: true
desc: >-
User agent to send. Unset to send the default.
Note that the value read from JavaScript is always the global value.
content.host_blocking.enabled:
default: true
@@ -559,6 +606,7 @@ content.local_storage:
content.media_capture:
default: ask
type: BoolAsk
supports_pattern: true
backend: QtWebEngine
desc: Allow websites to record audio/video.
@@ -575,6 +623,7 @@ content.netrc_file:
content.notifications:
default: ask
type: BoolAsk
supports_pattern: true
backend: QtWebKit
desc: Allow websites to show notifications.
@@ -588,6 +637,16 @@ content.pdfjs:
Note that the files can still be downloaded by clicking the download button
in the pdf.js viewer.
content.persistent_storage:
default: ask
type: BoolAsk
supports_pattern: true
backend:
QtWebKit: false
QtWebEngine: Qt 5.11
desc: Allow websites to request persistent storage quota via
`navigator.webkitPersistentStorage.requestQuota`.
content.plugins:
default: false
type: Bool
@@ -625,9 +684,20 @@ content.proxy_dns_requests:
backend: QtWebKit
desc: Send DNS requests over the configured proxy.
content.register_protocol_handler:
default: ask
type: BoolAsk
supports_pattern: true
backend:
QtWebKit: false
QtWebEngine: Qt 5.11
desc: Allow websites to register protocol handlers via
`navigator.registerProtocolHandler`.
content.ssl_strict:
default: ask
type: BoolAsk
supports_pattern: true
desc: Validate SSL handshakes.
content.user_stylesheets:
@@ -644,6 +714,19 @@ content.webgl:
supports_pattern: true
desc: Enable WebGL.
content.webrtc_public_interfaces_only:
default: false
type: Bool
backend:
QtWebKit: false
QtWebEngine: Qt 5.9.2
desc: >-
Only expose public interfaces via WebRTC.
On Qt 5.9, this option requires a restart.
On Qt 5.10, this option doesn't work at all because of a Qt bug.
On Qt >= 5.11, no restart is required.
content.xss_auditing:
type: Bool
default: false
@@ -961,6 +1044,11 @@ hints.uppercase:
## input
input.escape_quits_reporter:
type: Bool
default: True
desc: Allow Escape to quit the crash reporter.
input.forward_unbound_keys:
default: auto
type:
@@ -1344,7 +1432,7 @@ tabs.title.alignment:
desc: Alignment of the text inside of tabs.
tabs.title.format:
default: '{index}: {title}'
default: '{audio}{index}: {title}'
type:
name: FormatString
fields:
@@ -1359,6 +1447,7 @@ tabs.title.format:
- private
- current_url
- protocol
- audio
none_ok: true
desc: |
Format to use for the tab title.
@@ -1376,6 +1465,7 @@ tabs.title.format:
* `{private}`: Indicates when private mode is enabled.
* `{current_url}`: URL of the current web page.
* `{protocol}`: Protocol (http/https/...) of the current web page.
* `{audio}`: Indicator for audio/mute status.
tabs.title.format_pinned:
default: '{index}'
@@ -1393,6 +1483,7 @@ tabs.title.format_pinned:
- private
- current_url
- protocol
- audio
none_ok: true
desc: Format to use for the tab title for pinned tabs. The same placeholders
like for `tabs.title.format` are defined.
@@ -1560,6 +1651,7 @@ window.title_format:
- private
- current_url
- protocol
- audio
default: '{perc}{title}{title_sep}qutebrowser'
desc: |
Format to use for the window title. The same placeholders like for
@@ -2271,6 +2363,7 @@ bindings.default:
;R: hint --rapid links window
;d: hint links download
;t: hint inputs
gi: hint inputs --first
h: scroll left
j: scroll down
k: scroll up
@@ -2329,6 +2422,7 @@ bindings.default:
gf: view-source
gt: set-cmd-text -s :buffer
<Ctrl-Tab>: tab-focus last
<Ctrl-Shift-Tab>: nop
<Ctrl-^>: tab-focus last
<Ctrl-V>: enter-mode passthrough
<Ctrl-Q>: quit
@@ -2361,6 +2455,7 @@ bindings.default:
<Ctrl-Return>: follow-selected -t
.: repeat-command
<Ctrl-p>: tab-pin
<Alt-m>: tab-mute
q: record-macro
"@": run-macro
tsh: config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload

View File

@@ -44,9 +44,15 @@ class BackendError(Error):
"""Raised when this setting is unavailable with the current backend."""
def __init__(self, name, backend):
super().__init__("The {} setting is not available with the {} "
"backend!".format(name, backend.name))
def __init__(self, name, backend, raw_backends):
if raw_backends is None or not raw_backends[backend.name]:
msg = ("The {} setting is not available with the {} backend!"
.format(name, backend.name))
else:
msg = ("The {} setting needs {} with the {} backend!"
.format(name, raw_backends[backend.name], backend.name))
super().__init__(msg)
class NoPatternError(Error):

View File

@@ -233,6 +233,14 @@ class YamlConfig(QObject):
if errors:
raise configexc.ConfigFileErrors('autoconfig.yml', errors)
def _migrate_bool(self, settings, name, true_value, false_value):
"""Migrate a boolean in the settings."""
if name in settings:
for scope, val in settings[name].items():
if isinstance(val, bool):
settings[name][scope] = true_value if val else false_value
self._mark_changed()
def _handle_migrations(self, settings):
"""Migrate older configs to the newest format."""
# Simple renamed/deleted options
@@ -268,14 +276,9 @@ class YamlConfig(QObject):
del settings['bindings.default']
self._mark_changed()
# Option to show favicons only for pinned tabs changed the type of
# tabs.favicons.show from Bool to String
name = 'tabs.favicons.show'
if name in settings:
for scope, val in settings[name].items():
if isinstance(val, bool):
settings[name][scope] = 'always' if val else 'never'
self._mark_changed()
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
self._migrate_bool(settings, 'qt.force_software_rendering',
'software-opengl', 'none')
return settings
@@ -573,7 +576,7 @@ def read_autoconfig():
"""Read the autoconfig.yml file."""
try:
config.instance.read_yaml()
except configexc.ConfigFileErrors as e:
except configexc.ConfigFileErrors:
raise # caught in outer block
except configexc.Error as e:
desc = configexc.ConfigErrorDesc("Error", e)

View File

@@ -83,9 +83,12 @@ def early_init(args):
def _init_envvars():
"""Initialize environment variables which need to be set early."""
if (objects.backend == usertypes.Backend.QtWebEngine and
config.val.qt.force_software_rendering):
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
if objects.backend == usertypes.Backend.QtWebEngine:
software_rendering = config.val.qt.force_software_rendering
if software_rendering == 'software-opengl':
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
elif software_rendering == 'qt-quick':
os.environ['QT_QUICK_BACKEND'] = 'software'
if config.val.qt.force_platform is not None:
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
@@ -163,11 +166,25 @@ def qt_args(namespace):
argv += ['--' + arg for arg in config.val.qt.args]
if (objects.backend == usertypes.Backend.QtWebEngine and
not qtutils.version_check('5.11', compiled=False)):
# WORKAROUND equivalent to
# https://codereview.qt-project.org/#/c/217932/
# Needed for Qt < 5.9.5 and < 5.10.1
argv.append('--disable-shared-workers')
if objects.backend == usertypes.Backend.QtWebEngine:
if not qtutils.version_check('5.11', compiled=False):
# WORKAROUND equivalent to
# https://codereview.qt-project.org/#/c/217932/
# Needed for Qt < 5.9.5 and < 5.10.1
argv.append('--disable-shared-workers')
if config.val.qt.force_software_rendering == 'chromium':
argv.append('--disable-gpu')
if not config.val.content.canvas_reading:
argv.append('--disable-reading-from-canvas')
if not qtutils.version_check('5.11'):
# On Qt 5.11, we can control this via QWebEngineSettings
if not config.val.content.autoplay:
argv.append('--autoplay-policy=user-gesture-required')
if config.val.content.webrtc_public_interfaces_only:
argv.append('--force-webrtc-ip-handling-policy='
'default_public_interface_only')
return argv

View File

@@ -1409,7 +1409,7 @@ class SearchEngineUrl(BaseType):
try:
value.format("")
except (KeyError, IndexError) as e:
except (KeyError, IndexError):
raise configexc.ValidationError(
value, "may not contain {...} (use {{ and }} for literal {/})")
except ValueError as e:

View File

@@ -28,6 +28,18 @@ from qutebrowser.misc import objects
UNSET = object()
class AttributeInfo:
"""Info about a settings attribute."""
def __init__(self, *attributes, converter=None):
self.attributes = attributes
if converter is None:
self.converter = lambda val: val
else:
self.converter = converter
class AbstractSettings:
"""Abstract base class for settings set via QWeb(Engine)Settings."""
@@ -50,12 +62,13 @@ class AbstractSettings:
"""
old_value = self.test_attribute(name)
for attribute in self._ATTRIBUTES[name]:
info = self._ATTRIBUTES[name]
for attribute in info.attributes:
if value is configutils.UNSET:
self._settings.resetAttribute(attribute)
new_value = self.test_attribute(name)
else:
self._settings.setAttribute(attribute, value)
self._settings.setAttribute(attribute, info.converter(value))
new_value = value
return old_value != new_value
@@ -66,7 +79,8 @@ class AbstractSettings:
If the setting resolves to a list of attributes, only the first
attribute is tested.
"""
return self._settings.testAttribute(self._ATTRIBUTES[name][0])
info = self._ATTRIBUTES[name]
return self._settings.testAttribute(info.attributes[0])
def set_font_size(self, name, value):
"""Set the given QWebSettings/QWebEngineSettings font size.

View File

@@ -74,7 +74,7 @@ function tryagain()
</td>
<td style="padding-left: 40px;">
<h1>Unable to load page</h1>
Error while opening {{ url }}<br>
Error while opening {{ url | default('page', true) }}<br>
<p id="error-message-text" style="color: #a31a1a;">{{ error }}</p><br><br>
<form name="bl">

View File

@@ -56,3 +56,5 @@ rules:
no-bitwise: "off"
no-ternary: "off"
max-lines: "off"
multiline-ternary: ["error", "always-multiline"]
max-lines-per-function: "off"

View File

@@ -780,6 +780,18 @@ window._qutebrowser.caret = (function() {
*/
CaretBrowsing.blinkFlag = true;
/**
* Whether we're running on Windows.
* @type {boolean}
*/
CaretBrowsing.isWindows = null;
/**
* Whether we're running on on old Qt 5.7.1.
* @type {boolean}
*/
CaretBrowsing.isOldQt = null;
/**
* Check if a node is a control that normally allows the user to interact
* with it using arrow keys. We won't override the arrow keys when such a
@@ -856,12 +868,24 @@ window._qutebrowser.caret = (function() {
};
CaretBrowsing.injectCaretStyles = function() {
const style = ".CaretBrowsing_Caret {" +
" position: absolute;" +
" z-index: 2147483647;" +
" min-height: 10px;" +
" background-color: #000;" +
"}";
const prefix = CaretBrowsing.isOldQt ? "-webkit-" : "";
const style = `
.CaretBrowsing_Caret {
position: absolute;
z-index: 2147483647;
min-height: 1em;
min-width: 0.2em;
animation: blink 1s step-end infinite;
--inherited-color: inherit;
background-color: var(--inherited-color, #000);
color: var(--inherited-color, #000);
mix-blend-mode: difference;
${prefix}filter: invert(85%);
}
@keyframes blink {
50% { visibility: hidden; }
}
`;
const node = document.createElement("style");
node.innerHTML = style;
document.body.appendChild(node);
@@ -1158,6 +1182,8 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
}
CaretBrowsing.stopAnimation();
};
CaretBrowsing.moveToBlock = function(paragraph, boundary) {
@@ -1176,6 +1202,8 @@ window._qutebrowser.caret = (function() {
window.setTimeout(() => {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
CaretBrowsing.stopAnimation();
};
CaretBrowsing.toggle = function(value) {
@@ -1242,6 +1270,17 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.updateIsCaretVisible();
};
CaretBrowsing.startAnimation = function() {
CaretBrowsing.caretElement.style.animationIterationCount = "infinite";
};
CaretBrowsing.stopAnimation = function() {
CaretBrowsing.caretElement.style.animationIterationCount = 0;
window.setTimeout(() => {
CaretBrowsing.startAnimation();
}, 1000);
};
CaretBrowsing.init = function() {
CaretBrowsing.isWindowFocused = document.hasFocus();
@@ -1279,8 +1318,9 @@ window._qutebrowser.caret = (function() {
return CaretBrowsing.selectionEnabled;
};
funcs.setPlatform = (platform) => {
funcs.setPlatform = (platform, qtVersion) => {
CaretBrowsing.isWindows = platform.startsWith("win");
CaretBrowsing.isOldQt = qtVersion === "5.7.1";
};
funcs.disableCaret = () => {

View File

@@ -145,9 +145,59 @@
}
};
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);
// ====== The actual user script source ====== //
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`
// more conventionally here.
const window = qute_gm_window_proxy;
// ====== The actual user script source ====== //
{{ scriptSource }}
// ====== End User Script ====== //
// ====== End User Script ====== //
};
{% else %}
// ====== The actual user script source ====== //
{{ scriptSource }}
// ====== End User Script ====== //
{% endif %}
})();

View File

@@ -114,7 +114,7 @@ window.loadHistory = (function() {
title.className = "title";
const link = document.createElement("a");
link.href = itemUrl;
link.innerHTML = itemTitle;
link.innerHTML = itemTitle; // Properly escaped in qutescheme.py
const host = document.createElement("span");
host.className = "hostname";
host.innerHTML = link.hostname;

View File

@@ -0,0 +1,30 @@
/**
* Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
*
* This file is part of qutebrowser.
*
* qutebrowser is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* qutebrowser is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* this is a hack based on the QupZilla solution, https://github.com/QupZilla/qupzilla/commit/d3f0d766fb052dc504de2426d42f235d96b5eb60
*
* We go to a qute://print which triggers the print, then we cancel the request.
*/
"use strict";
window.print = function() {
window.location = "qute://print";
};

View File

@@ -243,6 +243,7 @@ window._qutebrowser.webelem = (function() {
};
// Runs a function in a frame until the result is not null, then return
// If no frame succeds, return null
function run_frames(func) {
for (let i = 0; i < window.frames.length; ++i) {
const frame = window.frames[i];
@@ -328,8 +329,10 @@ window._qutebrowser.webelem = (function() {
return serialize_elem(elem);
};
// Function for returning a selection to python (so we can click it)
funcs.find_selected_link = () => {
// Function for returning a selection or focus to python (so we can click
// it). If nothing is selected but there is something focused, returns
// "focused"
funcs.find_selected_focused_link = () => {
const elem = window.getSelection().baseNode;
if (elem) {
return serialize_elem(elem.parentNode);
@@ -342,7 +345,11 @@ window._qutebrowser.webelem = (function() {
}
return null;
});
return serialized_frame_elem;
if (serialized_frame_elem) {
return serialized_frame_elem;
}
return funcs.find_focused() && "focused";
};
funcs.set_value = (id, value) => {

View File

@@ -19,6 +19,8 @@
"""Base class for vim-like key sequence parser."""
import string
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtGui import QKeySequence
@@ -136,7 +138,7 @@ class BaseKeyParser(QObject):
def _match_count(self, sequence, dry_run):
"""Try to match a key as count."""
txt = str(sequence[-1]) # To account for sequences changed above.
if (txt.isdigit() and self._supports_count and
if (txt in string.digits and self._supports_count and
not (not self._count and txt == '0')):
self._debug_log("Trying match as count")
assert len(txt) == 1, txt

View File

@@ -138,6 +138,37 @@ def _key_to_string(key):
'Dead_Hook': 'Hook',
'Dead_Horn': 'Horn',
'Dead_Stroke': '̵',
'Dead_Abovecomma': '̓',
'Dead_Abovereversedcomma': '̔',
'Dead_Doublegrave': '̏',
'Dead_Belowring': '̥',
'Dead_Belowmacron': '̱',
'Dead_Belowcircumflex': '̭',
'Dead_Belowtilde': '̰',
'Dead_Belowbreve': '̮',
'Dead_Belowdiaeresis': '̤',
'Dead_Invertedbreve': '̑',
'Dead_Belowcomma': '̦',
'Dead_Currency': '¤',
'Dead_a': 'a',
'Dead_A': 'A',
'Dead_e': 'e',
'Dead_E': 'E',
'Dead_i': 'i',
'Dead_I': 'I',
'Dead_o': 'o',
'Dead_O': 'O',
'Dead_u': 'u',
'Dead_U': 'U',
'Dead_Small_Schwa': 'ə',
'Dead_Capital_Schwa': 'Ə',
'Dead_Greek': 'Greek',
'Dead_Lowline': '̲',
'Dead_Aboveverticalline': '̍',
'Dead_Belowverticalline': '\u0329',
'Dead_Longsolidusoverlay': '̸',
'Memo': 'Memo',
'ToDoList': 'To Do List',
'Calendar': 'Calendar',

View File

@@ -30,6 +30,9 @@ from qutebrowser.config import config
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import usertypes, log, objreg, utils
INPUT_MODES = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough]
PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]
@attr.s(frozen=True)
class KeyEvent:
@@ -121,6 +124,7 @@ class ModeManager(QObject):
Attributes:
mode: The mode we're currently in.
_win_id: The window ID of this ModeManager
_prev_mode: Mode before a prompt popped up
_parsers: A dictionary of modes and their keyparsers.
_forward_unbound_keys: If we should forward unbound keys.
_releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was
@@ -144,6 +148,7 @@ class ModeManager(QObject):
super().__init__(parent)
self._win_id = win_id
self._parsers = {}
self._prev_mode = usertypes.KeyMode.normal
self.mode = usertypes.KeyMode.normal
self._releaseevents_to_pass = set()
@@ -236,13 +241,17 @@ class ModeManager(QObject):
"""
if not isinstance(mode, usertypes.KeyMode):
raise TypeError("Mode {} is no KeyMode member!".format(mode))
if mode == usertypes.KeyMode.normal:
self.leave(self.mode, reason='enter normal: {}'.format(reason))
return
log.modes.debug("Entering mode {}{}".format(
mode, '' if reason is None else ' (reason: {})'.format(reason)))
if mode not in self._parsers:
raise ValueError("No keyparser for mode {}".format(mode))
prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno)
if self.mode == mode or (self.mode in prompt_modes and
mode in prompt_modes):
if self.mode == mode or (self.mode in PROMPT_MODES and
mode in PROMPT_MODES):
log.modes.debug("Ignoring request as we're in mode {} "
"already.".format(self.mode))
return
@@ -254,6 +263,12 @@ class ModeManager(QObject):
return
log.modes.debug("Overriding mode {}.".format(self.mode))
self.left.emit(self.mode, mode, self._win_id)
if mode in PROMPT_MODES and self.mode in INPUT_MODES:
self._prev_mode = self.mode
else:
self._prev_mode = usertypes.KeyMode.normal
self.mode = mode
self.entered.emit(mode, self._win_id)
@@ -301,6 +316,9 @@ class ModeManager(QObject):
self.clear_keychain()
self.mode = usertypes.KeyMode.normal
self.left.emit(mode, self.mode, self._win_id)
if mode in PROMPT_MODES:
self.enter(self._prev_mode,
reason='restore mode before {}'.format(mode.name))
@cmdutils.register(instance='mode-manager', name='leave-mode',
not_modes=[usertypes.KeyMode.normal], scope='window')

View File

@@ -492,6 +492,7 @@ class MainWindow(QWidget):
self.tabbed_browser.cur_fullscreen_requested.connect(status.maybe_hide)
# command input / completion
mode_manager.entered.connect(self.tabbed_browser.on_mode_entered)
mode_manager.left.connect(self.tabbed_browser.on_mode_left)
cmd.clear_completion_selection.connect(
completion_obj.on_clear_completion_selection)

View File

@@ -24,7 +24,6 @@ import html
import collections
import attr
import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex,
QItemSelectionModel, QObject, QEventLoop)
from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit,
@@ -36,6 +35,7 @@ from qutebrowser.config import config
from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message
from qutebrowser.keyinput import modeman
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.qt import sip
prompt_queue = None

View File

@@ -22,7 +22,7 @@
import functools
import attr
from PyQt5.QtWidgets import QSizePolicy, QWidget
from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl
from PyQt5.QtGui import QIcon
@@ -238,6 +238,10 @@ class TabbedBrowser(QWidget):
functools.partial(self.on_window_close_requested, tab))
tab.renderer_process_terminated.connect(
functools.partial(self._on_renderer_process_terminated, tab))
tab.audio.muted_changed.connect(
functools.partial(self._on_audio_changed, tab))
tab.audio.recently_audible_changed.connect(
functools.partial(self._on_audio_changed, tab))
tab.new_tab_requested.connect(self.tabopen)
if not self.private:
web_history = objreg.get('web-history')
@@ -458,6 +462,8 @@ class TabbedBrowser(QWidget):
"related {}, idx {}".format(
url, background, related, idx))
prev_focus = QApplication.focusWidget()
if (config.val.tabs.tabs_are_windows and self.widget.count() > 0 and
not ignore_tabs_are_windows):
window = mainwindow.MainWindow(private=self.private)
@@ -487,11 +493,22 @@ class TabbedBrowser(QWidget):
tab.resize(self.widget.currentWidget().size())
self.widget.tab_index_changed.emit(self.widget.currentIndex(),
self.widget.count())
# Refocus webview in case we lost it by spawning a bg tab
self.widget.currentWidget().setFocus()
else:
self.widget.setCurrentWidget(tab)
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
# Still seems to be needed with Qt 5.11.1
tab.setFocus()
mode = modeman.instance(self._win_id).mode
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]:
# If we were in a command prompt, restore old focus
# The above commands need to be run to switch tabs
if prev_focus is not None:
prev_focus.setFocus()
tab.show()
self.new_tab.emit(tab, idx)
return tab
@@ -628,24 +645,32 @@ class TabbedBrowser(QWidget):
if config.val.tabs.tabs_are_windows:
self.widget.window().setWindowIcon(icon)
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode):
"""Save input mode when tabs.mode_on_change = restore."""
if (config.val.tabs.mode_on_change == 'restore' and
mode in modeman.INPUT_MODES):
tab = self.widget.currentWidget()
if tab is not None:
tab.data.input_mode = mode
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Give focus to current tab if command mode was left."""
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]:
widget = self.widget.currentWidget()
widget = self.widget.currentWidget()
if widget is None:
return
if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES:
log.modes.debug("Left status-input mode, focusing {!r}".format(
widget))
if widget is None:
return
widget.setFocus()
if config.val.tabs.mode_on_change == 'restore':
widget.data.input_mode = usertypes.KeyMode.normal
@pyqtSlot(int)
def on_current_changed(self, idx):
"""Set last-focused-tab and leave hinting mode when focus changed."""
mode_on_change = config.val.tabs.mode_on_change
modes_to_save = [usertypes.KeyMode.insert,
usertypes.KeyMode.passthrough]
if idx == -1 or self.shutting_down:
# closing the last tab (before quitting) or shutting down
return
@@ -654,26 +679,28 @@ class TabbedBrowser(QWidget):
log.webview.debug("on_current_changed got called with invalid "
"index {}".format(idx))
return
if self._now_focused is not None and mode_on_change == 'restore':
current_mode = modeman.instance(self._win_id).mode
if current_mode not in modes_to_save:
current_mode = usertypes.KeyMode.normal
self._now_focused.data.input_mode = current_mode
log.modes.debug("Current tab changed, focusing {!r}".format(tab))
tab.setFocus()
modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret]
if mode_on_change != 'persist':
modes_to_leave += modes_to_save
mm_instance = modeman.instance(self._win_id)
current_mode = mm_instance.mode
log.modes.debug("Mode before tab change: {} (mode_on_change = {})"
.format(current_mode.name, mode_on_change))
if mode_on_change == 'normal':
modes_to_leave += modeman.INPUT_MODES
for mode in modes_to_leave:
modeman.leave(self._win_id, mode, 'tab changed', maybe=True)
if mode_on_change == 'restore':
modeman.enter(self._win_id, tab.data.input_mode,
'restore input mode for tab')
if (mode_on_change == 'restore' and
current_mode not in modeman.PROMPT_MODES):
modeman.enter(self._win_id, tab.data.input_mode, 'restore')
if self._now_focused is not None:
objreg.register('last-focused-tab', self._now_focused, update=True,
scope='window', window=self._win_id)
log.modes.debug("Mode after tab change: {} (mode_on_change = {})"
.format(current_mode.name, mode_on_change))
self._now_focused = tab
self.current_tab_changed.emit(tab)
QTimer.singleShot(0, self._update_window_title)
@@ -733,6 +760,17 @@ class TabbedBrowser(QWidget):
self._update_window_title('scroll_pos')
self.widget.update_tab_title(idx, 'scroll_pos')
def _on_audio_changed(self, tab, _muted):
"""Update audio field in tab when mute or recentlyAudible changed."""
try:
idx = self._tab_index(tab)
except TabDeletedError:
# We can get signals for tabs we already deleted...
return
self.widget.update_tab_title(idx, 'audio')
if idx == self.widget.currentIndex():
self._update_window_title('audio')
def _on_renderer_process_terminated(self, tab, status, code):
"""Show an error when a renderer process terminated."""
if status == browsertab.TerminationStatus.normal:

View File

@@ -33,6 +33,7 @@ from PyQt5.QtGui import QIcon, QPalette, QColor
from qutebrowser.utils import qtutils, objreg, utils, usertypes, log
from qutebrowser.config import config
from qutebrowser.misc import objects
from qutebrowser.browser import browsertab
PixelMetrics = enum.IntEnum('PixelMetrics', ['icon_padding'],
@@ -172,6 +173,16 @@ class TabWidget(QTabWidget):
fields['perc_raw'] = tab.progress()
fields['backend'] = objects.backend.name
fields['private'] = ' [Private Mode] ' if tab.private else ''
try:
if tab.audio.is_muted():
fields['audio'] = '[M] '
elif tab.audio.is_recently_audible():
fields['audio'] = '[A] '
else:
fields['audio'] = ''
except browsertab.WebTabError:
# Muting is only implemented with QtWebEngine
fields['audio'] = ''
if tab.load_status() == usertypes.LoadStatus.loading:
fields['perc'] = '[{}%] '.format(tab.progress())

View File

@@ -185,7 +185,7 @@ def _handle_nouveau_graphics():
return
button = _Button("Force software rendering", 'qt.force_software_rendering',
True)
'chromium')
_show_dialog(
backend=usertypes.Backend.QtWebEngine,
because="you're using Nouveau graphics",
@@ -194,9 +194,9 @@ def _handle_nouveau_graphics():
"<p>This allows you to use the newer QtWebEngine backend (based "
"on Chromium) but could have noticeable performance impact "
"(depending on your hardware). "
"This sets the <i>qt.force_software_rendering = True</i> option "
"(if you have a <i>config.py</i> file, you'll need to set this "
"manually).</p>",
"This sets the <i>qt.force_software_rendering = 'chromium'</i> "
"option (if you have a <i>config.py</i> file, you'll need to set "
"this manually).</p>",
buttons=[button],
)
@@ -213,33 +213,41 @@ def _handle_wayland():
if platform not in ['wayland', 'wayland-egl']:
return
has_qt511 = qtutils.version_check('5.11', compiled=False)
if has_qt511 and config.val.qt.force_software_rendering == 'chromium':
return
buttons = []
text = "<p>You can work around this in one of the following ways:</p>"
if 'DISPLAY' in os.environ:
# XWayland is available, but QT_QPA_PLATFORM=wayland is set
button = _Button("Force XWayland", 'qt.force_platform', 'xcb')
_show_dialog(
backend=usertypes.Backend.QtWebEngine,
because="you're using Wayland",
text="<p>There are two ways to fix this:</p>"
"<p><b>Force Qt to use XWayland</b></p>"
buttons.append(_Button("Force XWayland", 'qt.force_platform', 'xcb'))
text += ("<p><b>Force Qt to use XWayland</b></p>"
"<p>This allows you to use the newer QtWebEngine backend "
"(based on Chromium). "
"This sets the <i>qt.force_platform = 'xcb'</i> option "
"(if you have a <i>config.py</i> file, you'll need to set "
"this manually).</p>",
buttons=[button],
)
"this manually).</p>")
else:
# XWayland is unavailable
_show_dialog(
backend=usertypes.Backend.QtWebEngine,
because="you're using Wayland without XWayland",
text="<p>There are two ways to fix this:</p>"
"<p><b>Set up XWayland</b></p>"
"<p>This allows you to use the newer QtWebEngine backend "
"(based on Chromium). "
)
text.append("<p><b>Set up XWayland</b></p>"
"<p>This allows you to use the newer QtWebEngine backend "
"(based on Chromium). ")
raise utils.Unreachable
if has_qt511:
buttons.append(_Button("Force software rendering",
'qt.force_software_rendering',
'chromium'))
text += ("<p><b>Forcing software rendering</b></p>"
"<p>This allows you to use the newer QtWebEngine backend "
"(based on Chromium) but could have noticeable performance "
"impact (depending on your hardware). This sets the "
"<i>qt.force_software_rendering = 'chromium'</i> option "
"(if you have a <i>config.py</i> file, you'll need to set "
"this manually).</p>")
_show_dialog(backend=usertypes.Backend.QtWebEngine,
because="you're using Wayland", text=text, buttons=buttons)
@attr.s

View File

@@ -158,6 +158,11 @@ class _CrashDialog(QDialog):
self._init_info_text()
self._init_buttons()
def keyPressEvent(self, e):
"""Prevent closing :report dialogs when pressing <Escape>."""
if config.val.input.escape_quits_reporter or e.key() != Qt.Key_Escape:
super().keyPressEvent(e)
def __repr__(self):
return utils.get_repr(self)
@@ -293,7 +298,7 @@ class _CrashDialog(QDialog):
self._paste_text = '\n\n'.join(lines)
try:
user = getpass.getuser()
except Exception as e:
except Exception:
log.misc.exception("Error while getting user")
user = 'unknown'
try:

View File

@@ -36,7 +36,6 @@ except ImportError:
import attr
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject,
QSocketNotifier, QTimer, QUrl)
from PyQt5.QtWidgets import QApplication
from qutebrowser.commands import cmdutils
from qutebrowser.misc import earlyinit, crashdialog, ipc
@@ -206,7 +205,6 @@ class CrashHandler(QObject):
gracefully.
"""
exc = (exctype, excvalue, tb)
qapp = QApplication.instance()
if not self._quitter.quit_status['crash']:
log.misc.error("ARGH, there was an exception while the crash "
@@ -223,14 +221,7 @@ class CrashHandler(QObject):
if is_ignored_exception or 'pdb-postmortem' in self._args.debug_flags:
# pdb exit, KeyboardInterrupt, ...
status = 0 if is_ignored_exception else 2
try:
self._quitter.shutdown(status)
return
except Exception:
log.init.exception("Error while shutting down")
qapp.quit()
return
sys.exit(usertypes.Exit.exception)
self._quitter.quit_status['crash'] = False
info = self._get_exception_info()

View File

@@ -246,7 +246,7 @@ def configure_pyqt():
from PyQt5.QtCore import pyqtRemoveInputHook
pyqtRemoveInputHook()
import sip
from qutebrowser.qt import sip
try:
# Added in sip 4.19.4
sip.enableoverflowchecking(True)

View File

@@ -498,7 +498,7 @@ def send_or_listen(args):
server = IPCServer(socketname)
server.listen()
return server
except AddressInUseError as e:
except AddressInUseError:
# This could be a race condition...
log.init.debug("Got AddressInUseError, trying again.")
time.sleep(0.5)

View File

@@ -266,6 +266,47 @@ class WrapperLayout(QLayout):
self._widget.deleteLater()
class PseudoLayout(QLayout):
"""A layout which isn't actually a real layout.
This is used to replace QWebEngineView's internal layout, as a WORKAROUND
for https://bugreports.qt.io/browse/QTBUG-68224 and other related issues.
This is partly inspired by https://codereview.qt-project.org/#/c/230894/
which does something similar as part of Qt.
"""
def addItem(self, item):
assert self.parent() is not None
item.widget().setParent(self.parent())
def removeItem(self, item):
item.widget().setParent(None)
def count(self):
return 0
def itemAt(self, _pos):
return None
def widget(self):
return self.parent().render_widget()
def setGeometry(self, rect):
"""Resize the render widget when the view is resized."""
widget = self.widget()
if widget is not None:
widget.setGeometry(rect)
def sizeHint(self):
"""Make sure the view has the sizeHint of the render widget."""
widget = self.widget()
if widget is not None:
return widget.sizeHint()
return QSize()
class FullscreenNotification(QLabel):
"""A label telling the user this page is now fullscreen."""

View File

@@ -24,7 +24,6 @@ import os.path
import itertools
import urllib
import sip
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
from PyQt5.QtWidgets import QApplication
import yaml
@@ -35,6 +34,7 @@ from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import config, configfiles
from qutebrowser.completion.models import miscmodels
from qutebrowser.mainwindow import mainwindow
from qutebrowser.qt import sip
default = object() # Sentinel value

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