Compare commits

..

1 Commits
v1.4.0 ... osx

Author SHA1 Message Date
Florian Bruhin
11f930db0d Add more macOS versions 2018-04-24 10:55:37 +02:00
165 changed files with 1052 additions and 3641 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

@@ -32,7 +32,7 @@ exclude = .*,__pycache__,resources.py
# D403: First word of the first line should be properly capitalized
# (false-positives)
# D413: Missing blank line after last section (not in pep257?)
# A003: Builtin name for class attribute (needed for overridden methods)
# A003: Builtin name for class attribute (needed for attrs)
ignore =
B001,B008,B305,
E128,E226,E265,E501,E402,E266,E722,E731,
@@ -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

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,21 +18,30 @@ 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=highsierra
osx_image: xcode9.3
language: generic
- os: osx
env: TESTENV=py36 OSX=sierra
osx_image: xcode9.2
language: generic
- os: osx
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode8
language: generic
# https://github.com/qutebrowser/qutebrowser/issues/2013
# - os: osx
# env: TESTENV=py35 OSX=yosemite

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

View File

@@ -85,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
----------------
@@ -198,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
@@ -576,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
-----------------
@@ -689,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
@@ -710,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

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

View File

@@ -109,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]:

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

@@ -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

@@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
check-manifest==0.37
check-manifest==0.36

View File

@@ -1,9 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
certifi==2018.4.16
certifi==2018.1.18
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.2.2
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
pycodestyle==2.3.1 # rq.filter: < 2.4.0
pep8-naming==0.5.0
pycodestyle==2.3.1
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
@@ -15,9 +15,3 @@ flake8-tuple
pep8-naming
pydocstyle
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.0.1
six==1.11.0
wheel==0.31.1
wheel==0.31.0

View File

@@ -1,17 +1,17 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
certifi==2018.4.16
certifi==2018.1.18
chardet==3.0.4
github3.py==1.1.0
idna==2.7
github3.py==1.0.2
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
certifi==2018.4.16
astroid==1.6.3
certifi==2018.1.18
chardet==3.0.4
github3.py==1.1.0
idna==2.7
github3.py==1.0.2
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.1.2
click==6.7
# colorama==0.3.9
coverage==4.5.1
EasyProcess==0.2.3
fields==5.0.0
Flask==1.0.2
Flask==0.12.2
glob2==0.6
hunter==2.0.2
hypothesis==3.65.0
hypothesis==3.55.1
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.0
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.8.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,261 +0,0 @@
#!/usr/bin/env python3
# Copyright 2018 Jay Kamat <jaygkamat@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 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 userscript allows for insertion of usernames and passwords from keepass
databases using pykeepass. Since it is a userscript, it must be run from
qutebrowser.
A sample invocation of this script is:
:spawn --userscript qute-keepass -p ~/KeePassFiles/MainDatabase.kdbx
And a sample binding
:bind --mode=insert <ctrl-i> spawn --userscript qute-keepass -p ~/KeePassFiles/MainDatabase.kdbx
-p or --path is a required argument.
--keyfile-path allows you to specify a keepass keyfile. If you only use a
keyfile, also add --no-password as well. Specifying --no-password without
--keyfile-path will lead to an error.
login information is inserted using :insert-text and :fake-key <Tab>, which
means you must have a cursor in position before initiating this userscript. If
you do not do this, you will get 'element not editable' errors.
If keepass takes a while to open the DB, you might want to consider reducing
the number of transform rounds in your database settings.
Dependencies: pykeepass (in python3), PyQt5. Without pykeepass, you will get an
exit code of 100.
********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!******************
WARNING: The login details are viewable as plaintext in qutebrowser's debug log
(qute://log) and could be compromised if you decide to submit a crash report!
********************!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!******************
"""
# pylint: disable=bad-builtin
import argparse
import enum
import functools
import os
import shlex
import subprocess
import sys
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QInputDialog, QLineEdit
try:
import pykeepass
except ImportError as e:
print("pykeepass not found: {}".format(str(e)), file=sys.stderr)
# Since this is a common error, try to print it to the FIFO if we can.
if 'QUTE_FIFO' in os.environ:
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
fifo.write('message-error "pykeepass failed to be imported."\n')
fifo.flush()
sys.exit(100)
argument_parser = argparse.ArgumentParser(
description="Fill passwords using keepass.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__)
argument_parser.add_argument('url', nargs='?', default=os.getenv('QUTE_URL'))
argument_parser.add_argument('--path', '-p', required=True,
help='Path to the keepass db.')
argument_parser.add_argument('--keyfile-path', '-k', default=None,
help='Path to a keepass keyfile')
argument_parser.add_argument(
'--no-password', action='store_true',
help='Supply if no password is required to unlock this database. '
'Only allowed with --keyfile-path')
argument_parser.add_argument(
'--dmenu-invocation', '-d', default='dmenu',
help='Invocation used to execute a dmenu-provider')
argument_parser.add_argument(
'--dmenu-format', '-f', default='{title}: {username}',
help='Format string for keys to display in dmenu.'
' Must generate a unique string.')
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')
group = argument_parser.add_mutually_exclusive_group()
group.add_argument('--username-fill-only', '-e',
action='store_true', help='Only insert username')
group.add_argument('--password-fill-only', '-w',
action='store_true', help='Only insert password')
CMD_DELAY = 50
class ExitCodes(enum.IntEnum):
"""Stores various exit codes groups to use."""
SUCCESS = 0
FAILURE = 1
# 1 is automatically used if Python throws an exception
NO_CANDIDATES = 2
USER_QUIT = 3
DB_OPEN_FAIL = 4
INTERNAL_ERROR = 10
def qute_command(command):
with open(os.environ['QUTE_FIFO'], 'w') as fifo:
fifo.write(command + '\n')
fifo.flush()
def stderr(to_print):
"""Extra functionality to echo out errors to qb ui."""
print(to_print, file=sys.stderr)
qute_command('message-error "{}"'.format(to_print))
def dmenu(items, invocation, encoding):
"""Runs dmenu with given arguments."""
command = shlex.split(invocation)
process = subprocess.run(command, input='\n'.join(items).encode(encoding),
stdout=subprocess.PIPE)
return process.stdout.decode(encoding).strip()
def get_password():
"""Get a keepass db password from user."""
_app = QApplication(sys.argv)
text, ok = QInputDialog.getText(
None, "KeePass DB Password",
"Please enter your KeePass Master Password",
QLineEdit.Password)
if not ok:
stderr('Password Prompt Rejected.')
sys.exit(ExitCodes.USER_QUIT)
return text
def find_candidates(args, host):
"""Finds candidates that match host"""
file_path = os.path.expanduser(args.path)
# TODO find a way to keep the db open, so we don't open (and query
# password) it every time
pw = None
if not args.no_password:
pw = get_password()
kf = args.keyfile_path
if kf:
kf = os.path.expanduser(kf)
try:
kp = pykeepass.PyKeePass(file_path, password=pw, keyfile=kf)
except Exception as e:
stderr("There was an error opening the DB: {}".format(str(e)))
return kp.find_entries(url="{}{}{}".format(".*", host, ".*"), regex=True)
def candidate_to_str(args, candidate):
"""Turns candidate into a human readable string for dmenu"""
return args.dmenu_format.format(title=candidate.title,
url=candidate.url,
username=candidate.username,
path=candidate.path,
uuid=candidate.uuid)
def candidate_to_secret(candidate):
"""Turns candidate into a generic (user, password) tuple"""
return (candidate.username, candidate.password)
def run(args):
"""Runs qute-keepass"""
if not args.url:
argument_parser.print_help()
return ExitCodes.FAILURE
url_host = QUrl(args.url).host()
if not url_host:
stderr('{} was not parsed as a valid URL!'.format(args.url))
return ExitCodes.INTERNAL_ERROR
# Find candidates matching the host of the given URL
candidates = find_candidates(args, url_host)
if not candidates:
stderr('No candidates for URL {!r} found!'.format(args.url))
return ExitCodes.NO_CANDIDATES
# Create a map so we can get turn the resulting string from dmenu back into
# a candidate
candidates_strs = list(map(functools.partial(candidate_to_str, args),
candidates))
candidates_map = dict(zip(candidates_strs, candidates))
if len(candidates) == 1:
selection = candidates.pop()
else:
selection = dmenu(candidates_strs,
args.dmenu_invocation,
args.io_encoding)
if selection not in candidates_map:
stderr("'{}' was not a valid entry!").format(selection)
return ExitCodes.USER_QUIT
selection = candidates_map[selection]
username, password = candidate_to_secret(selection)
insert_mode = ';; enter-mode insert' if args.insert_mode else ''
if args.username_fill_only:
qute_command('insert-text {}{}'.format(username, insert_mode))
elif args.password_fill_only:
qute_command('insert-text {}{}'.format(password, insert_mode))
else:
# Enter username and password using insert-key and fake-key <Tab>
# (which supports more passwords than fake-key only), then switch back
# into insert-mode, so the form can be directly submitted by hitting
# enter afterwards. It dosen't matter when we go into insert mode, but
# the other commands need to be be executed sequentially, so we add
# delays with later.
qute_command('insert-text {} ;;'
'later {} fake-key <Tab> ;;'
'later {} insert-text {}{}'
.format(username, CMD_DELAY,
CMD_DELAY * 2, password, insert_mode))
return ExitCodes.SUCCESS
if __name__ == '__main__':
arguments = argument_parser.parse_args()
sys.exit(run(arguments))

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

@@ -109,13 +109,6 @@ def dmenu(items, invocation, encoding):
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()
@@ -165,19 +158,15 @@ def main(arguments):
return ExitCodes.COULD_NOT_MATCH_PASSWORD
password = match.group(1)
insert_mode = ';; enter-mode insert' if arguments.insert_mode else ''
if arguments.username_only:
fake_key_raw(username)
qute_command('fake-key {}{}'.format(username, insert_mode))
elif arguments.password_only:
fake_key_raw(password)
qute_command('fake-key {}{}'.format(password, insert_mode))
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')
qute_command('fake-key {} ;; fake-key <Tab> ;; fake-key {}{}'.format(username, password, insert_mode))
return ExitCodes.SUCCESS

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, 2, 1)
__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()
@@ -787,13 +724,7 @@ class AbstractTab(QWidget):
if getattr(evt, 'posted', False):
raise utils.Unreachable("Can't re-use an event which was already "
"posted!")
recipient = self.event_target()
if recipient is None:
# https://github.com/qutebrowser/qutebrowser/issues/3888
log.webview.warning("Unable to find event target!")
return
evt.posted = True
QApplication.postEvent(recipient, evt)
@@ -816,7 +747,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 +823,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)
from qutebrowser.commands import cmdutils
from qutebrowser.browser import downloads
from qutebrowser.misc import objects
def _scripts_dir():
@@ -49,7 +48,6 @@ class GreasemonkeyScript:
def __init__(self, properties, code):
self._code = code
self.includes = []
self.matches = []
self.excludes = []
self.requires = []
self.description = None
@@ -58,7 +56,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
@@ -66,10 +63,8 @@ class GreasemonkeyScript:
self.namespace = value
elif name == 'description':
self.description = value
elif name == 'include':
elif name in ['include', 'match']:
self.includes.append(value)
elif name == 'match':
self.matches.append(value)
elif name in ['exclude', 'exclude_match']:
self.excludes.append(value)
elif name == 'run-at':
@@ -78,8 +73,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>.*)'
@@ -99,7 +92,7 @@ class GreasemonkeyScript:
props = ""
script = cls(re.findall(cls.PROPS_REGEX, props), source)
script.script_meta = props
if not script.includes and not script.matches:
if not script.includes:
script.includes = ['*']
return script
@@ -112,24 +105,19 @@ 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({
'name': self.name,
'description': self.description,
'matches': self.matches,
'matches': self.includes,
'includes': self.includes,
'excludes': self.excludes,
'run-at': self.run_at,
@@ -155,42 +143,6 @@ class MatchingScripts(object):
idle = attr.ib(default=attr.Factory(list))
class GreasemonkeyMatcher:
"""Check whether scripts should be loaded for a given URL."""
# https://wiki.greasespot.net/Include_and_exclude_rules#Greaseable_schemes
# Limit the schemes scripts can run on due to unreasonable levels of
# exploitability
GREASEABLE_SCHEMES = ['http', 'https', 'ftp', 'file']
def __init__(self, url):
self._url = url
self._url_string = url.toString(QUrl.FullyEncoded)
self.is_greaseable = url.scheme() in self.GREASEABLE_SCHEMES
def _match_pattern(self, pattern):
# For include and exclude rules if they start and end with '/' they
# should be treated as a (ecma syntax) regular expression.
if pattern.startswith('/') and pattern.endswith('/'):
matches = re.search(pattern[1:-1], self._url_string, flags=re.I)
return matches is not None
# Otherwise they are glob expressions.
return fnmatch.fnmatch(self._url_string, pattern)
def matches(self, script):
"""Check whether the URL matches filtering rules of the script."""
assert self.is_greaseable
matching_includes = any(self._match_pattern(pat)
for pat in script.includes)
matching_match = any(urlmatch.UrlPattern(pat).matches(self._url)
for pat in script.matches)
matching_excludes = any(self._match_pattern(pat)
for pat in script.excludes)
return (matching_includes or matching_match) and not matching_excludes
class GreasemonkeyManager(QObject):
"""Manager of userscripts and a Greasemonkey compatible environment.
@@ -202,6 +154,10 @@ class GreasemonkeyManager(QObject):
"""
scripts_reloaded = pyqtSignal()
# https://wiki.greasespot.net/Include_and_exclude_rules#Greaseable_schemes
# Limit the schemes scripts can run on due to unreasonable levels of
# exploitability
greaseable_schemes = ['http', 'https', 'ftp', 'file']
def __init__(self, parent=None):
super().__init__(parent)
@@ -353,17 +309,30 @@ class GreasemonkeyManager(QObject):
returns a tuple of lists of scripts meant to run at (document-start,
document-end, document-idle)
"""
matcher = GreasemonkeyMatcher(url)
if not matcher.is_greaseable:
if url.scheme() not in self.greaseable_schemes:
return MatchingScripts(url, [], [], [])
string_url = url.toString(QUrl.FullyEncoded)
def _match(pattern):
# For include and exclude rules if they start and end with '/' they
# should be treated as a (ecma syntax) regular expression.
if pattern.startswith('/') and pattern.endswith('/'):
matches = re.search(pattern[1:-1], string_url, flags=re.I)
return matches is not None
# Otherwise they are glob expressions.
return fnmatch.fnmatch(string_url, pattern)
tester = (lambda script:
any(_match(pat) for pat in script.includes) and
not any(_match(pat) for pat in script.excludes))
return MatchingScripts(
url=url,
start=[script for script in self._run_start
if matcher.matches(script)],
end=[script for script in self._run_end
if matcher.matches(script)],
idle=[script for script in self._run_idle
if matcher.matches(script)]
url,
[script for script in self._run_start if tester(script)],
[script for script in self._run_end if tester(script)],
[script for script in self._run_idle if tester(script)]
)
def all_scripts(self):

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
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."""
@@ -55,29 +54,6 @@ class ChildEventFilter(QObject):
obj, child))
assert obj is self._widget
child.installEventFilter(self._filter)
if qtutils.version_check('5.11', compiled=False, exact=True):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076
pass_modes = [usertypes.KeyMode.command,
usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno]
if modeman.instance(self._win_id).mode not in pass_modes:
tabbed_browser = objreg.get('tabbed-browser',
scope='window',
window=self._win_id)
current_index = tabbed_browser.widget.currentIndex()
try:
widget_index = tabbed_browser.widget.indexOf(
self._widget.parent())
except RuntimeError:
widget_index = -1
if current_index == widget_index:
QTimer.singleShot(0, self._widget.setFocus)
elif event.type() == QEvent.ChildRemoved:
child = event.child()
log.mouse.debug("{}: removed child {}".format(obj, child))
return False

View File

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

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

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

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,53 +21,38 @@
import glob
import os
import os.path
import re
import shutil
from PyQt5.QtCore import QLibraryInfo
from qutebrowser.utils import log, message, standarddir, qtutils
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
from qutebrowser.utils import log
def version(filename):
"""Extract the version number from the dictionary file name."""
match = dict_version_re.match(filename)
version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
match = version_re.fullmatch(filename)
if match is None:
message.warning(
"Found a dictionary with a malformed name: {}".format(filename))
return None
raise ValueError('the given dictionary file name is malformed: {}'
.format(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')
def local_files(code):
"""Return all installed dictionaries for the given code.
The returned dictionaries are sorted by version, therefore the latest will
be the first element. The list will be empty if no dictionaries are found.
"""
"""Return all installed dictionaries for the given code."""
pathname = os.path.join(dictionary_dir(), '{}*.bdic'.format(code))
matching_dicts = glob.glob(pathname)
versioned_dicts = []
for matching_dict in matching_dicts:
parsed_version = version(matching_dict)
if parsed_version is not None:
filename = os.path.basename(matching_dict)
log.config.debug('Found file for dict {}: {}'
.format(code, filename))
versioned_dicts.append((parsed_version, filename))
return [filename for version, filename
in sorted(versioned_dicts, reverse=True)]
files = []
for matching_dict in sorted(matching_dicts, key=version, reverse=True):
filename = os.path.basename(matching_dict)
log.config.debug('Found file for dict {}: {}'.format(code, filename))
files.append(filename)
return files
def local_filename(code):
@@ -78,16 +63,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

@@ -101,11 +101,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
def retry(self):
state = self._qt_item.state()
if state != QWebEngineDownloadItem.DownloadInterrupted:
log.downloads.warning(
"Trying to retry download in state {}".format(
debug.qenum_key(QWebEngineDownloadItem, state)))
return
assert state == QWebEngineDownloadItem.DownloadInterrupted, state
try:
self._qt_item.resume()

View File

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

View File

@@ -39,8 +39,8 @@ class WebEngineInspector(inspector.AbstractWebInspector):
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
self._set_widget(view)
def _inspect_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

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

View File

@@ -27,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,20 +176,24 @@ class ProfileSetter:
"""Initialize settings on the given profile."""
self.set_http_headers()
self.set_http_cache_size()
settings = self._profile.settings()
settings.setAttribute(
QWebEngineSettings.FullScreenSupportEnabled, True)
try:
settings.setAttribute(
QWebEngineSettings.FocusOnNavigationEnabled, False)
except AttributeError:
# Added in Qt 5.8
pass
self._init_attributes()
if qtutils.version_check('5.8'):
self._profile.setSpellCheckEnabled(True)
self.set_dictionary_language()
def _init_attributes(self):
"""Initialize hard-coded attributes."""
values = {
'FullScreenSupportEnabled': True,
'FocusOnNavigationEnabled': True,
}
settings = self._profile.settings()
for name, value in values.items():
attr = getattr(QWebEngineSettings, name, None)
if attr is not None:
settings.setAttribute(attr, value)
def set_http_headers(self):
"""Set the user agent and accept-language for the given profile.
@@ -249,7 +242,6 @@ class ProfileSetter:
log.config.debug("Found dicts: {}".format(filenames))
self._profile.setSpellCheckLanguages(filenames)
self._profile.setSpellCheckEnabled(bool(filenames))
def _update_settings(option):
@@ -294,12 +286,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,13 +935,26 @@ 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."""
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
# (seems to be back in later Qt versions as well)
self.search.clear()
if (qtutils.version_check('5.9', compiled=False) and
not qtutils.version_check('5.9.2', compiled=False)):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
self.search.clear()
super()._on_load_started()
self.data.netrc_used = False
@@ -1260,9 +1026,8 @@ class WebEngineTab(browsertab.AbstractTab):
log.config.debug(
"Loading {} again because of config change".format(
self._reload_url.toDisplayString()))
QTimer.singleShot(100, functools.partial(self.openurl,
self._reload_url,
predict=False))
QTimer.singleShot(100, lambda url=self._reload_url:
self.openurl(url, predict=False))
self._reload_url = None
if not qtutils.version_check('5.10', compiled=False):
@@ -1272,82 +1037,33 @@ 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
settings_needing_reload = {
needs_reload = {
'content.plugins',
'content.javascript.enabled',
'content.javascript.can_access_clipboard',
'content.javascript.can_access_clipboard',
'content.print_element_backgrounds',
'input.spatial_navigation',
'input.spatial_navigation',
}
assert settings_needing_reload.issubset(configdata.DATA)
assert needs_reload.issubset(configdata.DATA)
changed = self.settings.update_for_url(navigation.url)
reload_needed = changed & settings_needing_reload
# On Qt < 5.11, we don't don't need a reload when type == link_clicked.
# On Qt 5.11.0, we always need a reload.
# On Qt > 5.11.0, we never need a reload:
# https://codereview.qt-project.org/#/c/229525/1
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
if qtutils.version_check('5.11.1', compiled=False):
reload_needed = False
elif not qtutils.version_check('5.11.0', exact=True, compiled=False):
if navigation.navigation_type == navigation.Type.link_clicked:
reload_needed = False
if reload_needed:
if (changed & needs_reload and navigation.navigation_type !=
navigation.Type.link_clicked):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-66656
self._reload_url = navigation.url
def _connect_signals(self):
@@ -1362,6 +1078,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 +1103,7 @@ 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()
fp = self._widget.focusProxy()
assert fp is not None
return fp

View File

@@ -19,17 +19,17 @@
"""The main browser widget for QtWebEngine."""
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
import functools
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.misc import miscwidgets
from qutebrowser.qt import sip
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
class WebEngineView(QWebEngineView):
@@ -51,35 +51,6 @@ class WebEngineView(QWebEngineView):
parent=self)
self.setPage(page)
if qtutils.version_check('5.11', compiled=False):
# Set a PseudoLayout as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-68224
# and other related issues.
sip.delete(self.layout())
self._layout = miscwidgets.PseudoLayout(self)
def render_widget(self):
"""Get the RenderWidgetHostViewQt for this view.
Normally, this would always be the focusProxy().
However, it sometimes isn't, so we use this as a WORKAROUND for
https://bugreports.qt.io/browse/QTBUG-68727
"""
if 'lost-focusproxy' not in objreg.get('args').debug_flags:
proxy = self.focusProxy()
if proxy is not None:
return proxy
# We don't want e.g. a QMenu.
rwhv_class = 'QtWebEngineCore::RenderWidgetHostViewQtDelegateWidget'
children = [c for c in self.findChildren(QWidget)
if c.isVisible() and c.inherits(rwhv_class)]
log.webview.debug("Found possibly lost focusProxy: {}"
.format(children))
return children[-1] if children else None
def shutdown(self):
self.page().shutdown()
@@ -151,22 +122,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 +147,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 +315,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

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

View File

@@ -19,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

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

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

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

View File

@@ -138,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

@@ -19,8 +19,6 @@
"""The commandline in the statusbar."""
import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize
from PyQt5.QtWidgets import QSizePolicy
@@ -71,26 +69,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.textChanged.connect(self.updateGeometry)
self.textChanged.connect(self._incremental_search)
self._command_dispatcher = objreg.get(
'command-dispatcher', scope='window', window=self._win_id)
def _handle_search(self):
"""Check if the currently entered text is a search, and if so, run it.
Return:
True if a search was executed, False otherwise.
"""
search_prefixes = {
'/': self._command_dispatcher.search,
'?': functools.partial(
self._command_dispatcher.search, reverse=True)
}
if self.prefix() in search_prefixes:
search_fn = search_prefixes[self.prefix()]
search_fn(self.text()[1:])
return True
return False
def prefix(self):
"""Get the currently entered command prefix."""
text = self.text()
@@ -184,17 +162,17 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
Args:
rapid: Run the command without closing or clearing the command bar.
"""
prefixes = {
':': '',
'/': 'search -- ',
'?': 'search -r -- ',
}
text = self.text()
self.history.append(text)
was_search = self._handle_search()
if not rapid:
modeman.leave(self._win_id, usertypes.KeyMode.command,
'cmd accept')
if not was_search:
self.got_cmd[str].emit(text[1:])
self.got_cmd[str].emit(prefixes[text[0]] + text[1:])
@cmdutils.register(instance='status-command', scope='window')
def edit_command(self, run=False):
@@ -275,9 +253,15 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
width = self.fontMetrics().width(text)
return QSize(width, height)
@pyqtSlot()
def _incremental_search(self):
@pyqtSlot(str)
def _incremental_search(self, text):
if not config.val.search.incremental:
return
self._handle_search()
search_prefixes = {
'/': 'search -- ',
'?': 'search -r -- ',
}
if self.prefix() in ['/', '?']:
self.got_cmd[str].emit(search_prefixes[text[0]] + text[1:])

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,21 +487,8 @@ 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)
@@ -645,32 +626,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 +652,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 +731,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

@@ -45,7 +45,7 @@ class PyPIVersionClient(QObject):
arg: The error message, as string.
"""
API_URL = 'https://pypi.org/pypi/{}/json'
API_URL = 'https://pypi.python.org/pypi/{}/json'
success = pyqtSignal(str)
error = pyqtSignal(str)

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)

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