Merged upstream, made requested modifications to the search engine code and added tests
This commit is contained in:
parent
0a9806daf3
commit
35c2f95a58
@ -5,15 +5,15 @@ cache:
|
||||
build: off
|
||||
environment:
|
||||
PYTHONUNBUFFERED: 1
|
||||
PYTHON: C:\Python36\python.exe
|
||||
PYTHON: C:\Python36-x64\python.exe
|
||||
matrix:
|
||||
- TESTENV: py36-pyqt510
|
||||
- TESTENV: py36-pyqt511
|
||||
- TESTENV: pylint
|
||||
|
||||
install:
|
||||
- '%PYTHON% -m pip install -U pip'
|
||||
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
|
||||
- 'set PATH=%PATH%;C:\Python36'
|
||||
- 'set PATH=C:\Python36-x64;%PATH'
|
||||
|
||||
test_script:
|
||||
- '%PYTHON% -m tox -e %TESTENV%'
|
||||
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1,5 +1,5 @@
|
||||
qutebrowser/browser/history.py @rcorre
|
||||
qutebrowser/completion/* @rcorre
|
||||
qutebrowser/completion/** @rcorre
|
||||
qutebrowser/misc/sql.py @rcorre
|
||||
tests/end2end/features/completion.feature @rcorre
|
||||
tests/end2end/features/test_completion_bdd.py @rcorre
|
||||
|
5
.github/CONTRIBUTING.asciidoc
vendored
5
.github/CONTRIBUTING.asciidoc
vendored
@ -1,3 +1,8 @@
|
||||
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
|
||||
because of exams coming up. Review of non-trivial pull requests will thus be
|
||||
delayed until then. If you're reading this note after mid-September, please
|
||||
open an issue.
|
||||
|
||||
- Before you start to work on something, please leave a comment on the relevant
|
||||
issue (or open one). This makes sure there is no duplicate work done.
|
||||
|
||||
|
27
.travis.yml
27
.travis.yml
@ -20,18 +20,21 @@ matrix:
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt59
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt510-cov
|
||||
# We need a newer Xvfb as a WORKAROUND for:
|
||||
# https://bugreports.qt.io/browse/QTBUG-64928
|
||||
sudo: required
|
||||
env: TESTENV=py36-pyqt510
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe"
|
||||
packages:
|
||||
- xvfb
|
||||
apt:
|
||||
packages:
|
||||
- xfonts-base
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt511-cov
|
||||
# https://github.com/travis-ci/travis-ci/issues/9069
|
||||
- os: linux
|
||||
python: 3.7
|
||||
sudo: required
|
||||
dist: xenial
|
||||
env: TESTENV=py37-pyqt511
|
||||
- os: osx
|
||||
env: TESTENV=py36 OSX=sierra
|
||||
env: TESTENV=py37 OSX=sierra
|
||||
osx_image: xcode9.2
|
||||
language: generic
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2013
|
||||
@ -66,6 +69,10 @@ matrix:
|
||||
env: TESTENV=shellcheck
|
||||
services: docker
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/4055
|
||||
- os: linux
|
||||
env: TESTENV=py36-pyqt510
|
||||
|
||||
cache:
|
||||
directories:
|
||||
|
@ -59,7 +59,7 @@ Getting help
|
||||
|
||||
You can get help in the IRC channel
|
||||
irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on
|
||||
http://freenode.net/[Freenode]
|
||||
https://freenode.net/[Freenode]
|
||||
(https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a
|
||||
message to the
|
||||
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
|
||||
@ -96,8 +96,8 @@ Requirements
|
||||
|
||||
The following software and libraries are required to run qutebrowser:
|
||||
|
||||
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||
* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules:
|
||||
* https://www.python.org/[Python] 3.5 or newer (3.6 recommended)
|
||||
* https://www.qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules:
|
||||
- QtCore / qtbase
|
||||
- QtQuick (part of qtbase in some distributions)
|
||||
- QtSQL (part of qtbase in some distributions)
|
||||
@ -106,14 +106,14 @@ The following software and libraries are required to run qutebrowser:
|
||||
- QtWebKit - only the
|
||||
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
||||
supported
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.10 recommended) for Python 3
|
||||
* https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.11.2 recommended) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* https://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
* http://pygments.org/[pygments]
|
||||
* https://github.com/yaml/pyyaml[PyYAML]
|
||||
* http://www.attrs.org/[attrs]
|
||||
* https://www.attrs.org/[attrs]
|
||||
|
||||
The following libraries are optional:
|
||||
|
||||
@ -152,7 +152,7 @@ https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contr
|
||||
|
||||
Additionally, the following people have contributed graphics:
|
||||
|
||||
* Jad/link:http://yelostudio.com[yelo] (new icon)
|
||||
* Jad/link:https://yelostudio.com[yelo] (new icon)
|
||||
* WOFall (original icon)
|
||||
* regines (key binding cheatsheet)
|
||||
|
||||
@ -170,18 +170,15 @@ Active
|
||||
|
||||
* https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
|
||||
* https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
|
||||
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
||||
* https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
|
||||
* https://github.com/next-browser/next/[next] (Lisp, Emacs-like, GTK+ with WebKit)
|
||||
* https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine)
|
||||
* Chrome/Chromium addons:
|
||||
https://github.com/1995eaton/chromium-vim[cVim],
|
||||
http://vimium.github.io/[Vimium],
|
||||
https://vimium.github.io/[Vimium],
|
||||
https://github.com/brookhong/Surfingkeys[Surfingkeys],
|
||||
https://key.saka.io/[Saka Key]
|
||||
* Firefox addons (based on WebExtensions):
|
||||
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
|
||||
https://key.saka.io[Saka Key],
|
||||
https://github.com/ueokande/vim-vixen[Vim Vixen],
|
||||
https://github.com/shinglyu/QuantumVim[QuantumVim],
|
||||
https://github.com/cmcaine/tridactyl[Tridactyl] (working
|
||||
on a https://bugzilla.mozilla.org/show_bug.cgi?id=1215061[better API] for
|
||||
keyboard integration in Firefox).
|
||||
@ -192,17 +189,23 @@ Inactive
|
||||
* https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1,
|
||||
https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] -
|
||||
main inspiration for qutebrowser)
|
||||
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
* https://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
|
||||
WebKit1)
|
||||
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
|
||||
* https://wiki.archlinux.org/index.php?title=Jumanji[jumanji] (C, GTK+ with WebKit1,
|
||||
original site is gone but Arch Linux has some data)
|
||||
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
|
||||
* https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
|
||||
* Firefox addons (not based on WebExtensions or no recent activity):
|
||||
http://www.vimperator.org/[Vimperator],
|
||||
http://5digits.org/pentadactyl/[Pentadactyl],
|
||||
http://bug.5digits.org/pentadactyl/index[Pentadactyl],
|
||||
https://github.com/akhodakivskiy/VimFx[VimFx],
|
||||
https://key.saka.io[Saka Key],
|
||||
https://github.com/shinglyu/QuantumVim[QuantumVim],
|
||||
* Chrome/Chromium addons:
|
||||
https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome],
|
||||
https://github.com/jinzhu/vrome[Vrome]
|
||||
https://key.saka.io[Saka Key],
|
||||
https://github.com/1995eaton/chromium-vim[cVim],
|
||||
|
||||
License
|
||||
-------
|
||||
@ -229,4 +232,4 @@ display PDF files in the browser. Windows releases come with a bundled pdf.js.
|
||||
pdf.js is distributed under the terms of the Apache License. You can
|
||||
find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the
|
||||
Windows release or after running `scripts/dev/update_3rdparty.py`), or online
|
||||
http://www.apache.org/licenses/LICENSE-2.0.html[here].
|
||||
https://www.apache.org/licenses/LICENSE-2.0.html[here].
|
||||
|
@ -15,12 +15,90 @@ 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 (unreleased)
|
||||
v1.5.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- The qute-pass userscript now has optional OTP support.
|
||||
- When `:spawn --userscript` is called with a count, that count is now
|
||||
passed to userscripts as `$QUTE_COUNT`.
|
||||
- New `content.mouse_lock` setting to handle HTML5 pointer locking.
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `:repeat` command now takes a count which is multiplied with the given
|
||||
"times" argument.
|
||||
- The default keybinding to leave passthrough mode was changed from `<Ctrl-V>`
|
||||
to `<Shift-Escape>`, which makes pasting from the clipboard easier in
|
||||
passthrough mode and is also unlikely to conflict with webpage bindings.
|
||||
- The `app_id` is now set to `qutebrowser` for Wayland.
|
||||
- `Command` or `Cmd` can now be used (instead of `Meta`) to map the Command key
|
||||
on macOS.
|
||||
|
||||
v1.4.2 (unreleased)
|
||||
-------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- The `content.xss_auditing` setting is now enabled by default, to mirror
|
||||
Chromium's rather than Qt's default behavior.
|
||||
- Long URLs in the statusbar are now elided at the end rather than in the
|
||||
middle, to make sure the hostname is completely visible whenever possible.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Crash in Qt 5.7.1 when a website uses `window.print()`.
|
||||
- The workaround for Nouveau graphic drivers now works properly again.
|
||||
- Crash when using `:follow-selected` with a link which is outside of the view.
|
||||
- Workaround for windows not showing as urgent with some window managers
|
||||
(like i3).
|
||||
- Crash when opening URLs with some unicode characters (IDNA 2008). Those URLs
|
||||
still won't open though, due to missing support in Qt.
|
||||
- Crash when a download directory which can't be created is configured.
|
||||
- Crash in the `importer.py` script when importing Chrome bookmarks from newer Chrome versions.
|
||||
- The `content.webrtc_public_interfaces_only` option didn't work on Qt 5.11 previously (it now does).
|
||||
Note it still does not work on Qt 5.10 (due to a Qt bug) and Qt < 5.9.2.
|
||||
- Repeated escaping of entries in `qute://log` when refreshing page.
|
||||
- The host blocker doesn't block 0.0.0.0 anymore.
|
||||
|
||||
v1.4.1
|
||||
------
|
||||
|
||||
Security
|
||||
~~~~~~~~
|
||||
|
||||
- CVE-2018-10895: Fix CSRF issue on the qute://settings page, leading to
|
||||
possible arbitrary code execution. See the related GitHub issue for details:
|
||||
https://github.com/qutebrowser/qutebrowser/issues/4060
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Rare crash when an error occurs in downloads.
|
||||
- Newlines are now stripped from the :version pastebin URL.
|
||||
- There's a new `mkvenv-pypi-old` environment in `tox.ini` which installs an
|
||||
older Qt, which is needed on Ubuntu 16.04.
|
||||
- Worked around a Qt issue which redirects to a `chrome-error://` page when
|
||||
trying to use U2F.
|
||||
- The `link_pyqt.py` script now works correctly with PyQt 5.11.
|
||||
- The Windows installer now uninstalls the old version before installing the
|
||||
new one, fixing issues with qutebrowser not starting after installing v1.4.0
|
||||
over v1.3.3.
|
||||
- The `:buffer` completion now sorts tabs with indices >= 10 correctly again.
|
||||
|
||||
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
|
||||
@ -40,11 +118,14 @@ Added
|
||||
* 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
|
||||
@ -55,8 +136,17 @@ Added
|
||||
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.79.
|
||||
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
|
||||
@ -87,26 +177,58 @@ Changed
|
||||
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 (unreleased)
|
||||
-------------------
|
||||
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
|
||||
------
|
||||
|
@ -5,6 +5,11 @@ The Compiler <mail@qutebrowser.org>
|
||||
:data-uri:
|
||||
:toc:
|
||||
|
||||
IMPORTANT: I'm currently (July 2018) more busy than usual until September,
|
||||
because of exams coming up. Review of non-trivial pull requests will thus be
|
||||
delayed until then. If you're reading this note after mid-September, please
|
||||
open an issue.
|
||||
|
||||
I `<3` footnote:[Of course, that says `<3` in HTML.] contributors!
|
||||
|
||||
This document contains guidelines for contributing to qutebrowser, as well as
|
||||
@ -88,7 +93,7 @@ git format-patch origin/master <1>
|
||||
Running qutebrowser
|
||||
-------------------
|
||||
|
||||
After link:install.asciidoc#tox[installing qutebrowser via tox], you can run
|
||||
After link:install.html#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.
|
||||
|
||||
@ -689,8 +694,6 @@ New PyQt release
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* See above.
|
||||
* Install new PyQt in Windows VM (32- and 64-bit).
|
||||
* Download new installer and update PyQt installer path in `ci_install.py`.
|
||||
* Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions.
|
||||
|
||||
qutebrowser release
|
||||
@ -712,8 +715,8 @@ qutebrowser release
|
||||
as closed.
|
||||
|
||||
* Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`.
|
||||
* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* Windows: Run `git checkout v1.X.Y; py -3.6 scripts\dev\build_release.py --asciidoc C:\Python27\python %userprofile%\bin\asciidoc-8.6.10\asciidoc.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand).
|
||||
* On server:
|
||||
- Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand).
|
||||
- Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser`
|
||||
|
@ -5,10 +5,10 @@ The Compiler <mail@qutebrowser.org>
|
||||
|
||||
[qanda]
|
||||
What is qutebrowser based on?::
|
||||
qutebrowser uses http://www.python.org/[Python], http://qt.io/[Qt] and
|
||||
http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
qutebrowser uses https://www.python.org/[Python], https://www.qt.io/[Qt] and
|
||||
https://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
+
|
||||
The concept of it is largely inspired by http://portix.bitbucket.org/dwb/[dwb]
|
||||
The concept of it is largely inspired by https://bitbucket.org/portix/dwb/[dwb]
|
||||
and http://www.vimperator.org/vimperator[Vimperator]. Many actions and
|
||||
key bindings are similar to dwb.
|
||||
|
||||
@ -16,34 +16,34 @@ Why another browser?::
|
||||
It might be hard to believe, but I didn't find any browser which I was
|
||||
happy with, so I started to write my own. Also, I needed a project to get
|
||||
into writing GUI applications with Python and
|
||||
link:http://qt.io/[Qt]/link:http://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
link:https://www.qt.io/[Qt]/link:https://www.riverbankcomputing.com/software/pyqt/intro[PyQt].
|
||||
+
|
||||
Read the next few questions to find out why I was unhappy with existing
|
||||
software.
|
||||
|
||||
What's wrong with link:http://portix.bitbucket.org/dwb/[dwb]/link:http://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/link:http://pwmt.org/projects/jumanji/[jumanji]/... (projects based on WebKitGTK)?::
|
||||
Most of them are based on the http://webkitgtk.org/[WebKitGTK+]
|
||||
http://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
|
||||
What's wrong with link:https://bitbucket.org/portix/dwb/[dwb]/link:https://sourceforge.net/projects/vimprobable/[vimprobable]/link:https://mason-larobina.github.io/luakit/[luakit]/jumanji/... (projects based on WebKitGTK)?::
|
||||
Most of them are based on the https://webkitgtk.org/[WebKitGTK+]
|
||||
https://webkitgtk.org/reference/webkitgtk/stable/index.html[WebKit1] API,
|
||||
which causes a lot of crashes. As the GTK API using WebKit1 is
|
||||
https://lists.webkit.org/pipermail/webkit-gtk/2014-March/001821.html[deprecated],
|
||||
these bugs are never going to be fixed.
|
||||
+
|
||||
When qutebrowser was created, the newer
|
||||
http://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
||||
https://webkitgtk.org/reference/webkit2gtk/stable/index.html[WebKit2 API] lacked
|
||||
basic features like proxy support, and almost no projects have started porting
|
||||
to WebKit2. In the meantime, this situation has improved a bit, but there are
|
||||
still only a few projects which have some kind of WebKit2 support (see the
|
||||
https://github.com/qutebrowser/qutebrowser#similar-projects[list of
|
||||
alternatives]).
|
||||
+
|
||||
qutebrowser uses http://qt.io/[Qt] and
|
||||
http://wiki.qt.io/QtWebEngine[QtWebEngine] by default (and supports
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] optionally). QtWebEngine is based on
|
||||
qutebrowser uses https://www.qt.io/[Qt] and
|
||||
https://wiki.qt.io/QtWebEngine[QtWebEngine] by default (and supports
|
||||
https://wiki.qt.io/QtWebKit[QtWebKit] optionally). QtWebEngine is based on
|
||||
Google's https://www.chromium.org/Home[Chromium]. With an up-to-date Qt, it has
|
||||
much more man-power behind it than WebKitGTK+ has, and thus supports more modern
|
||||
web features - it's also arguably more secure.
|
||||
|
||||
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
|
||||
What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:http://bug.5digits.org/pentadactyl/[Pentadactyl]/link:http://www.vimperator.org/vimperator[Vimperator]?::
|
||||
Firefox likes to break compatibility with addons on each upgrade, gets
|
||||
slower and more bloated with every upgrade, and has some
|
||||
https://blog.mozilla.org/advancingcontent/2014/02/11/publisher-transformation-with-users-at-the-center/[horrible
|
||||
@ -51,20 +51,20 @@ What's wrong with https://www.mozilla.org/en-US/firefox/new/[Firefox] and link:h
|
||||
+
|
||||
Also, developing addons for it is a nightmare.
|
||||
|
||||
What's wrong with http://www.chromium.org/Home[Chromium] and https://vimium.github.io/[Vimium]?::
|
||||
What's wrong with https://www.chromium.org/Home[Chromium] and https://vimium.github.io/[Vimium]?::
|
||||
The Chrome plugin API doesn't seem to allow much freedom for plugin
|
||||
writers, which results in Vimium not really having all the features you'd
|
||||
expect from a proper minimal, vim-like browser.
|
||||
|
||||
Why Python?::
|
||||
I enjoy writing Python since 2011, which made it one of the possible
|
||||
choices. I wanted to use http://qt.io/[Qt] because of
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
|
||||
http://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
|
||||
choices. I wanted to use https://www.qt.io/[Qt] because of
|
||||
https://wiki.qt.io/QtWebKit[QtWebKit] so I didn't have
|
||||
https://wiki.qt.io/Category:LanguageBindings[many other choices]. I don't
|
||||
like C++ and can't write it very well, so that wasn't an alternative.
|
||||
|
||||
But isn't Python too slow for a browser?::
|
||||
http://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
|
||||
https://www.infoworld.com/d/application-development/van-rossum-python-not-too-slow-188715[No.]
|
||||
I believe efficiency while coding is a lot more important than efficiency
|
||||
while running. Also, most of the heavy lifting of qutebrowser is done by Qt
|
||||
and WebKit in C++, with the
|
||||
@ -74,7 +74,7 @@ Is qutebrowser secure?::
|
||||
Most security issues are in the backend (which handles networking,
|
||||
rendering, JavaScript, etc.) and not qutebrowser itself.
|
||||
+
|
||||
qutebrowser uses http://wiki.qt.io/QtWebEngine[QtWebEngine] by default.
|
||||
qutebrowser uses https://wiki.qt.io/QtWebEngine[QtWebEngine] by default.
|
||||
QtWebEngine is based on Google's https://www.chromium.org/Home[Chromium]. While
|
||||
Qt only updates to a new Chromium release on every minor Qt release (all ~6
|
||||
months), every patch release backports security fixes from newer Chromium
|
||||
@ -84,26 +84,41 @@ do anything. Chromium's process isolation and
|
||||
https://chromium.googlesource.com/chromium/src/+/master/docs/design/sandbox.md[sandboxing]
|
||||
features are also enabled as a second line of defense.
|
||||
+
|
||||
http://wiki.qt.io/QtWebKit[QtWebKit] is also supported as an alternative
|
||||
https://wiki.qt.io/QtWebKit[QtWebKit] is also supported as an alternative
|
||||
backend, but hasn't seen new releases
|
||||
https://github.com/annulen/webkit/releases[in a while]. It also doesn't have any
|
||||
process isolation or sandboxing.
|
||||
process isolation or sandboxing. See
|
||||
https://github.com/qutebrowser/qutebrowser/issues/4039[#4039] for more details.
|
||||
+
|
||||
Security issues in qutebrowser's code happen very rarely (as per March 2018,
|
||||
there has been one security issue caused by qutebrowser in over four years) and
|
||||
are fixed timely. To report security bugs, please contact me directly at
|
||||
mail@qutebrowser.org, GPG ID
|
||||
Security issues in qutebrowser's code happen very rarely (as per July 2018,
|
||||
there have been three security issues caused by qutebrowser in over 4.5 years).
|
||||
Those were handled appropriately
|
||||
(http://seclists.org/oss-sec/2018/q3/29[example]) and fixed timely. To report
|
||||
security bugs, please contact me directly at mail@qutebrowser.org, GPG ID
|
||||
https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072].
|
||||
|
||||
Is there an adblocker?::
|
||||
There is a host-based adblocker which takes /etc/hosts-like lists. A "real"
|
||||
adblocker has a
|
||||
http://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
|
||||
https://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
|
||||
impact] on browsing speed and
|
||||
https://blog.mozilla.org/nnethercote/2014/05/14/adblock-pluss-effect-on-firefoxs-memory-usage/[RAM
|
||||
usage], so implementing support for AdBlockPlus-like lists is currently not
|
||||
a priority.
|
||||
|
||||
How can I get No-Script-like behavior?::
|
||||
To disable JavaScript by default:
|
||||
+
|
||||
----
|
||||
:set content.javascript.enabled false
|
||||
----
|
||||
+
|
||||
The basic command for enabling JavaScript for the current host is `tsh`.
|
||||
This will allow JavaScript execution for the current session.
|
||||
Use `S` instead of `s` to make the exception permanent.
|
||||
With `H` instead of `h`, subdomains are included.
|
||||
With `u` instead of `h`, only the current URL is whitelisted (not the whole host).
|
||||
|
||||
How do I play Youtube videos with mpv?::
|
||||
You can easily add a key binding to play youtube videos inside a real video
|
||||
player - optionally even with hinting for links:
|
||||
@ -251,7 +266,7 @@ Unable to view flash content.::
|
||||
to use the flash plugin. Using the command `:set content.plugins true`
|
||||
in qutebrowser will enable plugins. Packages for flash should
|
||||
be provided for your platform or it can be obtained from
|
||||
http://get.adobe.com/flashplayer/[Adobe].
|
||||
https://get.adobe.com/flashplayer/[Adobe].
|
||||
|
||||
Experiencing freezing on sites like duckduckgo and youtube.::
|
||||
This issue could be caused by stale plugin files installed by `mozplugger`
|
||||
@ -300,5 +315,5 @@ My issue is not listed.::
|
||||
https://github.com/qutebrowser/qutebrowser/issues[the issue tracker] or
|
||||
using the `:report` command.
|
||||
If you are reporting a segfault, make sure you read the
|
||||
link:stacktrace.asciidoc[guide] on how to report them with all needed
|
||||
link:doc/stacktrace.html[guide] on how to report them with all needed
|
||||
information.
|
||||
|
@ -918,6 +918,9 @@ Repeat a given command.
|
||||
* +'times'+: How many times to repeat.
|
||||
* +'command'+: The command to run, with optional args.
|
||||
|
||||
==== count
|
||||
Multiplies with 'times' when given.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
* With this command, +;;+ is interpreted literally instead of splitting off a second command.
|
||||
@ -1197,6 +1200,9 @@ Spawn a command in a shell.
|
||||
* +*-o*+, +*--output*+: Whether the output should be shown in a new tab.
|
||||
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||
|
||||
==== count
|
||||
Given to userscripts as $QUTE_COUNT.
|
||||
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
|
@ -395,6 +395,7 @@ 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]
|
||||
- https://github.com/evannagle/qutebrowser-dracula-theme[Dracula]
|
||||
|
||||
Avoiding flake8 errors
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -143,6 +143,7 @@
|
||||
|<<content.local_content_can_access_remote_urls,content.local_content_can_access_remote_urls>>|Allow locally loaded documents to access remote URLs.
|
||||
|<<content.local_storage,content.local_storage>>|Enable support for HTML 5 local storage and Web SQL.
|
||||
|<<content.media_capture,content.media_capture>>|Allow websites to record audio/video.
|
||||
|<<content.mouse_lock,content.mouse_lock>>|Allow websites to lock your mouse pointer.
|
||||
|<<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.
|
||||
@ -563,6 +564,7 @@ Default:
|
||||
* +pass:[g0]+: +pass:[tab-focus 1]+
|
||||
* +pass:[gB]+: +pass:[set-cmd-text -s :bookmark-load -t]+
|
||||
* +pass:[gC]+: +pass:[tab-clone]+
|
||||
* +pass:[gD]+: +pass:[tab-give]+
|
||||
* +pass:[gO]+: +pass:[set-cmd-text :open -t -r {url:pretty}]+
|
||||
* +pass:[gU]+: +pass:[navigate up -t]+
|
||||
* +pass:[g^]+: +pass:[tab-focus 1]+
|
||||
@ -634,7 +636,7 @@ Default:
|
||||
* +pass:[}}]+: +pass:[navigate next -t]+
|
||||
- +pass:[passthrough]+:
|
||||
|
||||
* +pass:[<Ctrl-V>]+: +pass:[leave-mode]+
|
||||
* +pass:[<Shift-Escape>]+: +pass:[leave-mode]+
|
||||
- +pass:[prompt]+:
|
||||
|
||||
* +pass:[<Alt-B>]+: +pass:[rl-backward-word]+
|
||||
@ -1571,6 +1573,8 @@ Default: +pass:[iso-8859-1]+
|
||||
Allow websites to share screen content.
|
||||
On Qt < 5.10, a dialog box is always displayed, even if this is set to "true".
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -1610,6 +1614,8 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.geolocation
|
||||
Allow websites to request geolocations.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -1623,6 +1629,9 @@ Default: +pass:[ask]+
|
||||
[[content.headers.accept_language]]
|
||||
=== content.headers.accept_language
|
||||
Value to send in the `Accept-Language` header.
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
@ -1632,6 +1641,8 @@ Default: +pass:[en-US,en]+
|
||||
=== content.headers.custom
|
||||
Custom headers for qutebrowser HTTP requests.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Dict>>
|
||||
|
||||
Default: empty
|
||||
@ -1641,6 +1652,8 @@ Default: empty
|
||||
Value to send in the `DNT` header.
|
||||
When this is set to true, qutebrowser asks websites to not track your identity. If set to null, the DNT header is not sent at all.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[true]+
|
||||
@ -1665,6 +1678,9 @@ This setting is only available with the QtWebKit backend.
|
||||
[[content.headers.user_agent]]
|
||||
=== content.headers.user_agent
|
||||
User agent to send. Unset to send the default.
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,String>>
|
||||
|
||||
@ -1844,6 +1860,8 @@ Default: +pass:[true]+
|
||||
=== content.media_capture
|
||||
Allow websites to record audio/video.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -1856,6 +1874,26 @@ Default: +pass:[ask]+
|
||||
|
||||
This setting is only available with the QtWebEngine backend.
|
||||
|
||||
[[content.mouse_lock]]
|
||||
=== content.mouse_lock
|
||||
Allow websites to lock your mouse pointer.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
|
||||
* +true+
|
||||
* +false+
|
||||
* +ask+
|
||||
|
||||
Default: +pass:[ask]+
|
||||
|
||||
On QtWebEngine, this setting requires Qt 5.8 or newer.
|
||||
|
||||
On QtWebKit, this setting is unavailable.
|
||||
|
||||
[[content.netrc_file]]
|
||||
=== content.netrc_file
|
||||
Netrc-file for HTTP authentication.
|
||||
@ -1869,6 +1907,8 @@ Default: empty
|
||||
=== content.notifications
|
||||
Allow websites to show notifications.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -1896,6 +1936,8 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.persistent_storage
|
||||
Allow websites to request persistent storage quota via `navigator.webkitPersistentStorage.requestQuota`.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -1968,6 +2010,8 @@ This setting is only available with the QtWebKit backend.
|
||||
=== content.register_protocol_handler
|
||||
Allow websites to register protocol handlers via `navigator.registerProtocolHandler`.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -1986,6 +2030,8 @@ On QtWebKit, this setting is unavailable.
|
||||
=== content.ssl_strict
|
||||
Validate SSL handshakes.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,BoolAsk>>
|
||||
|
||||
Valid values:
|
||||
@ -2038,13 +2084,13 @@ Default: +pass:[false]+
|
||||
[[content.xss_auditing]]
|
||||
=== content.xss_auditing
|
||||
Monitor load requests for cross-site scripting attempts.
|
||||
Suspicious scripts will be blocked and reported in the inspector's JavaScript console. Enabling this feature might have an impact on performance.
|
||||
Suspicious scripts will be blocked and reported in the inspector's JavaScript console.
|
||||
|
||||
This setting supports URL patterns.
|
||||
|
||||
Type: <<types,Bool>>
|
||||
|
||||
Default: +pass:[false]+
|
||||
Default: +pass:[true]+
|
||||
|
||||
[[downloads.location.directory]]
|
||||
=== downloads.location.directory
|
||||
|
@ -222,6 +222,38 @@ There are prebuilt RPMs available at https://software.opensuse.org/download.html
|
||||
|
||||
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
|
||||
|
||||
On Slackware
|
||||
------------
|
||||
|
||||
qutebrowser is available in the 3rd party repository at http://slackbuilds.org[slackbuilds.org]
|
||||
|
||||
An easy way to install it is with sbopkg (frontend for slackbuilds.org) available at http://sbopkg.org[sbopkg.org]
|
||||
|
||||
sbopkg can be run with a dialog screen interface, or via command line options.
|
||||
|
||||
After installing the latest sbopkg package, choose your release version, and sync the repo.
|
||||
|
||||
----
|
||||
sbopkg -V 14.2
|
||||
sbopkg -r
|
||||
----
|
||||
|
||||
The pyPEG2 and MarkupSafe dependencies both need building for python3. You can either set PYTHON3=yes in the shell or set those as options in the dialog menu for each.
|
||||
|
||||
Generate a queue file for qutebrowser and dependencies:
|
||||
|
||||
----
|
||||
sqg -p qutebrowser
|
||||
----
|
||||
|
||||
Then load the queue in the dialog queue menu or via:
|
||||
|
||||
----
|
||||
PYTHON3=yes sbopkg -i qutebrowser
|
||||
----
|
||||
|
||||
If you use the dialog screen you can deselect any already-installed packages that you don't need/want to rebuild before starting the build process.
|
||||
|
||||
On OpenBSD
|
||||
----------
|
||||
|
||||
@ -392,6 +424,10 @@ https://docs.python.org/3/library/venv.html[virtual environment]:
|
||||
$ tox -e mkvenv-pypi
|
||||
----
|
||||
|
||||
If your system comes with Python 3.5.3 or older (such as Ubuntu 16.04 LTS), use
|
||||
`tox -e mkvenv-pypi-old` instead. This installs an older Qt version (5.10) due
|
||||
to bugs in newer versions.
|
||||
|
||||
This installs all needed Python dependencies in a `.venv` subfolder.
|
||||
|
||||
This comes with an up-to-date Qt/PyQt including QtWebEngine, but has a few
|
||||
@ -407,6 +443,14 @@ caveats:
|
||||
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
|
||||
qutebrowser if you want SSL to work in certain downloads (e.g. for
|
||||
`:adblock-update` or `:download`).
|
||||
* On Ubuntu (tested on 18.04), you will need to install the `libssl1.0.0`
|
||||
package (`apt install libssl1.0.0`). Then, in the qutebrowser git
|
||||
repository, create a directory named `libssl` (`mkdir libssl`), and link
|
||||
`libcrypto.so.1.0.0` and `libssl.so.1.0.0` into it without the versioning
|
||||
part in their names (`ln -s /usr/lib/x86_64-linux-gnu/libcrypto.so.1.0.0
|
||||
libssl/libcrypto.so` and `ln -s /usr/lib/x86_64-linux-gnu/libssl.so.1.0.0
|
||||
libssl/libssl.so`). Now you can start qutebrowser issuing `export
|
||||
LD_LIBRARY_PATH=$(pwd)/libssl` beforehand.
|
||||
- It comes with a QtWebEngine compiled without proprietary codec support (such
|
||||
as h.264).
|
||||
|
||||
|
@ -45,12 +45,13 @@ In `command` mode:
|
||||
- `QUTE_URL`: The current URL.
|
||||
- `QUTE_TITLE`: The title of the current page.
|
||||
- `QUTE_SELECTED_TEXT`: The text currently selected on the page.
|
||||
- `QUTE_COUNT`: The `count` from the spawn command running the userscript.
|
||||
|
||||
In `hints` mode:
|
||||
|
||||
- `QUTE_URL`: The URL selected via hints.
|
||||
- `QUTE_SELECTED_TEXT`: The plain text of the element selected via hints.
|
||||
- `QUTE_SELECTED_HTML` The HTML of the element selected via hints.
|
||||
- `QUTE_SELECTED_HTML`: The HTML of the element selected via hints.
|
||||
|
||||
Sending commands
|
||||
----------------
|
||||
|
@ -13,7 +13,7 @@
|
||||
height="682.66669"
|
||||
id="svg2"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
version="1.0"
|
||||
sodipodi:docname="cheatsheet.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
@ -33,7 +33,7 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.7536248"
|
||||
inkscape:cx="430.72917"
|
||||
inkscape:cx="613.20834"
|
||||
inkscape:cy="268.64059"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
@ -3085,7 +3085,9 @@
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara4056"> (to index/left/right)</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3858">gC - clone tab </flowPara><flowPara
|
||||
id="flowPara3858">gC - clone tab</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara6098">gD - detach tab </flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3860">gf - view page source</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
@ -3097,9 +3099,9 @@
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3921">sf - save config</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3925">ss - set setting</flowPara><flowPara
|
||||
id="flowPara3925">ss - set setting (sl: temp)</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3927">sl - set temp. setting</flowPara><flowPara
|
||||
id="flowPara3927" /><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
id="flowPara3929">sk - bind key</flowPara><flowPara
|
||||
style="font-size:10.66666698px;line-height:1.25;font-family:sans-serif;fill:#000000;stroke-width:1.06666672"
|
||||
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 181 KiB |
@ -40,6 +40,9 @@ Section "Install"
|
||||
; Uninstall old versions
|
||||
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{633F41F9-FE9B-42D1-9CC4-718CBD01EE11}'
|
||||
ExecWait 'MsiExec.exe /quiet /qn /norestart /X{9331D947-AC86-4542-A755-A833429C6E69}'
|
||||
IfFileExists "$INSTDIR\uninst.exe" 0 +2
|
||||
ExecWait "$INSTDIR\uninst.exe /S _?=$INSTDIR"
|
||||
CreateDirectory "$INSTDIR"
|
||||
|
||||
SetOutPath "$INSTDIR"
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
certifi==2018.4.16
|
||||
certifi==2018.8.13
|
||||
chardet==3.0.4
|
||||
codecov==2.0.15
|
||||
coverage==4.5.1
|
||||
idna==2.7
|
||||
requests==2.18.4
|
||||
urllib3==1.22
|
||||
requests==2.19.1
|
||||
urllib3==1.23
|
||||
|
@ -2,14 +2,14 @@
|
||||
|
||||
attrs==18.1.0
|
||||
flake8==3.5.0
|
||||
flake8-bugbear==18.2.0
|
||||
flake8-bugbear==18.8.0
|
||||
flake8-builtins==1.4.1 # rq.filter: != 1.4.0
|
||||
flake8-comprehensions==1.4.1
|
||||
flake8-copyright==0.2.0
|
||||
flake8-debugger==3.1.0
|
||||
flake8-deprecated==1.3
|
||||
flake8-docstrings==1.3.0
|
||||
flake8-future-import==0.4.4
|
||||
flake8-future-import==0.4.5
|
||||
flake8-mock==0.3
|
||||
flake8-per-file-ignores==0.6
|
||||
flake8-polyfill==1.0.2
|
||||
|
@ -3,6 +3,6 @@
|
||||
appdirs==1.4.3
|
||||
packaging==17.1
|
||||
pyparsing==2.2.0
|
||||
setuptools==39.2.0
|
||||
setuptools==40.1.0
|
||||
six==1.11.0
|
||||
wheel==0.31.1
|
||||
|
@ -1,7 +1,7 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
altgraph==0.15
|
||||
altgraph==0.16.1
|
||||
future==0.16.0
|
||||
macholib==1.9
|
||||
pefile==2017.11.5
|
||||
macholib==1.10
|
||||
pefile==2018.8.8
|
||||
PyInstaller==3.3.1
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 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.8.13
|
||||
chardet==3.0.4
|
||||
github3.py==1.1.0
|
||||
idna==2.7
|
||||
@ -11,8 +11,8 @@ mccabe==0.6.1
|
||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||
python-dateutil==2.7.3
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
requests==2.19.1
|
||||
six==1.11.0
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.22
|
||||
urllib3==1.23
|
||||
wrapt==1.10.11
|
||||
|
@ -1,18 +1,20 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
astroid==1.6.5
|
||||
certifi==2018.4.16
|
||||
astroid==2.0.4
|
||||
certifi==2018.8.13
|
||||
chardet==3.0.4
|
||||
github3.py==1.1.0
|
||||
idna==2.7
|
||||
isort==4.3.4
|
||||
lazy-object-proxy==1.3.1
|
||||
mccabe==0.6.1
|
||||
pylint==1.9.2
|
||||
pylint==2.1.1
|
||||
python-dateutil==2.7.3
|
||||
./scripts/dev/pylint_checkers
|
||||
requests==2.18.4
|
||||
requests==2.19.1
|
||||
six==1.11.0
|
||||
typed-ast==1.1.0
|
||||
typing==3.6.4
|
||||
uritemplate==3.0.0
|
||||
urllib3==1.22
|
||||
urllib3==1.23
|
||||
wrapt==1.10.11
|
||||
|
@ -1,4 +0,0 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.10 # rq.filter: != 5.10.1
|
||||
sip==4.19.8
|
@ -1,2 +0,0 @@
|
||||
PyQt5==5.10.0
|
||||
#@ filter: PyQt5 != 5.10.1
|
@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.10.1
|
||||
sip==4.19.8
|
||||
PyQt5==5.11.2
|
||||
PyQt5-sip==4.19.12
|
||||
|
@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
docutils==0.14
|
||||
pyroma==2.3.1
|
||||
pyroma==2.4
|
||||
|
@ -35,8 +35,4 @@ git+https://github.com/pallets/markupsafe.git
|
||||
hg+http://bitbucket.org/birkenfeld/pygments-main
|
||||
hg+https://bitbucket.org/fdik/pypeg
|
||||
git+https://github.com/python-attrs/attrs.git
|
||||
|
||||
# Fails to build:
|
||||
# gcc: error: ext/_yaml.c: No such file or directory
|
||||
# hg+https://bitbucket.org/xi/pyyaml
|
||||
PyYAML==3.12
|
||||
git+https://github.com/yaml/pyyaml.git
|
||||
|
@ -1,8 +1,10 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
atomicwrites==1.1.5
|
||||
attrs==18.1.0
|
||||
beautifulsoup4==4.6.0
|
||||
cheroot==6.3.1
|
||||
backports.functools-lru-cache==1.5
|
||||
beautifulsoup4==4.6.3
|
||||
cheroot==6.4.0
|
||||
click==6.7
|
||||
# colorama==0.3.9
|
||||
coverage==4.5.1
|
||||
@ -11,30 +13,30 @@ fields==5.0.0
|
||||
Flask==1.0.2
|
||||
glob2==0.6
|
||||
hunter==2.0.2
|
||||
hypothesis==3.57.0
|
||||
hypothesis==3.69.0
|
||||
itsdangerous==0.24
|
||||
# Jinja2==2.10
|
||||
Mako==1.0.7
|
||||
# MarkupSafe==1.0
|
||||
more-itertools==4.2.0
|
||||
more-itertools==4.3.0
|
||||
parse==1.8.4
|
||||
parse-type==0.4.2
|
||||
pluggy==0.6.0
|
||||
py==1.5.3
|
||||
pluggy==0.7.1
|
||||
py==1.5.4
|
||||
py-cpuinfo==4.0.0
|
||||
pytest==3.6.1
|
||||
pytest==3.6.4 # rq.filter: != 3.7, != 3.7.1, != 3.7.2
|
||||
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.0
|
||||
pytest-repeat==0.4.1
|
||||
pytest-qt==3.0.0
|
||||
pytest-repeat==0.6.0
|
||||
pytest-rerunfailures==4.1
|
||||
pytest-travis-fold==1.3.0
|
||||
pytest-xvfb==1.1.0
|
||||
PyVirtualDisplay==0.2.1
|
||||
six==1.11.0
|
||||
vulture==0.27
|
||||
vulture==0.29
|
||||
Werkzeug==0.14.1
|
||||
|
@ -4,7 +4,7 @@ coverage
|
||||
Flask
|
||||
hunter
|
||||
hypothesis
|
||||
pytest
|
||||
pytest<3.7
|
||||
pytest-bdd
|
||||
pytest-benchmark
|
||||
pytest-cov
|
||||
@ -19,3 +19,4 @@ pytest-xvfb
|
||||
vulture
|
||||
|
||||
#@ ignore: Jinja2, MarkupSafe, colorama
|
||||
#@ filter: pytest != 3.7, != 3.7.1, != 3.7.2
|
||||
|
@ -1,7 +1,9 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
pluggy==0.6.0
|
||||
py==1.5.3
|
||||
packaging==17.1
|
||||
pluggy==0.7.1
|
||||
py==1.5.4
|
||||
pyparsing==2.2.0
|
||||
six==1.11.0
|
||||
tox==3.0.0
|
||||
tox==3.2.1
|
||||
virtualenv==16.0.0
|
||||
|
@ -1,3 +1,3 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
vulture==0.27
|
||||
vulture==0.29
|
||||
|
@ -25,9 +25,17 @@ demonstration can be seen here: https://i.imgur.com/KN3XuZP.gif.
|
||||
USAGE = """The domain of the site has to appear as a segment in the pass path, for example: "github.com/cryzed" or
|
||||
"websites/github.com". How the username and password are determined is freely configurable using the CLI arguments. 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."""
|
||||
[USERNAME]<Tab>[PASSWORD], which is compatible with almost all login forms.
|
||||
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), pass.
|
||||
Suggested bindings similar to Uzbl's `formfiller` script:
|
||||
|
||||
config.bind('<z><l>', 'spawn --userscript qute-pass')
|
||||
config.bind('<z><u><l>', 'spawn --userscript qute-pass --username-only')
|
||||
config.bind('<z><p><l>', 'spawn --userscript qute-pass --password-only')
|
||||
config.bind('<z><o><l>', 'spawn --userscript qute-pass --otp-only')
|
||||
"""
|
||||
|
||||
EPILOG = """Dependencies: tldextract (Python 3 module), pass, pass-otp (optional).
|
||||
For issues and feedback please use: https://github.com/cryzed/qutebrowser-userscripts.
|
||||
|
||||
WARNING: The login details are viewable as plaintext in qutebrowser's debug log (qute://log) and might be shared if
|
||||
@ -66,6 +74,7 @@ argument_parser.add_argument('--merge-candidates', '-m', action='store_true',
|
||||
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')
|
||||
group.add_argument('--otp-only', '-o', action='store_true', help='Only insert OTP code')
|
||||
|
||||
stderr = functools.partial(print, file=sys.stderr)
|
||||
|
||||
@ -87,7 +96,7 @@ def qute_command(command):
|
||||
|
||||
def find_pass_candidates(domain, password_store_path):
|
||||
candidates = []
|
||||
for path, directories, file_names in os.walk(password_store_path):
|
||||
for path, directories, file_names in os.walk(password_store_path, followlinks=True):
|
||||
if directories or domain not in path.split(os.path.sep):
|
||||
continue
|
||||
|
||||
@ -98,11 +107,19 @@ def find_pass_candidates(domain, password_store_path):
|
||||
return candidates
|
||||
|
||||
|
||||
def pass_(path, encoding):
|
||||
process = subprocess.run(['pass', path], stdout=subprocess.PIPE)
|
||||
def _run_pass(command, encoding):
|
||||
process = subprocess.run(command, stdout=subprocess.PIPE)
|
||||
return process.stdout.decode(encoding).strip()
|
||||
|
||||
|
||||
def pass_(path, encoding):
|
||||
return _run_pass(['pass', path], encoding)
|
||||
|
||||
|
||||
def pass_otp(path, encoding):
|
||||
return _run_pass(['pass', 'otp', path], encoding)
|
||||
|
||||
|
||||
def dmenu(items, invocation, encoding):
|
||||
command = shlex.split(invocation)
|
||||
process = subprocess.run(command, input='\n'.join(items).encode(encoding), stdout=subprocess.PIPE)
|
||||
@ -169,6 +186,9 @@ def main(arguments):
|
||||
fake_key_raw(username)
|
||||
elif arguments.password_only:
|
||||
fake_key_raw(password)
|
||||
elif arguments.otp_only:
|
||||
otp = pass_otp(selection, arguments.io_encoding)
|
||||
fake_key_raw(otp)
|
||||
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
|
||||
|
@ -63,4 +63,8 @@ qt_log_ignore =
|
||||
^inotify_add_watch\(".*"\) failed: "No space left on device"
|
||||
^QSettings::value: Empty key passed
|
||||
^Icon theme ".*" not found
|
||||
^Error receiving trust for a CA certificate
|
||||
xfail_strict = true
|
||||
filterwarnings =
|
||||
# This happens in many qutebrowser dependencies...
|
||||
ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning
|
||||
|
@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 3, 2)
|
||||
__version_info__ = (1, 4, 1)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
@ -104,6 +104,7 @@ def run(args):
|
||||
qApp = Application(args)
|
||||
qApp.setOrganizationName("qutebrowser")
|
||||
qApp.setApplicationName("qutebrowser")
|
||||
qApp.setDesktopFileName("qutebrowser")
|
||||
qApp.setApplicationVersion(qutebrowser.__version__)
|
||||
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
|
||||
|
||||
@ -129,6 +130,9 @@ def run(args):
|
||||
sys.exit(usertypes.Exit.err_ipc)
|
||||
|
||||
if server is None:
|
||||
if args.backend is not None:
|
||||
log.init.warning(
|
||||
"Backend from the running instance will be used")
|
||||
sys.exit(usertypes.Exit.ok)
|
||||
else:
|
||||
server.got_args.connect(lambda args, target_arg, cwd:
|
||||
|
@ -234,7 +234,9 @@ class HostBlocker:
|
||||
hosts = parts[1:]
|
||||
|
||||
for host in hosts:
|
||||
if '.' in host and not host.endswith('.localdomain'):
|
||||
if ('.' in host and
|
||||
not host.endswith('.localdomain') and
|
||||
host != '0.0.0.0'):
|
||||
self._blocked_hosts.add(host)
|
||||
|
||||
return True
|
||||
|
@ -22,11 +22,11 @@
|
||||
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
|
||||
from PyQt5.QtWidgets import QWidget, QApplication, QDialog
|
||||
from PyQt5.QtPrintSupport import QPrintDialog
|
||||
|
||||
import pygments
|
||||
import pygments.lexers
|
||||
@ -38,6 +38,7 @@ 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)
|
||||
@ -187,8 +188,9 @@ class AbstractPrinting:
|
||||
|
||||
"""Attribute of AbstractTab for printing the page."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, tab):
|
||||
self._widget = None
|
||||
self._tab = tab
|
||||
|
||||
def check_pdf_support(self):
|
||||
raise NotImplementedError
|
||||
@ -212,6 +214,29 @@ class AbstractPrinting:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def show_dialog(self):
|
||||
"""Print with a QPrintDialog."""
|
||||
self.check_printer_support()
|
||||
|
||||
def print_callback(ok):
|
||||
"""Called when printing finished."""
|
||||
if not ok:
|
||||
message.error("Printing failed!")
|
||||
diag.deleteLater()
|
||||
|
||||
def do_print():
|
||||
"""Called when the dialog was closed."""
|
||||
self.to_printer(diag.printer(), print_callback)
|
||||
|
||||
diag = QPrintDialog(self._tab)
|
||||
if utils.is_mac:
|
||||
# For some reason we get a segfault when using open() on macOS
|
||||
ret = diag.exec_()
|
||||
if ret == QDialog.Accepted:
|
||||
do_print()
|
||||
else:
|
||||
diag.open(do_print)
|
||||
|
||||
|
||||
class AbstractSearch(QObject):
|
||||
|
||||
@ -829,12 +854,20 @@ class AbstractTab(QWidget):
|
||||
navigation.navigation_type,
|
||||
navigation.is_main_frame))
|
||||
|
||||
if (navigation.navigation_type == navigation.Type.link_clicked and
|
||||
not navigation.url.isValid()):
|
||||
msg = urlutils.get_errstring(navigation.url,
|
||||
"Invalid link clicked")
|
||||
message.error(msg)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
if not navigation.url.isValid():
|
||||
# Also a WORKAROUND for missing IDNA 2008 support in QUrl, see
|
||||
# https://bugreports.qt.io/browse/QTBUG-60364
|
||||
|
||||
if navigation.navigation_type == navigation.Type.link_clicked:
|
||||
msg = urlutils.get_errstring(navigation.url,
|
||||
"Invalid link clicked")
|
||||
message.error(msg)
|
||||
self.data.open_target = usertypes.ClickTarget.normal
|
||||
|
||||
log.webview.debug("Ignoring invalid URL {} in "
|
||||
"acceptNavigationRequest: {}".format(
|
||||
navigation.url.toDisplayString(),
|
||||
navigation.url.errorString()))
|
||||
navigation.accepted = False
|
||||
|
||||
def handle_auto_insert_mode(self, ok):
|
||||
@ -893,10 +926,6 @@ class AbstractTab(QWidget):
|
||||
self._progress = perc
|
||||
self.load_progress.emit(perc)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def url(self, requested=False):
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -25,9 +25,9 @@ import shlex
|
||||
import functools
|
||||
import typing
|
||||
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
|
||||
from PyQt5.QtWidgets import QApplication, QTabBar
|
||||
from PyQt5.QtCore import pyqtSlot, Qt, QUrl, QEvent, QUrlQuery
|
||||
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
|
||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||
|
||||
from qutebrowser.commands import userscripts, cmdexc, cmdutils, runners
|
||||
from qutebrowser.config import config, configdata
|
||||
@ -415,27 +415,6 @@ class CommandDispatcher:
|
||||
tab.printing.to_pdf(filename)
|
||||
log.misc.debug("Print to file: {}".format(filename))
|
||||
|
||||
def _print(self, tab):
|
||||
"""Print with a QPrintDialog."""
|
||||
def print_callback(ok):
|
||||
"""Called when printing finished."""
|
||||
if not ok:
|
||||
message.error("Printing failed!")
|
||||
diag.deleteLater()
|
||||
|
||||
def do_print():
|
||||
"""Called when the dialog was closed."""
|
||||
tab.printing.to_printer(diag.printer(), print_callback)
|
||||
|
||||
diag = QPrintDialog(tab)
|
||||
if utils.is_mac:
|
||||
# For some reason we get a segfault when using open() on macOS
|
||||
ret = diag.exec_()
|
||||
if ret == QDialog.Accepted:
|
||||
do_print()
|
||||
else:
|
||||
diag.open(do_print)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', name='print',
|
||||
scope='window')
|
||||
@cmdutils.argument('count', count=True)
|
||||
@ -453,22 +432,15 @@ class CommandDispatcher:
|
||||
return
|
||||
|
||||
try:
|
||||
if pdf:
|
||||
tab.printing.check_pdf_support()
|
||||
else:
|
||||
tab.printing.check_printer_support()
|
||||
if preview:
|
||||
tab.printing.check_preview_support()
|
||||
self._print_preview(tab)
|
||||
elif pdf:
|
||||
self._print_pdf(tab, pdf)
|
||||
else:
|
||||
tab.printing.show_dialog()
|
||||
except browsertab.WebTabError as e:
|
||||
raise cmdexc.CommandError(e)
|
||||
|
||||
if preview:
|
||||
self._print_preview(tab)
|
||||
elif pdf:
|
||||
self._print_pdf(tab, pdf)
|
||||
else:
|
||||
self._print(tab)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def tab_clone(self, bg=False, window=False):
|
||||
"""Duplicate the current tab.
|
||||
@ -1201,8 +1173,9 @@ class CommandDispatcher:
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||
maxsplit=0, no_replace_variables=True)
|
||||
@cmdutils.argument('count', count=True)
|
||||
def spawn(self, cmdline, userscript=False, verbose=False,
|
||||
output=False, detach=False):
|
||||
output=False, detach=False, count=None):
|
||||
"""Spawn a command in a shell.
|
||||
|
||||
Args:
|
||||
@ -1216,6 +1189,7 @@ class CommandDispatcher:
|
||||
output: Whether the output should be shown in a new tab.
|
||||
detach: Whether the command should be detached from qutebrowser.
|
||||
cmdline: The commandline to execute.
|
||||
count: Given to userscripts as $QUTE_COUNT.
|
||||
"""
|
||||
cmdutils.check_exclusive((userscript, detach), 'ud')
|
||||
try:
|
||||
@ -1239,7 +1213,7 @@ class CommandDispatcher:
|
||||
if userscript:
|
||||
def _selection_callback(s):
|
||||
try:
|
||||
runner = self._run_userscript(s, cmd, args, verbose)
|
||||
runner = self._run_userscript(s, cmd, args, verbose, count)
|
||||
runner.finished.connect(_on_proc_finished)
|
||||
except cmdexc.CommandError as e:
|
||||
message.error(str(e))
|
||||
@ -1266,19 +1240,23 @@ class CommandDispatcher:
|
||||
"""Open main startpage in current tab."""
|
||||
self.openurl(config.val.url.start_pages[0])
|
||||
|
||||
def _run_userscript(self, selection, cmd, args, verbose):
|
||||
def _run_userscript(self, selection, cmd, args, verbose, count):
|
||||
"""Run a userscript given as argument.
|
||||
|
||||
Args:
|
||||
cmd: The userscript to run.
|
||||
args: Arguments to pass to the userscript.
|
||||
verbose: Show notifications when the command started/exited.
|
||||
count: Exposed to the userscript.
|
||||
"""
|
||||
env = {
|
||||
'QUTE_MODE': 'command',
|
||||
'QUTE_SELECTED_TEXT': selection,
|
||||
}
|
||||
|
||||
if count is not None:
|
||||
env['QUTE_COUNT'] = str(count)
|
||||
|
||||
idx = self._current_index()
|
||||
if idx != -1:
|
||||
env['QUTE_TITLE'] = self._tabbed_browser.widget.page_title(idx)
|
||||
@ -1455,6 +1433,7 @@ class CommandDispatcher:
|
||||
if tab.data.inspector is None:
|
||||
tab.data.inspector = inspector.create()
|
||||
tab.data.inspector.inspect(page)
|
||||
tab.data.inspector.show()
|
||||
else:
|
||||
tab.data.inspector.toggle(page)
|
||||
except inspector.WebInspectorError as e:
|
||||
|
@ -29,7 +29,6 @@ import pathlib
|
||||
import tempfile
|
||||
import enum
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex,
|
||||
QTimer, QAbstractListModel, QUrl)
|
||||
|
||||
@ -37,6 +36,7 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import (usertypes, standarddir, utils, message, log,
|
||||
qtutils)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole)
|
||||
@ -80,9 +80,9 @@ def download_dir():
|
||||
ddir = directory
|
||||
|
||||
try:
|
||||
os.makedirs(ddir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(ddir, exist_ok=True)
|
||||
except OSError as e:
|
||||
message.error("Failed to create download directory: {}".format(e))
|
||||
|
||||
return ddir
|
||||
|
||||
@ -692,9 +692,7 @@ class AbstractDownloadItem(QObject):
|
||||
global last_used_directory
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self._filename))
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(os.path.dirname(self._filename), exist_ok=True)
|
||||
except OSError as e:
|
||||
self._die(e.strerror)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -58,6 +58,7 @@ class GreasemonkeyScript:
|
||||
self.run_at = None
|
||||
self.script_meta = None
|
||||
self.runs_on_sub_frames = True
|
||||
self.jsworld = "main"
|
||||
for name, value in properties:
|
||||
if name == 'name':
|
||||
self.name = value
|
||||
@ -77,6 +78,8 @@ class GreasemonkeyScript:
|
||||
self.runs_on_sub_frames = False
|
||||
elif name == 'require':
|
||||
self.requires.append(value)
|
||||
elif name == 'qute-js-world':
|
||||
self.jsworld = value
|
||||
|
||||
HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n'
|
||||
PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)'
|
||||
@ -142,7 +145,7 @@ class GreasemonkeyScript:
|
||||
|
||||
|
||||
@attr.s
|
||||
class MatchingScripts(object):
|
||||
class MatchingScripts:
|
||||
|
||||
"""All userscripts registered to run on a particular url."""
|
||||
|
||||
|
@ -29,7 +29,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
|
||||
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils
|
||||
from qutebrowser.utils import message, usertypes, log, urlutils, utils, debug
|
||||
from qutebrowser.browser import downloads
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
@ -307,7 +307,14 @@ class DownloadItem(downloads.AbstractDownloadItem):
|
||||
"""Handle QNetworkReply errors."""
|
||||
if code == QNetworkReply.OperationCanceledError:
|
||||
return
|
||||
self._die(self._reply.errorString())
|
||||
|
||||
if self._reply is None:
|
||||
error = "Unknown error: {}".format(
|
||||
debug.qenum_key(QNetworkReply, code))
|
||||
else:
|
||||
error = self._reply.errorString()
|
||||
|
||||
self._die(error)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_read_timer_timeout(self):
|
||||
|
@ -24,6 +24,7 @@ Module attributes:
|
||||
_HANDLERS: The handlers registered via decorators.
|
||||
"""
|
||||
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
@ -31,20 +32,29 @@ import textwrap
|
||||
import mimetypes
|
||||
import urllib
|
||||
import collections
|
||||
import base64
|
||||
|
||||
try:
|
||||
import secrets
|
||||
except ImportError:
|
||||
# New in Python 3.6
|
||||
secrets = None
|
||||
|
||||
import pkg_resources
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrlQuery, QUrl
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
|
||||
import qutebrowser
|
||||
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"
|
||||
spawn_output = ":spawn was never called"
|
||||
csrf_token = None
|
||||
|
||||
|
||||
_HANDLERS = {}
|
||||
@ -123,12 +133,12 @@ class add_handler: # noqa: N801,N806 pylint: disable=invalid-name
|
||||
|
||||
def wrong_backend_handler(self, url):
|
||||
"""Show an error page about using the invalid backend."""
|
||||
html = jinja.render('error.html',
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()))
|
||||
return 'text/html', html
|
||||
src = jinja.render('error.html',
|
||||
title="Error while opening qute://url",
|
||||
url=url.toDisplayString(),
|
||||
error='{} is not available with this '
|
||||
'backend'.format(url.toDisplayString()))
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
def data_for_url(url):
|
||||
@ -177,8 +187,6 @@ def data_for_url(url):
|
||||
except OSError as e:
|
||||
# FIXME:qtwebengine how to handle this?
|
||||
raise QuteSchemeOSError(e)
|
||||
except QuteSchemeError:
|
||||
raise
|
||||
|
||||
assert mimetype is not None, url
|
||||
if mimetype == 'text/html' and isinstance(data, str):
|
||||
@ -196,11 +204,11 @@ def qute_bookmarks(_url):
|
||||
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
|
||||
key=lambda x: x[0]) # Sort by name
|
||||
|
||||
html = jinja.render('bookmarks.html',
|
||||
title='Bookmarks',
|
||||
bookmarks=bookmarks,
|
||||
quickmarks=quickmarks)
|
||||
return 'text/html', html
|
||||
src = jinja.render('bookmarks.html',
|
||||
title='Bookmarks',
|
||||
bookmarks=bookmarks,
|
||||
quickmarks=quickmarks)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('tabs')
|
||||
@ -218,10 +226,10 @@ def qute_tabs(_url):
|
||||
urlstr = tab.url().toDisplayString()
|
||||
tabs[str(win_id)].append((tab.title(), urlstr))
|
||||
|
||||
html = jinja.render('tabs.html',
|
||||
title='Tabs',
|
||||
tab_list_by_window=tabs)
|
||||
return 'text/html', html
|
||||
src = jinja.render('tabs.html',
|
||||
title='Tabs',
|
||||
tab_list_by_window=tabs)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
def history_data(start_time, offset=None):
|
||||
@ -241,8 +249,9 @@ def history_data(start_time, offset=None):
|
||||
end_time = start_time - 24*60*60
|
||||
entries = hist.entries_between(end_time, start_time)
|
||||
|
||||
return [{"url": e.url, "title": e.title or e.url, "time": e.atime}
|
||||
for e in entries]
|
||||
return [{"url": e.url,
|
||||
"title": html.escape(e.title) or html.escape(e.url),
|
||||
"time": e.atime} for e in entries]
|
||||
|
||||
|
||||
@add_handler('history')
|
||||
@ -287,25 +296,25 @@ def qute_javascript(url):
|
||||
@add_handler('pyeval')
|
||||
def qute_pyeval(_url):
|
||||
"""Handler for qute://pyeval."""
|
||||
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||
return 'text/html', html
|
||||
src = jinja.render('pre.html', title='pyeval', content=pyeval_output)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('spawn-output')
|
||||
def qute_spawn_output(_url):
|
||||
"""Handler for qute://spawn-output."""
|
||||
html = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||
return 'text/html', html
|
||||
src = jinja.render('pre.html', title='spawn output', content=spawn_output)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('version')
|
||||
@add_handler('verizon')
|
||||
def qute_version(_url):
|
||||
"""Handler for qute://version."""
|
||||
html = jinja.render('version.html', title='Version info',
|
||||
version=version.version(),
|
||||
copyright=qutebrowser.__copyright__)
|
||||
return 'text/html', html
|
||||
src = jinja.render('version.html', title='Version info',
|
||||
version=version.version(),
|
||||
copyright=qutebrowser.__copyright__)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('plainlog')
|
||||
@ -323,8 +332,8 @@ def qute_plainlog(url):
|
||||
if not level:
|
||||
level = 'vdebug'
|
||||
text = log.ram_handler.dump_log(html=False, level=level)
|
||||
html = jinja.render('pre.html', title='log', content=text)
|
||||
return 'text/html', html
|
||||
src = jinja.render('pre.html', title='log', content=text)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('log')
|
||||
@ -343,8 +352,8 @@ def qute_log(url):
|
||||
level = 'vdebug'
|
||||
html_log = log.ram_handler.dump_log(html=True, level=level)
|
||||
|
||||
html = jinja.render('log.html', title='log', content=html_log)
|
||||
return 'text/html', html
|
||||
src = jinja.render('log.html', title='log', content=html_log)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('gpl')
|
||||
@ -415,12 +424,12 @@ def qute_help(url):
|
||||
@add_handler('backend-warning')
|
||||
def qute_backend_warning(_url):
|
||||
"""Handler for qute://backend-warning."""
|
||||
html = jinja.render('backend-warning.html',
|
||||
distribution=version.distribution(),
|
||||
Distribution=version.Distribution,
|
||||
version=pkg_resources.parse_version,
|
||||
title="Legacy backend warning")
|
||||
return 'text/html', html
|
||||
src = jinja.render('backend-warning.html',
|
||||
distribution=version.distribution(),
|
||||
Distribution=version.Distribution,
|
||||
version=pkg_resources.parse_version,
|
||||
title="Legacy backend warning")
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
def _qute_settings_set(url):
|
||||
@ -447,13 +456,30 @@ def _qute_settings_set(url):
|
||||
@add_handler('settings')
|
||||
def qute_settings(url):
|
||||
"""Handler for qute://settings. View/change qute configuration."""
|
||||
global csrf_token
|
||||
|
||||
if url.path() == '/set':
|
||||
if url.password() != csrf_token:
|
||||
message.error("Invalid CSRF token for qute://settings!")
|
||||
raise QuteSchemeError("Invalid CSRF token!",
|
||||
QNetworkReply.ContentAccessDenied)
|
||||
return _qute_settings_set(url)
|
||||
|
||||
html = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
confget=config.instance.get_str)
|
||||
return 'text/html', html
|
||||
# Requests to qute://settings/set should only be allowed from
|
||||
# qute://settings. As an additional security precaution, we generate a CSRF
|
||||
# token to use here.
|
||||
if secrets:
|
||||
csrf_token = secrets.token_urlsafe()
|
||||
else:
|
||||
# On Python < 3.6, from secrets.py
|
||||
token = base64.urlsafe_b64encode(os.urandom(32))
|
||||
csrf_token = token.rstrip(b'=').decode('ascii')
|
||||
|
||||
src = jinja.render('settings.html', title='settings',
|
||||
configdata=configdata,
|
||||
confget=config.instance.get_str,
|
||||
csrf_token=csrf_token)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('bindings')
|
||||
@ -467,9 +493,9 @@ def qute_bindings(_url):
|
||||
for mode in modes:
|
||||
bindings[mode] = config.key_instance.get_bindings_for(mode)
|
||||
|
||||
html = jinja.render('bindings.html', title='Bindings',
|
||||
bindings=bindings)
|
||||
return 'text/html', html
|
||||
src = jinja.render('bindings.html', title='Bindings',
|
||||
bindings=bindings)
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('back')
|
||||
@ -478,10 +504,10 @@ def qute_back(url):
|
||||
|
||||
Simple page to free ram / lazy load a site, goes back on focusing the tab.
|
||||
"""
|
||||
html = jinja.render(
|
||||
src = jinja.render(
|
||||
'back.html',
|
||||
title='Suspended: ' + urllib.parse.unquote(url.fragment()))
|
||||
return 'text/html', html
|
||||
return 'text/html', src
|
||||
|
||||
|
||||
@add_handler('configdiff')
|
||||
|
@ -34,21 +34,22 @@ class CallSuper(Exception):
|
||||
"""Raised when the caller should call the superclass instead."""
|
||||
|
||||
|
||||
def custom_headers():
|
||||
def custom_headers(url):
|
||||
"""Get the combined custom headers."""
|
||||
headers = {}
|
||||
|
||||
dnt_config = config.val.content.headers.do_not_track
|
||||
dnt_config = config.instance.get('content.headers.do_not_track', url=url)
|
||||
if dnt_config is not None:
|
||||
dnt = b'1' if dnt_config else b'0'
|
||||
headers[b'DNT'] = dnt
|
||||
headers[b'X-Do-Not-Track'] = dnt
|
||||
|
||||
conf_headers = config.val.content.headers.custom
|
||||
conf_headers = config.instance.get('content.headers.custom', url=url)
|
||||
for header, value in conf_headers.items():
|
||||
headers[header.encode('ascii')] = value.encode('ascii')
|
||||
|
||||
accept_language = config.val.content.headers.accept_language
|
||||
accept_language = config.instance.get('content.headers.accept_language',
|
||||
url=url)
|
||||
if accept_language is not None:
|
||||
headers[b'Accept-Language'] = accept_language.encode('ascii')
|
||||
|
||||
@ -156,7 +157,7 @@ def ignore_certificate_errors(url, errors, abort_on):
|
||||
Return:
|
||||
True if the error should be ignored, False otherwise.
|
||||
"""
|
||||
ssl_strict = config.val.content.ssl_strict
|
||||
ssl_strict = config.instance.get('content.ssl_strict', url=url)
|
||||
log.webview.debug("Certificate errors {!r}, strict {}".format(
|
||||
errors, ssl_strict))
|
||||
|
||||
@ -213,7 +214,7 @@ def feature_permission(url, option, msg, yes_action, no_action, abort_on,
|
||||
The Question object if a question was asked (and blocking=False),
|
||||
None otherwise.
|
||||
"""
|
||||
config_val = config.instance.get(option)
|
||||
config_val = config.instance.get(option, url=url)
|
||||
if config_val == 'ask':
|
||||
if url.isValid():
|
||||
urlstr = url.toString(QUrl.RemovePassword | QUrl.FullyEncoded)
|
||||
@ -312,10 +313,10 @@ def netrc_authentication(url, authenticator):
|
||||
(user, _account, password) = authenticators
|
||||
except FileNotFoundError:
|
||||
log.misc.debug("No .netrc file found")
|
||||
except OSError:
|
||||
log.misc.exception("Unable to read the netrc file")
|
||||
except netrc.NetrcParseError:
|
||||
log.misc.exception("Error when parsing the netrc file")
|
||||
except OSError as e:
|
||||
log.misc.exception("Unable to read the netrc file: {}".format(e))
|
||||
except netrc.NetrcParseError as e:
|
||||
log.misc.exception("Error when parsing the netrc file: {}".format(e))
|
||||
|
||||
if user is None:
|
||||
return False
|
||||
|
@ -240,8 +240,7 @@ class BookmarkManager(UrlMarkManager):
|
||||
|
||||
def _init_lineparser(self):
|
||||
bookmarks_directory = os.path.join(standarddir.config(), 'bookmarks')
|
||||
if not os.path.isdir(bookmarks_directory):
|
||||
os.makedirs(bookmarks_directory)
|
||||
os.makedirs(bookmarks_directory, exist_ok=True)
|
||||
|
||||
bookmarks_subdir = os.path.join('bookmarks', 'urls')
|
||||
self._lineparser = lineparser.LineParser(
|
||||
|
@ -28,6 +28,10 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
|
||||
"""A wrapper over a QWebEngineCertificateError."""
|
||||
|
||||
def __init__(self, error):
|
||||
super().__init__(error)
|
||||
self.ignore = False
|
||||
|
||||
def __str__(self):
|
||||
return self._error.errorDescription()
|
||||
|
||||
@ -37,5 +41,8 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
|
||||
self._error.error()),
|
||||
string=str(self))
|
||||
|
||||
def url(self):
|
||||
return self._error.url()
|
||||
|
||||
def is_overridable(self):
|
||||
return self._error.isOverridable()
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
"""A request interceptor taking care of adblocking and custom headers."""
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtWebEngineCore import (QWebEngineUrlRequestInterceptor,
|
||||
QWebEngineUrlRequestInfo)
|
||||
|
||||
@ -68,15 +69,29 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
|
||||
info.firstPartyUrl().toDisplayString(),
|
||||
resource_type, navigation_type))
|
||||
|
||||
url = info.requestUrl()
|
||||
firstparty = info.firstPartyUrl()
|
||||
|
||||
if ((url.scheme(), url.host(), url.path()) ==
|
||||
('qute', 'settings', '/set')):
|
||||
if (firstparty != QUrl('qute://settings/') or
|
||||
info.resourceType() !=
|
||||
QWebEngineUrlRequestInfo.ResourceTypeXhr):
|
||||
log.webview.warning("Blocking malicious request from {} to {}"
|
||||
.format(firstparty.toDisplayString(),
|
||||
url.toDisplayString()))
|
||||
info.block(True)
|
||||
return
|
||||
|
||||
# FIXME:qtwebengine only block ads for NavigationTypeOther?
|
||||
if self._host_blocker.is_blocked(info.requestUrl()):
|
||||
if self._host_blocker.is_blocked(url):
|
||||
log.webview.info("Request to {} blocked by host blocker.".format(
|
||||
info.requestUrl().host()))
|
||||
url.host()))
|
||||
info.block(True)
|
||||
|
||||
for header, value in shared.custom_headers():
|
||||
for header, value in shared.custom_headers(url=url):
|
||||
info.setHttpHeader(header, value)
|
||||
|
||||
user_agent = config.val.content.headers.user_agent
|
||||
user_agent = config.instance.get('content.headers.user_agent', url=url)
|
||||
if user_agent is not None:
|
||||
info.setHttpHeader(b'User-Agent', user_agent.encode('ascii'))
|
||||
|
@ -21,10 +21,12 @@
|
||||
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from PyQt5.QtCore import QLibraryInfo
|
||||
from qutebrowser.utils import log, message
|
||||
from qutebrowser.utils import log, message, standarddir, qtutils
|
||||
|
||||
dict_version_re = re.compile(r".+-(?P<version>[0-9]+-[0-9]+?)\.bdic")
|
||||
|
||||
@ -39,9 +41,12 @@ def version(filename):
|
||||
return tuple(int(n) for n in match.group('version').split('-'))
|
||||
|
||||
|
||||
def dictionary_dir():
|
||||
def dictionary_dir(old=False):
|
||||
"""Return the path (str) to the QtWebEngine's dictionaries directory."""
|
||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||
if qtutils.version_check('5.10', compiled=False) and not old:
|
||||
datapath = standarddir.data()
|
||||
else:
|
||||
datapath = QLibraryInfo.location(QLibraryInfo.DataPath)
|
||||
return os.path.join(datapath, 'qtwebengine_dictionaries')
|
||||
|
||||
|
||||
@ -73,3 +78,16 @@ def local_filename(code):
|
||||
"""
|
||||
all_installed = local_files(code)
|
||||
return os.path.splitext(all_installed[0])[0] if all_installed else None
|
||||
|
||||
|
||||
def init():
|
||||
"""Initialize the dictionary path if supported."""
|
||||
if qtutils.version_check('5.10', compiled=False):
|
||||
new_dir = dictionary_dir()
|
||||
old_dir = dictionary_dir(old=True)
|
||||
os.environ['QTWEBENGINE_DICTIONARIES_PATH'] = new_dir
|
||||
try:
|
||||
if os.path.exists(old_dir) and not os.path.exists(new_dir):
|
||||
shutil.copytree(old_dir, new_dir)
|
||||
except OSError:
|
||||
log.misc.exception("Failed to copy old dictionaries")
|
||||
|
@ -203,6 +203,8 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
url = self.resolve_url(baseurl)
|
||||
if url is None:
|
||||
return True
|
||||
if baseurl.scheme() == url.scheme(): # e.g. a qute:// link
|
||||
return False
|
||||
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
||||
|
||||
def _click_editable(self, click_target):
|
||||
|
@ -37,6 +37,7 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
if qtutils.version_check('5.11', compiled=False):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||
profile.installUrlSchemeHandler(b'chrome-error', self)
|
||||
profile.installUrlSchemeHandler(b'chrome-extension', self)
|
||||
|
||||
def requestStarted(self, job):
|
||||
"""Handle a request for a qute: scheme.
|
||||
@ -49,13 +50,33 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
"""
|
||||
url = job.requestUrl()
|
||||
|
||||
if url.scheme() == 'chrome-error':
|
||||
if url.scheme() in ['chrome-error', 'chrome-extension']:
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
|
||||
return
|
||||
|
||||
assert job.requestMethod() == b'GET'
|
||||
# Only the browser itself or qute:// pages should access any of those
|
||||
# URLs.
|
||||
# The request interceptor further locks down qute://settings/set.
|
||||
try:
|
||||
initiator = job.initiator()
|
||||
except AttributeError:
|
||||
# Added in Qt 5.11
|
||||
pass
|
||||
else:
|
||||
if initiator.isValid() and initiator.scheme() != 'qute':
|
||||
log.misc.warning("Blocking malicious request from {} to {}"
|
||||
.format(initiator.toDisplayString(),
|
||||
url.toDisplayString()))
|
||||
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||
return
|
||||
|
||||
if job.requestMethod() != b'GET':
|
||||
job.fail(QWebEngineUrlRequestJob.RequestDenied)
|
||||
return
|
||||
|
||||
assert url.scheme() == 'qute'
|
||||
|
||||
log.misc.debug("Got request for {}".format(url.toDisplayString()))
|
||||
try:
|
||||
mimetype, data = qutescheme.data_for_url(url)
|
||||
|
@ -166,6 +166,8 @@ class WebEngineSettings(websettings.AbstractSettings):
|
||||
# Qt 5.11
|
||||
'content.autoplay':
|
||||
('PlaybackRequiresUserGesture', lambda val: not val),
|
||||
'content.webrtc_public_interfaces_only':
|
||||
('WebRTCPublicInterfacesOnly', None),
|
||||
}
|
||||
for name, (attribute, converter) in new_attributes.items():
|
||||
try:
|
||||
@ -298,6 +300,8 @@ def init(args):
|
||||
not hasattr(QWebEnginePage, 'setInspectedPage')): # only Qt < 5.11
|
||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
||||
|
||||
spell.init()
|
||||
|
||||
_init_profiles()
|
||||
config.instance.changed.connect(_update_settings)
|
||||
|
||||
|
@ -25,7 +25,6 @@ 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)
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
@ -34,14 +33,15 @@ from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||
|
||||
from qutebrowser.config import configdata, config
|
||||
from qutebrowser.browser import browsertab, mouse, shared
|
||||
from qutebrowser.browser import browsertab, mouse, shared, webelem
|
||||
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
|
||||
interceptor, webenginequtescheme,
|
||||
cookies, webenginedownloads,
|
||||
webenginesettings)
|
||||
webenginesettings, certificateerror)
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
|
||||
message, objreg, jinja, debug)
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
_qute_scheme_handler = None
|
||||
@ -360,7 +360,11 @@ class WebEngineCaret(browsertab.AbstractCaret):
|
||||
if elem.is_link():
|
||||
log.webview.debug("Found link in selection, clicking. ClickTarget "
|
||||
"{}, elem {}".format(click_type, elem))
|
||||
elem.click(click_type)
|
||||
try:
|
||||
elem.click(click_type)
|
||||
except webelem.Error as e:
|
||||
message.error(str(e))
|
||||
return
|
||||
|
||||
def follow_selected(self, *, tab=False):
|
||||
if self._tab.search.search_displayed:
|
||||
@ -703,6 +707,18 @@ class _WebEnginePermissions(QObject):
|
||||
QWebEnginePage.MediaVideoCapture: 'record video',
|
||||
QWebEnginePage.MediaAudioVideoCapture: 'record audio/video',
|
||||
}
|
||||
try:
|
||||
options.update({
|
||||
QWebEnginePage.MouseLock:
|
||||
'content.mouse_lock',
|
||||
})
|
||||
messages.update({
|
||||
QWebEnginePage.MouseLock:
|
||||
'hide your mouse pointer',
|
||||
})
|
||||
except AttributeError:
|
||||
# Added in Qt 5.8
|
||||
pass
|
||||
try:
|
||||
options.update({
|
||||
QWebEnginePage.DesktopVideoCapture:
|
||||
@ -788,6 +804,7 @@ class _WebEngineScripts(QObject):
|
||||
super().__init__(parent)
|
||||
self._tab = tab
|
||||
self._widget = None
|
||||
self._greasemonkey = objreg.get('greasemonkey')
|
||||
|
||||
def connect_signals(self):
|
||||
config.instance.changed.connect(self._on_config_changed)
|
||||
@ -853,9 +870,16 @@ class _WebEngineScripts(QObject):
|
||||
self._inject_early_js('js', js_code, subframes=True)
|
||||
self._init_stylesheet()
|
||||
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
greasemonkey.scripts_reloaded.connect(self._inject_userscripts)
|
||||
self._inject_userscripts()
|
||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
||||
# response to urlChanged.
|
||||
if not qtutils.version_check('5.8'):
|
||||
self._tab.url_changed.connect(
|
||||
self._inject_greasemonkey_scripts_for_url)
|
||||
else:
|
||||
self._greasemonkey.scripts_reloaded.connect(
|
||||
self._inject_all_greasemonkey_scripts)
|
||||
self._inject_all_greasemonkey_scripts()
|
||||
|
||||
def _init_stylesheet(self):
|
||||
"""Initialize custom stylesheets.
|
||||
@ -872,40 +896,77 @@ class _WebEngineScripts(QObject):
|
||||
)
|
||||
self._inject_early_js('stylesheet', js_code, subframes=True)
|
||||
|
||||
def _inject_userscripts(self):
|
||||
"""Register user JavaScript files with the global profiles."""
|
||||
# The Greasemonkey metadata block support in QtWebEngine only starts at
|
||||
# Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in
|
||||
# response to urlChanged.
|
||||
if not qtutils.version_check('5.8'):
|
||||
@pyqtSlot(QUrl)
|
||||
def _inject_greasemonkey_scripts_for_url(self, url):
|
||||
matching_scripts = self._greasemonkey.scripts_for(url)
|
||||
self._inject_greasemonkey_scripts(
|
||||
matching_scripts.start, QWebEngineScript.DocumentCreation, True)
|
||||
self._inject_greasemonkey_scripts(
|
||||
matching_scripts.end, QWebEngineScript.DocumentReady, False)
|
||||
self._inject_greasemonkey_scripts(
|
||||
matching_scripts.idle, QWebEngineScript.Deferred, False)
|
||||
|
||||
@pyqtSlot()
|
||||
def _inject_all_greasemonkey_scripts(self):
|
||||
scripts = self._greasemonkey.all_scripts()
|
||||
self._inject_greasemonkey_scripts(scripts)
|
||||
|
||||
def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None,
|
||||
remove_first=True):
|
||||
"""Register user JavaScript files with the current tab.
|
||||
|
||||
Args:
|
||||
scripts: A list of GreasemonkeyScripts, or None to add all
|
||||
known by the Greasemonkey subsystem.
|
||||
injection_point: The QWebEngineScript::InjectionPoint stage
|
||||
to inject the script into, None to use
|
||||
auto-detection.
|
||||
remove_first: Whether to remove all previously injected
|
||||
scripts before adding these ones.
|
||||
"""
|
||||
if sip.isdeleted(self._widget):
|
||||
return
|
||||
|
||||
# Since we are inserting scripts into profile.scripts they won't
|
||||
# just get replaced by new gm scripts like if we were injecting them
|
||||
# ourselves so we need to remove all gm scripts, while not removing
|
||||
# any other stuff that might have been added. Like the one for
|
||||
# stylesheets.
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
scripts = self._widget.page().scripts()
|
||||
for script in scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug('Removing script: {}'
|
||||
.format(script.name()))
|
||||
removed = scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
# Since we are inserting scripts into a per-tab collection,
|
||||
# rather than just injecting scripts on page load, we need to
|
||||
# make sure we replace existing scripts, not just add new ones.
|
||||
# While, taking care not to remove any other scripts that might
|
||||
# have been added elsewhere, like the one for stylesheets.
|
||||
page_scripts = self._widget.page().scripts()
|
||||
if remove_first:
|
||||
for script in page_scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug('Removing script: {}'
|
||||
.format(script.name()))
|
||||
removed = page_scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
# Then add the new scripts.
|
||||
for script in greasemonkey.all_scripts():
|
||||
# @run-at (and @include/@exclude/@match) is parsed by
|
||||
# QWebEngineScript.
|
||||
if not scripts:
|
||||
return
|
||||
|
||||
for script in scripts:
|
||||
new_script = QWebEngineScript()
|
||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||
try:
|
||||
world = int(script.jsworld)
|
||||
except ValueError:
|
||||
try:
|
||||
world = _JS_WORLD_MAP[usertypes.JsWorld[
|
||||
script.jsworld.lower()]]
|
||||
except KeyError:
|
||||
log.greasemonkey.error(
|
||||
"script {} has invalid value for '@qute-js-world'"
|
||||
": {}".format(script.name, script.jsworld))
|
||||
continue
|
||||
new_script.setWorldId(world)
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
# Override the @run-at value parsed by QWebEngineScript if desired.
|
||||
if injection_point:
|
||||
new_script.setInjectionPoint(injection_point)
|
||||
log.greasemonkey.debug('adding script: {}'
|
||||
.format(new_script.name()))
|
||||
scripts.insert(new_script)
|
||||
page_scripts.insert(new_script)
|
||||
|
||||
|
||||
class WebEngineTab(browsertab.AbstractTab):
|
||||
@ -931,7 +992,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebEngineZoom(tab=self, parent=self)
|
||||
self.search = WebEngineSearch(parent=self)
|
||||
self.printing = WebEnginePrinting()
|
||||
self.printing = WebEnginePrinting(tab=self)
|
||||
self.elements = WebEngineElements(tab=self)
|
||||
self.action = WebEngineAction(tab=self)
|
||||
self.audio = WebEngineAudio(parent=self)
|
||||
@ -979,6 +1040,9 @@ 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)
|
||||
@ -1137,11 +1201,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
@pyqtSlot()
|
||||
def _on_load_started(self):
|
||||
"""Clear search when a new load is started if needed."""
|
||||
if (qtutils.version_check('5.9', compiled=False) and
|
||||
not qtutils.version_check('5.9.2', compiled=False)):
|
||||
# WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-61506
|
||||
self.search.clear()
|
||||
# WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-61506
|
||||
# (seems to be back in later Qt versions as well)
|
||||
self.search.clear()
|
||||
super()._on_load_started()
|
||||
self.data.netrc_used = False
|
||||
|
||||
@ -1225,6 +1288,34 @@ 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.
|
||||
@ -1240,10 +1331,10 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
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()
|
||||
try:
|
||||
self.printing.show_dialog()
|
||||
except browsertab.WebTabError as e:
|
||||
message.error(str(e))
|
||||
navigation.accepted = False
|
||||
|
||||
if not navigation.accepted or not navigation.is_main_frame:
|
||||
|
@ -19,18 +19,17 @@
|
||||
|
||||
"""The main browser widget for QtWebEngine."""
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtQuickWidgets import QQuickWidget
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
||||
QWebEngineScript)
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
|
||||
|
||||
from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.browser.webengine import webenginesettings, certificateerror
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
|
||||
from qutebrowser.utils import log, debug, usertypes, objreg, qtutils
|
||||
from qutebrowser.misc import miscwidgets
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@ -71,10 +70,10 @@ class WebEngineView(QWebEngineView):
|
||||
if proxy is not None:
|
||||
return proxy
|
||||
|
||||
# This should only find the RenderWidgetHostViewQtDelegateWidget,
|
||||
# but not e.g. a QMenu
|
||||
children = [c for c in self.findChildren(QQuickWidget)
|
||||
if c.isVisible()]
|
||||
# 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))
|
||||
@ -152,11 +151,13 @@ class WebEnginePage(QWebEnginePage):
|
||||
|
||||
Signals:
|
||||
certificate_error: Emitted on certificate errors.
|
||||
Needs to be directly connected to a slot setting the
|
||||
'ignore' attribute.
|
||||
shutting_down: Emitted when the page is shutting down.
|
||||
navigation_request: Emitted on acceptNavigationRequest.
|
||||
"""
|
||||
|
||||
certificate_error = pyqtSignal()
|
||||
certificate_error = pyqtSignal(certificateerror.CertificateErrorWrapper)
|
||||
shutting_down = pyqtSignal()
|
||||
navigation_request = pyqtSignal(usertypes.NavigationRequest)
|
||||
|
||||
@ -166,7 +167,6 @@ class WebEnginePage(QWebEnginePage):
|
||||
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):
|
||||
@ -181,36 +181,9 @@ class WebEnginePage(QWebEnginePage):
|
||||
|
||||
def certificateError(self, error):
|
||||
"""Handle certificate errors coming from Qt."""
|
||||
self.certificate_error.emit()
|
||||
url = error.url()
|
||||
error = certificateerror.CertificateErrorWrapper(error)
|
||||
log.webview.debug("Certificate error: {}".format(error))
|
||||
|
||||
url_string = url.toDisplayString()
|
||||
error_page = jinja.render(
|
||||
'error.html', title="Error loading page: {}".format(url_string),
|
||||
url=url_string, error=str(error))
|
||||
|
||||
if error.is_overridable():
|
||||
ignore = shared.ignore_certificate_errors(
|
||||
url, [error], abort_on=[self.loadStarted, self.shutting_down])
|
||||
else:
|
||||
log.webview.error("Non-overridable certificate error: "
|
||||
"{}".format(error))
|
||||
ignore = False
|
||||
|
||||
# We can't really know when to show an error page, as the error might
|
||||
# have happened when loading some resource.
|
||||
# However, self.url() is not available yet and self.requestedUrl()
|
||||
# might not match the URL we get from the error - so we just apply a
|
||||
# heuristic here.
|
||||
# See https://bugreports.qt.io/browse/QTBUG-56207
|
||||
log.webview.debug("ignore {}, URL {}, requested {}".format(
|
||||
ignore, url, self.requestedUrl()))
|
||||
if not ignore and url.matches(self.requestedUrl(), QUrl.RemoveScheme):
|
||||
self.setHtml(error_page)
|
||||
|
||||
return ignore
|
||||
self.certificate_error.emit(error)
|
||||
return error.ignore
|
||||
|
||||
def javaScriptConfirm(self, url, js_msg):
|
||||
"""Override javaScriptConfirm to use qutebrowser prompts."""
|
||||
@ -288,43 +261,3 @@ class WebEnginePage(QWebEnginePage):
|
||||
is_main_frame=is_main_frame)
|
||||
self.navigation_request.emit(navigation)
|
||||
return navigation.accepted
|
||||
|
||||
@pyqtSlot('QUrl')
|
||||
def _inject_userjs(self, url):
|
||||
"""Inject userscripts registered for `url` into the current page."""
|
||||
if qtutils.version_check('5.8'):
|
||||
# Handled in webenginetab with the builtin Greasemonkey
|
||||
# support.
|
||||
return
|
||||
|
||||
# Using QWebEnginePage.scripts() to hold the user scripts means
|
||||
# we don't have to worry ourselves about where to inject the
|
||||
# page but also means scripts hang around for the tab lifecycle.
|
||||
# So clear them here.
|
||||
scripts = self.scripts()
|
||||
for script in scripts.toList():
|
||||
if script.name().startswith("GM-"):
|
||||
log.greasemonkey.debug("Removing script: {}"
|
||||
.format(script.name()))
|
||||
removed = scripts.remove(script)
|
||||
assert removed, script.name()
|
||||
|
||||
def _add_script(script, injection_point):
|
||||
new_script = QWebEngineScript()
|
||||
new_script.setInjectionPoint(injection_point)
|
||||
new_script.setWorldId(QWebEngineScript.MainWorld)
|
||||
new_script.setSourceCode(script.code())
|
||||
new_script.setName("GM-{}".format(script.name))
|
||||
new_script.setRunsOnSubFrames(script.runs_on_sub_frames)
|
||||
log.greasemonkey.debug("Adding script: {}"
|
||||
.format(new_script.name()))
|
||||
scripts.insert(new_script)
|
||||
|
||||
greasemonkey = objreg.get('greasemonkey')
|
||||
matching_scripts = greasemonkey.scripts_for(url)
|
||||
for script in matching_scripts.start:
|
||||
_add_script(script, QWebEngineScript.DocumentCreation)
|
||||
for script in matching_scripts.end:
|
||||
_add_script(script, QWebEngineScript.DocumentReady)
|
||||
for script in matching_scripts.idle:
|
||||
_add_script(script, QWebEngineScript.Deferred)
|
||||
|
@ -312,9 +312,9 @@ class _Downloader:
|
||||
for style in styles:
|
||||
style = webkitelem.WebKitElement(style, tab=self.tab)
|
||||
# The Mozilla Developer Network says:
|
||||
# type: This attribute defines the styling language as a MIME type
|
||||
# (charset should not be specified). This attribute is optional and
|
||||
# default to text/css if it's missing.
|
||||
# > type: This attribute defines the styling language as a MIME
|
||||
# > type (charset should not be specified). This attribute is
|
||||
# > optional and default to text/css if it's missing.
|
||||
# https://developer.mozilla.org/en/docs/Web/HTML/Element/style
|
||||
if 'type' in style and style['type'] != 'text/css':
|
||||
continue
|
||||
|
@ -111,11 +111,13 @@ def dirbrowser_html(path):
|
||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||
|
||||
|
||||
def handler(request):
|
||||
def handler(request, _operation, _current_url):
|
||||
"""Handler for a file:// URL.
|
||||
|
||||
Args:
|
||||
request: QNetworkRequest to answer to.
|
||||
_operation: The HTTP operation being done.
|
||||
_current_url: The page we're on currently.
|
||||
|
||||
Return:
|
||||
A QNetworkReply for directories, None for files.
|
||||
|
@ -373,14 +373,7 @@ class NetworkManager(QNetworkAccessManager):
|
||||
req, proxy_error, QNetworkReply.UnknownProxyError,
|
||||
self)
|
||||
|
||||
scheme = req.url().scheme()
|
||||
if scheme in self._scheme_handlers:
|
||||
result = self._scheme_handlers[scheme](req)
|
||||
if result is not None:
|
||||
result.setParent(self)
|
||||
return result
|
||||
|
||||
for header, value in shared.custom_headers():
|
||||
for header, value in shared.custom_headers(url=req.url()):
|
||||
req.setRawHeader(header, value)
|
||||
|
||||
host_blocker = objreg.get('host-blocker')
|
||||
@ -416,5 +409,12 @@ class NetworkManager(QNetworkAccessManager):
|
||||
req.url().toDisplayString(),
|
||||
current_url.toDisplayString()))
|
||||
|
||||
scheme = req.url().scheme()
|
||||
if scheme in self._scheme_handlers:
|
||||
result = self._scheme_handlers[scheme](req, op, current_url)
|
||||
if result is not None:
|
||||
result.setParent(self)
|
||||
return result
|
||||
|
||||
self.set_referer(req, current_url)
|
||||
return super().createRequest(op, req, outgoing_data)
|
||||
|
@ -21,27 +21,46 @@
|
||||
|
||||
import mimetypes
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkReply
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkAccessManager
|
||||
|
||||
from qutebrowser.browser import pdfjs, qutescheme
|
||||
from qutebrowser.browser.webkit.network import networkreply
|
||||
from qutebrowser.utils import log, usertypes, qtutils
|
||||
|
||||
|
||||
def handler(request):
|
||||
def handler(request, operation, current_url):
|
||||
"""Scheme handler for qute:// URLs.
|
||||
|
||||
Args:
|
||||
request: QNetworkRequest to answer to.
|
||||
operation: The HTTP operation being done.
|
||||
current_url: The page we're on currently.
|
||||
|
||||
Return:
|
||||
A QNetworkReply.
|
||||
"""
|
||||
if operation != QNetworkAccessManager.GetOperation:
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, "Unsupported request type",
|
||||
QNetworkReply.ContentOperationNotPermittedError)
|
||||
|
||||
url = request.url()
|
||||
|
||||
if ((url.scheme(), url.host(), url.path()) ==
|
||||
('qute', 'settings', '/set')):
|
||||
if current_url != QUrl('qute://settings/'):
|
||||
log.webview.warning("Blocking malicious request from {} to {}"
|
||||
.format(current_url.toDisplayString(),
|
||||
url.toDisplayString()))
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, "Invalid qute://settings request",
|
||||
QNetworkReply.ContentAccessDenied)
|
||||
|
||||
try:
|
||||
mimetype, data = qutescheme.data_for_url(request.url())
|
||||
mimetype, data = qutescheme.data_for_url(url)
|
||||
except qutescheme.NoHandlerFound:
|
||||
errorstr = "No handler found for {}!".format(
|
||||
request.url().toDisplayString())
|
||||
errorstr = "No handler found for {}!".format(url.toDisplayString())
|
||||
return networkreply.ErrorNetworkReply(
|
||||
request, errorstr, QNetworkReply.ContentNotFoundError)
|
||||
except qutescheme.QuteSchemeOSError as e:
|
||||
|
@ -23,7 +23,6 @@ import re
|
||||
import functools
|
||||
import xml.etree.ElementTree
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF,
|
||||
QSize)
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
@ -35,6 +34,7 @@ from qutebrowser.browser import browsertab, shared
|
||||
from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem,
|
||||
webkitsettings)
|
||||
from qutebrowser.utils import qtutils, usertypes, utils, log, debug
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
class WebKitAction(browsertab.AbstractAction):
|
||||
@ -658,7 +658,7 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
tab=self, parent=self)
|
||||
self.zoom = WebKitZoom(tab=self, parent=self)
|
||||
self.search = WebKitSearch(parent=self)
|
||||
self.printing = WebKitPrinting()
|
||||
self.printing = WebKitPrinting(tab=self)
|
||||
self.elements = WebKitElements(tab=self)
|
||||
self.action = WebKitAction(tab=self)
|
||||
self.audio = WebKitAudio(parent=self)
|
||||
@ -808,6 +808,10 @@ class WebKitTab(browsertab.AbstractTab):
|
||||
if navigation.is_main_frame:
|
||||
self.settings.update_for_url(navigation.url)
|
||||
|
||||
@pyqtSlot()
|
||||
def _on_ssl_errors(self):
|
||||
self._has_ssl_errors = True
|
||||
|
||||
def _connect_signals(self):
|
||||
view = self._widget
|
||||
page = view.page()
|
||||
|
@ -22,7 +22,6 @@
|
||||
import html
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
||||
@ -35,6 +34,7 @@ from qutebrowser.browser import pdfjs, shared
|
||||
from qutebrowser.browser.webkit import http
|
||||
from qutebrowser.browser.webkit.network import networkmanager
|
||||
from qutebrowser.utils import message, usertypes, log, jinja, objreg
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
class BrowserPage(QWebPage):
|
||||
@ -212,7 +212,8 @@ class BrowserPage(QWebPage):
|
||||
page = pdfjs.generate_pdfjs_page(reply.url())
|
||||
except pdfjs.PDFJSNotFound:
|
||||
page = jinja.render('no_pdfjs.html',
|
||||
url=reply.url().toDisplayString())
|
||||
url=reply.url().toDisplayString(),
|
||||
title="PDF.js not found")
|
||||
self.mainFrame().setContent(page.encode('utf-8'), 'text/html',
|
||||
reply.url())
|
||||
reply.deleteLater()
|
||||
@ -415,7 +416,7 @@ class BrowserPage(QWebPage):
|
||||
|
||||
def userAgentForUrl(self, url):
|
||||
"""Override QWebPage::userAgentForUrl to customize the user agent."""
|
||||
ua = config.val.content.headers.user_agent
|
||||
ua = config.instance.get('content.headers.user_agent', url=url)
|
||||
if ua is None:
|
||||
return super().userAgentForUrl(url)
|
||||
else:
|
||||
|
@ -122,8 +122,8 @@ def _buffer(skip_win_id=None):
|
||||
tabs.append(("{}/{}".format(win_id, idx + 1),
|
||||
tab.url().toDisplayString(),
|
||||
tabbed_browser.widget.page_title(idx)))
|
||||
cat = listcategory.ListCategory("{}".format(win_id), tabs,
|
||||
delete_func=delete_buffer)
|
||||
cat = listcategory.ListCategory(
|
||||
str(win_id), tabs, delete_func=delete_buffer, sort=False)
|
||||
model.add_category(cat)
|
||||
|
||||
return model
|
||||
|
@ -60,10 +60,14 @@ def url(*, info):
|
||||
quickmarks = [(url, name) for (name, url)
|
||||
in objreg.get('quickmark-manager').marks.items()]
|
||||
bookmarks = objreg.get('bookmark-manager').marks.items()
|
||||
searchengines = config.val.url.searchengines.items()
|
||||
categories = config.val.url.open_categories_shown
|
||||
# pylint: disable=bad-config-option
|
||||
searchengines = {k:v for k, v in config.val.url.searchengines.items()
|
||||
if k not in "DEFAULT"}.items()
|
||||
# pylint: enable=bad-config-option
|
||||
categories = config.val.completion.open_categories
|
||||
models = {}
|
||||
|
||||
|
||||
if searchengines and "searchengines" in categories:
|
||||
models["searchengines"] = listcategory.ListCategory(
|
||||
'Search engines', searchengines, sort=False)
|
||||
|
@ -56,7 +56,7 @@ class Option:
|
||||
@attr.s
|
||||
class Migrations:
|
||||
|
||||
"""Nigrated options in configdata.yml.
|
||||
"""Migrated options in configdata.yml.
|
||||
|
||||
Attributes:
|
||||
renamed: A dict mapping old option names to new names.
|
||||
|
@ -321,6 +321,7 @@ content.windowed_fullscreen:
|
||||
content.desktop_capture:
|
||||
type: BoolAsk
|
||||
default: ask
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Allow websites to share screen content.
|
||||
|
||||
@ -350,14 +351,28 @@ content.frame_flattening:
|
||||
content.geolocation:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
desc: Allow websites to request geolocations.
|
||||
|
||||
content.mouse_lock:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend:
|
||||
QtWebKit: false
|
||||
QtWebEngine: Qt 5.8
|
||||
desc: Allow websites to lock your mouse pointer.
|
||||
|
||||
content.headers.accept_language:
|
||||
type:
|
||||
name: String
|
||||
none_ok: true
|
||||
supports_pattern: true
|
||||
default: en-US,en
|
||||
desc: Value to send in the `Accept-Language` header.
|
||||
desc: >-
|
||||
Value to send in the `Accept-Language` header.
|
||||
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
content.headers.custom:
|
||||
default: {}
|
||||
@ -370,6 +385,7 @@ content.headers.custom:
|
||||
name: String
|
||||
encoding: ascii
|
||||
none_ok: true
|
||||
supports_pattern: true
|
||||
desc: Custom headers for qutebrowser HTTP requests.
|
||||
|
||||
content.headers.do_not_track:
|
||||
@ -377,6 +393,7 @@ content.headers.do_not_track:
|
||||
name: Bool
|
||||
none_ok: true
|
||||
default: true
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Value to send in the `DNT` header.
|
||||
|
||||
@ -451,7 +468,11 @@ content.headers.user_agent:
|
||||
Gecko"
|
||||
- IE 11.0 for Desktop Win7 64-bit
|
||||
|
||||
desc: User agent to send. Unset to send the default.
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
User agent to send. Unset to send the default.
|
||||
|
||||
Note that the value read from JavaScript is always the global value.
|
||||
|
||||
content.host_blocking.enabled:
|
||||
default: true
|
||||
@ -594,6 +615,7 @@ content.local_storage:
|
||||
content.media_capture:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend: QtWebEngine
|
||||
desc: Allow websites to record audio/video.
|
||||
|
||||
@ -610,6 +632,7 @@ content.netrc_file:
|
||||
content.notifications:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend: QtWebKit
|
||||
desc: Allow websites to show notifications.
|
||||
|
||||
@ -626,6 +649,7 @@ content.pdfjs:
|
||||
content.persistent_storage:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend:
|
||||
QtWebKit: false
|
||||
QtWebEngine: Qt 5.11
|
||||
@ -672,6 +696,7 @@ content.proxy_dns_requests:
|
||||
content.register_protocol_handler:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
backend:
|
||||
QtWebKit: false
|
||||
QtWebEngine: Qt 5.11
|
||||
@ -681,6 +706,7 @@ content.register_protocol_handler:
|
||||
content.ssl_strict:
|
||||
default: ask
|
||||
type: BoolAsk
|
||||
supports_pattern: true
|
||||
desc: Validate SSL handshakes.
|
||||
|
||||
content.user_stylesheets:
|
||||
@ -712,14 +738,13 @@ content.webrtc_public_interfaces_only:
|
||||
|
||||
content.xss_auditing:
|
||||
type: Bool
|
||||
default: false
|
||||
default: true
|
||||
supports_pattern: true
|
||||
desc: >-
|
||||
Monitor load requests for cross-site scripting attempts.
|
||||
|
||||
Suspicious scripts will be blocked and reported in the inspector's
|
||||
JavaScript console. Enabling this feature might have an impact on
|
||||
performance.
|
||||
JavaScript console.
|
||||
|
||||
# emacs: '
|
||||
|
||||
@ -853,6 +878,18 @@ downloads.location.suggestion:
|
||||
- both: Show download path and filename.
|
||||
desc: What to display in the download filename input.
|
||||
|
||||
completion.open_categories:
|
||||
type:
|
||||
name: FlagList
|
||||
valid_values: [searchengines, quickmarks, bookmarks, history]
|
||||
none_ok: true
|
||||
default:
|
||||
- searchengines
|
||||
- quickmarks
|
||||
- bookmarks
|
||||
- history
|
||||
desc: Which categories to show (in which order) in the :open completion.
|
||||
|
||||
downloads.open_dispatcher:
|
||||
type:
|
||||
name: String
|
||||
@ -1565,19 +1602,6 @@ url.open_base_url:
|
||||
default: false
|
||||
desc: Open base URL of the searchengine if a searchengine shortcut is invoked without parameters.
|
||||
|
||||
url.open_categories_shown:
|
||||
type:
|
||||
name: List
|
||||
valtype: String
|
||||
none_ok: true
|
||||
default:
|
||||
- searchengines
|
||||
- quickmarks
|
||||
- bookmarks
|
||||
- history
|
||||
desc: Which categories to show in the :open dialogue. The order of this list is used for ordering the categories.
|
||||
|
||||
|
||||
url.searchengines:
|
||||
default:
|
||||
DEFAULT: https://duckduckgo.com/?q={}
|
||||
@ -2452,6 +2476,7 @@ bindings.default:
|
||||
.: repeat-command
|
||||
<Ctrl-p>: tab-pin
|
||||
<Alt-m>: tab-mute
|
||||
gD: tab-give
|
||||
q: record-macro
|
||||
"@": run-macro
|
||||
tsh: config-cycle -p -t -u *://{url:host}/* content.javascript.enabled ;; reload
|
||||
@ -2477,7 +2502,7 @@ bindings.default:
|
||||
<Ctrl-B>: hint all tab-bg
|
||||
<Escape>: leave-mode
|
||||
passthrough:
|
||||
<Ctrl-V>: leave-mode
|
||||
<Shift-Escape>: leave-mode
|
||||
command:
|
||||
<Ctrl-P>: command-history-prev
|
||||
<Ctrl-N>: command-history-next
|
||||
|
@ -89,6 +89,8 @@ def _init_envvars():
|
||||
os.environ['QT_XCB_FORCE_SOFTWARE_OPENGL'] = '1'
|
||||
elif software_rendering == 'qt-quick':
|
||||
os.environ['QT_QUICK_BACKEND'] = 'software'
|
||||
elif software_rendering == 'chromium':
|
||||
os.environ['QT_WEBENGINE_DISABLE_NOUVEAU_WORKAROUND'] = '1'
|
||||
|
||||
if config.val.qt.force_platform is not None:
|
||||
os.environ['QT_QPA_PLATFORM'] = config.val.qt.force_platform
|
||||
|
@ -3,7 +3,8 @@
|
||||
{% block script %}
|
||||
var cset = function(option, value) {
|
||||
// FIXME:conf we might want some error handling here?
|
||||
var url = "qute://settings/set?option=" + encodeURIComponent(option);
|
||||
var url = "qute://user:{{csrf_token}}@settings/set"
|
||||
url += "?option=" + encodeURIComponent(option);
|
||||
url += "&value=" + encodeURIComponent(value);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url);
|
||||
|
@ -57,3 +57,5 @@ rules:
|
||||
no-ternary: "off"
|
||||
max-lines: "off"
|
||||
multiline-ternary: ["error", "always-multiline"]
|
||||
max-lines-per-function: "off"
|
||||
require-unicode-regexp: "off"
|
||||
|
@ -114,7 +114,7 @@ window.loadHistory = (function() {
|
||||
title.className = "title";
|
||||
const link = document.createElement("a");
|
||||
link.href = itemUrl;
|
||||
link.innerHTML = itemTitle;
|
||||
link.innerHTML = itemTitle; // Properly escaped in qutescheme.py
|
||||
const host = document.createElement("span");
|
||||
host.className = "hostname";
|
||||
host.innerHTML = link.hostname;
|
||||
|
@ -138,6 +138,37 @@ def _key_to_string(key):
|
||||
'Dead_Hook': 'Hook',
|
||||
'Dead_Horn': 'Horn',
|
||||
|
||||
'Dead_Stroke': '̵',
|
||||
'Dead_Abovecomma': '̓',
|
||||
'Dead_Abovereversedcomma': '̔',
|
||||
'Dead_Doublegrave': '̏',
|
||||
'Dead_Belowring': '̥',
|
||||
'Dead_Belowmacron': '̱',
|
||||
'Dead_Belowcircumflex': '̭',
|
||||
'Dead_Belowtilde': '̰',
|
||||
'Dead_Belowbreve': '̮',
|
||||
'Dead_Belowdiaeresis': '̤',
|
||||
'Dead_Invertedbreve': '̑',
|
||||
'Dead_Belowcomma': '̦',
|
||||
'Dead_Currency': '¤',
|
||||
'Dead_a': 'a',
|
||||
'Dead_A': 'A',
|
||||
'Dead_e': 'e',
|
||||
'Dead_E': 'E',
|
||||
'Dead_i': 'i',
|
||||
'Dead_I': 'I',
|
||||
'Dead_o': 'o',
|
||||
'Dead_O': 'O',
|
||||
'Dead_u': 'u',
|
||||
'Dead_U': 'U',
|
||||
'Dead_Small_Schwa': 'ə',
|
||||
'Dead_Capital_Schwa': 'Ə',
|
||||
'Dead_Greek': 'Greek',
|
||||
'Dead_Lowline': '̲',
|
||||
'Dead_Aboveverticalline': '̍',
|
||||
'Dead_Belowverticalline': '\u0329',
|
||||
'Dead_Longsolidusoverlay': '̸',
|
||||
|
||||
'Memo': 'Memo',
|
||||
'ToDoList': 'To Do List',
|
||||
'Calendar': 'Calendar',
|
||||
@ -239,8 +270,10 @@ def _parse_special_key(keystr):
|
||||
replacements = (
|
||||
('control', 'ctrl'),
|
||||
('windows', 'meta'),
|
||||
('mod1', 'alt'),
|
||||
('mod4', 'meta'),
|
||||
('command', 'meta'),
|
||||
('cmd', 'meta'),
|
||||
('mod1', 'alt'),
|
||||
('less', '<'),
|
||||
('greater', '>'),
|
||||
)
|
||||
|
@ -24,7 +24,8 @@ import base64
|
||||
import itertools
|
||||
import functools
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QTimer, Qt
|
||||
from PyQt5.QtCore import (pyqtSlot, QRect, QPoint, QTimer, Qt,
|
||||
QCoreApplication, QEventLoop)
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
|
||||
|
||||
from qutebrowser.commands import runners, cmdutils
|
||||
@ -98,6 +99,9 @@ def raise_window(window, alert=True):
|
||||
window.setWindowState(window.windowState() & ~Qt.WindowMinimized)
|
||||
window.setWindowState(window.windowState() | Qt.WindowActive)
|
||||
window.raise_()
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-69568
|
||||
QCoreApplication.processEvents(
|
||||
QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers)
|
||||
window.activateWindow()
|
||||
|
||||
if alert:
|
||||
|
@ -24,7 +24,6 @@ import html
|
||||
import collections
|
||||
|
||||
import attr
|
||||
import sip
|
||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex,
|
||||
QItemSelectionModel, QObject, QEventLoop)
|
||||
from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit,
|
||||
@ -36,6 +35,7 @@ from qutebrowser.config import config
|
||||
from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message
|
||||
from qutebrowser.keyinput import modeman
|
||||
from qutebrowser.commands import cmdutils, cmdexc
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
prompt_queue = None
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
import enum
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt, QUrl
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtProperty, QUrl
|
||||
|
||||
from qutebrowser.mainwindow.statusbar import textbase
|
||||
from qutebrowser.config import config
|
||||
@ -80,8 +80,7 @@ class UrlText(textbase.TextBase):
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""Override TextBase.__init__ to elide in the middle by default."""
|
||||
super().__init__(parent, Qt.ElideMiddle)
|
||||
super().__init__(parent)
|
||||
self.setObjectName(self.__class__.__name__)
|
||||
config.set_register_stylesheet(self)
|
||||
self._hover_url = None
|
||||
|
@ -506,7 +506,8 @@ class TabbedBrowser(QWidget):
|
||||
usertypes.KeyMode.yesno]:
|
||||
# If we were in a command prompt, restore old focus
|
||||
# The above commands need to be run to switch tabs
|
||||
prev_focus.setFocus()
|
||||
if prev_focus is not None:
|
||||
prev_focus.setFocus()
|
||||
|
||||
tab.show()
|
||||
self.new_tab.emit(tab, idx)
|
||||
|
@ -54,6 +54,10 @@ class TabWidget(QTabWidget):
|
||||
tab_index_changed = pyqtSignal(int, int)
|
||||
new_tab_requested = pyqtSignal('QUrl', bool, bool)
|
||||
|
||||
# Strings for controlling the mute/audible text
|
||||
MUTE_STRING = '[M] '
|
||||
AUDIBLE_STRING = '[A] '
|
||||
|
||||
def __init__(self, win_id, parent=None):
|
||||
super().__init__(parent)
|
||||
bar = TabBar(win_id, self)
|
||||
@ -175,9 +179,9 @@ class TabWidget(QTabWidget):
|
||||
fields['private'] = ' [Private Mode] ' if tab.private else ''
|
||||
try:
|
||||
if tab.audio.is_muted():
|
||||
fields['audio'] = '[M] '
|
||||
fields['audio'] = TabWidget.MUTE_STRING
|
||||
elif tab.audio.is_recently_audible():
|
||||
fields['audio'] = '[A] '
|
||||
fields['audio'] = TabWidget.AUDIBLE_STRING
|
||||
else:
|
||||
fields['audio'] = ''
|
||||
except browsertab.WebTabError:
|
||||
|
@ -172,6 +172,11 @@ def _nvidia_shader_workaround():
|
||||
|
||||
|
||||
def _handle_nouveau_graphics():
|
||||
"""Force software rendering when using the Nouveau driver.
|
||||
|
||||
WORKAROUND for https://bugreports.qt.io/browse/QTBUG-41242
|
||||
Should be fixed in Qt 5.10 via https://codereview.qt-project.org/#/c/208664/
|
||||
"""
|
||||
assert objects.backend == usertypes.Backend.QtWebEngine, objects.backend
|
||||
|
||||
if os.environ.get('QUTE_SKIP_NOUVEAU_CHECK'):
|
||||
@ -181,7 +186,11 @@ def _handle_nouveau_graphics():
|
||||
return
|
||||
|
||||
if (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or
|
||||
'QT_XCB_FORCE_SOFTWARE_OPENGL' in os.environ):
|
||||
# qt.force_software_rendering = 'software-opengl'
|
||||
'QT_XCB_FORCE_SOFTWARE_OPENGL' in os.environ or
|
||||
# qt.force_software_rendering = 'chromium', also see:
|
||||
# https://build.opensuse.org/package/view_file/openSUSE:Factory/libqt5-qtwebengine/disable-gpu-when-using-nouveau-boo-1005323.diff?expand=1
|
||||
'QT_WEBENGINE_DISABLE_NOUVEAU_WORKAROUND' in os.environ):
|
||||
return
|
||||
|
||||
button = _Button("Force software rendering", 'qt.force_software_rendering',
|
||||
|
@ -206,6 +206,7 @@ def _check_modules(modules):
|
||||
# https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e
|
||||
messages = ['invalid escape sequence',
|
||||
'Flags not at the start of the expression']
|
||||
# pylint: disable=bad-continuation
|
||||
with log.ignore_py_warnings(
|
||||
category=DeprecationWarning,
|
||||
message=r'({})'.format('|'.join(messages))
|
||||
@ -216,6 +217,7 @@ def _check_modules(modules):
|
||||
category=ImportWarning,
|
||||
message=r'Not importing directory .*: missing __init__'
|
||||
):
|
||||
# pylint: enable=bad-continuation
|
||||
importlib.import_module(name)
|
||||
except ImportError as e:
|
||||
_die(text, e)
|
||||
@ -246,7 +248,7 @@ def configure_pyqt():
|
||||
from PyQt5.QtCore import pyqtRemoveInputHook
|
||||
pyqtRemoveInputHook()
|
||||
|
||||
import sip
|
||||
from qutebrowser.qt import sip
|
||||
try:
|
||||
# Added in sip 4.19.4
|
||||
sip.enableoverflowchecking(True)
|
||||
|
@ -140,11 +140,9 @@ class ExternalEditor(QObject):
|
||||
# the file from the external editor, see
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/1767
|
||||
with tempfile.NamedTemporaryFile(
|
||||
# pylint: disable=bad-continuation
|
||||
mode='w', prefix=prefix,
|
||||
encoding=config.val.editor.encoding,
|
||||
delete=False) as fobj:
|
||||
# pylint: enable=bad-continuation
|
||||
if text:
|
||||
fobj.write(text)
|
||||
return fobj.name
|
||||
|
@ -72,8 +72,7 @@ class BaseLineParser(QObject):
|
||||
Return:
|
||||
True if the file should be saved, False otherwise.
|
||||
"""
|
||||
if not os.path.exists(self._configdir):
|
||||
os.makedirs(self._configdir, 0o755)
|
||||
os.makedirs(self._configdir, 0o755, exist_ok=True)
|
||||
return True
|
||||
|
||||
def _after_save(self):
|
||||
|
@ -24,7 +24,6 @@ import os.path
|
||||
import itertools
|
||||
import urllib
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
import yaml
|
||||
@ -35,6 +34,7 @@ from qutebrowser.commands import cmdexc, cmdutils
|
||||
from qutebrowser.config import config, configfiles
|
||||
from qutebrowser.completion.models import miscmodels
|
||||
from qutebrowser.mainwindow import mainwindow
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
default = object() # Sentinel value
|
||||
|
@ -74,10 +74,10 @@ class SqliteError(SqlError):
|
||||
'13', # SQLITE_FULL
|
||||
]
|
||||
# At least in init(), we can get errors like this:
|
||||
# type: ConnectionError
|
||||
# database text: out of memory
|
||||
# driver text: Error opening database
|
||||
# error code: -1
|
||||
# > type: ConnectionError
|
||||
# > database text: out of memory
|
||||
# > driver text: Error opening database
|
||||
# > error code: -1
|
||||
environmental_strings = [
|
||||
"out of memory",
|
||||
]
|
||||
|
@ -29,7 +29,6 @@ try:
|
||||
except ImportError:
|
||||
hunter = None
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import QUrl
|
||||
# so it's available for :debug-pyeval
|
||||
from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import
|
||||
@ -40,6 +39,7 @@ from qutebrowser.commands import cmdutils, runners, cmdexc
|
||||
from qutebrowser.config import config, configdata
|
||||
from qutebrowser.misc import consolewidget
|
||||
from qutebrowser.utils.version import pastebin_version
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
|
||||
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
|
||||
@ -74,13 +74,18 @@ def later(ms: int, command, win_id):
|
||||
|
||||
@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True)
|
||||
@cmdutils.argument('win_id', win_id=True)
|
||||
def repeat(times: int, command, win_id):
|
||||
@cmdutils.argument('count', count=True)
|
||||
def repeat(times: int, command, win_id, count=None):
|
||||
"""Repeat a given command.
|
||||
|
||||
Args:
|
||||
times: How many times to repeat.
|
||||
command: The command to run, with optional args.
|
||||
count: Multiplies with 'times' when given.
|
||||
"""
|
||||
if count is not None:
|
||||
times *= count
|
||||
|
||||
if times < 0:
|
||||
raise cmdexc.CommandError("A negative count doesn't make sense.")
|
||||
commandrunner = runners.CommandRunner(win_id)
|
||||
@ -365,7 +370,7 @@ def window_only(current_win_id):
|
||||
@cmdutils.register()
|
||||
def nop():
|
||||
"""Do nothing."""
|
||||
return
|
||||
pass
|
||||
|
||||
|
||||
@cmdutils.register()
|
||||
|
28
qutebrowser/qt.py
Normal file
28
qutebrowser/qt.py
Normal file
@ -0,0 +1,28 @@
|
||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||
|
||||
# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||
#
|
||||
# This file is part of qutebrowser.
|
||||
#
|
||||
# qutebrowser is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# qutebrowser is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Wrappers around Qt/PyQt code."""
|
||||
|
||||
# pylint: disable=unused-import
|
||||
# PyQt 5.11 comes with a bundled sip,
|
||||
# for older PyQt versions it's a separate module.
|
||||
try:
|
||||
from PyQt5 import sip
|
||||
except ImportError:
|
||||
import sip
|
@ -25,6 +25,7 @@ import html as pyhtml
|
||||
import logging
|
||||
import contextlib
|
||||
import collections
|
||||
import copy
|
||||
import faulthandler
|
||||
import traceback
|
||||
import warnings
|
||||
@ -209,6 +210,11 @@ def _init_py_warnings():
|
||||
"""Initialize Python warning handling."""
|
||||
warnings.simplefilter('default')
|
||||
warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning)
|
||||
# This happens in many qutebrowser dependencies...
|
||||
warnings.filterwarnings('ignore', category=DeprecationWarning,
|
||||
message="Using or importing the ABCs from "
|
||||
"'collections' instead of from 'collections.abc' "
|
||||
"is deprecated, and in 3.8 it will stop working")
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
@ -563,16 +569,14 @@ class RAMHandler(logging.Handler):
|
||||
https://github.com/qutebrowser/qutebrowser/issues/34
|
||||
"""
|
||||
minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL)
|
||||
lines = []
|
||||
fmt = self.html_formatter.format if html else self.format
|
||||
self.acquire()
|
||||
try:
|
||||
records = list(self._data)
|
||||
lines = [fmt(record)
|
||||
for record in self._data
|
||||
if record.levelno >= minlevel]
|
||||
finally:
|
||||
self.release()
|
||||
for record in records:
|
||||
if record.levelno >= minlevel:
|
||||
lines.append(fmt(record))
|
||||
return '\n'.join(lines)
|
||||
|
||||
def change_log_capacity(self, capacity):
|
||||
@ -632,17 +636,18 @@ class HTMLFormatter(logging.Formatter):
|
||||
self._colordict['reset'] = '</font>'
|
||||
|
||||
def format(self, record):
|
||||
record.__dict__.update(self._colordict)
|
||||
if record.levelname in self._log_colors:
|
||||
color = self._log_colors[record.levelname]
|
||||
record.log_color = self._colordict[color]
|
||||
record_clone = copy.copy(record)
|
||||
record_clone.__dict__.update(self._colordict)
|
||||
if record_clone.levelname in self._log_colors:
|
||||
color = self._log_colors[record_clone.levelname]
|
||||
record_clone.log_color = self._colordict[color]
|
||||
else:
|
||||
record.log_color = ''
|
||||
record_clone.log_color = ''
|
||||
for field in ['msg', 'filename', 'funcName', 'levelname', 'module',
|
||||
'name', 'pathname', 'processName', 'threadName']:
|
||||
data = str(getattr(record, field))
|
||||
setattr(record, field, pyhtml.escape(data))
|
||||
msg = super().format(record)
|
||||
data = str(getattr(record_clone, field))
|
||||
setattr(record_clone, field, pyhtml.escape(data))
|
||||
msg = super().format(record_clone)
|
||||
if not msg.endswith(self._colordict['reset']):
|
||||
msg += self._colordict['reset']
|
||||
return msg
|
||||
|
@ -290,10 +290,7 @@ def _create(path):
|
||||
0700. If the destination directory exists already the permissions
|
||||
should not be changed.
|
||||
"""
|
||||
try:
|
||||
os.makedirs(path, 0o700)
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(path, 0o700, exist_ok=True)
|
||||
|
||||
|
||||
def _init_dirs(args=None):
|
||||
|
@ -29,6 +29,8 @@ import ipaddress
|
||||
import fnmatch
|
||||
import urllib.parse
|
||||
|
||||
from PyQt5.QtCore import QUrl
|
||||
|
||||
from qutebrowser.utils import utils, qtutils
|
||||
|
||||
|
||||
@ -177,6 +179,15 @@ class UrlPattern:
|
||||
assert self._host is None
|
||||
return
|
||||
|
||||
if parsed.netloc.startswith('['):
|
||||
# Using QUrl parsing to minimize ipv6 addresses
|
||||
url = QUrl()
|
||||
url.setHost(parsed.hostname)
|
||||
if not url.isValid():
|
||||
raise ParseError(url.errorString())
|
||||
self._host = url.host()
|
||||
return
|
||||
|
||||
# FIXME what about multiple dots?
|
||||
host_parts = parsed.hostname.rstrip('.').split('.')
|
||||
if host_parts[0] == '*':
|
||||
|
@ -645,7 +645,7 @@ def yaml_load(f):
|
||||
end = datetime.datetime.now()
|
||||
|
||||
delta = (end - start).total_seconds()
|
||||
deadline = 5 if 'CI' in os.environ else 2
|
||||
deadline = 10 if 'CI' in os.environ else 2
|
||||
if delta > deadline: # pragma: no cover
|
||||
log.misc.warning(
|
||||
"YAML load took unusually long, please report this at "
|
||||
|
@ -313,12 +313,29 @@ def _chromium_version():
|
||||
http://code.qt.io/cgit/qt/qtwebengine.git/tree/tools/scripts/version_resolver.py#n41
|
||||
|
||||
Quick reference:
|
||||
|
||||
Qt 5.7: Chromium 49
|
||||
49.0.2623.111 (2016-03-02)
|
||||
5.7.1: Security fixes up to 54.0.2840.87 (2016-10-19)
|
||||
|
||||
Qt 5.8: Chromium 53
|
||||
53.0.2785.148 (2016-08-31)
|
||||
5.8.0: Security fixes up to 55.0.2883.75 (2016-12-01)
|
||||
|
||||
Qt 5.9: Chromium 56
|
||||
(LTS) 56.0.2924.122 (2017-01-25)
|
||||
5.9.6: Security fixes up to 66.0.3359.170 (2018-04-17)
|
||||
|
||||
Qt 5.10: Chromium 61
|
||||
61.0.3163.140 (2017-09-05)
|
||||
5.10.1: Security fixes up to 64.0.3282.140 (2018-01-24)
|
||||
|
||||
Qt 5.11: Chromium 65
|
||||
65.0.3325.151 (.1: .230) (2018-03-06)
|
||||
5.11.1: Security fixes up to 67.0.3396.87 (2018-05-29)
|
||||
|
||||
Qt 5.12: Chromium 69 (?)
|
||||
current dev branch: 67.0.3396.76 (2018-05-29)
|
||||
|
||||
Also see https://www.chromium.org/developers/calendar
|
||||
"""
|
||||
@ -433,6 +450,11 @@ def opengl_vendor(): # pragma: no cover
|
||||
"""
|
||||
assert QApplication.instance()
|
||||
|
||||
override = os.environ.get('QUTE_FAKE_OPENGL_VENDOR')
|
||||
if override is not None:
|
||||
log.init.debug("Using override {}".format(override))
|
||||
return override
|
||||
|
||||
old_context = QOpenGLContext.currentContext()
|
||||
old_surface = None if old_context is None else old_context.surface()
|
||||
|
||||
@ -442,12 +464,12 @@ def opengl_vendor(): # pragma: no cover
|
||||
ctx = QOpenGLContext()
|
||||
ok = ctx.create()
|
||||
if not ok:
|
||||
log.init.debug("opengl_vendor: Creating context failed!")
|
||||
log.init.debug("Creating context failed!")
|
||||
return None
|
||||
|
||||
ok = ctx.makeCurrent(surface)
|
||||
if not ok:
|
||||
log.init.debug("opengl_vendor: Making context current failed!")
|
||||
log.init.debug("Making context current failed!")
|
||||
return None
|
||||
|
||||
try:
|
||||
@ -461,12 +483,11 @@ def opengl_vendor(): # pragma: no cover
|
||||
try:
|
||||
vf = ctx.versionFunctions(vp)
|
||||
except ImportError as e:
|
||||
log.init.debug("opengl_vendor: Importing version functions "
|
||||
"failed: {}".format(e))
|
||||
log.init.debug("Importing version functions failed: {}".format(e))
|
||||
return None
|
||||
|
||||
if vf is None:
|
||||
log.init.debug("opengl_vendor: Getting version functions failed!")
|
||||
log.init.debug("Getting version functions failed!")
|
||||
return None
|
||||
|
||||
return vf.glGetString(vf.GL_VENDOR)
|
||||
@ -484,6 +505,7 @@ def pastebin_version(pbclient=None):
|
||||
|
||||
def _on_paste_version_success(url):
|
||||
global pastebin_url
|
||||
url = url.strip()
|
||||
_yank_url(url)
|
||||
pbclient.deleteLater()
|
||||
pastebin_url = url
|
||||
|
@ -7,4 +7,4 @@ Jinja2==2.10
|
||||
MarkupSafe==1.0
|
||||
Pygments==2.2.0
|
||||
pyPEG2==2.15.2
|
||||
PyYAML==3.12
|
||||
PyYAML==3.13
|
||||
|
@ -45,7 +45,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir,
|
||||
import qutebrowser
|
||||
from scripts import utils
|
||||
# from scripts.dev import update_3rdparty
|
||||
from scripts.dev import gen_versioninfo
|
||||
|
||||
|
||||
def call_script(name, *args, python=sys.executable):
|
||||
@ -239,31 +238,16 @@ def build_windows():
|
||||
except FileNotFoundError:
|
||||
python_x64 = r'C:\Python{}\python.exe'.format(ver)
|
||||
|
||||
try:
|
||||
reg32_key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE,
|
||||
r'SOFTWARE\WOW6432Node\Python\PythonCore'
|
||||
r'\{}-32\InstallPath'.format(dot_ver))
|
||||
python_x86 = winreg.QueryValueEx(reg32_key, 'ExecutablePath')[0]
|
||||
except FileNotFoundError:
|
||||
python_x86 = r'C:\Python{}-32\python.exe'.format(ver)
|
||||
|
||||
out_pyinstaller = os.path.join('dist', 'qutebrowser')
|
||||
out_32 = os.path.join('dist',
|
||||
'qutebrowser-{}-x86'.format(qutebrowser.__version__))
|
||||
out_64 = os.path.join('dist',
|
||||
'qutebrowser-{}-x64'.format(qutebrowser.__version__))
|
||||
|
||||
artifacts = []
|
||||
|
||||
from scripts.dev import gen_versioninfo
|
||||
utils.print_title("Updating VersionInfo file")
|
||||
gen_versioninfo.main()
|
||||
|
||||
utils.print_title("Running pyinstaller 32bit")
|
||||
_maybe_remove(out_32)
|
||||
call_tox('pyinstaller', '-r', python=python_x86)
|
||||
shutil.move(out_pyinstaller, out_32)
|
||||
patch_windows(out_32)
|
||||
|
||||
utils.print_title("Running pyinstaller 64bit")
|
||||
_maybe_remove(out_64)
|
||||
call_tox('pyinstaller', '-r', python=python_x64)
|
||||
@ -271,39 +255,22 @@ def build_windows():
|
||||
patch_windows(out_64)
|
||||
|
||||
utils.print_title("Building installers")
|
||||
subprocess.run(['makensis.exe',
|
||||
'/DVERSION={}'.format(qutebrowser.__version__),
|
||||
'misc/qutebrowser.nsi'], check=True)
|
||||
subprocess.run(['makensis.exe',
|
||||
'/DX64',
|
||||
'/DVERSION={}'.format(qutebrowser.__version__),
|
||||
'misc/qutebrowser.nsi'], check=True)
|
||||
|
||||
name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__)
|
||||
name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__)
|
||||
|
||||
artifacts += [
|
||||
(os.path.join('dist', name_32),
|
||||
'application/vnd.microsoft.portable-executable',
|
||||
'Windows 32bit installer'),
|
||||
(os.path.join('dist', name_64),
|
||||
'application/vnd.microsoft.portable-executable',
|
||||
'Windows 64bit installer'),
|
||||
]
|
||||
|
||||
utils.print_title("Running 32bit smoke test")
|
||||
smoke_test(os.path.join(out_32, 'qutebrowser.exe'))
|
||||
utils.print_title("Running 64bit smoke test")
|
||||
smoke_test(os.path.join(out_64, 'qutebrowser.exe'))
|
||||
|
||||
utils.print_title("Zipping 32bit standalone...")
|
||||
name = 'qutebrowser-{}-windows-standalone-win32'.format(
|
||||
qutebrowser.__version__)
|
||||
shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32))
|
||||
artifacts.append(('{}.zip'.format(name),
|
||||
'application/zip',
|
||||
'Windows 32bit standalone'))
|
||||
|
||||
utils.print_title("Zipping 64bit standalone...")
|
||||
name = 'qutebrowser-{}-windows-standalone-amd64'.format(
|
||||
qutebrowser.__version__)
|
||||
|
@ -43,11 +43,6 @@ travis_retry() {
|
||||
return $result
|
||||
}
|
||||
|
||||
brew_install() {
|
||||
brew update
|
||||
brew install "$@"
|
||||
}
|
||||
|
||||
pip_install() {
|
||||
travis_retry python3 -m pip install "$@"
|
||||
}
|
||||
@ -62,7 +57,10 @@ check_pyqt() {
|
||||
python3 <<EOF
|
||||
import sys
|
||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion
|
||||
from sip import SIP_VERSION_STR
|
||||
try:
|
||||
from PyQt.sip import SIP_VERSION_STR
|
||||
except ModuleNotFoundError:
|
||||
from sip import SIP_VERSION_STR
|
||||
|
||||
print("Python {}".format(sys.version))
|
||||
print("PyQt5 {}".format(PYQT_VERSION_STR))
|
||||
@ -84,8 +82,8 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then
|
||||
|
||||
brew --version
|
||||
brew update
|
||||
brew upgrade python
|
||||
brew install qt5 pyqt5 libyaml
|
||||
brew upgrade python libyaml
|
||||
brew install qt5 pyqt5
|
||||
|
||||
pip_install -r misc/requirements/requirements-tox.txt
|
||||
python3 -m pip --version
|
||||
|
@ -36,6 +36,7 @@ import attr
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
from qutebrowser.browser.webengine import spell
|
||||
from qutebrowser.config import configdata
|
||||
from qutebrowser.utils import standarddir
|
||||
|
||||
|
||||
API_URL = 'https://chromium.googlesource.com/chromium/deps/hunspell_dictionaries.git/+/master/'
|
||||
@ -257,6 +258,8 @@ def remove_old(languages):
|
||||
def main():
|
||||
if configdata.DATA is None:
|
||||
configdata.init()
|
||||
standarddir.init(None)
|
||||
|
||||
parser = get_argparser()
|
||||
argv = sys.argv[1:]
|
||||
args = parser.parse_args(argv)
|
||||
|
@ -333,6 +333,8 @@ def import_chrome(profile, bookmark_types, output_format):
|
||||
|
||||
def bm_tree_walk(bm, template):
|
||||
"""Recursive function to walk through bookmarks."""
|
||||
if not isinstance(bm, dict):
|
||||
return
|
||||
assert 'type' in bm, bm
|
||||
if bm['type'] == 'url':
|
||||
if urllib.parse.urlparse(bm['url']).scheme != 'chrome':
|
||||
|
@ -136,7 +136,15 @@ def link_pyqt(executable, venv_path):
|
||||
executable: The python executable where the source files are present.
|
||||
venv_path: The path to the virtualenv site-packages.
|
||||
"""
|
||||
sip_file = get_lib_path(executable, 'sip')
|
||||
try:
|
||||
get_lib_path(executable, 'PyQt5.sip')
|
||||
except Error:
|
||||
# There is no PyQt5.sip, so we need to copy the toplevel sip.
|
||||
sip_file = get_lib_path(executable, 'sip')
|
||||
else:
|
||||
# There is a PyQt5.sip, it'll get copied with the PyQt5 dir.
|
||||
sip_file = None
|
||||
|
||||
sipconfig_file = get_lib_path(executable, 'sipconfig', required=False)
|
||||
pyqt_dir = os.path.dirname(get_lib_path(executable, 'PyQt5.QtCore'))
|
||||
|
||||
|
@ -25,7 +25,6 @@ import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import sip
|
||||
import pytest
|
||||
import hypothesis
|
||||
from PyQt5.QtCore import qVersion, PYQT_VERSION
|
||||
@ -38,14 +37,20 @@ from helpers.messagemock import message_mock
|
||||
from helpers.fixtures import * # noqa: F403
|
||||
from qutebrowser.utils import qtutils, standarddir, usertypes, utils, version
|
||||
from qutebrowser.misc import objects
|
||||
from qutebrowser.qt import sip
|
||||
|
||||
import qutebrowser.app # To register commands
|
||||
|
||||
|
||||
ON_CI = 'CI' in os.environ
|
||||
|
||||
|
||||
# Set hypothesis settings
|
||||
hypothesis.settings.register_profile('default',
|
||||
hypothesis.settings(deadline=600))
|
||||
hypothesis.settings.load_profile('default')
|
||||
hypothesis.settings.register_profile('ci',
|
||||
hypothesis.settings(deadline=None))
|
||||
hypothesis.settings.load_profile('ci' if ON_CI else 'default')
|
||||
|
||||
|
||||
def _apply_platform_markers(config, item):
|
||||
@ -60,8 +65,8 @@ def _apply_platform_markers(config, item):
|
||||
"Can't be run when frozen"),
|
||||
('frozen', not getattr(sys, 'frozen', False),
|
||||
"Can only run when frozen"),
|
||||
('ci', 'CI' not in os.environ, "Only runs on CI."),
|
||||
('no_ci', 'CI' in os.environ, "Skipped on CI."),
|
||||
('ci', not ON_CI, "Only runs on CI."),
|
||||
('no_ci', ON_CI, "Skipped on CI."),
|
||||
('issue2478', utils.is_windows and config.webengine,
|
||||
"Broken with QtWebEngine on Windows"),
|
||||
('issue3572',
|
||||
|
@ -85,8 +85,8 @@ def _get_version_tag(tag):
|
||||
do_skip = {
|
||||
'==': not qtutils.version_check(version, exact=True,
|
||||
compiled=False),
|
||||
'>=': not qtutils.version_check(version),
|
||||
'<': qtutils.version_check(version),
|
||||
'>=': not qtutils.version_check(version, compiled=False),
|
||||
'<': qtutils.version_check(version, compiled=False),
|
||||
'!=': qtutils.version_check(version, exact=True, compiled=False),
|
||||
}
|
||||
return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag)
|
||||
|
10
tests/end2end/data/issue4011.html
Normal file
10
tests/end2end/data/issue4011.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><img src="x" onerror="console.log('XSS')">foo</title>
|
||||
</head>
|
||||
<body>
|
||||
foo
|
||||
</body>
|
||||
</html>
|
20
tests/end2end/data/misc/qutescheme_csrf.html
Normal file
20
tests/end2end/data/misc/qutescheme_csrf.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>CSRF issues with qute://settings</title>
|
||||
<script type="text/javascript">
|
||||
function add_img() {
|
||||
const elem = document.createElement("img")
|
||||
elem.src = "qute://settings/set?option=auto_save.interval&value=invalid";
|
||||
document.body.appendChild(elem);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<form action="qute://settings/set?option=auto_save.interval&value=invalid" method="post"><button type="submit" id="via-form">Via form</button></form>
|
||||
<input type="button" onclick="add_img()" value="Via img" id="via-img">
|
||||
<a href="qute://settings/set?option=auto_save.interval&value=invalid" id="via-link">Via link</a>
|
||||
<a href="/redirect-to?url=qute://settings/set%3Foption=auto_save.interval%26value=invalid" id="via-redirect">Via redirect</a>
|
||||
</body>
|
||||
</html>
|
11
tests/end2end/data/userscripts/hello_if_count
Executable file
11
tests/end2end/data/userscripts/hello_if_count
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "$QUTE_COUNT" -eq 5 ]; then
|
||||
|
||||
echo "message-info 'Count is five!'" >> "$QUTE_FIFO"
|
||||
|
||||
elif [ -z "$QUTE_COUNT" ]; then
|
||||
|
||||
echo "message-info 'No count!'" >> "$QUTE_FIFO"
|
||||
|
||||
fi
|
@ -3,6 +3,7 @@
|
||||
Feature: Going back and forward.
|
||||
Testing the :back/:forward commands.
|
||||
|
||||
@skip # Too flaky
|
||||
Scenario: Going back/forward
|
||||
Given I open data/backforward/1.txt
|
||||
When I open data/backforward/2.txt
|
||||
@ -74,6 +75,7 @@ Feature: Going back and forward.
|
||||
url: http://localhost:*/data/backforward/1.txt
|
||||
- url: http://localhost:*/data/backforward/2.txt
|
||||
|
||||
@flaky
|
||||
Scenario: Going back with count.
|
||||
Given I open data/backforward/1.txt
|
||||
When I open data/backforward/2.txt
|
||||
|
@ -321,6 +321,7 @@ Feature: Caret mode
|
||||
- data/caret.html
|
||||
- data/hello.txt (active)
|
||||
|
||||
@flaky
|
||||
Scenario: :follow-selected with link tabbing (without JS)
|
||||
When I set content.javascript.enabled to false
|
||||
And I run :leave-mode
|
||||
@ -329,6 +330,7 @@ Feature: Caret mode
|
||||
And I run :follow-selected
|
||||
Then data/hello.txt should be loaded
|
||||
|
||||
@flaky
|
||||
Scenario: :follow-selected with link tabbing (with JS)
|
||||
When I set content.javascript.enabled to true
|
||||
And I run :leave-mode
|
||||
@ -337,6 +339,7 @@ Feature: Caret mode
|
||||
And I run :follow-selected
|
||||
Then data/hello.txt should be loaded
|
||||
|
||||
@flaky
|
||||
Scenario: :follow-selected with link tabbing in a tab (without JS)
|
||||
When I set content.javascript.enabled to false
|
||||
And I run :leave-mode
|
||||
@ -345,6 +348,7 @@ Feature: Caret mode
|
||||
And I run :follow-selected --tab
|
||||
Then data/hello.txt should be loaded
|
||||
|
||||
@flaky
|
||||
Scenario: :follow-selected with link tabbing in a tab (with JS)
|
||||
When I set content.javascript.enabled to true
|
||||
And I run :leave-mode
|
||||
|
@ -211,6 +211,7 @@ Feature: Downloading things from a website.
|
||||
# works e.g. on a connection loss, which we can't test automatically.
|
||||
Then "Retrying downloads is unsupported *" should not be logged
|
||||
|
||||
@flaky
|
||||
Scenario: Retrying with count
|
||||
When I run :download http://localhost:(port)/data/downloads/download.bin
|
||||
And I run :download http://localhost:(port)/does-not-exist
|
||||
|
@ -86,6 +86,7 @@ Feature: Opening external editors
|
||||
When I run :edit-url -t -b
|
||||
Then the error "Only one of -t/-b/-w can be given!" should be shown
|
||||
|
||||
@flaky
|
||||
Scenario: Editing a URL with invalid URL
|
||||
When I set url.auto_search to never
|
||||
And I open data/hello.txt
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user