Compare commits

..

46 Commits
nsis ... v1.3.3

Author SHA1 Message Date
Florian Bruhin
ad9b50601c Release v1.3.3 2018-06-21 23:30:51 +02:00
Florian Bruhin
c88a94f1cc Update changelog
(cherry picked from commit 66fc3a30dd)
2018-06-21 23:30:38 +02:00
Florian Bruhin
13332cd2cf Fix shadowing of 'html' name
(cherry picked from commit 0864ad4069)
2018-06-21 22:34:05 +02:00
Florian Bruhin
48808e59cb Re-add waiting for QQuickWidget
Apparently this is still needed on some PyQt versions.

(cherry picked from commit 9a5439e5d0)
2018-06-21 22:23:58 +02:00
Florian Bruhin
5571ef4e62 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.

(cherry picked from commit c87757a913)
2018-06-21 22:23:42 +02:00
Florian Bruhin
b4de889df9 Update changelog 2018-06-21 21:45:43 +02:00
Florian Bruhin
4c9360237f Fix XSS issue on qute://history
Fixes #4011

(cherry picked from commit 5a7869f2fe)
2018-06-21 21:43:53 +02:00
Florian Bruhin
10538738e0 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.

(cherry picked from commit 62d8b5b574)
2018-06-21 21:43:47 +02:00
Florian Bruhin
bd5f84bddf Always clear searches between page loads
Looks like this wasn't properly fixed in Qt for some reason.
Fixes #3693
See #2728 and bef372e5f5

(cherry picked from commit 3399f2df96)
2018-06-21 21:43:30 +02:00
Florian Bruhin
93ae6ad592 Properly add QtQuickWidgets dependency
(cherry picked from commit e5405f0ae9)
2018-06-21 00:22:36 +02:00
Florian Bruhin
146f8e72ae 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.

(cherry picked from commit 67c67db230)
2018-06-11 21:45:09 +02:00
Florian Bruhin
f61662fa52 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).

(cherry picked from commit b63e06561d)
2018-06-11 14:02:26 +02:00
Florian Bruhin
e9b4c2a66e Release v1.3.2 2018-06-10 15:58:54 +02:00
Florian Bruhin
6a7ab7edb3 Remove unused import
(cherry picked from commit 7949335a2b)
2018-06-09 21:45:13 +02:00
Florian Bruhin
f7f96484e8 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.

(cherry picked from commit ec88c15390)
2018-06-09 20:11:00 +02:00
Florian Bruhin
840d2e4423 Further simplify getting focusProxy children
(cherry picked from commit d32d541ac0)
2018-06-08 17:11:33 +02:00
Florian Bruhin
e54f2a090a Improve RWHV typecheck for focusProxy
(cherry picked from commit cc497bf2ea)
2018-06-08 15:20:33 +02:00
Florian Bruhin
55ce4b7ed2 Exclude QMenu when trying to find the missing focusProxy
(cherry picked from commit 9725d9ce33)
2018-06-08 15:20:31 +02:00
Florian Bruhin
3daf823da8 Show children in focusProxy workaround
(cherry picked from commit 1531961aeb)
2018-06-08 15:20:29 +02:00
Florian Bruhin
72feb2c19f Fix check for reloads on Qt < 5.11
Equivalent commit on master: 91b4106dcf
This was broken in 900efe4a36
2018-06-08 08:57:26 +02:00
Florian Bruhin
6d04508490 Remove unused import
(cherry picked from commit 4614ad5063)
2018-06-07 18:02:11 +02:00
Florian Bruhin
93ebd846ab 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.

(cherry picked from commit b1506274c5)
2018-06-07 16:04:37 +02:00
Florian Bruhin
9ee473a54c Go back to using an invalid scheme for invalid_link.html
Otherwise, this breaks the tests on Qt 5.10

(cherry picked from commit 596041c40e)
2018-06-07 15:42:02 +02:00
Florian Bruhin
d1cced0da4 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

(cherry picked from commit 89f4333df1)
2018-06-07 14:36:12 +02:00
Florian Bruhin
22644c41da Add a workaround for chrome-error:// loops on Qt 5.11
See #3661

(cherry picked from commit 0c0d204fd4)
2018-06-07 14:36:10 +02:00
Florian Bruhin
6b5857ef7d 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

(cherry picked from commit 999513d5d8)
2018-06-07 14:36:07 +02:00
Florian Bruhin
b0f4cc6924 Use a valid scheme in invalid_link.html
This is to avoid triggering QTBUG-63378 which fails differently with a custom
scheme.

See #3661

(cherry picked from commit d059197bc9)
2018-06-07 14:36:03 +02:00
Florian Bruhin
218637af84 Reenable Qt 5.11 tests on Travis
Fixes #3661

(cherry picked from commit eb6478dd3e)
2018-06-07 14:35:56 +02:00
Florian Bruhin
dd84845f01 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

(cherry picked from commit 8cc3804119)
2018-06-06 21:14:20 +02:00
Florian Bruhin
cf53f9042a Only set PseudoLayout with Qt 5.11
(cherry picked from commit 456fdc55cc)
2018-06-06 20:30:42 +02:00
Florian Bruhin
f48266f72f Fix lint
(cherry picked from commit 7e31897dcc)
2018-06-06 20:30:39 +02:00
Florian Bruhin
5dac848968 Handle resizing via PseudoLayout
This fixes the scenario where we just get a grey view when opening a link in a
tab from DuckDuckGo.

(cherry picked from commit 5147fc832c)
2018-06-06 20:30:37 +02:00
Florian Bruhin
341aa1e700 Try harder to get the RenderWidgetHostViewQt
(cherry picked from commit ec6c5ebb69)
2018-06-06 20:30:34 +02:00
Florian Bruhin
ebf81c06ae Initial proof of concept for pseudo layout
Fixes #3920 - hopefully properly this time...

(cherry picked from commit cee88cd7ca)
2018-06-06 20:30:31 +02:00
Florian Bruhin
a3eb8d6561 travis: Allow Archlinux to fail for now
See #3661

(cherry picked from commit 6fc3546923)
2018-05-29 13:19:36 +02:00
Florian Bruhin
af4b02bf46 setup.py: Set long_description_content_type
Otherwise, Warehouse (new PyPI) refuses the upload...

(cherry picked from commit 52c44d3da6)
2018-05-29 11:23:48 +02:00
Florian Bruhin
ac29c579ff Release v1.3.1 2018-05-29 11:14:01 +02:00
Florian Bruhin
db5ec363cd Update changelog for v1.3.1 2018-05-29 11:12:33 +02:00
Florian Bruhin
f352c72d1d Fix lint
(cherry picked from commit 12e0edbcd0)
2018-05-28 07:44:49 +02:00
Florian Bruhin
b1f1a0cafa Add some more logging for #3920
(cherry picked from commit 17cfb0d39c)
2018-05-28 07:44:44 +02:00
Florian Bruhin
749056ff90 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.

(cherry picked from commit 7162f15348)
2018-05-28 07:44:36 +02:00
Florian Bruhin
e7b00ace73 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

(cherry picked from commit 29ad252278)
2018-05-22 12:29:09 +02:00
Florian Bruhin
442bdd4a4f 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.

(cherry picked from commit 71ad8bdb47)
2018-05-22 09:41:13 +02:00
Florian Bruhin
900efe4a36 Fix reload for JavaScript support on Qt 5.11
(cherry picked from commit 6ccd69dad2)
2018-05-17 17:54:39 +02:00
Florian Bruhin
f8a78a0962 Add workaround for the "split page" Qt bug (QTBUG-68224)
Fixes #3920

(cherry picked from commit 44d26f77a5)
2018-05-17 14:25:38 +02:00
Florian Bruhin
60e8abaa89 Improve configuration docs
(cherry picked from commit 20efaeff19)
2018-05-17 14:25:33 +02:00
145 changed files with 877 additions and 2877 deletions

View File

@@ -5,15 +5,15 @@ cache:
build: off
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python36-x64\python.exe
PYTHON: C:\Python36\python.exe
matrix:
- TESTENV: py36-pyqt511
- TESTENV: py36-pyqt510
- TESTENV: pylint
install:
- '%PYTHON% -m pip install -U pip'
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
- 'set PATH=C:\Python36-x64;%PATH'
- 'set PATH=%PATH%;C:\Python36'
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/**/*.py : D100,D101,D401
/tests/**/test_*.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,8 +1,3 @@
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,4 +41,3 @@ 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,19 +18,20 @@ matrix:
python: 3.5
env: TESTENV=py35-pyqt571
- os: linux
env: TESTENV=py36-pyqt59
env: TESTENV=py36-pyqt59-cov
- os: linux
env: TESTENV=py36-pyqt510
- os: linux
env: TESTENV=py36-pyqt511-cov
# https://github.com/travis-ci/travis-ci/issues/9069
- os: linux
python: 3.7
# We need a newer Xvfb as a WORKAROUND for:
# https://bugreports.qt.io/browse/QTBUG-64928
sudo: required
dist: xenial
env: TESTENV=py37-pyqt511
addons:
apt:
sources:
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
packages:
- xvfb
- os: osx
env: TESTENV=py37 OSX=sierra
env: TESTENV=py36 OSX=sierra
osx_image: xcode9.2
language: generic
# https://github.com/qutebrowser/qutebrowser/issues/2013

View File

@@ -1,5 +1,6 @@
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
@@ -26,18 +27,20 @@ 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,6 +9,8 @@ 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"]
@@ -97,7 +99,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.11.1 recommended) with the following modules:
* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
@@ -107,7 +109,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.11.2 recommended) for Python 3
(5.10 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,132 +15,6 @@ 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
------
@@ -191,7 +65,6 @@ Fixed
- 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,11 +5,6 @@ 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
@@ -90,16 +85,6 @@ 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
----------------
@@ -203,8 +188,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
@@ -581,23 +566,6 @@ 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
-----------------
@@ -694,6 +662,8 @@ 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
@@ -715,8 +685,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; 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).
* 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).
* 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,7 +111,6 @@ 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.
@@ -531,7 +530,7 @@ Show help about a command or setting.
[[hint]]
=== hint
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*] [*--first*]
Syntax: +:hint [*--mode* 'mode'] [*--add-history*] [*--rapid*]
['group'] ['target'] ['args' ['args' ...]]+
Start hinting.
@@ -601,7 +600,6 @@ 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.
@@ -1285,13 +1283,6 @@ 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.
@@ -1365,16 +1356,12 @@ Show version information.
[[view-source]]
=== view-source
Syntax: +:view-source [*--edit*] [*--pygments*]+
Syntax: +:view-source [*--edit*]+
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

@@ -390,12 +390,6 @@ 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
^^^^^^^^^^^^^^^^^^^^^^

View File

@@ -109,15 +109,13 @@
|<<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.desktop_capture,content.desktop_capture>>|Allow websites to share screen content.
|<<content.developer_extras,content.developer_extras>>|Enable extra tools for Web developers.
|<<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.
@@ -146,17 +144,14 @@
|<<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.
@@ -205,7 +200,6 @@
|<<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.
@@ -493,7 +487,6 @@ 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]+
@@ -507,7 +500,6 @@ 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]+
@@ -570,7 +562,6 @@ 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}]+
@@ -1471,19 +1462,6 @@ 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.
@@ -1518,18 +1496,6 @@ 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.
@@ -1540,12 +1506,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. On QtWebEngine, this is the same as no-3rdparty.
* +no-unknown-3rdparty+: Accept cookies from the same origin only, unless a cookie is already set for the domain.
* +never+: Don't accept cookies at all.
Default: +pass:[no-3rdparty]+
On QtWebEngine, this setting requires Qt 5.11 or newer.
This setting is only available with the QtWebKit backend.
[[content.cookies.store]]
=== content.cookies.store
@@ -1565,22 +1531,16 @@ Type: <<types,String>>
Default: +pass:[iso-8859-1]+
[[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".
[[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.
This setting supports URL patterns.
Type: <<types,Bool>>
Type: <<types,BoolAsk>>
Default: +pass:[false]+
Valid values:
* +true+
* +false+
* +ask+
Default: +pass:[ask]+
This setting is only available with the QtWebKit backend.
[[content.dns_prefetch]]
=== content.dns_prefetch
@@ -1611,8 +1571,6 @@ 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:
@@ -1626,9 +1584,6 @@ 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>>
@@ -1638,8 +1593,6 @@ Default: +pass:[en-US,en]+
=== content.headers.custom
Custom headers for qutebrowser HTTP requests.
This setting supports URL patterns.
Type: <<types,Dict>>
Default: empty
@@ -1649,8 +1602,6 @@ 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]+
@@ -1675,9 +1626,6 @@ 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>>
@@ -1857,8 +1805,6 @@ Default: +pass:[true]+
=== content.media_capture
Allow websites to record audio/video.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
@@ -1884,8 +1830,6 @@ Default: empty
=== content.notifications
Allow websites to show notifications.
This setting supports URL patterns.
Type: <<types,BoolAsk>>
Valid values:
@@ -1909,26 +1853,6 @@ 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.
@@ -1983,32 +1907,10 @@ 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:
@@ -2037,19 +1939,6 @@ 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).
@@ -2496,14 +2385,6 @@ 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.
@@ -2696,19 +2577,12 @@ 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 and can be useful in other scenarios related to graphic issues.
This is needed for QtWebEngine to work with Nouveau drivers.
This setting requires a restart.
Type: <<types,String>>
Type: <<types,Bool>>
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]+
Default: +pass:[false]+
This setting is only available with the QtWebEngine backend.
@@ -3170,12 +3044,11 @@ 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:[{audio}{index}: {title}]+
Default: +pass:[{index}: {title}]+
[[tabs.title.format_pinned]]
=== tabs.title.format_pinned

View File

@@ -57,8 +57,7 @@ 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 (make sure to install all the
downloaded qutebrowser deb files in one apt command):
After downloading, install the packages:
----
# apt install ./python3-pypeg2_*_all.deb
@@ -382,8 +381,8 @@ $ git clone https://github.com/qutebrowser/qutebrowser.git
$ cd qutebrowser
----
Installing dependencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Installing depdendencies (including Qt)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
@@ -392,10 +391,6 @@ 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
*-B* 'BASEDIR', *--basedir* '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. This is not needed anymore since Qt 5.11 where the inspector is always enabled and secure.
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.
=== 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).
*-d*, *--debug*::
*--debug*::
Turn on debugging options.
*--json-logging*::
@@ -87,7 +87,7 @@ show it.
*--nowindow*::
Don't show the main window.
*-T*, *--temp-basedir*::
*--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.
*-D* 'DEBUG_FLAGS', *--debug-flag* 'DEBUG_FLAGS'::
*--debug-flag* 'DEBUG_FLAGS'::
Pass name of debugging feature to be turned on.
// QUTE_OPTIONS_END

View File

@@ -15,8 +15,6 @@ 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,9 +40,4 @@
<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,7 +1,6 @@
[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,8 +40,6 @@ 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,8 +59,7 @@ exe = EXE(pyz,
debug=False,
strip=False,
upx=False,
console=False,
version='misc/file_version_info.txt')
console=False )
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.7
requests==2.19.1
idna==2.6
requests==2.18.4
urllib3==1.22

View File

@@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
attrs==18.1.0
attrs==17.4.0
flake8==3.5.0
flake8-bugbear==18.2.0
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
flake8-builtins==1.3.1
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.7.0
pep8-naming==0.5.0
pycodestyle==2.3.1 # rq.filter: < 2.4.0
pydocstyle==2.1.1
pyflakes==2.0.0
pyflakes==1.6.0
six==1.11.0
snowballstemmer==1.2.1
typing==3.6.4

View File

@@ -1,6 +1,6 @@
flake8
flake8-bugbear
flake8-builtins!=1.4.0
flake8-builtins
flake8-comprehensions
flake8-copyright
flake8-debugger
@@ -18,6 +18,3 @@ 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.2.0
setuptools==39.1.0
six==1.11.0
wheel==0.31.1
wheel==0.31.0

View File

@@ -4,14 +4,14 @@
certifi==2018.4.16
chardet==3.0.4
github3.py==1.1.0
idna==2.7
idna==2.6
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.3
python-dateutil==2.7.2
./scripts/dev/pylint_checkers
requests==2.19.1
requests==2.18.4
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.5
astroid==1.6.3
certifi==2018.4.16
chardet==3.0.4
github3.py==1.1.0
idna==2.7
idna==2.6
isort==4.3.4
lazy-object-proxy==1.3.1
mccabe==0.6.1
pylint==1.9.2
python-dateutil==2.7.3
pylint==1.8.4
python-dateutil==2.7.2
./scripts/dev/pylint_checkers
requests==2.19.1
requests==2.18.4
six==1.11.0
uritemplate==3.0.0
urllib3==1.22

View File

@@ -0,0 +1,4 @@
# 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

@@ -0,0 +1,2 @@
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.11.2
PyQt5-sip==4.19.11
PyQt5==5.10.1
sip==4.19.8

View File

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

View File

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

View File

@@ -35,4 +35,8 @@ 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
git+https://github.com/yaml/pyyaml.git
# Fails to build:
# gcc: error: ext/_yaml.c: No such file or directory
# hg+https://bitbucket.org/xi/pyyaml
PyYAML==3.12

View File

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

View File

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

View File

@@ -1,172 +0,0 @@
#!/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}
MPV_FLAGS=${MPV_FLAGS:- --force-window --no-terminal --keep-open=yes --ytdl --ytdl-raw-options=yes-playlist=}
IFS=" " read -r -a video_command <<< "$MPV_COMMAND $MPV_FLAGS"
js() {

View File

@@ -30,7 +30,6 @@ 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: .*
@@ -63,8 +62,4 @@ 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, 4, 0)
__version_info__ = (1, 3, 3)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@@ -22,22 +22,18 @@
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)
@@ -99,8 +95,6 @@ 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).
@@ -112,7 +106,6 @@ 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)
@@ -157,31 +150,10 @@ class AbstractAction:
raise WebTabError("{} is not a valid web action!".format(name))
self._widget.triggerPageAction(member)
def show_source(self,
pygments=False): # pylint: disable=redefined-outer-name
def show_source(self):
"""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:
@@ -442,13 +414,6 @@ 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
@@ -634,33 +599,6 @@ 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.
@@ -755,7 +693,6 @@ 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()
@@ -816,7 +753,6 @@ 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()
@@ -893,6 +829,10 @@ 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,6 +566,12 @@ 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
@@ -1455,7 +1461,6 @@ 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:
@@ -1516,15 +1521,11 @@ class CommandDispatcher:
)
@cmdutils.register(instance='command-dispatcher', scope='window')
def view_source(self, edit=False, pygments=False):
def view_source(self, edit=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:
@@ -1532,15 +1533,14 @@ class CommandDispatcher:
except cmdexc.CommandError as e:
message.error(str(e))
return
if current_url.scheme() == 'view-source' or tab.data.viewing_source:
if current_url.scheme() == 'view-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(pygments)
tab.action.show_source()
@cmdutils.register(instance='command-dispatcher', scope='window',
debug=True)
@@ -1674,7 +1674,7 @@ class CommandDispatcher:
"""
try:
elem.set_value(text)
except webelem.OrphanedError:
except webelem.OrphanedError as e:
message.error('Edited element vanished')
ed.backup()
except webelem.Error as e:
@@ -2230,20 +2230,3 @@ 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,6 +29,7 @@ import pathlib
import tempfile
import enum
import sip
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
QTimer, QAbstractListModel, QUrl)
@@ -36,7 +37,6 @@ 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,10 +31,9 @@ import attr
from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from qutebrowser.utils import (log, standarddir, jinja, objreg, utils,
javascript, urlmatch, version, usertypes)
javascript, urlmatch)
from qutebrowser.commands import cmdutils
from qutebrowser.browser import downloads
from qutebrowser.misc import objects
def _scripts_dir():
@@ -58,7 +57,6 @@ 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
@@ -78,8 +76,6 @@ 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>.*)'
@@ -112,18 +108,13 @@ 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,
use_proxy=use_proxy)
scriptSource=self._code)
def _meta_json(self):
return json.dumps({

View File

@@ -22,7 +22,6 @@
import collections
import functools
import math
import os
import re
import html
import enum
@@ -155,7 +154,6 @@ 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.
@@ -168,14 +166,12 @@ 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."""
@@ -244,18 +240,7 @@ class HintActions:
if url.scheme() == 'mailto':
flags |= QUrl.RemoveScheme
urlstr = url.toString(flags)
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)
utils.set_clipboard(urlstr, selection=sel)
msg = "Yanked URL to {}: {}".format(
"primary selection" if sel else "clipboard",
@@ -627,9 +612,6 @@ 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()
@@ -638,8 +620,7 @@ 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,
first=False):
*args, win_id, mode=None, add_history=False, rapid=False):
"""Start hinting.
Args:
@@ -650,7 +631,6 @@ 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.
@@ -714,8 +694,7 @@ 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.yank, Target.yank_primary]:
Target.download, Target.normal, Target.current]:
pass
elif target == Target.tab and config.val.tabs.background:
pass
@@ -734,7 +713,6 @@ 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:
@@ -929,9 +907,6 @@ 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, pyqtSignal
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
@@ -52,11 +52,6 @@ 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',
@@ -162,7 +157,6 @@ 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.
@@ -174,7 +168,6 @@ 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,8 +87,6 @@ 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):
@@ -101,4 +99,3 @@ 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, qtutils, objreg
from qutebrowser.utils import message, log, usertypes, qtutils
from qutebrowser.keyinput import modeman
@@ -40,12 +40,11 @@ class ChildEventFilter(QObject):
_widget: The widget expected to send out childEvents.
"""
def __init__(self, eventfilter, widget, win_id, parent=None):
def __init__(self, eventfilter, widget, 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."""
@@ -58,22 +57,7 @@ class ChildEventFilter(QObject):
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)
QTimer.singleShot(0, self._widget.setFocus)
elif event.type() == QEvent.ChildRemoved:
child = event.child()
log.mouse.debug("{}: removed child {}".format(obj, child))

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, debug
from qutebrowser.utils import message, usertypes, log, urlutils, utils
from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
@@ -307,14 +307,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
"""Handle QNetworkReply errors."""
if code == QNetworkReply.OperationCanceledError:
return
if self._reply is None:
error = "Unknown error: {}".format(
debug.qenum_key(QNetworkReply, code))
else:
error = self._reply.errorString()
self._die(error)
self._die(self._reply.errorString())
@pyqtSlot()
def _on_read_timer_timeout(self):

View File

@@ -34,6 +34,7 @@ import urllib
import collections
import pkg_resources
import sip
from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
@@ -41,7 +42,6 @@ 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"
@@ -178,7 +178,7 @@ def data_for_url(url):
except OSError as e:
# FIXME:qtwebengine how to handle this?
raise QuteSchemeOSError(e)
except QuteSchemeError:
except QuteSchemeError as e:
raise
assert mimetype is not None, url
@@ -242,7 +242,7 @@ 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,
return [{"url": html.escape(e.url),
"title": html.escape(e.title) or html.escape(e.url),
"time": e.atime} for e in entries]

View File

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

View File

@@ -28,10 +28,6 @@ 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()
@@ -41,8 +37,5 @@ 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

@@ -1,48 +0,0 @@
# 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,22 +19,20 @@
"""A request interceptor taking care of adblocking and custom headers."""
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
QWebEngineUrlRequestInfo)
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
from qutebrowser.config import config
from qutebrowser.browser import shared
from qutebrowser.utils import utils, log, debug
from qutebrowser.utils import utils, log
class RequestInterceptor(QWebEngineUrlRequestInterceptor):
"""Handle ad blocking and custom headers."""
def __init__(self, host_blocker, args, parent=None):
def __init__(self, host_blocker, parent=None):
super().__init__(parent)
self._host_blocker = host_blocker
self._args = args
def install(self, profile):
"""Install the interceptor on the given QWebEngineProfile."""
@@ -56,29 +54,15 @@ 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(url):
if self._host_blocker.is_blocked(info.requestUrl()):
log.webview.info("Request to {} blocked by host blocker.".format(
url.host()))
info.requestUrl().host()))
info.block(True)
for header, value in shared.custom_headers(url=url):
for header, value in shared.custom_headers():
info.setHttpHeader(header, value)
user_agent = config.instance.get('content.headers.user_agent', url=url)
user_agent = config.val.content.headers.user_agent
if user_agent is not None:
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))

View File

@@ -21,12 +21,10 @@
import glob
import os
import os.path
import re
import shutil
from PyQt5.QtCore import QLibraryInfo
from qutebrowser.utils import log, message, standarddir, qtutils
from qutebrowser.utils import log, message
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
@@ -41,12 +39,9 @@ def version(filename):
return tuple(int(n) for n in match.group('version').split('-'))
def dictionary_dir(old=False):
def dictionary_dir():
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
if qtutils.version_check('5.10', compiled=False) and not old:
datapath = standarddir.data()
else:
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
return os.path.join(datapath, 'qtwebengine_dictionaries')
@@ -78,16 +73,3 @@ 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

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

View File

@@ -37,7 +37,6 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
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.
@@ -50,7 +49,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
"""
url = job.requestUrl()
if url.scheme() in ['chrome-error', 'chrome-extension']:
if url.scheme() == 'chrome-error':
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
return

View File

@@ -27,12 +27,10 @@ Module attributes:
import os
from PyQt5.QtGui import QFont
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
QWebEnginePage)
from PyQt5.QtWebEngineWidgets import QWebEngineSettings, QWebEngineProfile
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
@@ -89,40 +87,35 @@ class WebEngineSettings(websettings.AbstractSettings):
_ATTRIBUTES = {
'content.xss_auditing':
Attr(QWebEngineSettings.XSSAuditingEnabled),
[QWebEngineSettings.XSSAuditingEnabled],
'content.images':
Attr(QWebEngineSettings.AutoLoadImages),
[QWebEngineSettings.AutoLoadImages],
'content.javascript.enabled':
Attr(QWebEngineSettings.JavascriptEnabled),
[QWebEngineSettings.JavascriptEnabled],
'content.javascript.can_open_tabs_automatically':
Attr(QWebEngineSettings.JavascriptCanOpenWindows),
[QWebEngineSettings.JavascriptCanOpenWindows],
'content.javascript.can_access_clipboard':
Attr(QWebEngineSettings.JavascriptCanAccessClipboard),
[QWebEngineSettings.JavascriptCanAccessClipboard],
'content.plugins':
Attr(QWebEngineSettings.PluginsEnabled),
[QWebEngineSettings.PluginsEnabled],
'content.hyperlink_auditing':
Attr(QWebEngineSettings.HyperlinkAuditingEnabled),
[QWebEngineSettings.HyperlinkAuditingEnabled],
'content.local_content_can_access_remote_urls':
Attr(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
[QWebEngineSettings.LocalContentCanAccessRemoteUrls],
'content.local_content_can_access_file_urls':
Attr(QWebEngineSettings.LocalContentCanAccessFileUrls),
[QWebEngineSettings.LocalContentCanAccessFileUrls],
'content.webgl':
Attr(QWebEngineSettings.WebGLEnabled),
[QWebEngineSettings.WebGLEnabled],
'content.local_storage':
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
[QWebEngineSettings.LocalStorageEnabled],
'input.spatial_navigation':
Attr(QWebEngineSettings.SpatialNavigationEnabled),
[QWebEngineSettings.SpatialNavigationEnabled],
'input.links_included_in_focus_chain':
Attr(QWebEngineSettings.LinksIncludedInFocusChain),
[QWebEngineSettings.LinksIncludedInFocusChain],
'scrolling.smooth':
Attr(QWebEngineSettings.ScrollAnimatorEnabled),
[QWebEngineSettings.ScrollAnimatorEnabled],
}
_FONT_SIZES = {
@@ -161,19 +154,15 @@ class WebEngineSettings(websettings.AbstractSettings):
# Attributes which don't exist in all Qt versions.
new_attributes = {
# Qt 5.8
'content.print_element_backgrounds':
('PrintElementBackgrounds', None),
# Qt 5.11
'content.autoplay':
('PlaybackRequiresUserGesture', lambda val: not val),
'content.print_element_backgrounds': 'PrintElementBackgrounds',
}
for name, (attribute, converter) in new_attributes.items():
for name, attribute in new_attributes.items():
try:
value = getattr(QWebEngineSettings, attribute)
except AttributeError:
continue
self._ATTRIBUTES[name] = Attr(value, converter=converter)
self._ATTRIBUTES[name] = [value]
class ProfileSetter:
@@ -187,17 +176,8 @@ class ProfileSetter:
"""Initialize settings on the given profile."""
self.set_http_headers()
self.set_http_cache_size()
settings = self._profile.settings()
settings.setAttribute(
self._profile.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()
@@ -294,12 +274,9 @@ def _init_profiles():
def init(args):
"""Initialize the global QWebSettings."""
if (args.enable_webengine_inspector and
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
if args.enable_webengine_inspector:
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
spell.init()
_init_profiles()
config.instance.changed.connect(_update_settings)

View File

@@ -25,8 +25,9 @@ import sys
import re
import html as html_utils
import sip
from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
QUrl, QTimer, QObject, qVersion)
QUrl, QTimer)
from PyQt5.QtGui import QKeyEvent, QIcon
from PyQt5.QtNetwork import QAuthenticator
from PyQt5.QtWidgets import QApplication
@@ -36,12 +37,11 @@ from qutebrowser.config import configdata, config
from qutebrowser.browser import browsertab, mouse, shared
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
interceptor, webenginequtescheme,
cookies, webenginedownloads,
webenginesettings, certificateerror)
webenginedownloads,
webenginesettings)
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,9 +62,8 @@ def init():
log.init.debug("Initializing request interceptor...")
host_blocker = objreg.get('host-blocker')
args = objreg.get('args')
req_interceptor = interceptor.RequestInterceptor(
host_blocker, args=args, parent=app)
host_blocker, parent=app)
req_interceptor.install(webenginesettings.default_profile)
req_interceptor.install(webenginesettings.private_profile)
@@ -74,18 +73,6 @@ 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 = {
@@ -110,11 +97,7 @@ class WebEngineAction(browsertab.AbstractAction):
"""Save the current page."""
self._widget.triggerPageAction(QWebEnginePage.SavePage)
def show_source(self, pygments=False):
if pygments:
self._show_source_pygments()
return
def show_source(self):
try:
self._widget.triggerPageAction(QWebEnginePage.ViewSource)
except AttributeError:
@@ -246,8 +229,7 @@ class WebEngineCaret(browsertab.AbstractCaret):
self._tab.search.clear()
self._tab.run_js_async(
javascript.assemble('caret',
'setPlatform', sys.platform, qVersion()))
javascript.assemble('caret', 'setPlatform', sys.platform))
self._js_call('setInitialCursor', self._selection_cb)
def _selection_cb(self, enabled):
@@ -344,11 +326,6 @@ 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:
@@ -371,11 +348,14 @@ class WebEngineCaret(browsertab.AbstractCaret):
log.webview.debug("Clicking a searched link via fake key press.")
# send a fake enter, clicking the orange selection box
self._follow_enter(tab)
if tab:
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
else:
self._tab.key_press(Qt.Key_Enter)
else:
# click an existing blue selection
js_code = javascript.assemble('webelem',
'find_selected_focused_link')
js_code = javascript.assemble('webelem', 'find_selected_link')
self._tab.run_js_async(js_code, lambda jsret:
self._follow_selected_cb(jsret, tab))
@@ -626,172 +606,42 @@ class WebEngineElements(browsertab.AbstractElements):
self._tab.run_js_async(js_code, js_cb)
class WebEngineAudio(browsertab.AbstractAudio):
class WebEngineTab(browsertab.AbstractTab):
"""QtWebEngine implemementations related to audio/muting."""
"""A QtWebEngine tab in the browser.
def _connect_signals(self):
page = self._widget.page()
page.audioMutedChanged.connect(self.muted_changed)
page.recentlyAudibleChanged.connect(self.recently_audible_changed)
Signals:
_load_finished_fake:
Used in place of unreliable loadFinished
"""
def set_muted(self, muted: bool):
page = self._widget.page()
page.setAudioMuted(muted)
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223
_load_finished_fake = pyqtSignal(bool)
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):
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
config.instance.changed.connect(self._on_config_changed)
self._init_js()
@pyqtSlot(str)
def _on_config_changed(self, option):
@@ -803,7 +653,7 @@ class _WebEngineScripts(QObject):
"""Update the custom stylesheet in existing tabs."""
css = shared.get_user_stylesheet()
code = javascript.assemble('stylesheet', 'set_css', css)
self._tab.run_js_async(code)
self.run_js_async(code)
def _inject_early_js(self, name, js_code, *,
world=QWebEngineScript.ApplicationWorld,
@@ -838,7 +688,7 @@ class _WebEngineScripts(QObject):
if not script.isNull():
scripts.remove(script)
def init(self):
def _init_js(self):
"""Initialize global qutebrowser JavaScript."""
js_code = javascript.wrap_global(
'scripts',
@@ -846,24 +696,13 @@ class _WebEngineScripts(QObject):
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()
# 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()
greasemonkey = objreg.get('greasemonkey')
greasemonkey.scripts_reloaded.connect(self._inject_userscripts)
self._inject_userscripts()
def _init_stylesheet(self):
"""Initialize custom stylesheets.
@@ -880,123 +719,40 @@ class _WebEngineScripts(QObject):
)
self._inject_early_js('stylesheet', js_code, subframes=True)
@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):
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'):
return
# 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()
# 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()
if not scripts:
return
for script in scripts:
# Then add the new scripts.
for script in greasemonkey.all_scripts():
# @run-at (and @include/@exclude/@match) is parsed by
# QWebEngineScript.
new_script = QWebEngineScript()
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.setWorldId(QWebEngineScript.MainWorld)
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()))
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
scripts.insert(new_script)
def _install_event_filter(self):
fp = self._widget.focusProxy()
@@ -1004,7 +760,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,
win_id=self.win_id, parent=self)
parent=self)
self._widget.installEventFilter(self._child_event_filter)
@pyqtSlot()
@@ -1024,9 +780,6 @@ class WebEngineTab(browsertab.AbstractTab):
url: The QUrl to open.
predict: If set to False, predicted_navigation is not emitted.
"""
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)
@@ -1182,6 +935,18 @@ 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."""
@@ -1272,55 +1037,16 @@ 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.
This is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
"""
"""If we know we're going to visit an URL soon, change the settings."""
super()._on_predicted_navigation(url)
if not qtutils.version_check('5.11.1', compiled=False):
self.settings.update_for_url(url)
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
@@ -1328,8 +1054,10 @@ class WebEngineTab(browsertab.AbstractTab):
'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 settings_needing_reload.issubset(configdata.DATA)
@@ -1338,16 +1066,14 @@ class WebEngineTab(browsertab.AbstractTab):
# 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):
# TODO on Qt > 5.11.0, we hopefully never need a reload:
# https://codereview.qt-project.org/#/c/229525/1
if 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:
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
self._reload_url = navigation.url
def _connect_signals(self):
@@ -1362,6 +1088,7 @@ 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)
@@ -1386,10 +1113,5 @@ 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.render_widget()

View File

@@ -19,17 +19,20 @@
"""The main browser widget for QtWebEngine."""
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
import functools
import sip
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
QWebEngineScript)
from qutebrowser.browser import shared
from qutebrowser.browser.webengine import webenginesettings, certificateerror
from qutebrowser.browser.webengine import certificateerror, webenginesettings
from qutebrowser.config import config
from qutebrowser.utils import log, debug, usertypes, objreg, qtutils
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
from qutebrowser.misc import miscwidgets
from qutebrowser.qt import sip
class WebEngineView(QWebEngineView):
@@ -65,10 +68,9 @@ class WebEngineView(QWebEngineView):
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
proxy = self.focusProxy()
if proxy is not None:
return proxy
# We don't want e.g. a QMenu.
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
@@ -151,22 +153,23 @@ 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(certificateerror.CertificateErrorWrapper)
certificate_error = pyqtSignal()
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):
@@ -175,15 +178,97 @@ 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)
self.certificate_error.emit(error)
return error.ignore
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
def javaScriptConfirm(self, url, js_msg):
"""Override javaScriptConfirm to use qutebrowser prompts."""
@@ -261,3 +346,43 @@ 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,8 +28,7 @@ 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, debug)
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
from qutebrowser.browser import shared
from qutebrowser.browser.webkit import certificateerror
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
@@ -148,7 +147,6 @@ 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
@@ -380,7 +378,7 @@ class NetworkManager(QNetworkAccessManager):
result.setParent(self)
return result
for header, value in shared.custom_headers(url=req.url()):
for header, value in shared.custom_headers():
req.setRawHeader(header, value)
host_blocker = objreg.get('host-blocker')
@@ -408,13 +406,5 @@ 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

@@ -19,10 +19,11 @@
"""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):
@@ -35,6 +36,9 @@ class WebKitInspector(inspector.AbstractWebInspector):
self._set_widget(qwebinspector)
def inspect(self, page):
settings = QWebSettings.globalSettings()
settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
if not config.val.content.developer_extras:
raise inspector.WebInspectorError(
"Please enable content.developer_extras before using the "
"webinspector!")
self._widget.setPage(page)
self.show()

View File

@@ -30,7 +30,6 @@ 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
@@ -45,48 +44,50 @@ class WebKitSettings(websettings.AbstractSettings):
_ATTRIBUTES = {
'content.images':
Attr(QWebSettings.AutoLoadImages),
[QWebSettings.AutoLoadImages],
'content.javascript.enabled':
Attr(QWebSettings.JavascriptEnabled),
[QWebSettings.JavascriptEnabled],
'content.javascript.can_open_tabs_automatically':
Attr(QWebSettings.JavascriptCanOpenWindows),
[QWebSettings.JavascriptCanOpenWindows],
'content.javascript.can_close_tabs':
Attr(QWebSettings.JavascriptCanCloseWindows),
[QWebSettings.JavascriptCanCloseWindows],
'content.javascript.can_access_clipboard':
Attr(QWebSettings.JavascriptCanAccessClipboard),
[QWebSettings.JavascriptCanAccessClipboard],
'content.plugins':
Attr(QWebSettings.PluginsEnabled),
[QWebSettings.PluginsEnabled],
'content.webgl':
Attr(QWebSettings.WebGLEnabled),
[QWebSettings.WebGLEnabled],
'content.hyperlink_auditing':
Attr(QWebSettings.HyperlinkAuditingEnabled),
[QWebSettings.HyperlinkAuditingEnabled],
'content.local_content_can_access_remote_urls':
Attr(QWebSettings.LocalContentCanAccessRemoteUrls),
[QWebSettings.LocalContentCanAccessRemoteUrls],
'content.local_content_can_access_file_urls':
Attr(QWebSettings.LocalContentCanAccessFileUrls),
[QWebSettings.LocalContentCanAccessFileUrls],
'content.dns_prefetch':
Attr(QWebSettings.DnsPrefetchEnabled),
[QWebSettings.DnsPrefetchEnabled],
'content.frame_flattening':
Attr(QWebSettings.FrameFlatteningEnabled),
[QWebSettings.FrameFlatteningEnabled],
'content.cache.appcache':
Attr(QWebSettings.OfflineWebApplicationCacheEnabled),
[QWebSettings.OfflineWebApplicationCacheEnabled],
'content.local_storage':
Attr(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
[QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled],
'content.developer_extras':
[QWebSettings.DeveloperExtrasEnabled],
'content.print_element_backgrounds':
Attr(QWebSettings.PrintElementBackgrounds),
[QWebSettings.PrintElementBackgrounds],
'content.xss_auditing':
Attr(QWebSettings.XSSAuditingEnabled),
[QWebSettings.XSSAuditingEnabled],
'input.spatial_navigation':
Attr(QWebSettings.SpatialNavigationEnabled),
[QWebSettings.SpatialNavigationEnabled],
'input.links_included_in_focus_chain':
Attr(QWebSettings.LinksIncludedInFocusChain),
[QWebSettings.LinksIncludedInFocusChain],
'zoom.text_only':
Attr(QWebSettings.ZoomTextOnly),
[QWebSettings.ZoomTextOnly],
'scrolling.smooth':
Attr(QWebSettings.ScrollAnimatorEnabled),
[QWebSettings.ScrollAnimatorEnabled],
}
_FONT_SIZES = {

View File

@@ -23,6 +23,11 @@ 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
@@ -33,8 +38,7 @@ from PyQt5.QtPrintSupport import QPrinter
from qutebrowser.browser import browsertab, shared
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
webkitsettings)
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
from qutebrowser.qt import sip
from qutebrowser.utils import qtutils, objreg, usertypes, utils, log, debug
class WebKitAction(browsertab.AbstractAction):
@@ -51,8 +55,28 @@ class WebKitAction(browsertab.AbstractAction):
"""Save the current page."""
raise browsertab.UnsupportedOperationError
def show_source(self, pygments=False):
self._show_source_pygments()
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)
class WebKitPrinting(browsertab.AbstractPrinting):
@@ -353,22 +377,11 @@ class WebKitCaret(browsertab.AbstractCaret):
QWebSettings.JavascriptEnabled):
if tab:
self._tab.data.override_target = usertypes.ClickTarget.tab
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();
}
""")
self._tab.run_js_async(
'window.getSelection().anchorNode.parentNode.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(
@@ -627,20 +640,6 @@ 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."""
@@ -661,7 +660,6 @@ 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)
@@ -808,10 +806,6 @@ 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,6 +22,7 @@
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
@@ -34,7 +35,6 @@ 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,8 +212,7 @@ class BrowserPage(QWebPage):
page = pdfjs.generate_pdfjs_page(reply.url())
except pdfjs.PDFJSNotFound:
page = jinja.render('no_pdfjs.html',
url=reply.url().toDisplayString(),
title="PDF.js not found")
url=reply.url().toDisplayString())
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
reply.url())
reply.deleteLater()
@@ -416,7 +415,7 @@ class BrowserPage(QWebPage):
def userAgentForUrl(self, url):
"""Override QWebPage::userAgentForUrl to customize the user agent."""
ua = config.instance.get('content.headers.user_agent', url=url)
ua = config.val.content.headers.user_agent
if ua is None:
return super().userAgentForUrl(url)
else:

View File

@@ -19,7 +19,7 @@
"""The main browser widgets."""
from PyQt5.QtCore import pyqtSignal, Qt, QUrl
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QStyleFactory
from PyQt5.QtWebKit import QWebSettings
@@ -78,6 +78,10 @@ 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):
@@ -126,6 +130,32 @@ 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,7 +123,6 @@ 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)
@@ -171,8 +170,6 @@ 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())
@@ -236,8 +233,6 @@ 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_before_cursor: The prior value of before_cursor.
_last_completion_func: The completion function used for the last text.
"""
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_before_cursor = None
self._last_completion_func = 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_before_cursor = None
self._last_completion_func = 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_before_cursor = None
self._last_completion_func = None
return
if before_cursor != self._last_before_cursor:
self._last_before_cursor = before_cursor
if func != self._last_completion_func:
self._last_completion_func = func
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,18 +64,13 @@ def value(optname, *values, info):
opt = info.config.get_opt(optname)
default = opt.typ.to_str(opt.default)
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)
cur_cat = listcategory.ListCategory(
"Current/Default",
[(current, "Current value"), (default, "Default value")])
model.add_category(cur_cat)
vals = opt.typ.complete() or []
vals = [x for x in vals if x[0] not in values]
if vals:
vals = opt.typ.complete()
if vals is not None:
model.add_category(listcategory.ListCategory("Completions", vals))
return model

View File

@@ -276,8 +276,7 @@ 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,
opt.raw_backends)
raise configexc.BackendError(opt.name, objects.backend)
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:
except AttributeError as e:
raise AttributeError("Did not find type {} for {}".format(
type_name, name))
@@ -149,9 +149,6 @@ 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,24 +150,14 @@ force_software_rendering:
renamed: qt.force_software_rendering
qt.force_software_rendering:
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
type: Bool
default: false
backend: QtWebEngine
restart: true
desc: >-
Force software rendering for QtWebEngine.
This is needed for QtWebEngine to work with Nouveau drivers and can be
useful in other scenarios related to graphic issues.
This is needed for QtWebEngine to work with Nouveau drivers.
qt.force_platform:
type:
@@ -214,17 +204,6 @@ 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:
@@ -237,16 +216,6 @@ 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
@@ -281,17 +250,14 @@ content.cache.appcache:
content.cookies.accept:
default: no-3rdparty
backend:
QtWebKit: true
QtWebEngine: Qt 5.11
backend: QtWebKit
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. On QtWebEngine, this is the
same as no-3rdparty."
a cookie is already set for the domain."
- never: "Don't accept cookies at all."
desc: Which cookies to accept.
@@ -318,18 +284,16 @@ content.windowed_fullscreen:
desc: >-
Limit fullscreen to the browser window (does not expand to fill the screen).
content.desktop_capture:
type: BoolAsk
default: ask
supports_pattern: true
desc: >-
Allow websites to share screen content.
On Qt < 5.10, a dialog box is always displayed, even if this is set to
"true".
content.developer_extras:
deleted: true
type: Bool
default: false
backend: QtWebKit
desc: >-
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.dns_prefetch:
default: true
@@ -351,19 +315,14 @@ 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.
Note that the value read from JavaScript is always the global value.
desc: Value to send in the `Accept-Language` header.
content.headers.custom:
default: {}
@@ -376,7 +335,6 @@ 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:
@@ -384,7 +342,6 @@ content.headers.do_not_track:
name: Bool
none_ok: true
default: true
supports_pattern: true
desc: >-
Value to send in the `DNT` header.
@@ -459,11 +416,7 @@ content.headers.user_agent:
Gecko"
- IE 11.0 for Desktop Win7 64-bit
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.
desc: User agent to send. Unset to send the default.
content.host_blocking.enabled:
default: true
@@ -606,7 +559,6 @@ content.local_storage:
content.media_capture:
default: ask
type: BoolAsk
supports_pattern: true
backend: QtWebEngine
desc: Allow websites to record audio/video.
@@ -623,7 +575,6 @@ content.netrc_file:
content.notifications:
default: ask
type: BoolAsk
supports_pattern: true
backend: QtWebKit
desc: Allow websites to show notifications.
@@ -637,16 +588,6 @@ 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
@@ -684,20 +625,9 @@ 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:
@@ -714,19 +644,6 @@ 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
@@ -1044,11 +961,6 @@ 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:
@@ -1432,7 +1344,7 @@ tabs.title.alignment:
desc: Alignment of the text inside of tabs.
tabs.title.format:
default: '{audio}{index}: {title}'
default: '{index}: {title}'
type:
name: FormatString
fields:
@@ -1447,7 +1359,6 @@ tabs.title.format:
- private
- current_url
- protocol
- audio
none_ok: true
desc: |
Format to use for the tab title.
@@ -1465,7 +1376,6 @@ 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}'
@@ -1483,7 +1393,6 @@ 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.
@@ -1651,7 +1560,6 @@ 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
@@ -2363,7 +2271,6 @@ 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
@@ -2422,7 +2329,6 @@ 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
@@ -2455,7 +2361,6 @@ 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,15 +44,9 @@ class BackendError(Error):
"""Raised when this setting is unavailable with the current backend."""
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)
def __init__(self, name, backend):
super().__init__("The {} setting is not available with the {} "
"backend!".format(name, backend.name))
class NoPatternError(Error):

View File

@@ -233,14 +233,6 @@ 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
@@ -276,9 +268,14 @@ class YamlConfig(QObject):
del settings['bindings.default']
self._mark_changed()
self._migrate_bool(settings, 'tabs.favicons.show', 'always', 'never')
self._migrate_bool(settings, 'qt.force_software_rendering',
'software-opengl', 'none')
# 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()
return settings
@@ -576,7 +573,7 @@ def read_autoconfig():
"""Read the autoconfig.yml file."""
try:
config.instance.read_yaml()
except configexc.ConfigFileErrors:
except configexc.ConfigFileErrors as e:
raise # caught in outer block
except configexc.Error as e:
desc = configexc.ConfigErrorDesc("Error", e)

View File

@@ -83,12 +83,9 @@ def early_init(args):
def _init_envvars():
"""Initialize environment variables which need to be set early."""
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 (objects.backend == usertypes.Backend.QtWebEngine and
config.val.qt.force_software_rendering):
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
if config.val.qt.force_platform is not None:
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
@@ -166,25 +163,11 @@ def qt_args(namespace):
argv += ['--' + arg for arg in config.val.qt.args]
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')
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')
return argv

View File

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

View File

@@ -28,18 +28,6 @@ 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."""
@@ -62,13 +50,12 @@ class AbstractSettings:
"""
old_value = self.test_attribute(name)
info = self._ATTRIBUTES[name]
for attribute in info.attributes:
for attribute in self._ATTRIBUTES[name]:
if value is configutils.UNSET:
self._settings.resetAttribute(attribute)
new_value = self.test_attribute(name)
else:
self._settings.setAttribute(attribute, info.converter(value))
self._settings.setAttribute(attribute, value)
new_value = value
return old_value != new_value
@@ -79,8 +66,7 @@ class AbstractSettings:
If the setting resolves to a list of attributes, only the first
attribute is tested.
"""
info = self._ATTRIBUTES[name]
return self._settings.testAttribute(info.attributes[0])
return self._settings.testAttribute(self._ATTRIBUTES[name][0])
def set_font_size(self, name, value):
"""Set the given QWebSettings/QWebEngineSettings font size.

View File

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

View File

@@ -780,18 +780,6 @@ 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
@@ -868,24 +856,12 @@ window._qutebrowser.caret = (function() {
};
CaretBrowsing.injectCaretStyles = function() {
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 style = ".CaretBrowsing_Caret {" +
" position: absolute;" +
" z-index: 2147483647;" +
" min-height: 10px;" +
" background-color: #000;" +
"}";
const node = document.createElement("style");
node.innerHTML = style;
document.body.appendChild(node);
@@ -1182,8 +1158,6 @@ window._qutebrowser.caret = (function() {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
}
CaretBrowsing.stopAnimation();
};
CaretBrowsing.moveToBlock = function(paragraph, boundary) {
@@ -1202,8 +1176,6 @@ window._qutebrowser.caret = (function() {
window.setTimeout(() => {
CaretBrowsing.updateCaretOrSelection(true);
}, 0);
CaretBrowsing.stopAnimation();
};
CaretBrowsing.toggle = function(value) {
@@ -1270,17 +1242,6 @@ 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();
@@ -1318,9 +1279,8 @@ window._qutebrowser.caret = (function() {
return CaretBrowsing.selectionEnabled;
};
funcs.setPlatform = (platform, qtVersion) => {
funcs.setPlatform = (platform) => {
CaretBrowsing.isWindows = platform.startsWith("win");
CaretBrowsing.isOldQt = qtVersion === "5.7.1";
};
funcs.disableCaret = () => {

View File

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

View File

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

View File

@@ -1,30 +0,0 @@
/**
* 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,7 +243,6 @@ 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];
@@ -329,10 +328,8 @@ window._qutebrowser.webelem = (function() {
return serialize_elem(elem);
};
// 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 = () => {
// Function for returning a selection to python (so we can click it)
funcs.find_selected_link = () => {
const elem = window.getSelection().baseNode;
if (elem) {
return serialize_elem(elem.parentNode);
@@ -345,11 +342,7 @@ window._qutebrowser.webelem = (function() {
}
return null;
});
if (serialized_frame_elem) {
return serialized_frame_elem;
}
return funcs.find_focused() && "focused";
return serialized_frame_elem;
};
funcs.set_value = (id, value) => {

View File

@@ -138,37 +138,6 @@ 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,9 +30,6 @@ 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:
@@ -124,7 +121,6 @@ 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
@@ -148,7 +144,6 @@ 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()
@@ -241,17 +236,13 @@ 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))
if self.mode == mode or (self.mode in PROMPT_MODES and
mode in PROMPT_MODES):
prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno)
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
@@ -263,12 +254,6 @@ 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)
@@ -316,9 +301,6 @@ 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,7 +492,6 @@ 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,6 +24,7 @@ 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,
@@ -35,7 +36,6 @@ 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, QApplication
from PyQt5.QtWidgets import QSizePolicy, QWidget
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl
from PyQt5.QtGui import QIcon
@@ -238,10 +238,6 @@ 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')
@@ -462,8 +458,6 @@ 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)
@@ -493,22 +487,12 @@ 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
@@ -645,32 +629,24 @@ 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."""
widget = self.widget.currentWidget()
if widget is None:
return
if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES:
if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]:
widget = self.widget.currentWidget()
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
@@ -679,28 +655,26 @@ 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]
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
if mode_on_change != 'persist':
modes_to_leave += modes_to_save
for mode in modes_to_leave:
modeman.leave(self._win_id, mode, 'tab changed', maybe=True)
if (mode_on_change == 'restore' and
current_mode not in modeman.PROMPT_MODES):
modeman.enter(self._win_id, tab.data.input_mode, 'restore')
if mode_on_change == 'restore':
modeman.enter(self._win_id, tab.data.input_mode,
'restore input mode for tab')
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)
@@ -760,17 +734,6 @@ 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,7 +33,6 @@ 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'],
@@ -173,16 +172,6 @@ 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',
'chromium')
True)
_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 = 'chromium'</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 = True</i> option "
"(if you have a <i>config.py</i> file, you'll need to set this "
"manually).</p>",
buttons=[button],
)
@@ -213,41 +213,33 @@ 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
buttons.append(_Button("Force XWayland", 'qt.force_platform', 'xcb'))
text += ("<p><b>Force Qt to use XWayland</b></p>"
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>"
"<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>")
"this manually).</p>",
buttons=[button],
)
else:
text.append("<p><b>Set up XWayland</b></p>"
"<p>This allows you to use the newer QtWebEngine backend "
"(based on Chromium). ")
if has_qt511:
buttons.append(_Button("Force software rendering",
'qt.force_software_rendering',
'chromium'))
text += ("<p><b>Forcing software rendering</b></p>"
# 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) 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>")
"(based on Chromium). "
)
_show_dialog(backend=usertypes.Backend.QtWebEngine,
because="you're using Wayland", text=text, buttons=buttons)
raise utils.Unreachable
@attr.s

View File

@@ -158,11 +158,6 @@ 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)
@@ -298,7 +293,7 @@ class _CrashDialog(QDialog):
self._paste_text = '\n\n'.join(lines)
try:
user = getpass.getuser()
except Exception:
except Exception as e:
log.misc.exception("Error while getting user")
user = 'unknown'
try:

View File

@@ -36,6 +36,7 @@ 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
@@ -205,6 +206,7 @@ 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 "
@@ -221,7 +223,14 @@ class CrashHandler(QObject):
if is_ignored_exception or 'pdb-postmortem' in self._args.debug_flags:
# pdb exit, KeyboardInterrupt, ...
sys.exit(usertypes.Exit.exception)
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
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()
from qutebrowser.qt import sip
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:
except AddressInUseError as e:
# This could be a race condition...
log.init.debug("Got AddressInUseError, trying again.")
time.sleep(0.5)

View File

@@ -24,6 +24,7 @@ import os.path
import itertools
import urllib
import sip
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
from PyQt5.QtWidgets import QApplication
import yaml
@@ -34,7 +35,6 @@ 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

View File

@@ -29,6 +29,7 @@ try:
except ImportError:
hunter = None
import sip
from PyQt5.QtCore import QUrl
# so it's available for :debug-pyeval
from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import
@@ -39,7 +40,6 @@ from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import config, configdata
from qutebrowser.misc import consolewidget
from qutebrowser.utils.version import pastebin_version
from qutebrowser.qt import sip
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
@@ -189,10 +189,10 @@ def debug_cache_stats():
tabbed_browser_info = tab_bar._minimum_tab_size_hint_helper.cache_info()
# pylint: enable=protected-access
log.misc.info('is_valid_prefix: {}'.format(prefix_info))
log.misc.info('_render_stylesheet: {}'.format(render_stylesheet_info))
log.misc.info('history: {}'.format(history_info))
log.misc.info('tab width cache: {}'.format(tabbed_browser_info))
log.misc.debug('is_valid_prefix: {}'.format(prefix_info))
log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info))
log.misc.debug('history: {}'.format(history_info))
log.misc.debug('tab width cache: {}'.format(tabbed_browser_info))
@cmdutils.register(debug=True)

View File

@@ -1,28 +0,0 @@
# 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/>.
"""Wrappers around Qt/PyQt code."""
# pylint: disable=unused-import
# PyQt 5.11 comes with a bundled sip,
# for older PyQt versions it's a separate module.
try:
from PyQt5 import sip
except ImportError:
import sip

View File

@@ -61,8 +61,7 @@ def get_argparser():
"""Get the argparse parser."""
parser = argparse.ArgumentParser(prog='qutebrowser',
description=qutebrowser.__description__)
parser.add_argument('-B', '--basedir', help="Base directory for all "
"storage.")
parser.add_argument('--basedir', help="Base directory for all storage.")
parser.add_argument('-V', '--version', help="Show version and quit.",
action='store_true')
parser.add_argument('-s', '--set', help="Set a temporary setting for "
@@ -86,9 +85,7 @@ def get_argparser():
"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.")
"for more details.")
parser.add_argument('--json-args', help=argparse.SUPPRESS)
parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS)
@@ -105,7 +102,7 @@ def get_argparser():
help="How many lines of the debug log to keep in RAM "
"(-1: unlimited).",
default=2000, type=int)
debug.add_argument('-d', '--debug', help="Turn on debugging options.",
debug.add_argument('--debug', help="Turn on debugging options.",
action='store_true')
debug.add_argument('--json-logging', action='store_true', help="Output log"
" lines in JSON format (one object per line).")
@@ -115,8 +112,8 @@ def get_argparser():
action='store_true')
debug.add_argument('--nowindow', action='store_true', help="Don't show "
"the main window.")
debug.add_argument('-T', '--temp-basedir', action='store_true', help="Use "
"a temporary basedir.")
debug.add_argument('--temp-basedir', action='store_true', help="Use a "
"temporary basedir.")
debug.add_argument('--no-err-windows', action='store_true', help="Don't "
"show any error windows (used for tests/smoke.py).")
debug.add_argument('--qt-arg', help="Pass an argument with a value to Qt. "
@@ -126,9 +123,9 @@ def get_argparser():
action='append')
debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.",
nargs=1, action='append')
debug.add_argument('-D', '--debug-flag', type=debug_flag_error,
default=[], help="Pass name of debugging feature to be"
" turned on.", action='append', dest='debug_flags')
debug.add_argument('--debug-flag', type=debug_flag_error, default=[],
help="Pass name of debugging feature to be turned on.",
action='append', dest='debug_flags')
parser.add_argument('command', nargs='*', help="Commands to execute on "
"startup.", metavar=':command')
# URLs will actually be in command
@@ -148,7 +145,7 @@ def logfilter_error(logfilter):
Args:
logfilter: A comma separated list of logger names.
"""
if set(logfilter.lstrip('!').split(',')).issubset(log.LOGGER_NAMES):
if set(logfilter.split(',')).issubset(log.LOGGER_NAMES):
return logfilter
else:
raise argparse.ArgumentTypeError(
@@ -162,12 +159,9 @@ def debug_flag_error(flag):
Available flags:
debug-exit: Turn on debugging of late exit.
pdb-postmortem: Drop into pdb on exceptions.
no-sql-history: Don't store history items.
no-scroll-filtering: Process all scrolling updates.
log-requests: Log all network requests.
"""
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history',
'no-scroll-filtering', 'log-requests', 'lost-focusproxy']
'no-scroll-filtering']
if flag in valid_flags:
return flag

View File

@@ -182,16 +182,9 @@ def init_log(args):
root = logging.getLogger()
global console_filter
if console is not None:
if not args.logfilter:
negate = False
names = None
elif args.logfilter.startswith('!'):
negate = True
names = args.logfilter[1:].split(',')
else:
negate = False
names = args.logfilter.split(',')
console_filter = LogFilter(names, negate)
console_filter = LogFilter(None)
if args.logfilter is not None:
console_filter.names = args.logfilter.split(',')
console.addFilter(console_filter)
root.addHandler(console)
if ram is not None:
@@ -209,11 +202,6 @@ def _init_py_warnings():
"""Initialize Python warning handling."""
warnings.simplefilter('default')
warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning)
# This happens in many qutebrowser dependencies...
warnings.filterwarnings('ignore', category=DeprecationWarning,
message="Using or importing the ABCs from "
"'collections' instead of from 'collections.abc' "
"is deprecated, and in 3.8 it will stop working")
@contextlib.contextmanager
@@ -510,14 +498,12 @@ class LogFilter(logging.Filter):
comma-separated list instead.
Attributes:
names: A list of record names to filter.
negated: Whether names is a list of records to log or to suppress.
_names: A list of names that should be logged.
"""
def __init__(self, names, negate=False):
def __init__(self, names):
super().__init__()
self.names = names
self.negated = negate
def filter(self, record):
"""Determine if the specified record is to be logged."""
@@ -528,12 +514,12 @@ class LogFilter(logging.Filter):
return True
for name in self.names:
if record.name == name:
return not self.negated
return True
elif not record.name.startswith(name):
continue
elif record.name[len(name)] == '.':
return not self.negated
return self.negated
return True
return False
class RAMHandler(logging.Handler):

View File

@@ -510,13 +510,11 @@ def sanitize_filename(name, replacement='_'):
def set_clipboard(data, selection=False):
"""Set the clipboard to some given data."""
global fake_clipboard
if selection and not supports_selection():
raise SelectionUnsupportedError
if log_clipboard:
what = 'primary selection' if selection else 'clipboard'
log.misc.debug("Setting fake {}: {}".format(what, json.dumps(data)))
fake_clipboard = data
else:
mode = QClipboard.Selection if selection else QClipboard.Clipboard
QApplication.clipboard().setText(data, mode=mode)

View File

@@ -310,7 +310,7 @@ def _chromium_version():
"""Get the Chromium version for QtWebEngine.
This can also be checked by looking at this file with the right Qt tag:
http://code.qt.io/cgit/qt/qtwebengine.git/tree/tools/scripts/version_resolver.py#n41
https://github.com/qt/qtwebengine/blob/dev/tools/scripts/version_resolver.py#L41
Quick reference:
Qt 5.7: Chromium 49
@@ -318,7 +318,7 @@ def _chromium_version():
Qt 5.9: Chromium 56
Qt 5.10: Chromium 61
Qt 5.11: Chromium 65
Qt 5.12: Chromium 69 (? - current dev branch: 67)
Qt 5.12: Chromium 69 (?)
Also see https://www.chromium.org/developers/calendar
"""
@@ -399,7 +399,6 @@ def version():
lines += [
'Frozen: {}'.format(hasattr(sys, 'frozen')),
"Imported from {}".format(importpath),
"Using Python from {}".format(sys.executable),
"Qt library executable path: {}, data path: {}".format(
QLibraryInfo.location(QLibraryInfo.LibraryExecutablesPath),
QLibraryInfo.location(QLibraryInfo.DataPath)
@@ -484,7 +483,6 @@ def pastebin_version(pbclient=None):
def _on_paste_version_success(url):
global pastebin_url
url = url.strip()
_yank_url(url)
pbclient.deleteLater()
pastebin_url = url

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