Merge pull request #1 from qutebrowser/master

Update Fork
This commit is contained in:
Penaz 2017-07-24 10:39:08 +02:00 committed by GitHub
commit cd27363126
431 changed files with 8658 additions and 7152 deletions

View File

@ -5,16 +5,15 @@ cache:
build: off
environment:
PYTHONUNBUFFERED: 1
PYTHON: C:\Python36\python.exe
matrix:
- TESTENV: py34
- TESTENV: py36-pyqt58
PYTHON: C:\Python36\python.exe
- TESTENV: unittests-frozen
- TESTENV: pylint
install:
- C:\Python27\python -u scripts\dev\ci\appveyor_install.py
- set PATH=%PATH%;C:\Python36
- '%PYTHON% -m pip install -U pip'
- '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
- 'set PATH=%PATH%;C:\Python36'
test_script:
- C:\Python34\Scripts\tox -e %TESTENV%
- '%PYTHON% -m tox -e %TESTENV%'

View File

@ -11,6 +11,7 @@ exclude = .*,__pycache__,resources.py
# (for pytest's __tracebackhide__)
# F401: Unused import
# N802: function name should be lowercase
# N806: variable in function should be lowercase
# P101: format string does contain unindexed parameters
# P102: docstring does contain unindexed parameters
# P103: other string does contain unindexed parameters
@ -35,10 +36,10 @@ max-complexity = 12
putty-auto-ignore = True
putty-ignore =
/# pylint: disable=invalid-name/ : +N801,N806
/# pylint: disable=wildcard-import/ : +F403
/# pragma: no mccabe/ : +C901
tests/*/test_*.py : +D100,D101,D401
tests/unit/browser/webkit/test_history.py : +N806
tests/conftest.py : +F403
tests/unit/browser/test_history.py : +N806
tests/helpers/fixtures.py : +N806
tests/unit/browser/webkit/http/test_content_disposition.py : +D400
scripts/dev/ci/appveyor_install.py : +FI53

8
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,8 @@
qutebrowser/browser/history.py @rcorre
qutebrowser/completion/* @rcorre
qutebrowser/misc/sql.py @rcorre
tests/end2end/features/completion.feature @rcorre
tests/end2end/features/test_completion_bdd.py @rcorre
tests/unit/browser/test_history.py @rcorre
tests/unit/completion/* @rcorre
tests/unit/misc/test_sql.py @rcorre

View File

@ -1,2 +1,2 @@
<!-- If this is a bug report, please remember to mention your version info from
the `qute:version` page or `qutebrowser --version` -->
`:open qute:version` or `qutebrowser --version` -->

View File

@ -30,11 +30,7 @@ disable=no-self-use,
broad-except,
bare-except,
eval-used,
exec-used,
file-ignored,
wrong-import-order,
ungrouped-imports,
redefined-variable-type,
suppressed-message,
too-many-return-statements,
duplicate-code,
@ -53,12 +49,9 @@ no-docstring-rgx=(^_|^main$)
[FORMAT]
max-line-length=79
ignore-long-lines=(<?https?://|^# Copyright 201\d|# (pylint|flake8): disable=)
ignore-long-lines=(<?https?://|^# Copyright 201\d)
expected-line-ending-format=LF
[SIMILARITIES]
min-similarity-lines=8
[VARIABLES]
dummy-variables-rgx=_.*
@ -69,12 +62,10 @@ max-args=10
valid-metaclass-classmethod-first-arg=cls
[TYPECHECK]
# WORKAROUND for https://github.com/PyCQA/astroid/pull/357
ignored-modules=pytest
# MsgType added as WORKAROUND for
# https://bitbucket.org/logilab/pylint/issues/690/
# UnsetObject because pylint infers any objreg.get(...) as UnsetObject.
ignored-classes=qutebrowser.utils.objreg.UnsetObject,
qutebrowser.browser.webkit.webelem.WebElementWrapper,
scripts.dev.check_coverage.MsgType,
qutebrowser.browser.downloads.UnsupportedAttribute
ignored-modules=PyQt5,PyQt5.QtWebKit
[IMPORTS]
# WORKAROUND
# For some reason, pylint doesn't know about some Python 3 modules on
# AppVeyor...
known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types

1
.pyup.yml Normal file
View File

@ -0,0 +1 @@
schedule: "every week"

View File

@ -1,6 +1,7 @@
sudo: required
dist: trusty
language: generic
group: edge
matrix:
include:
@ -15,9 +16,6 @@ matrix:
- os: linux
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
services: docker
- os: linux
env: DOCKER=archlinux-ng
services: docker
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
@ -25,14 +23,18 @@ matrix:
language: python
python: 3.6
env: TESTENV=py36-pyqt571
- os: linux
language: python
python: 3.6
env: TESTENV=py36-pyqt58
- os: linux
language: python
python: 3.5
env: TESTENV=py35-pyqt58
env: TESTENV=py35-pyqt59
- os: linux
language: python
python: 3.6
env: TESTENV=py36-pyqt58
env: TESTENV=py36-pyqt59
- os: osx
env: TESTENV=py36 OSX=elcapitan
osx_image: xcode7.3
@ -41,7 +43,9 @@ matrix:
# env: TESTENV=py35 OSX=yosemite
# osx_image: xcode6.4
- os: linux
env: TESTENV=pylint
env: TESTENV=pylint PYTHON=python3.6
language: python
python: 3.6
- os: linux
env: TESTENV=flake8
- os: linux

View File

@ -14,37 +14,189 @@ This project adheres to http://semver.org/[Semantic Versioning].
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v0.11.0 (unreleased)
--------------------
v1.0.0 (unreleased)
-------------------
Breaking changes
~~~~~~~~~~~~~~~~
- Support for legacy QtWebKit (before 5.212 which is distributed
independently from Qt) is dropped.
- Support for Python 3.4 is dropped.
- Support for Qt before 5.7 is dropped.
- New dependency on the QtSql module and Qt sqlite support.
- New dependency on ruamel.yaml; dropped PyYAML dependency.
- The QtWebEngine backend is now used by default if available.
- New config system which ignores the old config file.
Major changes
~~~~~~~~~~~~~
- New completion engine based on sqlite, which allows to complete
the entire browsing history.
- Completely rewritten configuration system.
Added
~~~~~
- New `:clear-messages` command to clear shown messages.
- New `ui -> keyhint-delay` setting to configure the delay until
the keyhint overlay pops up.
- New `-s` option for `:open` to force a HTTPS scheme.
- New back/forward indicator in the statusbar
Changed
~~~~~~~
- When using QtWebEngine, the underlying Chromium version is now shown in the
version info.
- Improved `qute:history` page with lazy loading
- Messages are now hidden when clicked
- Paths like `C:Downloads` are now treated as absolute paths on Windows.
- PAC on QtWebKit now supports SOCK5 as type.
- Upgrading qutebrowser with a version older than v0.4.0 still running now won't
work properly anymore.
- Using `:download` now uses the page's title as filename.
- Using `:back` or `:forward` with a count now skips intermediate pages.
- When there are multiple messages shown, the timeout is increased.
- `:search` now only clears the search if one was displayed before, so pressing
`<Escape>` doesn't un-focus inputs anymore.
Fixes
~~~~~
- Exiting fullscreen via `:fullscreen` or buttons on a page now
restores the correct previous window state (maximized/fullscreen).
v0.11.1 (unreleased)
--------------------
Fixes
~~~~~
- Fixed empty space being shown after tabs in the tabbar in some cases.
- Fixed `:restart` in private browsing mode.
- Fixed printing on macOS.
- Closing a pinned tab via mouse now also prompts for confirmation.
- The "try again" button on error pages works correctly again.
- :spawn -u -d is now disallowed.
- :spawn -d shows error messages correctly now.
v0.11.0
-------
New dependencies
~~~~~~~~~~~~~~~~
- New dependency on `PyQt5.QtOpenGL` if QtWebEngine is used. QtWebEngine depends
on QtOpenGL already, but on distributions packaging split PyQt5 wrappers, the
wrappers for QtOpenGL are now required.
- New dependency on `PyOpenGL` if QtWebEngine is used.
Added
~~~~~
- Private browsing is now implemented for QtWebEngine, *and changed its
behavior*: The `general -> private-browsing` setting now only applies to newly
opened windows, and you can use the `-p` flag to `:open` to open a private
window.
- New "pinned tabs" feature, with a new `:tab-pin` command (bound
to `<Ctrl-p>` by default).
- (QtWebEngine) Implemented `:follow-selected`.
- New `:clear-messages` command to clear shown messages.
- New `ui -> keyhint-delay` setting to configure the delay until
the keyhint overlay pops up.
- New `-s` option for `:open` to force a HTTPS scheme.
- `:debug-log-filter` now accepts `none` as an argument to clear any log
filters.
- New `--debug-flag` argument which replaces `--debug-exit` and
`--pdb-postmortem`.
- New `tabs -> favicon-scale` option to scale up/down favicons.
- `colors -> statusbar.bg/fg.private` and `.command.private` to
customize statusbar colors for private windows.
- New `{private}` field displaying `[Private Mode]` for
`ui -> window-title-format` and `tabs -> title-format`.
- (QtWebEngine) Proxy support with Qt 5.7.1 (already was supported for 5.8 and
newer)
Changed
~~~~~~~
- To prevent elaborate phishing attacks, the Punycode version (`xn--*`) is now
shown in addition to the decoded version for international domain names
(IDN).
- Starting with legacy QtWebKit now shows a warning message.
*With the next release, support for it will be removed.*
- The Windows releases are redone from scratch, which means:
- They now use the new QtWebEngine backend
- The bundled Qt is updated from 5.5 to 5.9
- The bundled Python is updated from 3.4 to 3.6
- They are now generated with PyInstaller instead of cx_Freeze
- The installer is now generated using NSIS instead of being a MSI
- Improved `qute://history` page (with lazy loading)
- Crash reports are not public anymore.
- Paths like `C:` are now treated as absolute paths on Windows for downloads,
and invalid paths are handled properly.
- Comments in the config file are now placed before the individual options
instead of being before sections.
- Messages are now hidden when clicked.
- stdin is now closed immediately for processes spawned from qutebrowser.
- When `ui -> message-timeout` is set to 0, messages are now never cleared.
- Middle/right-clicking the blank parts of the tab bar (when vertical) now
closes the current tab.
- The adblocker now also blocks non-GET requests (e.g. POST).
- `javascript:` links can now be hinted.
- `:view-source`, `:tab-clone` and `:navigate --tab` now don't open the tab as
"explicit" anymore, i.e. (with the default settings) open it next to the
active tab.
- `qute:*` pages now use `qute://*` instead (e.g. `qute://version` instead of
`qute:version`), but the old versions are automatically redirected.
- Texts in prompts are now selectable.
- The default level for `:messages` is now `info`, not `error`
- Trying to focus the currently focused tab with `:tab-focus` now focuses the
last viewed tab.
- (QtWebEngine) With Qt 5.9, `content -> cookies-store` can now be set without
a restart.
- (QtWebEngine) With Qt 5.9, better error messages are now shown for failed
downloads.
- (QtWebEngine) The underlying Chromium version is now shown in the version
info.
- (QtWebKit) Renderer process crashes now show an error page on Qt 5.9 or newer.
- (QtWebKit) storage -> offline-web-application-storage` got renamed to `...-cache`
- (QtWebKit) PAC now supports SOCKS5 as type.
Fixed
~~~~~
- Added a workaround for a black screen with QtWebEngine with some setups
(the workaround requires PyOpenGL to be installed, but it's optional)
- Crash when trying to retry downloads with QtWebEngine
- Crash when cloning page without history
- Continuing a search after clearing it
- Crash when downloading a download resulting in a HTTP error
- Various rare crashes
- The macOS .dmg is now built against Qt 5.9 which fixes various
important issues (such as not being able to type dead keys).
- Fixed crash with `:download` on PyQt 5.9.
- Cloning a page without history doesn't crash anymore.
- When a download results in a HTTP error, it now shows the error correctly
instead of crashing.
- Pressing ctrl-c while a config error is shown works as intended now.
- When the key config isn't writable, we now show an error instead of crashing.
- Fixed crash when unbinding an unbound key in the key config.
- Fixed crash when using `:debug-log-filter` when `--filter` wasn't given on startup.
- Fixed crash with some invalid setting values.
- Continuing a search after clearing it now works correctly.
- The tabbar and completion should now be more consistently and correctly
styled with various system styles.
- Applying styiles in `qt5ct` now shouldn't crash anymore.
- The validation for colors in stylesheets is now less strict,
allowing for all valid Qt values.
- `data:` URLs now aren't added to the history anymore.
- Accidentally starting with Python 2 now shows a proper error message again.
- For some people, running some userscripts crashed - this should now be fixed.
- Various other rare crashes should now be fixed.
- The settings documentation was truncated with v0.10.1 which should now be
fixed.
- Scrolling to an anchor in a background tab now works correctly, and javascript
gets the correct window size for background tabs.
- (QtWebEngine) Added a workaround for a black screen with some setups
- (QtWebEngine) Starting with Nouveau graphics now shows an error message
instead of crashing in Qt.
- (QtWebEngine) Retrying downloads now shows an error instead of crashing.
- (QtWebEngine) Cloning a view-source tab now doesn't crash anymore.
- (QtWebEngine) `window.navigator.userAgent` is now set correctly when
customizing the user agent.
- (QtWebEngine) HTML fullscreen is now tracked for each tab separately, which
means it's not possible anymore to accidentally get stuck in fullscreen state
by closing a tab with a fullscreen video.
- (QtWebEngine) `:scroll-page` with `--bottom-navigate` now works correctly.
- (QtWebKit) The HTTP cache is disabled on Qt 5.7.1 and 5.8 now as it leads to
frequent crashes due to a Qt bug.
- (QtWebKit) Fixed Crash when a PAC file returns an invalid value.
v0.10.1
-------
@ -89,7 +241,7 @@ Added
- Open tabs are now auto-saved on each successful load and restored in case of a crash
- `:jseval` now has a `--file` flag so you can pass a javascript file
- `:session-save` now has a `--only-active-window` flag to only save the active window
- OS X builds are back, and built with QtWebEngine
- macOS builds are back, and built with QtWebEngine
Changed
~~~~~~~
@ -105,6 +257,18 @@ Changed
- `network -> proxy` can also be set to `pac+file://...` now to
use a local proxy autoconfig file (on QtWebKit)
Removed
~~~~~~~
- (QtWebKit) Various rarely customized settings were removed:
- `ui -> css-media-type` (defaults to desktop)
- `general -> site-specific-quirks` (now always turned on)
- `storage -> offline-storage-default-quota` (defaults to 5MB)
- `storage -> offline-web-application-cache-quota` (defaults to no quota)
- `storage -> object-cache-capacities` (default depends on disk space)
- `content -> css-regions` (now always turned off)
- `storage -> offline-storage-database` (merged into `storage -> local-storage`)
Fixed
~~~~~
@ -379,7 +543,7 @@ Fixed
- Fix crash when pressing enter without a command
- Adjust error message to point out QtWebEngine is unsupported with the OS
X .app currently.
- Hide Harfbuzz warning with the OS X .app
- Hide Harfbuzz warning with the macOS .app
v0.8.0
------
@ -742,7 +906,7 @@ Fixed
- Fixed scrolling to the very left/right with `:scroll-perc`.
- Using an external editor should now work correctly with some funny chars
(U+2028/U+2029/BOM).
- Movements in caret mode now should work correctly on OS X and Windows.
- Movements in caret mode now should work correctly on macOS and Windows.
- Fixed upgrade from earlier config versions.
- Fixed crash when killing a running userscript.
- Fixed characters being passed through when shifted with
@ -817,7 +981,7 @@ Changed
- The completion widget doesn't show a border anymore.
- The tabbar doesn't display ugly arrows anymore if there isn't enough space
for all tabs.
- Some insignificant Qt warnings which were printed on OS X are now hidden.
- Some insignificant Qt warnings which were printed on macOS are now hidden.
- Better support for Qt 5.5 and Python 3.5.
Fixed
@ -928,7 +1092,7 @@ Fixed
- Fixed AssertionError when closing many windows quickly.
- Various fixes for deprecated key bindings and auto-migrations.
- Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug).
- Fixed handling of keybindings containing Ctrl/Meta on OS X.
- Fixed handling of keybindings containing Ctrl/Meta on macOS.
- Fixed crash when downloading a URL without filename (e.g. magnet links) via "Save as...".
- Fixed exception when starting qutebrowser with `:set` as argument.
- Fixed horrible completion performance when the `shrink` option was set.
@ -1026,7 +1190,7 @@ Changed
- Add a `:search` command in addition to `/foo` so it's more visible and can be used from scripts.
- Various improvements to documentation, logging, and the crash reporter.
- Expand `~` to the users home directory with `:run-userscript`.
- Improve the userscript runner on Linux/OS X by using `QSocketNotifier`.
- Improve the userscript runner on Linux/macOS by using `QSocketNotifier`.
- Add luakit-like `gt`/`gT` keybindings to cycle through tabs.
- Show default value for config values in the completion.
- Clone tab icon, tab text and zoom level when cloning tabs.
@ -1046,7 +1210,7 @@ Changed
* `init_venv.py` and `run_checks.py` have been replaced by http://tox.readthedocs.org/[tox]. Install tox and run `tox -e mkvenv` instead.
* The tests now use http://pytest.org/[pytest]
* Many new tests added
* Mac Mini buildbot to run the tests on OS X.
* Mac Mini buildbot to run the tests on macOS.
* Coverage recording via http://nedbatchelder.com/code/coverage/[coverage.py].
* New `--pdb-postmortem argument` to drop into the pdb debugger on exceptions.
* Use https://github.com/ionelmc/python-hunter[hunter] for line tracing instead of a selfmade solution.
@ -1182,7 +1346,7 @@ Fixed
* Fix rare exception when a key is pressed shortly after opening a window
* Fix exception with certain invalid URLs like `http:foo:0`
* Work around Qt bug which renders checkboxes on OS X unusable
* Work around Qt bug which renders checkboxes on macOS unusable
* Fix exception when a local files can't be read in `:adblock-update`
* Hide 2 more Qt warnings.
* Add `!important` to hint CSS so websites don't override the hint look
@ -1218,7 +1382,7 @@ Changes
* Set zoom to default instead of 100% with `:zoom`/`=`.
* Adjust page zoom if default zoom changed.
* Force tabs to be focused on `:undo`.
* Replace manual installation instructions on OS X with homebrew/macports.
* Replace manual installation instructions on macOS with homebrew/macports.
* Allow min-/maximizing of print preview on Windows.
* Various documentation improvements.
* Various other small improvements and cleanups.

View File

@ -5,6 +5,12 @@ The Compiler <mail@qutebrowser.org>
:data-uri:
:toc:
IMPORTANT: I'm currently (July 2017) more busy than usual until September,
because of exams coming up. In addition to that, a new config system is coming
which will conflict with many non-trivial contributions. Because of that, please
refrain from contributing new features until then. If you're reading this note
after mid-September, please open an issue.
I `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
This document contains guidelines for contributing to qutebrowser, as well as
@ -42,6 +48,12 @@ be easy to solve]
* https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which
require little/no coding]
If you prefer C++ or Javascript to Python, see the relevant issues which involve
work in those languages:
* https://github.com/qutebrowser/qutebrowser/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3Ac%2B%2B[C++] (mostly work on Qt, the library behind qutebrowser)
* https://github.com/qutebrowser/qutebrowser/issues?q=is%3Aopen+is%3Aissue+label%3Ajavascript[JavaScript]
There are also some things to do if you don't want to write code:
* Help the community, e.g., on the mailinglist and the IRC channel.
@ -214,7 +226,7 @@ Documentation of used Python libraries:
* http://pygments.org/docs/[pygments]
* http://fdik.org/pyPEG/index.html[pyPEG2]
* http://pythonhosted.org/setuptools/[setuptools]
* http://cx-freeze.readthedocs.org/en/latest/overview.html[cx_Freeze]
* http://www.pyinstaller.org/[PyInstaller]
* https://pypi.python.org/pypi/colorama[colorama]
Related RFCs and standards:
@ -546,6 +558,28 @@ Rebuilding the website
If you want to rebuild the website, run `./scripts/asciidoc2html.py --website <outputdir>`.
Chrome URLs
~~~~~~~~~~~
With the QtWebEngine backend, qutebrowser supports several chrome:// urls which
can be useful for debugging:
- chrome://appcache-internals/
- chrome://blob-internals/
- chrome://gpu/
- chrome://histograms/
- chrome://indexeddb-internals/
- chrome://media-internals/
- chrome://network-errors/
- chrome://serviceworker-internals/
- chrome://webrtc-internals/
- chrome://crash/ (crashes the current renderer process!)
- chrome://kill/ (kills the current renderer process!)
- chrome://gpucrash/ (crashes qutebrowser!)
- chrome://gpuhang/ (hangs qutebrowser!)
- chrome://gpuclean/ (crashes the current renderer process!)
- chrome://ppapiflashcrash/
- chrome://ppapiflashhang/
Style conventions
-----------------
@ -654,8 +688,9 @@ qutebrowser release
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
- `python -m qutebrowser --basedir conf :quit`
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.x.y.conf`
- `sed '/^#/d' conf/config/qutebrowser.conf > tests/unit/config/old_configs/qutebrowser-v0.$x.$y.conf`
- `rm -r conf`
- git add
- commit
* Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Update changelog (remove *(unreleased)*)
@ -670,8 +705,8 @@ qutebrowser release
as closed.
* Linux: Run `python3 scripts/dev/build_release.py --upload v0.$x.$y`
* Windows: Run `C:\Python34_x32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v0.X.Y` (replace X/Y by hand)
* OS X: Run `python3 scripts/dev/build_release.py --upload v0.X.Y` (replace X/Y by hand)
* Windows: Run `C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v0.X.Y` (replace X/Y by hand)
* macOS: Run `python3 scripts/dev/build_release.py --upload v0.X.Y` (replace X/Y by hand)
* On server: Run `python3 scripts/dev/download_release.py v0.X.Y` (replace X/Y by hand)
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed
* Announce to qutebrowser and qutebrowser-announce mailinglist

View File

@ -72,8 +72,8 @@ Is there an adblocker?::
http://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 it properly might take some time and won't be done
for v0.1 if at all.
usage], so implementing support for AdBlockPlus-like lists is currently not
a priority.
How do I play Youtube videos with mpv?::
You can easily add a key binding to play youtube videos inside a real video
@ -124,6 +124,67 @@ When using quickmark, you can give them all names, like
`:open foodrecipes`, you will see a list of all the food recipe sites,
without having to remember the exact website title or address.
How do I use spell checking?::
Qutebrowser's support for spell checking is somewhat limited at the moment
(see https://github.com/qutebrowser/qutebrowser/issues/700[#700]), but it
can be done.
+
For QtWebKit:
. Install https://github.com/QupZilla/qtwebkit-plugins[qtwebkit-plugins].
. Note: with QtWebKit reloaded you may experience some issues. See
https://github.com/QupZilla/qtwebkit-plugins/issues/10[#10].
. The dictionary to use is taken from the `DICTIONARY` environment variable.
The default is `en_US`. For example to use Dutch spell check set `DICTIONARY`
to `nl_NL`; you can't use multiple dictionaries or change them at runtime at
the moment.
(also see the README file for `qtwebkit-plugins`).
. Remember to install the hunspell dictionaries if you don't have them already
(most distros should have packages for this).
+
For QtWebEngine:
. Not yet supported unfortunately :-( +
Adding it shouldn't be too hard though, since QtWebEngine 5.8 added an API for
this (see
https://github.com/qutebrowser/qutebrowser/issues/700#issuecomment-290780706[this
comment for a basic example]), so what are you waiting for and why aren't you
hacking qutebrowser yet?
How do I use Tor with qutebrowser?::
Start tor on your machine, and do `:set network proxy socks://localhost:9050/`
in qutebrowser. Note this won't give you the same amount of fingerprinting
protection that the Tor Browser does, but it's useful to be able to access
`.onion` sites.
Why does J move to the next (right) tab, and K to the previous (left) one?::
One reason is because https://bitbucket.org/portix/dwb[dwb] did it that way,
and qutebrowser's keybindings are designed to be compatible with dwb's.
The rationale behind it is that J is "down" in vim, and K is "up", which
corresponds nicely to "next"/"previous". It also makes much more sense with
vertical tabs (e.g. `:set tabs position left`).
What's the difference between insert and passthrough mode?::
They are quite similar, but insert mode has some bindings (like `Ctrl-e` to
open an editor) while passthrough mode only has escape bound. It might also
be useful to rebind escape to something else in passthrough mode only, to be
able to send an escape keypress to the website.
Why takes it longer to open an URL in qutebrowser than in chromium?::
When opening an URL in an existing instance the normal qutebrowser
Python script is started and a few PyQt libraries need to be
loaded until it is detected that there is an instance running
where the URL is then passed to. This takes some time.
One workaround is to use this
https://github.com/qutebrowser/qutebrowser/blob/master/scripts/open_url_in_instance.sh[script]
and place it in your $PATH with the name "qutebrowser". This
script passes the URL via an unix socket to qutebrowser (if its
running already) using socat which is much faster and starts a new
qutebrowser if it is not running already. Also check if you want
to use webengine as backend in line 17 and change it to your
needs.
== Troubleshooting
Configuration not saved after modifying config.::

View File

@ -1,6 +1,8 @@
Installing qutebrowser
======================
toc::[]
On Debian / Ubuntu
------------------
@ -15,15 +17,25 @@ still relatively easy!
You can use packages that are built for every release or build it yourself from git.
On Ubuntu 16.04 and 16.10 it's recommended to <<tox,install qutebrowser via tox>>
instead in order to be able to use the new QtWebEngine backend. Newer versions
have a QtWebEngine package in the repositories.
Using the packages
~~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
----
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml
# apt-get install python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml python3-pyqt5.qtsql libqt5sql5-sqlite
----
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to use the
newer QtWebEngine backend.
To do so, install `python3-pyqt5.qtwebengine` and `python3-pyqt5.qtopengl`, then
start qutebrowser with `--backend webengine`.
Get the qutebrowser package from the
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
@ -40,42 +52,13 @@ Build it from git
Install the dependencies via apt-get:
[NOTE]
==========================
On Debian, it's recommended to install the Qt packages from the
https://wiki.debian.org/DebianExperimental[experimental] repository as those
are a much newer version of Qt which is more stable.
Add the following line to your `/etc/apt/sources.list`:
----
deb http://ftp.debian.org/debian experimental main
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev python3-pyqt5.qtsql libqt5sql5-sqlite
----
Then install the packages like this:
----
# apt-get update
# apt-get install -t experimental python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-dev
# apt-get install python-tox
----
It's also recommended to pin those packages to receive updates by creating a
file `/etc/apt/preferences.d/qutebrowser` with the following contents:
----
Package: python3-pyqt5* libqt5*
Pin: release a=experimental
Pin-Priority: 800
----
==========================
For distributions other than Debian or if you prefer to not use the
experimental repo:
----
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev
----
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to install
`python3-pyqt5.qtwebengine` and start qutebrowser with `--backend webengine` in
order to use the new backend.
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
@ -102,6 +85,9 @@ qutebrowser is available in the official repositories for Fedora 22 and newer.
# dnf install qutebrowser
----
It's also recommended to install `qt5-qtwebengine` and start with `--backend
webengine` to use the new backend.
On Archlinux
------------
@ -111,6 +97,10 @@ qutebrowser is available in the official [community] repository.
# pacman -S qutebrowser
----
Archlinux packages an updated `qt5-webkit` package by default. If you want to
use the QtWebEngine backend instead, install `qt5-webengine` and start with
`--backend webengine`.
There is also a -git version available in the AUR:
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git].
@ -175,6 +165,10 @@ Make sure you have `python3_4` in your `PYTHON_TARGETS`
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary.
It's also recommended to install QtWebKit-NG via
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild],
or install Qt >= 5.7.1 with QtWebEngine in order to use an up-to-date backend.
If video or sound don't seem to work, try installing the gstreamer plugins:
----
@ -192,6 +186,10 @@ with:
# xbps-install qutebrowser
----
It's currently recommended to install `python3-PyQt5-webengine` and
`python3-PyQt5-opengl`, then start with `--backend webengine` to use the new
backend.
On NixOS
--------
@ -202,6 +200,9 @@ it with:
$ nix-env -i qutebrowser
----
It's recommended to install `qt5.qtwebengine` and start with
`--backend webengine` to use the new backend.
On openSUSE
-----------
@ -222,19 +223,19 @@ On OpenBSD
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports].
Manual install:
Install the package:
----
# pkg_add qutebrowser
----
Or alternatively, use the ports system :
----
# cd /usr/ports/www/qutebrowser
# make install
----
Or alternatively if you're using `-current` (or OpenBSD 6.1 once it's been released):
----
# pkg_add qutebrowser
----
On Windows
----------
@ -243,7 +244,7 @@ There are different ways to install qutebrowser on Windows:
Prebuilt binaries
~~~~~~~~~~~~~~~~~
Prebuilt standalone packages and MSI installers
Prebuilt standalone packages and installers
https://github.com/qutebrowser/qutebrowser/releases[are built] for every
release.
@ -276,13 +277,13 @@ $ pip install tox
Then <<tox,install qutebrowser via tox>>.
On OS X
-------
On macOS
--------
Prebuilt binary
~~~~~~~~~~~~~~~
The easiest way to install qutebrowser on OS X is to use the prebuilt `.app`
The easiest way to install qutebrowser on macOS is to use the prebuilt `.app`
files from the
https://github.com/qutebrowser/qutebrowser/releases[release page].
@ -344,17 +345,23 @@ Then run tox inside the qutebrowser repository to set up a
https://docs.python.org/3/library/venv.html[virtual environment]:
----
$ tox -e mkvenv
$ tox -e mkvenv-pypi
----
On Windows, run tox with the 'mkvenv-win' option, however make sure that ONLY Python3 is in your PATH before running tox.
If your distribution uses OpenSSL 1.1 (like Debian Stretch or Archlinux), you'll
need to set `LD_LIBRARY_PATH` to the OpenSSL 1.0 directory
(`export LD_LIBRARY_PATH=/usr/lib/openssl-1.0` on Archlinux) before starting
qutebrowser.
----
$ tox -e mkvenv-win
----
Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
local Qt install instead of installing PyQt in the virtualenv. However, unless
you have QtWebKit-NG or QtWebEngine available, qutebrowser will use the legacy
QtWebKit backend.
This installs all needed Python dependencies in a `.venv` subfolder. The
system-wide Qt5/PyQt5 installations are symlinked into the virtual environment.
On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
Python3 is in your PATH before running tox.
This installs all needed Python dependencies in a `.venv` subfolder.
You can then create a simple wrapper script to start qutebrowser somewhere in
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
@ -364,14 +371,6 @@ your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`):
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser "$@"
----
If you are developing on qutebrowser, you may want to redirect it to a local
config:
----
#!/bin/bash
~/path/to/qutebrowser/.venv/bin/python3 -m qutebrowser -c .qutebrowser-local "$@"
----
Updating
~~~~~~~~
@ -382,5 +381,5 @@ virtualenv. Thus it's recommended to run the following command to recreate the
virtualenv:
----
$ tox -r -e mkvenv
$ tox -r -e mkvenv-pypi
----

View File

@ -8,7 +8,7 @@ graft icons
graft doc/img
graft misc/apparmor
graft misc/userscripts
recursive-include scripts *.py
recursive-include scripts *.py *.sh
include qutebrowser/utils/testfile
include qutebrowser/git-commit-id
include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc
@ -30,20 +30,15 @@ prune tests
prune qutebrowser/3rdparty
prune misc/requirements
prune misc/docker
exclude .editorconfig
exclude pytest.ini
exclude qutebrowser.rcc
exclude .coveragerc
exclude .pylintrc
exclude qutebrowser/javascript/.eslintrc.yaml
exclude qutebrowser/javascript/.eslintignore
exclude doc/help
exclude .appveyor.yml
exclude .travis.yml
exclude .*
exclude codecov.yml
exclude .pydocstylerc
exclude misc/appveyor_install.py
exclude misc/qutebrowser.spec
exclude .flake8
exclude misc/qutebrowser.nsi
global-exclude __pycache__ *.pyc *.pyo

View File

@ -36,11 +36,8 @@ Downloads
---------
See the https://github.com/qutebrowser/qutebrowser/releases[github releases
page] for available downloads (currently a source archive, and standalone
packages as well as MSI installers for Windows).
See link:INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
qutebrowser running for various platforms.
page] for available downloads and the link:INSTALL.asciidoc[INSTALL] file for
detailed instructions on how to get qutebrowser running on various platforms.
Documentation
-------------
@ -71,7 +68,11 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
mailto:qutebrowser@lists.qutebrowser.org[].
There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
at mailto:qutebrowser-announce@lists.qutebrowser.org[].
at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also
get sent to the general qutebrowser@ list).
If you're a reddit user, there's a
https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there.
Contributions / Bugs
--------------------
@ -97,26 +98,36 @@ Requirements
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.4 or newer
* http://qt.io/[Qt] 5.2.0 or newer (5.5.1 recommended)
* QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG) or QtWebEngine
* http://www.python.org/[Python] 3.4 or newer (3.6 recommended) - note that
support for Python 3.4
https://github.com/qutebrowser/qutebrowser/issues/2742[will be dropped soon].
* http://qt.io/[Qt] 5.2.0 or newer (5.9 recommended - note that support for Qt
< 5.7.1 will be dropped soon) with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
- QtWebEngine, or
- QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG).
Note that support for legacy QtWebKit (before 5.212) will be
dropped soon.
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
(5.5.1 recommended) for Python 3
(5.9 recommended) for Python 3. Note that support for PyQt < 5.7 will be
dropped soon.
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments]
* http://pyyaml.org/wiki/PyYAML[PyYAML]
* http://pyopengl.sourceforge.net/[PyOpenGL] when using QtWebEngine
The following libraries are optional and provide a better user experience:
The following libraries are optional:
* http://cthedot.de/cssutils/[cssutils]
To generate the documentation for the `:help` command, when using the git
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed.
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to
display colored log output.
* http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml`
with QtWebKit)
* On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
output.
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
command, when using the git repository (rather than a release).
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies.
@ -140,209 +151,59 @@ get in touch!
Authors
-------
Contributors, sorted by the number of commits in descending order:
qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser
wouldn't be what it is without the help of
https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]!
// QUTE_AUTHORS_START
* Florian Bruhin
* Daniel Schadt
* Ryan Roden-Corrent
* Jan Verbeek
* Jakub Klinkovský
* Antoni Boucher
* Lamar Pavel
* Marshall Lochbaum
* Bruno Oliveira
* Alexander Cogneau
* Felix Van der Jeugt
* Daniel Karbach
* Imran Sobir
* Martin Tournoij
* Kevin Velghe
* Raphael Pierzina
* Joel Torstensson
* Patric Schmitz
* Tarcisio Fedrizzi
* Claude
* Corentin Julé
* meles5
* Philipp Hansch
* Panagiotis Ktistakis
* Artur Shaik
* Nathan Isom
* Thorsten Wißmann
* Austin Anderson
* Fritz Reichwald
* Jimmy
* Niklas Haas
* Maciej Wołczyk
* Spreadyy
* Alexey "Averrin" Nabrodov
* pkill9
* nanjekyejoannah
* avk
* ZDarian
* Milan Svoboda
* John ShaggyTwoDope Jenkins
* Clayton Craft
* Peter Vilim
* knaggita
* Oliver Caldwell
* Julian Weigt
* Tomasz Kramkowski
* Sebastian Frysztak
* Nikolay Amiantov
* Julie Engel
* Jonas Schürmann
* error800
* Michael Hoang
* Liam BEGUIN
* Daniel Fiser
* skinnay
* Zach-Button
* Samuel Walladge
* Peter Rice
* Ismail S
* Halfwit
* David Vogt
* Claire Cavanaugh
* rikn00
* kanikaa1234
* haitaka
* Nick Ginther
* Michał Góral
* Michael Ilsaas
* Martin Zimmermann
* Jussi Timperi
* Cosmin Popescu
* Brian Jackson
* thuck
* sbinix
* rsteube
* neeasade
* jnphilipp
* Yannis Rohloff
* Tobias Patzl
* Stefan Tatschner
* Samuel Loury
* Peter Michely
* Panashe M. Fundira
* Lucas Hoffmann
* Link
* Larry Hynes
* Kirill A. Shutemov
* Johannes Altmanninger
* Jeremy Kaplan
* Ismail
* Edgar Hipp
* Daryl Finlay
* arza
* adam
* Samir Benmendil
* Regina Hug
* Mathias Fussenegger
* Marcelo Santos
* Joel Bradshaw
* Jean-Louis Fuchs
* Franz Fellner
* Eric Drechsel
* zwarag
* xd1le
* rmortens
* oniondreams
* issue
* haxwithaxe
* evan
* dylan araps
* caveman
* addictedtoflames
* Xitian9
* Vasilij Schneidermann
* Tomas Orsava
* Tom Janson
* Tobias Werth
* Tim Harder
* Thiago Barroso Perrotta
* Sorokin Alexei
* Simon Désaulniers
* Rok Mandeljc
* Noah Huesser
* Moez Bouhlel
* Matthias Lisin
* Marcel Schilling
* Lazlow Carmichael
* Kevin Wang
* Ján Kobezda
* Johannes Martinsson
* Jean-Christophe Petkovich
* Jay Kamat
* Helen Sherwood-Taylor
* HalosGhost
* Gregor Pohl
* Eivind Uggedal
* Dietrich Daroch
* Derek Sivers
* Daniel Lu
* Arseniy Seroka
* Andy Balaam
* Andreas Fischer
* Akselmo
// QUTE_AUTHORS_END
The following people have contributed graphics:
Additionally, the following people have contributed graphics:
* Jad/link:http://yelostudio.com[yelo] (new icon)
* WOFall (original icon)
* regines (key binding cheatsheet)
Thanks / Similar projects
-------------------------
Also, thanks to everyone who contributed to one of qutebrowser's
link:doc/backers.asciidoc[crowdfunding campaigns]!
Many projects with a similar goal as qutebrowser exist:
* http://portix.bitbucket.org/dwb/[dwb] (C, GTK+ with WebKit1, currently
http://www.reddit.com/r/linux/comments/2huqbc/dwb_abandoned/[unmaintained] -
main inspiration for qutebrowser)
* https://github.com/fanglingsu/vimb[vimb] (C, GTK+ with WebKit1, active)
* http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
WebKit1, dead)
* http://surf.suckless.org/[surf] (C, GTK+ with WebKit1, active)
* https://mason-larobina.github.io/luakit/[luakit] (C/Lua, GTK+ with
WebKit1, not very active)
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1, not very
active)
* http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2, active)
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko,
active)
* https://github.com/AeroNotix/lispkit[lispkit] (quite new, lisp, GTK+ with
WebKit, active)
* http://www.vimperator.org/[Vimperator] (Firefox addon)
* http://5digits.org/pentadactyl/[Pentadactyl] (Firefox addon)
* https://github.com/akhodakivskiy/VimFx[VimFx] (Firefox addon)
* https://github.com/1995eaton/chromium-vim[cVim] (Chrome/Chromium addon)
* http://vimium.github.io/[vimium] (Chrome/Chromium addon)
* https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome] (Chrome/Chromium addon)
* https://github.com/jinzhu/vrome[Vrome] (Chrome/Chromium addon)
Similar projects
----------------
Many projects with a similar goal as qutebrowser exist.
Most of them were inspirations for qutebrowser in some way, thanks for that!
Thanks as well to the following projects and people for helping me with
problems and helpful hints:
Active
~~~~~~
* http://eric-ide.python-projects.org/[eric5] / Detlev Offenbach
* https://code.google.com/p/devicenzo/[devicenzo]
* portix
* seir
* nitroxleecher
* 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)
* Chrome/Chromium addons:
https://github.com/1995eaton/chromium-vim[cVim],
http://vimium.github.io/[Vimium],
https://github.com/brookhong/Surfingkeys[Surfingkeys],
http://saka-key.lusakasa.com/[Saka Key]
* Firefox addons (based on WebExtensions):
https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental),
http://saka-key.lusakasa.com/[Saka Key]
Also, thanks to:
Inactive
~~~~~~~~
* Everyone contributing to the link:doc/backers.asciidoc[crowdfunding].
* Everyone who had the patience to test qutebrowser before v0.1.
* Everyone triaging/fixing my bugs in the
https://bugreports.qt.io/secure/Dashboard.jspa[Qt bugtracker]
* Everyone answering my questions on http://stackoverflow.com/[Stack Overflow]
and in IRC.
* All the projects which were a great help while developing qutebrowser.
* 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
WebKit1)
* http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
* http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko)
* Firefox addons (not based on WebExtensions or no recent activity):
http://www.vimperator.org/[Vimperator],
http://5digits.org/pentadactyl/[Pentadactyl],
https://github.com/akhodakivskiy/VimFx[VimFx],
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]
License
-------

View File

@ -1,9 +1,7 @@
status:
project:
enabled: no
patch:
enabled: no
changes:
enabled: no
coverage:
status:
project: off
patch: off
changes: off
comment: off

View File

@ -1,13 +1,135 @@
Crowdfunding backers
====================
2017
----
Mid-2017, qutebrowser had its
https://www.kickstarter.com/projects/the-compiler/qutebrowser-v10-with-per-domain-settings[second crowdfunding]
with the goal of implementing the new config system and releasing v1.0.
Thanks a lot to the following people who contributed to it:
Gold sponsors
~~~~~~~~~~~~~
TODO
Silver sponsors
~~~~~~~~~~~~~~~
TODO
Other sponsors
~~~~~~~~~~~~~~
TODO: people with t-shirts or higher pledge levels
- 7scan
- Alex Suykov
- Alexey Zhikhartsev
- Allan Nordhøy
- Anirudh Sanjeev
- Anssi Puustinen
- Benedikt Steindorf
- Bernardo Kuri
- Blaise Duszynski
- Bostan
- Bruno Oliveira
- Colin Jacobs
- Daniel Andersson
- Danilo
- David Beley
- David Hollings
- David Parrish
- Derin Yarsuvat
- Dmytro Kostiuchenko
- Frederik Thorøe
- G4v4g4i
- Gyula Teleki
- H
- Hosaka
- Iordanis Grigoriou
- Isaac Sandaljian
- Jakub Podeszwik
- Jamie Anderson
- Jasper Woudenberg
- Jens Højgaard
- Johannes
- John Baber-Lucero
- Jonas Schürmann
- Kenichiro Ito
- Kenny Low
- Lars Ivar Igesund
- Lucas Aride Moulin
- Ludovic Chabant
- Lukas Gierth
- Marulkan
- Matthew Chun-Lum
- Matthew Cronen
- Matthew Quigley
- Michael Schönwälder
- Mika Kutila
- Mitchell Stokes
- Nathan Howell
- Nathan Schlehlein
- Noël Zindel
- Obri
- Patrik Peng
- Peter DiMarco
- Peter Rice
- Philipp Middendorf
- Pkill9
- Prescott
- Robotichead
- Roshless
- Ryan Ellis
- Ryan P Deslandes
- Sam Doshi
- Sam Stone
- Sean Herman
- Sebastian Frysztak
- Shelby Cruver
- SirCmpwn
- Soham Pal
- Stewart Webb
- Sven Reinecke
- Tom Bass
- Tomas Slusny
- Tomasz Kramkowski
- Tommy Thomas
- Vasilij Schneidermann
- Vlaaaaaaad
- beanieuptop
- demure
- evenorbert
- fishss
- gsnewmark
- guillermohs9
- hubcaps
- lobachevsky
- neodarz
- nihlaeth
- notbenh
- patrick suwanvithaya
- pyratebeard
- randm_dave
- sabreman
- toml
- vimja
- wiz
- 43 Anonymous
2016
----
Mid-2016, qutebrowser did run a http://igg.me/at/qutebrowser[crowdfunding] for
QtWebEngine support in qutebrowser.
Thanks a lot to the following people who contributed to it:
Gold sponsors
-------------
~~~~~~~~~~~~~
- Chris Salzberg
- Clayton Craft
@ -16,7 +138,7 @@ Gold sponsors
- 1 Anonymous
Day sponsors
------------
~~~~~~~~~~~~
- Agent 42
- Iggy Jackson
@ -28,7 +150,7 @@ Day sponsors
- 4 Anonymous
Other sponsors
--------------
~~~~~~~~~~~~~~
- AP M
- Alessandro Balzano

View File

@ -1,5 +1,5 @@
// DO NOT EDIT THIS FILE DIRECTLY!
// It is autogenerated from docstrings by running:
// It is autogenerated by running:
// $ python3 scripts/dev/src2asciidoc.py
= Commands
@ -82,9 +82,10 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|<<tab-move,tab-move>>|Move the current tab according to the argument and [count].
|<<tab-next,tab-next>>|Switch to the next tab, or switch [count] tabs forward.
|<<tab-only,tab-only>>|Close all tabs except for the current one.
|<<tab-pin,tab-pin>>|Pin/Unpin the current/[count]th tab.
|<<tab-prev,tab-prev>>|Switch to the previous tab, or switch [count] tabs back.
|<<unbind,unbind>>|Unbind a keychain.
|<<undo,undo>>|Re-open a closed tab (optionally skipping [count] closed tabs).
|<<undo,undo>>|Re-open a closed tab.
|<<view-source,view-source>>|Show the source of the current page in a new tab.
|<<window-only,window-only>>|Close all windows except for the current one.
|<<wq,wq>>|Save open pages and quit.
@ -544,7 +545,8 @@ For `increment` and `decrement`, the number to change the URL by. For `up`, the
[[open]]
=== open
Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] [*--secure*] ['url']+
Syntax: +:open [*--implicit*] [*--bg*] [*--tab*] [*--window*] [*--secure*] [*--private*]
['url']+
Open a URL in the current/[count]th tab.
@ -560,6 +562,7 @@ If the URL contains newlines, each line gets opened in its own tab.
* +*-t*+, +*--tab*+: Open in a new tab.
* +*-w*+, +*--window*+: Open in a new window.
* +*-s*+, +*--secure*+: Force HTTPS.
* +*-p*+, +*--private*+: Open a new window in private browsing mode.
==== count
The tab index to open the URL in.
@ -743,6 +746,7 @@ Load a session.
[[session-save]]
=== session-save
Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*]
[*--with-private*]
['name']+
Save a session.
@ -756,6 +760,7 @@ Save a session.
* +*-q*+, +*--quiet*+: Don't show confirmation message.
* +*-f*+, +*--force*+: Force saving internal sessions (starting with an underline).
* +*-o*+, +*--only-active-window*+: Saves only tabs of the currently active window.
* +*-p*+, +*--with-private*+: Include private windows.
[[set]]
=== set
@ -831,7 +836,7 @@ Duplicate the current tab.
[[tab-close]]
=== tab-close
Syntax: +:tab-close [*--prev*] [*--next*] [*--opposite*]+
Syntax: +:tab-close [*--prev*] [*--next*] [*--opposite*] [*--force*]+
Close the current/[count]th tab.
@ -840,6 +845,7 @@ Close the current/[count]th tab.
* +*-n*+, +*--next*+: Force selecting the tab after the current tab.
* +*-o*+, +*--opposite*+: Force selecting the tab in the opposite direction of what's configured in 'tabs->select-on-remove'.
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
==== count
The tab index to close
@ -892,13 +898,23 @@ How many tabs to switch forward.
[[tab-only]]
=== tab-only
Syntax: +:tab-only [*--prev*] [*--next*]+
Syntax: +:tab-only [*--prev*] [*--next*] [*--force*]+
Close all tabs except for the current one.
==== optional arguments
* +*-p*+, +*--prev*+: Keep tabs before the current.
* +*-n*+, +*--next*+: Keep tabs after the current.
* +*-f*+, +*--force*+: Avoid confirmation for pinned tabs.
[[tab-pin]]
=== tab-pin
Pin/Unpin the current/[count]th tab.
Pinning a tab shrinks it to tabs->pinned-width size. Attempting to close a pinned tab will cause a confirmation, unless --force is passed.
==== count
The tab index to pin or unpin
[[tab-prev]]
=== tab-prev
@ -920,7 +936,7 @@ Unbind a keychain.
[[undo]]
=== undo
Re-open a closed tab (optionally skipping [count] closed tabs).
Re-open a closed tab.
[[view-source]]
=== view-source
@ -1537,6 +1553,7 @@ These commands are mainly intended for debugging. They are hidden if qutebrowser
|<<debug-clear-ssl-errors,debug-clear-ssl-errors>>|Clear remembered SSL error answers.
|<<debug-console,debug-console>>|Show the debugging console.
|<<debug-crash,debug-crash>>|Crash for debugging purposes.
|<<debug-dump-history,debug-dump-history>>|Dump the history to a file in the old pre-SQL format.
|<<debug-dump-page,debug-dump-page>>|Dump the current page's content to a file.
|<<debug-log-capacity,debug-log-capacity>>|Change the number of log lines to be stored in RAM.
|<<debug-log-filter,debug-log-filter>>|Change the log filter for console logging.
@ -1571,6 +1588,15 @@ Crash for debugging purposes.
==== positional arguments
* +'typ'+: either 'exception' or 'segfault'.
[[debug-dump-history]]
=== debug-dump-history
Syntax: +:debug-dump-history 'dest'+
Dump the history to a file in the old pre-SQL format.
==== positional arguments
* +'dest'+: Where to write the file to.
[[debug-dump-page]]
=== debug-dump-page
Syntax: +:debug-dump-page [*--plain*] 'dest'+
@ -1599,7 +1625,8 @@ Syntax: +:debug-log-filter 'filters'+
Change the log filter for console logging.
==== positional arguments
* +'filters'+: A comma separated list of logger names.
* +'filters'+: A comma separated list of logger names. Can also be "none" to clear any existing filters.
[[debug-log-level]]
=== debug-log-level
@ -1654,7 +1681,7 @@ Syntax: +:debug-webaction 'action'+
Execute a webaction.
See http://doc.qt.io/qt-5/qwebpage.html#WebAction-enum for the available actions.
Available actions: http://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit) http://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine)
==== positional arguments
* +'action'+: The action to execute, e.g. MoveToNextChar.

View File

@ -1,5 +1,5 @@
// DO NOT EDIT THIS FILE DIRECTLY!
// It is autogenerated from docstrings by running:
// It is autogenerated by running:
// $ python3 scripts/dev/src2asciidoc.py
= Settings
@ -18,11 +18,10 @@
|<<general-auto-save-interval,auto-save-interval>>|How often (in milliseconds) to auto-save config/cookies/etc.
|<<general-editor,editor>>|The editor (and arguments) to use for the `open-editor` command.
|<<general-editor-encoding,editor-encoding>>|Encoding to use for editor.
|<<general-private-browsing,private-browsing>>|Do not record visited pages in the history or store web page icons.
|<<general-private-browsing,private-browsing>>|Open new windows in private browsing mode which does not record visited pages.
|<<general-developer-extras,developer-extras>>|Enable extra tools for Web developers.
|<<general-print-element-backgrounds,print-element-backgrounds>>|Whether the background color and images are also drawn when the page is printed.
|<<general-xss-auditing,xss-auditing>>|Whether load requests should be monitored for cross-site scripting attempts.
|<<general-site-specific-quirks,site-specific-quirks>>|Enable QtWebKit workarounds for broken sites.
|<<general-default-encoding,default-encoding>>|Default encoding to use for websites.
|<<general-new-instance-open-target,new-instance-open-target>>|How to open links in an existing instance if a new one is launched.
|<<general-new-instance-open-target.window,new-instance-open-target.window>>|Which window to choose when opening links as new tabs.
@ -48,7 +47,6 @@
|<<ui-frame-flattening,frame-flattening>>|Whether to expand each subframe to its contents.
|<<ui-user-stylesheet,user-stylesheet>>|User stylesheet to use (absolute filename or filename relative to the config directory). Will expand environment variables.
|<<ui-hide-scrollbar,hide-scrollbar>>|Hide the main scrollbar.
|<<ui-css-media-type,css-media-type>>|Set the CSS media type.
|<<ui-smooth-scrolling,smooth-scrolling>>|Whether to enable smooth scrolling for web pages. Note smooth scrolling does not work with the :scroll-px command.
|<<ui-remove-finished-downloads,remove-finished-downloads>>|Number of milliseconds to wait before removing finished downloads. Will not be removed if value is -1.
|<<ui-hide-statusbar,hide-statusbar>>|Whether to hide the statusbar unless a message is shown.
@ -126,10 +124,13 @@
|<<tabs-close-mouse-button,close-mouse-button>>|On which mouse button to close tabs.
|<<tabs-position,position>>|The position of the tab bar.
|<<tabs-show-favicons,show-favicons>>|Whether to show favicons in the tab bar.
|<<tabs-favicon-scale,favicon-scale>>|Scale for favicons in the tab bar. The tab size is unchanged, so big favicons also require extra `tabs->padding`.
|<<tabs-width,width>>|The width of the tab bar if it's vertical, in px or as percentage of the window.
|<<tabs-pinned-width,pinned-width>>|The width for pinned tabs with a horizontal tabbar, in px.
|<<tabs-indicator-width,indicator-width>>|Width of the progress indicator (0 to disable).
|<<tabs-tabs-are-windows,tabs-are-windows>>|Whether to open windows instead of tabs.
|<<tabs-title-format,title-format>>|The format to use for the tab title. The following placeholders are defined:
|<<tabs-title-format-pinned,title-format-pinned>>|The format to use for the tab title for pinned tabs. The same placeholders like for title-format are defined.
|<<tabs-title-alignment,title-alignment>>|Alignment of the text inside of tabs
|<<tabs-mousewheel-tab-switching,mousewheel-tab-switching>>|Switch between tabs using the mouse wheel.
|<<tabs-padding,padding>>|Padding for tabs (top, bottom, left, right).
@ -144,12 +145,8 @@
|<<storage-prompt-download-directory,prompt-download-directory>>|Whether to prompt the user for the download location.
|<<storage-remember-download-directory,remember-download-directory>>|Whether to remember the last used download directory.
|<<storage-maximum-pages-in-cache,maximum-pages-in-cache>>|The maximum number of pages to hold in the global memory page cache.
|<<storage-object-cache-capacities,object-cache-capacities>>|The capacities for the global memory cache for dead objects such as stylesheets or scripts. Syntax: cacheMinDeadCapacity, cacheMaxDead, totalCapacity.
|<<storage-offline-storage-default-quota,offline-storage-default-quota>>|Default quota for new offline storage databases.
|<<storage-offline-web-application-cache-quota,offline-web-application-cache-quota>>|Quota for the offline web application cache.
|<<storage-offline-storage-database,offline-storage-database>>|Whether support for the HTML 5 offline storage feature is enabled.
|<<storage-offline-web-application-storage,offline-web-application-storage>>|Whether support for the HTML 5 web application cache feature is enabled.
|<<storage-local-storage,local-storage>>|Whether support for the HTML 5 local storage feature is enabled.
|<<storage-offline-web-application-cache,offline-web-application-cache>>|Whether support for the HTML 5 web application cache feature is enabled.
|<<storage-local-storage,local-storage>>|Whether support for HTML 5 local storage and Web SQL is enabled.
|<<storage-cache-size,cache-size>>|Size of the HTTP network cache. Empty to use the default value.
|==============
@ -161,7 +158,6 @@
|<<content-allow-javascript,allow-javascript>>|Enables or disables the running of JavaScript programs.
|<<content-allow-plugins,allow-plugins>>|Enables or disables plugins in Web pages.
|<<content-webgl,webgl>>|Enables or disables WebGL.
|<<content-css-regions,css-regions>>|Enable or disable support for CSS regions.
|<<content-hyperlink-auditing,hyperlink-auditing>>|Enable or disable hyperlink auditing (<a ping>).
|<<content-geolocation,geolocation>>|Allow websites to request geolocations.
|<<content-notifications,notifications>>|Allow websites to show notifications.
@ -174,7 +170,7 @@
|<<content-local-content-can-access-remote-urls,local-content-can-access-remote-urls>>|Whether locally loaded documents are allowed to access remote urls.
|<<content-local-content-can-access-file-urls,local-content-can-access-file-urls>>|Whether locally loaded documents are allowed to access other local urls.
|<<content-cookies-accept,cookies-accept>>|Control which cookies to accept.
|<<content-cookies-store,cookies-store>>|Whether to store cookies. Note this option needs a restart with QtWebEngine.
|<<content-cookies-store,cookies-store>>|Whether to store cookies. Note this option needs a restart with QtWebEngine on Qt < 5.9.
|<<content-host-block-lists,host-block-lists>>|List of URLs of lists which contain hosts to block.
|<<content-host-blocking-enabled,host-blocking-enabled>>|Whether host blocking is enabled.
|<<content-host-blocking-whitelist,host-blocking-whitelist>>|List of domains that should always be loaded, despite being ad-blocked.
@ -220,10 +216,14 @@
|<<colors-completion.scrollbar.bg,completion.scrollbar.bg>>|Color of the scrollbar in completion view
|<<colors-statusbar.fg,statusbar.fg>>|Foreground color of the statusbar.
|<<colors-statusbar.bg,statusbar.bg>>|Background color of the statusbar.
|<<colors-statusbar.fg.private,statusbar.fg.private>>|Foreground color of the statusbar in private browsing mode.
|<<colors-statusbar.bg.private,statusbar.bg.private>>|Background color of the statusbar in private browsing mode.
|<<colors-statusbar.fg.insert,statusbar.fg.insert>>|Foreground color of the statusbar in insert mode.
|<<colors-statusbar.bg.insert,statusbar.bg.insert>>|Background color of the statusbar in insert mode.
|<<colors-statusbar.fg.command,statusbar.fg.command>>|Foreground color of the statusbar in command mode.
|<<colors-statusbar.bg.command,statusbar.bg.command>>|Background color of the statusbar in command mode.
|<<colors-statusbar.fg.command.private,statusbar.fg.command.private>>|Foreground color of the statusbar in private browsing + command mode.
|<<colors-statusbar.bg.command.private,statusbar.bg.command.private>>|Background color of the statusbar in private browsing + command mode.
|<<colors-statusbar.fg.caret,statusbar.fg.caret>>|Foreground color of the statusbar in caret mode.
|<<colors-statusbar.bg.caret,statusbar.bg.caret>>|Background color of the statusbar in caret mode.
|<<colors-statusbar.fg.caret-selection,statusbar.fg.caret-selection>>|Foreground color of the statusbar in caret mode with a selection
@ -394,7 +394,7 @@ Default: +pass:[utf-8]+
[[general-private-browsing]]
=== private-browsing
Do not record visited pages in the history or store web page icons.
Open new windows in private browsing mode which does not record visited pages.
Valid values:
@ -403,8 +403,6 @@ Valid values:
Default: +pass:[false]+
This setting is only available with the QtWebKit backend.
[[general-developer-extras]]
=== developer-extras
Enable extra tools for Web developers.
@ -445,26 +443,13 @@ Valid values:
Default: +pass:[false]+
[[general-site-specific-quirks]]
=== site-specific-quirks
Enable QtWebKit workarounds for broken sites.
Valid values:
* +true+
* +false+
Default: +pass:[true]+
This setting is only available with the QtWebKit backend.
[[general-default-encoding]]
=== default-encoding
Default encoding to use for websites.
The encoding must be a string describing an encoding such as _utf-8_, _iso-8859-1_, etc. If left empty a default value will be used.
The encoding must be a string describing an encoding such as _utf-8_, _iso-8859-1_, etc.
Default: empty
Default: +pass:[iso-8859-1]+
[[general-new-instance-open-target]]
=== new-instance-open-target
@ -581,6 +566,7 @@ Default: +pass:[bottom]+
[[ui-message-timeout]]
=== message-timeout
Time (in ms) to show messages in the statusbar for.
Set to 0 to never clear messages.
Default: +pass:[2000]+
@ -653,14 +639,6 @@ Valid values:
Default: +pass:[true]+
[[ui-css-media-type]]
=== css-media-type
Set the CSS media type.
Default: empty
This setting is only available with the QtWebKit backend.
[[ui-smooth-scrolling]]
=== smooth-scrolling
Whether to enable smooth scrolling for web pages. Note smooth scrolling does not work with the :scroll-px command.
@ -707,6 +685,8 @@ The format to use for the window title. The following placeholders are defined:
* `{scroll_pos}`: The page scroll position.
* `{host}`: The host of the current web page.
* `{backend}`: Either 'webkit' or 'webengine'
* `{private}` : Indicates when private mode is enabled.
Default: +pass:[{perc}{title}{title_sep}qutebrowser]+
@ -809,8 +789,6 @@ The proxy to use.
In addition to the listed values, you can use a `socks://...` or `http://...` URL.
This setting only works with Qt 5.8 or newer when using the QtWebEngine backend.
Valid values:
* +system+: Use the system wide proxy.
@ -921,7 +899,7 @@ How many URLs to show in the web history.
0: no history / -1: unlimited
Default: +pass:[1000]+
Default: +pass:[-1]+
[[completion-quick-complete]]
=== quick-complete
@ -1205,12 +1183,24 @@ Valid values:
Default: +pass:[true]+
[[tabs-favicon-scale]]
=== favicon-scale
Scale for favicons in the tab bar. The tab size is unchanged, so big favicons also require extra `tabs->padding`.
Default: +pass:[1.0]+
[[tabs-width]]
=== width
The width of the tab bar if it's vertical, in px or as percentage of the window.
Default: +pass:[20%]+
[[tabs-pinned-width]]
=== pinned-width
The width for pinned tabs with a horizontal tabbar, in px.
Default: +pass:[43]+
[[tabs-indicator-width]]
=== indicator-width
Width of the progress indicator (0 to disable).
@ -1241,9 +1231,17 @@ The format to use for the tab title. The following placeholders are defined:
* `{scroll_pos}`: The page scroll position.
* `{host}`: The host of the current web page.
* `{backend}`: Either 'webkit' or 'webengine'
* `{private}` : Indicates when private mode is enabled.
Default: +pass:[{index}: {title}]+
[[tabs-title-format-pinned]]
=== title-format-pinned
The format to use for the tab title for pinned tabs. The same placeholders like for title-format are defined.
Default: +pass:[{index}]+
[[tabs-title-alignment]]
=== title-alignment
Alignment of the text inside of tabs
@ -1319,55 +1317,12 @@ The Page Cache allows for a nicer user experience when navigating forth or back
For more information about the feature, please refer to: http://webkit.org/blog/427/webkit-page-cache-i-the-basics/
Default: empty
Default: +pass:[0]+
This setting is only available with the QtWebKit backend.
[[storage-object-cache-capacities]]
=== object-cache-capacities
The capacities for the global memory cache for dead objects such as stylesheets or scripts. Syntax: cacheMinDeadCapacity, cacheMaxDead, totalCapacity.
The _cacheMinDeadCapacity_ specifies the minimum number of bytes that dead objects should consume when the cache is under pressure.
_cacheMaxDead_ is the maximum number of bytes that dead objects should consume when the cache is *not* under pressure.
_totalCapacity_ specifies the maximum number of bytes that the cache should consume *overall*.
Default: empty
This setting is only available with the QtWebKit backend.
[[storage-offline-storage-default-quota]]
=== offline-storage-default-quota
Default quota for new offline storage databases.
Default: empty
This setting is only available with the QtWebKit backend.
[[storage-offline-web-application-cache-quota]]
=== offline-web-application-cache-quota
Quota for the offline web application cache.
Default: empty
This setting is only available with the QtWebKit backend.
[[storage-offline-storage-database]]
=== offline-storage-database
Whether support for the HTML 5 offline storage feature is enabled.
Valid values:
* +true+
* +false+
Default: +pass:[true]+
This setting is only available with the QtWebKit backend.
[[storage-offline-web-application-storage]]
=== offline-web-application-storage
[[storage-offline-web-application-cache]]
=== offline-web-application-cache
Whether support for the HTML 5 web application cache feature is enabled.
An application cache acts like an HTTP cache in some sense. For documents that use the application cache via JavaScript, the loader engine will first ask the application cache for the contents, before hitting the network.
@ -1385,7 +1340,7 @@ This setting is only available with the QtWebKit backend.
[[storage-local-storage]]
=== local-storage
Whether support for the HTML 5 local storage feature is enabled.
Whether support for HTML 5 local storage and Web SQL is enabled.
Valid values:
@ -1449,19 +1404,6 @@ Valid values:
Default: +pass:[true]+
[[content-css-regions]]
=== css-regions
Enable or disable support for CSS regions.
Valid values:
* +true+
* +false+
Default: +pass:[true]+
This setting is only available with the QtWebKit backend.
[[content-hyperlink-auditing]]
=== hyperlink-auditing
Enable or disable hyperlink auditing (<a ping>).
@ -1608,7 +1550,7 @@ This setting is only available with the QtWebKit backend.
[[content-cookies-store]]
=== cookies-store
Whether to store cookies. Note this option needs a restart with QtWebEngine.
Whether to store cookies. Note this option needs a restart with QtWebEngine on Qt < 5.9.
Valid values:
@ -1898,6 +1840,18 @@ Background color of the statusbar.
Default: +pass:[black]+
[[colors-statusbar.fg.private]]
=== statusbar.fg.private
Foreground color of the statusbar in private browsing mode.
Default: +pass:[${statusbar.fg}]+
[[colors-statusbar.bg.private]]
=== statusbar.bg.private
Background color of the statusbar in private browsing mode.
Default: +pass:[#666666]+
[[colors-statusbar.fg.insert]]
=== statusbar.fg.insert
Foreground color of the statusbar in insert mode.
@ -1922,6 +1876,18 @@ Background color of the statusbar in command mode.
Default: +pass:[${statusbar.bg}]+
[[colors-statusbar.fg.command.private]]
=== statusbar.fg.command.private
Foreground color of the statusbar in private browsing + command mode.
Default: +pass:[${statusbar.fg.private}]+
[[colors-statusbar.bg.command.private]]
=== statusbar.bg.command.private
Background color of the statusbar in private browsing + command mode.
Default: +pass:[${statusbar.bg.private}]+
[[colors-statusbar.fg.caret]]
=== statusbar.fg.caret
Foreground color of the statusbar in caret mode.
@ -2266,7 +2232,7 @@ Fonts used for the UI, with optional style/weight/size.
=== _monospace
Default monospace fonts.
Default: +pass:[Terminus, Monospace, &quot;DejaVu Sans Mono&quot;, Monaco, &quot;Bitstream Vera Sans Mono&quot;, &quot;Andale Mono&quot;, &quot;Courier New&quot;, Courier, &quot;Liberation Mono&quot;, monospace, Fixed, Consolas, Terminal]+
Default: +pass:[xos4 Terminus, Terminus, Monospace, &quot;DejaVu Sans Mono&quot;, Monaco, &quot;Bitstream Vera Sans Mono&quot;, &quot;Andale Mono&quot;, &quot;Courier New&quot;, Courier, &quot;Liberation Mono&quot;, monospace, Fixed, Consolas, Terminal]+
[[fonts-completion]]
=== completion
@ -2350,25 +2316,25 @@ Default: empty
=== web-size-minimum
The hard minimum font size.
Default: empty
Default: +pass:[0]+
[[fonts-web-size-minimum-logical]]
=== web-size-minimum-logical
The minimum logical font size that is applied when zooming out.
Default: empty
Default: +pass:[6]+
[[fonts-web-size-default]]
=== web-size-default
The default font size for regular text.
Default: empty
Default: +pass:[16]+
[[fonts-web-size-default-fixed]]
=== web-size-default-fixed
The default font size for fixed-pitch text.
Default: empty
Default: +pass:[13]+
[[fonts-keyhint]]
=== keyhint

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 46 KiB

196
doc/notes
View File

@ -1,196 +0,0 @@
henk's thoughts
===============
1. Power to the user! Protect privacy!
Things the browser should only do with explicit consent from the user, if
applicable the user should be able to choose which protocol/host/port triplets
to white/blacklist:
- load/run executable code, like js, flash, java applets, ... (think NoScript)
- requests to other domains, ports or using a different protocol than what the
user requested (think RequestPolicy)
- accept cookies
- storing/saving/caching things, e.g. open tabs ("session"), cookies, page
contents, browsing/download history, form data, ...
- send referrer
- disclose any (presence, type, version, settings, capabilities, etc.)
information about OS, browser, installed fonts, plugins, addons, etc.
2. Be efficient!
I tend to leave a lot of tabs open and nobody can deny that some websites
simply suck, so the browser should, unless told otherwise by the user:
- load tabs only when needed
- run code in tabs only when needed, i.e. when the tab is currently being
used/viewed (background tabs doing some JS magic even when they are not being
used can create a lot of unnecessary load on the machine)
- finish requests to the domain the user requested (e.g. www.example.org)
before doing any requests to other subdomains (e.g. images.example.org) and
finish those before doing requests to thirdparty domains (e.g. example.com)
3. Be stable!
- one site should not make the complete browser crash, only that site's tab
Upstream Bugs
=============
- Web inspector is blank unless .hide()/.show() is called.
Asked on SO: http://stackoverflow.com/q/23499159/2085149
TODO: Report to PyQt/Qt
- Report some other crashes
/u/angelic_sedition's thoughts
==============================
Well support for greasemonkey scripts and bookmarklets/js (which was mentioned
in the arch forum post) would be a big addition. What I've usually missed when
using other vim-like browsers is things that allow for different settings and
key bindings for different contexts. With that implemented I think I could
switch to a lightweight browser (and believe me, I'd like to) for the most part
and only use firefox when I needed downthemall or something.
For example, I have different bindings based on tab position that are reloaded
with a pentadactyl autocmd so that <space><homerow keys> will take me to tab
1-10 if I'm in that range or 2-20 if I'm in that range. I have an autocmd that
will run on completed downloads that passes the file path to a script that will
open ranger in a floating window with that file cut (this is basically like
using ranger to save files instead of the crappy gui popup).
I also have a few bindings based on tabgroups. Tabgroups are a firefox feature,
but I find them very useful for sorting things by topic so that only the tabs
I'm interested at the moment are visible.
Pentadactyl has a feature it calls groups. You can create a group that will
activate for sites/urls that match a pattern with some regex support. This
allows me, for example, to set up different (more convenient) bindings for
zooming only on images. I'll never need use the equivalent of vim n (next text
search match), so I can bind that to zoom. This allows setting up custom
quickmarks/gotos using the same keys for different websites. For example, on
reddit I have different g(some key) bindings to go to different subreddits.
This can also be used to pass certain keys directly to the site (e.g. for use
with RES). For sites that don't have modifiable bindings, I can use this with
pentadactyl's feedkeys or xdotool to create my own custom bindings. I even have
a binding that will call out to bash script with different arguments depending
on the site to download an image or an image gallery depending on the site (in
some cases passing the url to some cli program).
I've also noticed the lack of completion. For example, on "o" pentadactyl will
show sites (e.g. from history) that can be completed. I think I've been spoiled
by pentadactyl having completion for just about everything.
suckless surf ML post
=====================
From: Ben Woolley <tautolog_AT_gmail.com>
Date: Wed, 7 Jan 2015 18:29:25 -0800
Hi all,
This patch is a bit of a beast for surf. It is intended to be applied after
the disk cache patch. It breaks some internal interfaces, so it could
conflict with other patches.
I have been wanting a browser to implement a complete same-origin policy,
and have been investigating how to do this in various browsers for many
months. When I saw how surf opened new windows in a separate process, and
was so simple, I knew I could do it quickly. Over the last two weeks, I
have been developing this implementation on surf.
The basic idea is to prevent browser-based tracking as you browse from site
to site, or origin to origin. By "origin" domain, I mean the "first-party"
domain, the domain normally in the location bar (of the typical browser
interface). Each origin domain effectively gets its own browser profile,
and a browser process only ever deals with one origin domain at a time.
This isolates origins vertically, preventing cookies, disk cache, memory
cache, and window.name vulnerabilities. Basically, all known
vulnerabilities that google and Mozilla cite as counter-examples when they
explain why they haven't disabled third-party cookies yet.
When you are on msnbc.com, the tracking pixels will be stored in a cookie
file for msnbc.com. When you go to cnn.com, the tracking pixels will be
stored in a cookie file for cnn.com. You will not be tracked between them.
However, third-party cookies, and the caching of third party resources will
still work, but they will be isolated between origin domains. Instead of
blocking cookies and cache entries, they are "double-keyed", or *also*
keyed by origin.
There is a unidirectional communication channel, however, from one origin
to the next, through navigation from one origin to the next. That is, the
query string is passed from one origin to the next, and may embed
identifiers. One example is an affiliate link that identifies where the
lead came from. I have implemented what I call "horizontal isolation", in
the form of an "Origin Crossing Gate".
Whenever you follow a link to a new domain, or even are just redirected to
a new domain, a new window/tab is opened, and passed the referring origin
via -R. The page passed to -O, for example -O originprompt.html, is an HTML
page that is loaded in the new origin's context. That page tells you the
origin you were on, the new origin, and the full link, and you can decide
to go just to the new origin, or go to the full URL, after reviewing it for
tracking data.
Also, you may click links that store your trust of that relationship with
various expiration times, the same way you would trust geolocation requests
for a particular origin for a period of time. The database used is actually
the new origin's cookie file. Since the origin prompt is loaded in the new
origin's context, I can set a cookie on behalf of the new origin. The
expiration time of the trust is the expiration time of the cookie. The
cookie implementation in webkit automatically expires the trust as part of
how cookies work. Each time you cross an origin, the origin crossing page
checks the cookie to see if trust is still established. If so, it will use
window.location.replace() to continue on automatically. The initial page
renders blank until the trust is invalidated, in which case the content of
the gate is made visible.
However, the new origin is technically able to mess with those cookies, so
a website could set trust for an origin crossing. I have addressed that by
hashing the key with a salt, and setting the real expiration time as the
value, along with an HMAC to verify the contents of the value. If the
cookie is messed with in any way, the trust will be disabled, and the
prompt will appear again. So it has a fail-safe function.
I know it seems a bit convoluted, but it just started out as a nice little
rabbit hole, and I just wanted to get something workable. At first I
thought using the cookie expiration time was convenient, but then when I
realized that I needed to protect the cookie, things got a bit hairy. But
it works.
Each profile is, by default, stored in ~/.surf/origins/$origin/
The interesting side effect is that if there is a problem where a website
relies on the cross-site cookie vulnerability to make a connection, you can
simply make a symbolic link from one origin folder to another, and they
will share the same profile. And if you want to delete cookies and/or cache
for a particular origin, you just rm -rf the origin's profile folder, and
don't have to interfere with your other sites that are working just fine.
One thing I don't handle are cross-origins POSTs. They just end up as GET
requests right now. I intend to do something about that, but I haven't
figured that out yet.
I have only been using this functionality for a few days myself, so I have
absolutely no feedback yet. I wanted to provide the first implementation of
the management of identity as a system resource the same way that things
like geolocation, camera, and microphone resources are managed in browsers
and mobile apps.
Currently, Mozilla and Tor have are working on third-party tracking issues
in Firefox.
https://blog.mozilla.org/privacy/2014/11/10/introducing-polaris-privacy-initiative-to-accelerate-user-focused-privacy-online/
Up to this point, Tor has provided a patch that double-keys cookies with
the origin domain, but no other progress is visible. I have seen no
discussion of how horizontal isolation is supposed to happen, and I wanted
to show people that it can be done, and this is one way it can be done, and
to compel the other browser makers to catch up, and hopefully the community
can work toward a standard *without* the tracking loopholes, by showing
people what a *complete* solution looks like.
Thank you,
Ben Woolley
Patch: http://lists.suckless.org/dev/att-25070/0005-same-origin-policy.patch

View File

@ -31,7 +31,7 @@ image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding c
* Run `:adblock-update` to download adblock lists and activate adblocking.
* If you just cloned the repository, you'll need to run
`scripts/asciidoc2html.py` to generate the documentation.
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. (Currently not available with the QtWebEngine backend and on the OS X build - use the `:set` command instead)
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it. (Currently not available with the QtWebEngine backend and on the macOS build - use the `:set` command instead)
* Subscribe to
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist].

View File

@ -57,7 +57,7 @@ show it.
How URLs should be opened if there is already a qutebrowser instance running.
*--backend* '{webkit,webengine}'::
Which backend to use (webengine backend is EXPERIMENTAL!).
Which backend to use.
*--enable-webengine-inspector*::
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
@ -93,12 +93,6 @@ show it.
*--nowindow*::
Don't show the main window.
*--debug-exit*::
Turn on debugging of late exit.
*--pdb-postmortem*::
Drop into pdb on exceptions.
*--temp-basedir*::
Use a temporary basedir.
@ -110,6 +104,9 @@ show it.
*--qt-flag* 'QT_FLAG'::
Pass an argument to Qt as flag.
*--debug-flag* 'DEBUG_FLAGS'::
Pass name of debugging feature to be turned on.
// QUTE_OPTIONS_END
== FILES

View File

@ -60,7 +60,7 @@ Sending commands
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
executed.
On Unix/OS X, this is a named pipe and commands written to it will get executed
On Unix/macOS, this is a named pipe and commands written to it will get executed
immediately.
On Windows, this is a regular file, and the commands in it will be executed as

77
misc/qutebrowser.nsi Normal file
View File

@ -0,0 +1,77 @@
Name "qutebrowser"
Unicode true
RequestExecutionLevel admin
SetCompressor /solid lzma
!ifdef X64
OutFile "..\dist\qutebrowser-${VERSION}-amd64.exe"
InstallDir "$ProgramFiles64\qutebrowser"
!else
OutFile "..\dist\qutebrowser-${VERSION}-win32.exe"
InstallDir "$ProgramFiles\qutebrowser"
!endif
;Default installation folder
!include "MUI2.nsh"
;!include "MultiUser.nsh"
!define MUI_ABORTWARNING
;!define MULTIUSER_MUI
;!define MULTIUSER_INSTALLMODE_COMMANDLINE
!define MUI_ICON "../icons/qutebrowser.ico"
!define MUI_UNICON "../icons/qutebrowser.ico"
!insertmacro MUI_PAGE_LICENSE "..\COPYING"
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
; depends on admin status
;SetShellVarContext current
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}'
SetOutPath "$INSTDIR"
!ifdef X64
file /r "..\dist\qutebrowser-${VERSION}-x64\*.*"
!else
file /r "..\dist\qutebrowser-${VERSION}-x86\*.*"
!endif
SetShellVarContext all
CreateShortCut "$SMPROGRAMS\qutebrowser.lnk" "$INSTDIR\qutebrowser.exe"
;Create uninstaller
WriteUninstaller "$INSTDIR\uninst.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser" "DisplayName" "qutebrowser"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser" "UninstallString" '"$INSTDIR\uninst.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser" "QuietUninstallString" '"$INSTDIR\uninst.exe" /S'
SectionEnd
;--------------------------------
;Uninstaller Section
Section "Uninstall"
SetShellVarContext all
Delete "$SMPROGRAMS\qutebrowser.lnk"
RMDir /r "$INSTDIR\*.*"
RMDir "$INSTDIR"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qutebrowser"
SectionEnd

View File

@ -18,10 +18,10 @@ def get_data_files():
('../qutebrowser/git-commit-id', '')
]
if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
else:
print("Warning: excluding pdfjs as it's not present!")
# if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
# data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
# else:
# print("Warning: excluding pdfjs as it's not present!")
return data_files
@ -41,10 +41,10 @@ a = Analysis(['../qutebrowser/__main__.py'],
pathex=['misc'],
binaries=None,
datas=get_data_files(),
hiddenimports=[],
hiddenimports=['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0'],
hookspath=[],
runtime_hooks=[],
excludes=[],
excludes=['tkinter'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)

View File

@ -1,5 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
codecov==2.0.5
coverage==4.3.4
requests==2.13.0
certifi==2017.4.17
chardet==3.0.4
codecov==2.0.9
coverage==4.4.1
idna==2.5
requests==2.18.1
urllib3==1.21.1

View File

@ -1,3 +0,0 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
cx-Freeze==4.3.4 # rq.filter: < 5.0.0

View File

@ -1,5 +0,0 @@
cx-Freeze < 5.0.0
# We'll probably switch to PyInstaller soon, and 5.x doesn't install without a
# compiler?
#@ filter: cx-Freeze < 5.0.0

View File

@ -3,18 +3,21 @@
flake8==2.6.2 # rq.filter: < 3.0.0
flake8-copyright==0.2.0
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
flake8-deprecated==1.1
flake8-docstrings==1.0.3
flake8-deprecated==1.2
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
flake8-future-import==0.4.3
flake8-mock==0.3
flake8-pep3101==1.0
flake8-pep3101==1.0 # rq.filter: < 1.1
flake8-polyfill==1.0.1
flake8-putty==0.4.0
flake8-string-format==0.2.3
flake8-tidy-imports==1.0.6
flake8-tuple==0.2.12
flake8-tidy-imports==1.1.0
flake8-tuple==0.2.13
mccabe==0.6.1
packaging==16.8
pep8-naming==0.4.1
pycodestyle==2.3.1
pydocstyle==1.1.1
pydocstyle==1.1.1 # rq.filter: < 2.0.0
pyflakes==1.5.0
pyparsing==2.2.0
six==1.10.0

View File

@ -2,16 +2,16 @@ flake8<3.0.0
flake8-copyright
flake8-debugger!=2.0.0
flake8-deprecated
flake8-docstrings
flake8-docstrings<1.1.0
flake8-future-import
flake8-mock
flake8-pep3101
flake8-pep3101<1.1
flake8-putty
flake8-string-format
flake8-tidy-imports
flake8-tuple
pep8-naming
pydocstyle
pydocstyle<2.0.0
pyflakes
# Pinned to 2.0.0 otherwise
@ -21,6 +21,9 @@ mccabe==0.6.1
# Waiting until flake8-putty updated
#@ filter: flake8 < 3.0.0
#@ filter: pydocstyle < 2.0.0
#@ filter: flake8-docstrings < 1.1.0
#@ filter: flake8-pep3101 < 1.1
# https://github.com/JBKahn/flake8-debugger/issues/5
#@ filter: flake8-debugger != 2.0.0

View File

@ -3,6 +3,6 @@
appdirs==1.4.3
packaging==16.8
pyparsing==2.2.0
setuptools==34.3.3
setuptools==36.2.0
six==1.10.0
wheel==0.29.0

View File

@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
-e git+https://github.com/xoviat/pyinstaller.git@qtweb#egg=PyInstaller

View File

@ -1,4 +1,4 @@
-e git+https://github.com/pyinstaller/pyinstaller.git@develop#egg=PyInstaller
-e git+https://github.com/xoviat/pyinstaller.git@qtweb#egg=PyInstaller
# remove @commit-id for scm installs
#@ replace: @.*# @develop#
#@ replace: @.*# @qtweb#

View File

@ -1,14 +1,18 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
editdistance==0.3.1
certifi==2017.4.17
chardet==3.0.4
github3.py==0.9.6
isort==4.2.5
lazy-object-proxy==1.2.2
idna==2.5
isort==4.2.15
lazy-object-proxy==1.3.1
mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers
requests==2.13.0
requests==2.18.1
six==1.10.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.21.1
wrapt==1.10.10

View File

@ -1,13 +1,18 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
astroid==1.4.9
astroid==1.5.3
certifi==2017.4.17
chardet==3.0.4
github3.py==0.9.6
isort==4.2.5
lazy-object-proxy==1.2.2
idna==2.5
isort==4.2.15
lazy-object-proxy==1.3.1
mccabe==0.6.1
pylint==1.6.5
pylint==1.7.2
./scripts/dev/pylint_checkers
requests==2.13.0
requests==2.18.1
six==1.10.0
uritemplate==3.0.0
uritemplate.py==3.0.2
urllib3==1.21.1
wrapt==1.10.10

View File

@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.8.2
sip==4.19.2
PyQt5==5.9
sip==4.19.3

View File

@ -27,7 +27,6 @@ git+https://github.com/pytest-dev/pytest-qt.git
git+https://github.com/pytest-dev/pytest-repeat.git
git+https://github.com/pytest-dev/pytest-rerunfailures.git
git+https://github.com/abusalimov/pytest-travis-fold.git
git+https://github.com/fschulze/pytest-warnings.git
git+https://github.com/The-Compiler/pytest-xvfb.git
hg+https://bitbucket.org/gutworth/six
hg+https://bitbucket.org/jendrikseipp/vulture

View File

@ -1,36 +1,39 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
beautifulsoup4==4.5.3
cheroot==5.4.0
beautifulsoup4==4.6.0
cheroot==5.7.0
click==6.7
coverage==4.3.4
decorator==4.0.11
# colorama==0.3.9
coverage==4.4.1
decorator==4.1.1
EasyProcess==0.2.3
Flask==0.12
fields==5.0.0
Flask==0.12.2
glob2==0.5
httpbin==0.5.0
hypothesis==3.7.0
hunter==1.4.1
hypothesis==3.13.0
itsdangerous==0.24
# Jinja2==2.9.5
Mako==1.0.6
# Jinja2==2.9.6
Mako==1.0.7
# MarkupSafe==1.0
parse==1.8.0
parse==1.8.2
parse-type==0.3.4
py==1.4.33
pytest==3.0.7
pytest-bdd==2.18.1
py==1.4.34
pytest==3.1.3
pytest-bdd==2.18.2
pytest-benchmark==3.0.0
pytest-catchlog==1.2.2
pytest-cov==2.4.0
pytest-cov==2.5.1
pytest-faulthandler==1.3.1
pytest-instafail==0.3.0
pytest-mock==1.6.0
pytest-qt==2.1.0
pytest-qt==2.1.2
pytest-repeat==0.4.1
pytest-rerunfailures==2.1.0
pytest-rerunfailures==2.2
pytest-travis-fold==1.2.0
pytest-warnings==0.2.0
pytest-xvfb==1.0.0
PyVirtualDisplay==0.2.1
vulture==0.13
Werkzeug==0.12.1
six==1.10.0
vulture==0.16
Werkzeug==0.12.2

View File

@ -2,6 +2,7 @@ beautifulsoup4
cheroot
coverage
Flask
hunter
httpbin
hypothesis
pytest
@ -16,8 +17,7 @@ pytest-qt
pytest-repeat
pytest-rerunfailures
pytest-travis-fold
pytest-warnings
pytest-xvfb
vulture
#@ ignore: Jinja2, MarkupSafe
#@ ignore: Jinja2, MarkupSafe, colorama

View File

@ -1,6 +1,6 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py
pluggy==0.4.0
py==1.4.33
tox==2.6.0
py==1.4.34
tox==2.7.0
virtualenv==15.1.0

View File

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

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
# Copyright 2015 jnphilipp <me@jnphilipp.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -9,7 +9,7 @@ directly ask me via IRC (nickname thorsten\`) in #qutebrowser on freenode.
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
WARNING: the passwords are stored in qutebrowser's
debug log reachable via the url qute:log
debug log reachable via the url qute://log
$blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
Usage: run as a userscript form qutebrowser, e.g.:

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -2,20 +2,32 @@
#
# Executes python-readability on current page and opens the summary as new tab.
#
# Depends on the python-readability package, or its fork:
#
# - https://github.com/buriy/python-readability
# - https://github.com/bookieio/breadability
#
# Usage:
# :spawn --userscript readability
#
from __future__ import absolute_import
import codecs, os
from readability.readability import Document
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html')
if not os.path.exists(os.path.dirname(tmpfile)):
os.makedirs(os.path.dirname(tmpfile))
with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
doc = Document(source.read())
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
data = source.read()
try:
from breadability.readable import Article as reader
doc = reader(data)
content = doc.readable
except ImportError:
from readability import Document
doc = Document(data)
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
with codecs.open(tmpfile, 'w', 'utf-8') as target:
target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')

View File

@ -1,12 +1,13 @@
[pytest]
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail --pythonwarnings error
testpaths = tests
markers =
gui: Tests using the GUI (e.g. spawning widgets)
posix: Tests which only can run on a POSIX OS.
windows: Tests which only can run on Windows.
linux: Tests which only can run on Linux.
osx: Tests which only can run on OS X.
not_osx: Tests which can not run on OS X.
mac: Tests which only can run on macOS.
not_mac: Tests which can not run on macOS.
not_frozen: Tests which can't be run if sys.frozen is True.
no_xvfb: Tests which can't be run with Xvfb.
frozen: Tests which can only be run if sys.frozen is True.
@ -20,7 +21,7 @@ markers =
qtwebkit_ng_xfail: Tests failing with QtWebKit-NG
qtwebkit_ng_skip: Tests skipped with QtWebKit-NG
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
qtwebengine_osx_xfail: Tests which fail on OS X with QtWebEngine
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
js_prompt: Tests needing to display a javascript prompt
this: Used to mark tests during development
no_invalid_lines: Don't fail on unparseable lines in end2end tests
@ -45,6 +46,7 @@ qt_log_ignore =
^QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to .*
^QGeoclueMaster error creating GeoclueMasterClient\.
^Geoclue error: Process org\.freedesktop\.Geoclue\.Master exited with status 127
^QDBusConnection: name 'org.freedesktop.Geoclue.Master' had owner '' but we thought it was ':1.1'
^QObject::connect: Cannot connect \(null\)::stateChanged\(QNetworkSession::State\) to QNetworkReplyHttpImpl::_q_networkSessionStateChanged\(QNetworkSession::State\)
^QXcbClipboard: Cannot transfer data, no data available
^load glyph failed
@ -53,4 +55,5 @@ qt_log_ignore =
^QPainter::end: Painter ended with \d+ saved states
^QSslSocket: cannot resolve SSLv[23]_(client|server)_method
^QQuickWidget::invalidateRenderControl could not make context current
^libpng warning: iCCP: known incorrect sRGB profile
xfail_strict = true

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -22,11 +22,11 @@
import os.path
__author__ = "Florian Bruhin"
__copyright__ = "Copyright 2014-2016 Florian Bruhin (The Compiler)"
__copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
__license__ = "GPL"
__maintainer__ = __author__
__email__ = "mail@qutebrowser.org"
__version_info__ = (0, 10, 1)
__version_info__ = (0, 11, 0)
__version__ = '.'.join(str(e) for e in __version_info__)
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -42,9 +42,10 @@ except ImportError:
import qutebrowser
import qutebrowser.resources
from qutebrowser.completion.models import instances as completionmodels
from qutebrowser.completion.models import miscmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import style, config, websettings, configexc
from qutebrowser.config.parsers import keyconf
from qutebrowser.browser import (urlmarks, adblock, history, browsertab,
downloads)
from qutebrowser.browser.network import proxy
@ -53,10 +54,10 @@ from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
crashsignal, earlyinit)
crashsignal, earlyinit, objects, sql)
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir, error, debug)
objreg, usertypes, standarddir, error)
# We import utilcmds to run the cmdutils.register decorators.
@ -136,7 +137,7 @@ def init(args, crash_handler):
try:
_init_modules(args, crash_handler)
except (OSError, UnicodeDecodeError) as e:
except (OSError, UnicodeDecodeError, browsertab.WebTabError) as e:
error.handle_fatal_exc(e, args, "Error while initializing!",
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
@ -157,7 +158,7 @@ def init(args, crash_handler):
QDesktopServices.setUrlHandler('https', open_desktopservices_url)
QDesktopServices.setUrlHandler('qute', open_desktopservices_url)
QTimer.singleShot(10, functools.partial(_init_late_modules, args))
objreg.get('web-history').import_txt()
log.init.debug("Init done!")
crash_handler.raise_crashdlg()
@ -170,12 +171,15 @@ def _init_icon():
for size in [16, 24, 32, 48, 64, 96, 128, 256, 512]:
filename = ':/icons/qutebrowser-{}x{}.png'.format(size, size)
pixmap = QPixmap(filename)
qtutils.ensure_not_null(pixmap)
fallback_icon.addPixmap(pixmap)
qtutils.ensure_not_null(fallback_icon)
if pixmap.isNull():
log.init.warning("Failed to load {}".format(filename))
else:
fallback_icon.addPixmap(pixmap)
icon = QIcon.fromTheme('qutebrowser', fallback_icon)
qtutils.ensure_not_null(icon)
qApp.setWindowIcon(icon)
if icon.isNull():
log.init.warning("Failed to load icon")
else:
qApp.setWindowIcon(icon)
def _process_args(args):
@ -192,14 +196,14 @@ def _process_args(args):
session_manager = objreg.get('session-manager')
if not session_manager.did_load:
log.init.debug("Initializing main window...")
window = mainwindow.MainWindow()
window = mainwindow.MainWindow(private=None)
if not args.nowindow:
window.show()
qApp.setActiveWindow(window)
process_pos_args(args.command)
_open_startpage()
_open_quickstart(args)
_open_special_pages(args)
delta = datetime.datetime.now() - earlyinit.START_TIME
log.init.debug("Init finished after {}s".format(delta.total_seconds()))
@ -316,23 +320,40 @@ def _open_startpage(win_id=None):
tabbed_browser.tabopen(url)
def _open_quickstart(args):
"""Open quickstart if it's the first start.
def _open_special_pages(args):
"""Open special notification pages which are only shown once.
Currently this is:
- Quickstart page if it's the first start.
- Legacy QtWebKit warning if needed.
Args:
args: The argparse namespace.
"""
if args.basedir is not None:
# With --basedir given, don't open quickstart.
# With --basedir given, don't open anything.
return
state_config = objreg.get('state-config')
try:
quickstart_done = state_config['general']['quickstart-done'] == '1'
except KeyError:
quickstart_done = False
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
# Legacy QtWebKit warning
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
not qtutils.is_qtwebkit_ng())
warning_shown = state_config['general'].get('backend-warning-shown') == '1'
if not warning_shown and needs_warning:
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
background=False)
state_config['general']['backend-warning-shown'] = '1'
# Quickstart page
quickstart_done = state_config['general'].get('quickstart-done') == '1'
if not quickstart_done:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
tabbed_browser.tabopen(
QUrl('https://www.qutebrowser.org/quickstart.html'))
state_config['general']['quickstart-done'] = '1'
@ -340,8 +361,9 @@ def _open_quickstart(args):
def _save_version():
"""Save the current version to the state config."""
state_config = objreg.get('state-config')
state_config['general']['version'] = qutebrowser.__version__
state_config = objreg.get('state-config', None)
if state_config is not None:
state_config['general']['version'] = qutebrowser.__version__
def on_focus_changed(_old, new):
@ -389,10 +411,8 @@ def _init_modules(args, crash_handler):
log.init.debug("Initializing network...")
networkmanager.init()
if qtutils.version_check('5.8'):
# Otherwise we can only initialize it for QtWebKit because of crashes
log.init.debug("Initializing proxy...")
proxy.init()
log.init.debug("Initializing proxy...")
proxy.init()
log.init.debug("Initializing readline-bridge...")
readline_bridge = readline.ReadlineBridge()
@ -402,6 +422,17 @@ def _init_modules(args, crash_handler):
config.init(qApp)
save_manager.init_autosave()
log.init.debug("Initializing keys...")
keyconf.init(qApp)
log.init.debug("Initializing sql...")
try:
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
except sql.SqlException as e:
error.handle_fatal_exc(e, args, 'Error initializing SQL',
pre_text='Error initializing SQL')
sys.exit(usertypes.Exit.err_init)
log.init.debug("Initializing web history...")
history.init(qApp)
@ -438,9 +469,6 @@ def _init_modules(args, crash_handler):
diskcache = cache.DiskCache(standarddir.cache(), parent=qApp)
objreg.register('cache', diskcache)
log.init.debug("Initializing completions...")
completionmodels.init()
log.init.debug("Misc initialization...")
if config.get('ui', 'hide-wayland-decoration'):
os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1'
@ -451,23 +479,6 @@ def _init_modules(args, crash_handler):
browsertab.init()
def _init_late_modules(args):
"""Initialize modules which can be inited after the window is shown."""
log.init.debug("Reading web history...")
reader = objreg.get('web-history').async_read()
with debug.log_time(log.init, 'Reading history'):
while True:
QApplication.processEvents()
try:
next(reader)
except StopIteration:
break
except (OSError, UnicodeDecodeError) as e:
error.handle_fatal_exc(e, args, "Error while initializing!",
pre_text="Error while initializing")
sys.exit(usertypes.Exit.err_init)
class Quitter:
"""Utility class to quit/restart the QApplication.
@ -615,7 +626,7 @@ class Quitter:
# Save the session if one is given.
if session is not None:
session_manager = objreg.get('session-manager')
session_manager.save(session)
session_manager.save(session, with_private=True)
# Open a new process and immediately shutdown the existing one
try:
args, cwd = self._get_restart_args(pages, session)
@ -647,14 +658,14 @@ class Quitter:
self._shutting_down = True
log.destroy.debug("Shutting down with status {}, session {}...".format(
status, session))
session_manager = objreg.get('session-manager')
if session is not None:
session_manager.save(session, last_window=last_window,
load_next_time=True)
elif config.get('general', 'save-session'):
session_manager.save(sessions.default, last_window=last_window,
load_next_time=True)
session_manager = objreg.get('session-manager', None)
if session_manager is not None:
if session is not None:
session_manager.save(session, last_window=last_window,
load_next_time=True)
elif config.get('general', 'save-session'):
session_manager.save(sessions.default, last_window=last_window,
load_next_time=True)
if prompt.prompt_queue.shutdown():
# If shutdown was called while we were asking a question, we're in
@ -671,7 +682,7 @@ class Quitter:
# event loop, so we can shut down immediately.
self._shutdown(status, restart=restart)
def _shutdown(self, status, restart):
def _shutdown(self, status, restart): # noqa
"""Second stage of shutdown."""
log.destroy.debug("Stage 2 of shutting down...")
if qApp is None:
@ -680,7 +691,9 @@ class Quitter:
# Remove eventfilter
try:
log.destroy.debug("Removing eventfilter...")
qApp.removeEventFilter(objreg.get('event-filter'))
event_filter = objreg.get('event-filter', None)
if event_filter is not None:
qApp.removeEventFilter(event_filter)
except AttributeError:
pass
# Close all windows
@ -722,13 +735,15 @@ class Quitter:
# Now we can hopefully quit without segfaults
log.destroy.debug("Deferring QApplication::exit...")
objreg.get('signal-handler').deactivate()
objreg.get('session-manager').delete_autosave()
session_manager = objreg.get('session-manager', None)
if session_manager is not None:
session_manager.delete_autosave()
# We use a singleshot timer to exit here to minimize the likelihood of
# segfaults.
QTimer.singleShot(0, functools.partial(qApp.exit, status))
@cmdutils.register(instance='quitter', name='wq')
@cmdutils.argument('name', completion=usertypes.Completion.sessions)
@cmdutils.argument('name', completion=miscmodels.session)
def save_and_quit(self, name=sessions.default):
"""Save open pages and quit.
@ -784,7 +799,7 @@ class Application(QApplication):
def exit(self, status):
"""Extend QApplication::exit to log the event."""
log.destroy.debug("Now calling QApplication::exit.")
if self._args.debug_exit:
if 'debug-exit' in self._args.debug_flags:
if hunter is None:
print("Not logging late shutdown because hunter could not be "
"imported!", file=sys.stderr)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -21,7 +21,7 @@
import itertools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QApplication
@ -35,11 +35,12 @@ from qutebrowser.browser import mouse, hints
tab_id_gen = itertools.count(0)
def create(win_id, parent=None):
def create(win_id, private, parent=None):
"""Get a QtWebKit/QtWebEngine tab object.
Args:
win_id: The window ID where the tab will be shown.
private: Whether the tab is a private/off the record tab.
parent: The Qt parent to set.
"""
# Importing modules here so we don't depend on QtWebEngine without the
@ -51,7 +52,8 @@ def create(win_id, parent=None):
else:
from qutebrowser.browser.webkit import webkittab
tab_class = webkittab.WebKitTab
return tab_class(win_id=win_id, mode_manager=mode_manager, parent=parent)
return tab_class(win_id=win_id, mode_manager=mode_manager, private=private,
parent=parent)
def init():
@ -94,6 +96,8 @@ class TabData:
viewing_source: Set if we're currently showing a source view.
override_target: Override for open_target for fake clicks (like hints).
Only used for QtWebKit.
pinned: Flag to pin the tab.
fullscreen: Whether the tab has a video shown fullscreen currently.
"""
def __init__(self):
@ -101,11 +105,21 @@ class TabData:
self.viewing_source = False
self.inspector = None
self.override_target = None
self.pinned = False
self.fullscreen = False
class AbstractAction:
"""Attribute of AbstractTab for Qt WebActions."""
"""Attribute of AbstractTab for Qt WebActions.
Class attributes (overridden by subclasses):
action_class: The class actions are defined on (QWeb{Engine,}Page)
action_base: The type of the actions (QWeb{Engine,}Page.WebAction)
"""
action_class = None
action_base = None
def __init__(self):
self._widget = None
@ -118,6 +132,13 @@ class AbstractAction:
"""Save the current page."""
raise NotImplementedError
def run_string(self, name):
"""Run a webaction based on its name."""
member = getattr(self.action_class, name, None)
if not isinstance(member, self.action_base):
raise WebTabError("{} is not a valid web action!".format(name))
self._widget.triggerPageAction(member)
class AbstractPrinting:
@ -155,6 +176,8 @@ class AbstractSearch(QObject):
Attributes:
text: The last thing this view was searched for.
search_displayed: Whether we're currently displaying search results in
this view.
_flags: The flags of the last search (needs to be set by subclasses).
_widget: The underlying WebView widget.
"""
@ -163,6 +186,7 @@ class AbstractSearch(QObject):
super().__init__(parent)
self._widget = None
self.text = None
self.search_displayed = False
def search(self, text, *, ignore_case=False, reverse=False,
result_cb=None):
@ -441,11 +465,21 @@ class AbstractHistory:
def current_idx(self):
raise NotImplementedError
def back(self):
raise NotImplementedError
def back(self, count=1):
idx = self.current_idx() - count
if idx >= 0:
self._go_to_item(self._item_at(idx))
else:
self._go_to_item(self._item_at(0))
raise WebTabError("At beginning of history.")
def forward(self):
raise NotImplementedError
def forward(self, count=1):
idx = self.current_idx() + count
if idx < len(self):
self._go_to_item(self._item_at(idx))
else:
self._go_to_item(self._item_at(len(self) - 1))
raise WebTabError("At end of history.")
def can_go_back(self):
raise NotImplementedError
@ -453,6 +487,12 @@ class AbstractHistory:
def can_go_forward(self):
raise NotImplementedError
def _item_at(self, i):
raise NotImplementedError
def _go_to_item(self, item):
raise NotImplementedError
def serialize(self):
"""Serialize into an opaque format understood by self.deserialize."""
raise NotImplementedError
@ -524,6 +564,7 @@ class AbstractTab(QWidget):
Attributes:
history: The AbstractHistory for the current tab.
registry: The ObjectRegistry associated with this tab.
private: Whether private browsing is turned on for this tab.
_load_status: loading status of this page
Accessible via load_status() method.
@ -563,7 +604,8 @@ class AbstractTab(QWidget):
fullscreen_requested = pyqtSignal(bool)
renderer_process_terminated = pyqtSignal(TerminationStatus, int)
def __init__(self, win_id, mode_manager, parent=None):
def __init__(self, *, win_id, mode_manager, private, parent=None):
self.private = private
self.win_id = win_id
self.tab_id = next(tab_id_gen)
super().__init__(parent)
@ -740,6 +782,10 @@ class AbstractTab(QWidget):
def clear_ssl_errors(self):
raise NotImplementedError
def key_press(self, key, modifier=Qt.NoModifier):
"""Send a fake key event to this tab."""
raise NotImplementedError
def dump_async(self, callback, *, plain=False):
"""Dump the current page to a file ascync.
@ -771,7 +817,7 @@ class AbstractTab(QWidget):
def icon(self):
raise NotImplementedError
def set_html(self, html, base_url):
def set_html(self, html, base_url=QUrl()):
raise NotImplementedError
def networkaccessmanager(self):

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -20,22 +20,15 @@
"""Command dispatcher for TabbedBrowser."""
import os
import sys
import os.path
import shlex
import functools
from PyQt5.QtWidgets import QApplication, QTabBar
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtPrintSupport import QPrintDialog, QPrintPreviewDialog
try:
from PyQt5.QtWebKitWidgets import QWebPage
except ImportError:
QWebPage = None
try:
from PyQt5.QtWebEngineWidgets import QWebEnginePage
except ImportError:
QWebEnginePage = None
import pygments
import pygments.lexers
import pygments.formatters
@ -46,10 +39,10 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
webelem, downloads)
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils, typing)
objreg, utils, typing, debug)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import instances, sortfilter
from qutebrowser.completion.models import urlmodel, miscmodels
class CommandDispatcher:
@ -75,10 +68,10 @@ class CommandDispatcher:
def __repr__(self):
return utils.get_repr(self)
def _new_tabbed_browser(self):
def _new_tabbed_browser(self, private):
"""Get a tabbed-browser from a new window."""
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow()
new_window = mainwindow.MainWindow(private=private)
new_window.show()
return new_window.tabbed_browser
@ -118,7 +111,7 @@ class CommandDispatcher:
return widget
def _open(self, url, tab=False, background=False, window=False,
explicit=True):
explicit=True, private=None):
"""Helper function to open a page.
Args:
@ -126,12 +119,17 @@ class CommandDispatcher:
tab: Whether to open in a new tab.
background: Whether to open in the background.
window: Whether to open in a new window
private: If opening a new window, open it in private browsing mode.
If not given, inherit the current window's mode.
"""
urlutils.raise_cmdexc_if_invalid(url)
tabbed_browser = self._tabbed_browser
cmdutils.check_exclusive((tab, background, window), 'tbw')
if window:
tabbed_browser = self._new_tabbed_browser()
cmdutils.check_exclusive((tab, background, window, private), 'tbwp')
if window and private is None:
private = self._tabbed_browser.private
if window or private:
tabbed_browser = self._new_tabbed_browser(private)
tabbed_browser.tabopen(url)
elif tab:
tabbed_browser.tabopen(url, background=False, explicit=explicit)
@ -160,12 +158,14 @@ class CommandDispatcher:
else:
return None
def _tab_focus_last(self):
def _tab_focus_last(self, *, show_error=True):
"""Select the tab which was last focused."""
try:
tab = objreg.get('last-focused-tab', scope='window',
window=self._win_id)
except KeyError:
if not show_error:
return
raise cmdexc.CommandError("No last focused tab!")
idx = self._tabbed_browser.indexOf(tab)
if idx == -1:
@ -205,24 +205,21 @@ class CommandDispatcher:
"{!r}!".format(conf_selection))
return None
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_close(self, prev=False, next_=False, opposite=False, count=None):
"""Close the current/[count]th tab.
def _tab_close(self, tab, prev=False, next_=False, opposite=False):
"""Helper function for tab_close be able to handle message.async.
Args:
tab: Tab object to select be closed.
prev: Force selecting the tab before the current tab.
next_: Force selecting the tab after the current tab.
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
count: The tab index to close, or None
"""
tab = self._cntwidget(count)
if tab is None:
return
tabbar = self._tabbed_browser.tabBar()
selection_override = self._get_selection_override(prev, next_,
opposite)
if selection_override is None:
self._tabbed_browser.close_tab(tab)
else:
@ -231,12 +228,55 @@ class CommandDispatcher:
self._tabbed_browser.close_tab(tab)
tabbar.setSelectionBehaviorOnRemove(old_selection_behavior)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def tab_close(self, prev=False, next_=False, opposite=False,
force=False, count=None):
"""Close the current/[count]th tab.
Args:
prev: Force selecting the tab before the current tab.
next_: Force selecting the tab after the current tab.
opposite: Force selecting the tab in the opposite direction of
what's configured in 'tabs->select-on-remove'.
force: Avoid confirmation for pinned tabs.
count: The tab index to close, or None
"""
tab = self._cntwidget(count)
if tab is None:
return
close = functools.partial(self._tab_close, tab, prev,
next_, opposite)
self._tabbed_browser.tab_close_prompt_if_pinned(tab, force, close)
@cmdutils.register(instance='command-dispatcher', scope='window',
name='tab-pin')
@cmdutils.argument('count', count=True)
def tab_pin(self, count=None):
"""Pin/Unpin the current/[count]th tab.
Pinning a tab shrinks it to tabs->pinned-width size.
Attempting to close a pinned tab will cause a confirmation,
unless --force is passed.
Args:
count: The tab index to pin or unpin, or None
"""
tab = self._cntwidget(count)
if tab is None:
return
to_pin = not tab.data.pinned
self._tabbed_browser.set_tab_pinned(tab, to_pin)
@cmdutils.register(instance='command-dispatcher', name='open',
maxsplit=0, scope='window')
@cmdutils.argument('url', completion=usertypes.Completion.url)
@cmdutils.argument('url', completion=urlmodel.url)
@cmdutils.argument('count', count=True)
def openurl(self, url=None, implicit=False,
bg=False, tab=False, window=False, count=None, secure=False):
bg=False, tab=False, window=False, count=None, secure=False,
private=False):
"""Open a URL in the current/[count]th tab.
If the URL contains newlines, each line gets opened in its own tab.
@ -250,6 +290,7 @@ class CommandDispatcher:
clicking on a link).
count: The tab index to open the URL in, or None.
secure: Force HTTPS.
private: Open a new window in private browsing mode.
"""
if url is None:
urls = [config.get('general', 'default-page')]
@ -262,8 +303,10 @@ class CommandDispatcher:
if not window and i > 0:
tab = False
bg = True
if tab or bg or window:
self._open(cur_url, tab, bg, window, not implicit)
if tab or bg or window or private:
self._open(cur_url, tab, bg, window, explicit=not implicit,
private=private)
else:
curtab = self._cntwidget(count)
if curtab is None:
@ -274,6 +317,8 @@ class CommandDispatcher:
else:
# Explicit count with a tab that doesn't exist.
return
elif curtab.data.pinned:
message.info("Tab is pinned!")
else:
curtab.openurl(cur_url)
@ -379,9 +424,18 @@ class CommandDispatcher:
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)
diag.open(lambda: tab.printing.to_printer(diag.printer(),
print_callback))
if sys.platform == 'darwin':
# 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')
@ -438,10 +492,11 @@ class CommandDispatcher:
# The new tab could be in a new tabbed_browser (e.g. because of
# tabs-are-windows being set)
if window:
new_tabbed_browser = self._new_tabbed_browser()
new_tabbed_browser = self._new_tabbed_browser(
private=self._tabbed_browser.private)
else:
new_tabbed_browser = self._tabbed_browser
newtab = new_tabbed_browser.tabopen(background=bg, explicit=True)
newtab = new_tabbed_browser.tabopen(background=bg)
new_tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=newtab.win_id)
idx = new_tabbed_browser.indexOf(newtab)
@ -455,6 +510,7 @@ class CommandDispatcher:
newtab.data.keep_icon = True
newtab.history.deserialize(history)
newtab.zoom.set_factor(curtab.zoom.factor())
new_tabbed_browser.set_tab_pinned(newtab, curtab.data.pinned)
return newtab
@cmdutils.register(instance='command-dispatcher', scope='window')
@ -481,15 +537,13 @@ class CommandDispatcher:
else:
widget = self._current_widget()
for _ in range(count):
try:
if forward:
if not widget.history.can_go_forward():
raise cmdexc.CommandError("At end of history.")
widget.history.forward()
widget.history.forward(count)
else:
if not widget.history.can_go_back():
raise cmdexc.CommandError("At beginning of history.")
widget.history.back()
widget.history.back(count)
except browsertab.WebTabError as e:
raise cmdexc.CommandError(e)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
@ -568,7 +622,7 @@ class CommandDispatcher:
tab=tab, background=bg, window=window)
elif where in ['up', 'increment', 'decrement']:
new_url = handlers[where](url, count)
self._open(new_url, tab, bg, window)
self._open(new_url, tab, bg, window, explicit=False)
else: # pragma: no cover
raise ValueError("Got called with invalid value {} for "
"`where'.".format(where))
@ -634,7 +688,7 @@ class CommandDispatcher:
scope='window')
@cmdutils.argument('count', count=True)
@cmdutils.argument('horizontal', flag='x')
def scroll_perc(self, perc: float=None, horizontal=False, count=None):
def scroll_perc(self, perc: float = None, horizontal=False, count=None):
"""Scroll to a specific percentage of the page.
The percentage can be given either as argument or as count.
@ -670,7 +724,7 @@ class CommandDispatcher:
@cmdutils.argument('bottom_navigate', metavar='ACTION',
choices=('next', 'increment'))
def scroll_page(self, x: float, y: float, *,
top_navigate: str=None, bottom_navigate: str=None,
top_navigate: str = None, bottom_navigate: str = None,
count=1):
"""Scroll the frame page-wise.
@ -807,7 +861,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('count', count=True)
def zoom(self, zoom: int=None, count=None):
def zoom(self, zoom=None, count=None):
"""Set the zoom level for the current tab.
The zoom can be given as argument or as [count]. If neither is
@ -818,6 +872,13 @@ class CommandDispatcher:
zoom: The zoom percentage to set.
count: The zoom percentage to set.
"""
if zoom is not None:
try:
zoom = int(zoom.rstrip('%'))
except ValueError:
raise cmdexc.CommandError("zoom: Invalid int value {}"
.format(zoom))
level = count if count is not None else zoom
if level is None:
level = config.get('ui', 'default-zoom')
@ -830,27 +891,42 @@ class CommandDispatcher:
message.info("Zoom level: {}%".format(level), replace=True)
@cmdutils.register(instance='command-dispatcher', scope='window')
def tab_only(self, prev=False, next_=False):
def tab_only(self, prev=False, next_=False, force=False):
"""Close all tabs except for the current one.
Args:
prev: Keep tabs before the current.
next_: Keep tabs after the current.
force: Avoid confirmation for pinned tabs.
"""
cmdutils.check_exclusive((prev, next_), 'pn')
cur_idx = self._tabbed_browser.currentIndex()
assert cur_idx != -1
def _to_close(i):
"""Helper method to check if a tab should be closed or not."""
return not (i == cur_idx or
(prev and i < cur_idx) or
(next_ and i > cur_idx))
# Check to see if we are closing any pinned tabs
if not force:
for i, tab in enumerate(self._tabbed_browser.widgets()):
if _to_close(i) and tab.data.pinned:
self._tabbed_browser.tab_close_prompt_if_pinned(
tab,
force,
lambda: self.tab_only(
prev=prev, next_=next_, force=True))
return
for i, tab in enumerate(self._tabbed_browser.widgets()):
if (i == cur_idx or (prev and i < cur_idx) or
(next_ and i > cur_idx)):
continue
else:
if _to_close(i):
self._tabbed_browser.close_tab(tab)
@cmdutils.register(instance='command-dispatcher', scope='window')
def undo(self):
"""Re-open a closed tab (optionally skipping [count] closed tabs)."""
"""Re-open a closed tab."""
try:
self._tabbed_browser.undo()
except IndexError:
@ -934,7 +1010,7 @@ class CommandDispatcher:
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window')
@cmdutils.argument('index', completion=usertypes.Completion.tab)
@cmdutils.argument('index', completion=miscmodels.buffer)
def buffer(self, index):
"""Select tab by index or url/title best match.
@ -950,11 +1026,10 @@ class CommandDispatcher:
for part in index_parts:
int(part)
except ValueError:
model = instances.get(usertypes.Completion.tab)
sf = sortfilter.CompletionFilterModel(source=model)
sf.set_pattern(index)
if sf.count() > 0:
index = sf.data(sf.first_item())
model = miscmodels.buffer()
model.set_pattern(index)
if model.count() > 0:
index = model.data(model.first_item())
index_parts = index.split('/', 1)
else:
raise cmdexc.CommandError(
@ -1003,12 +1078,15 @@ class CommandDispatcher:
last tab.
count: The tab index to focus, starting with 1.
"""
index = count if count is not None else index
if index == 'last':
self._tab_focus_last()
return
index = count if count is not None else index
if index is None:
elif index == self._current_index() + 1:
self._tab_focus_last(show_error=False)
return
elif index is None:
self.tab_next()
return
@ -1082,6 +1160,7 @@ class CommandDispatcher:
detach: Whether the command should be detached from qutebrowser.
cmdline: The commandline to execute.
"""
cmdutils.check_exclusive((userscript, detach), 'ud')
try:
cmd, *args = shlex.split(cmdline)
except ValueError as e:
@ -1156,8 +1235,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@cmdutils.argument('name',
completion=usertypes.Completion.quickmark_by_name)
@cmdutils.argument('name', completion=miscmodels.quickmark)
def quickmark_load(self, name, tab=False, bg=False, window=False):
"""Load a quickmark.
@ -1175,8 +1253,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@cmdutils.argument('name',
completion=usertypes.Completion.quickmark_by_name)
@cmdutils.argument('name', completion=miscmodels.quickmark)
def quickmark_del(self, name=None):
"""Delete a quickmark.
@ -1233,12 +1310,12 @@ class CommandDispatcher:
except urlmarks.Error as e:
raise cmdexc.CommandError(str(e))
else:
msg = "Bookmarked {}!" if was_added else "Removed bookmark {}!"
msg = "Bookmarked {}" if was_added else "Removed bookmark {}"
message.info(msg.format(url.toDisplayString()))
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
@cmdutils.argument('url', completion=miscmodels.bookmark)
def bookmark_load(self, url, tab=False, bg=False, window=False,
delete=False):
"""Load a bookmark.
@ -1260,7 +1337,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0)
@cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url)
@cmdutils.argument('url', completion=miscmodels.bookmark)
def bookmark_del(self, url=None):
"""Delete a bookmark.
@ -1365,8 +1442,18 @@ class CommandDispatcher:
download_manager.get_mhtml(tab, target)
else:
qnam = tab.networkaccessmanager()
download_manager.get(self._current_url(), user_agent=user_agent,
qnam=qnam, target=target)
suggested_fn = downloads.suggested_fn_from_title(
self._current_url().path(), tab.title()
)
download_manager.get(
self._current_url(),
user_agent=user_agent,
qnam=qnam,
target=target,
suggested_fn=suggested_fn
)
@cmdutils.register(instance='command-dispatcher', scope='window')
def view_source(self):
@ -1377,19 +1464,22 @@ class CommandDispatcher:
if tab.data.viewing_source:
raise cmdexc.CommandError("Already viewing source!")
try:
current_url = self._current_url()
except cmdexc.CommandError as e:
message.error(str(e))
return
def show_source_cb(source):
"""Show source as soon as it's ready."""
lexer = pygments.lexers.HtmlLexer()
formatter = pygments.formatters.HtmlFormatter(full=True,
linenos='table')
formatter = pygments.formatters.HtmlFormatter(
full=True, linenos='table',
title='Source for {}'.format(current_url.toDisplayString()))
highlighted = pygments.highlight(source, lexer, formatter)
try:
current_url = self._current_url()
except cmdexc.CommandError as e:
message.error(str(e))
return
new_tab = self._tabbed_browser.tabopen(explicit=True)
new_tab.set_html(highlighted, current_url)
new_tab = self._tabbed_browser.tabopen()
new_tab.set_html(highlighted)
new_tab.data.viewing_source = True
tab.dump_async(show_source_cb)
@ -1431,7 +1521,7 @@ class CommandDispatcher:
@cmdutils.register(instance='command-dispatcher', name='help',
scope='window')
@cmdutils.argument('topic', completion=usertypes.Completion.helptopic)
@cmdutils.argument('topic', completion=miscmodels.helptopic)
def show_help(self, tab=False, bg=False, window=False, topic=None):
r"""Show help about a command or setting.
@ -1472,7 +1562,7 @@ class CommandDispatcher:
self._open(url, tab, bg, window)
@cmdutils.register(instance='command-dispatcher', scope='window')
def messages(self, level='error', plain=False, tab=False, bg=False,
def messages(self, level='info', plain=False, tab=False, bg=False,
window=False):
"""Show a log of past messages.
@ -1505,6 +1595,7 @@ class CommandDispatcher:
if text is None:
message.error("Could not get text from the focused element.")
return
assert isinstance(text, str), text
ed = editor.ExternalEditor(self._tabbed_browser)
ed.editing_finished.connect(functools.partial(
@ -1651,7 +1742,8 @@ class CommandDispatcher:
"""
self.set_mark("'")
tab = self._current_widget()
tab.search.clear()
if tab.search.search_displayed:
tab.search.clear()
if not text:
return
@ -1904,33 +1996,20 @@ class CommandDispatcher:
def debug_webaction(self, action, count=1):
"""Execute a webaction.
See http://doc.qt.io/qt-5/qwebpage.html#WebAction-enum for the
available actions.
Available actions:
http://doc.qt.io/archives/qt-5.5/qwebpage.html#WebAction-enum (WebKit)
http://doc.qt.io/qt-5/qwebenginepage.html#WebAction-enum (WebEngine)
Args:
action: The action to execute, e.g. MoveToNextChar.
count: How many times to repeat the action.
"""
tab = self._current_widget()
if tab.backend == usertypes.Backend.QtWebKit:
assert QWebPage is not None
member = getattr(QWebPage, action, None)
base = QWebPage.WebAction
elif tab.backend == usertypes.Backend.QtWebEngine:
assert QWebEnginePage is not None
member = getattr(QWebEnginePage, action, None)
base = QWebEnginePage.WebAction
if not isinstance(member, base):
raise cmdexc.CommandError("{} is not a valid web action!".format(
action))
for _ in range(count):
# This whole command is backend-specific anyways, so it makes no
# sense to introduce some API for this.
# pylint: disable=protected-access
tab._widget.triggerPageAction(member)
try:
tab.action.run_string(action)
except browsertab.WebTabError as e:
raise cmdexc.CommandError(str(e))
@cmdutils.register(instance='command-dispatcher', scope='window',
maxsplit=0, no_cmd_split=True)
@ -2095,6 +2174,10 @@ class CommandDispatcher:
window = self._tabbed_browser.window()
if window.isFullScreen():
window.showNormal()
window.setWindowState(
window.state_before_fullscreen & ~Qt.WindowFullScreen)
else:
window.state_before_fullscreen = window.windowState()
window.showFullScreen()
log.misc.debug('state before fullscreen: {}'.format(
debug.qflags_key(Qt, window.state_before_fullscreen)))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -182,6 +182,28 @@ def transform_path(path):
return path
def suggested_fn_from_title(url_path, title=None):
"""Suggest a filename depending on the URL extension and page title.
Args:
url_path: a string with the URL path
title: the page title string
Return:
The download filename based on the title, or None if the extension is
not found in the whitelist (or if there is no page title).
"""
ext_whitelist = [".html", ".htm", ".php", ""]
_, ext = os.path.splitext(url_path)
if ext.lower() in ext_whitelist and title:
suggested_fn = utils.sanitize_filename(title)
if not suggested_fn.lower().endswith((".html", ".htm")):
suggested_fn += ".html"
else:
suggested_fn = None
return suggested_fn
class NoFilenameError(Exception):
"""Raised when we can't find out a filename in DownloadTarget."""
@ -952,7 +974,7 @@ class DownloadModel(QAbstractListModel):
@cmdutils.register(instance='download-model', scope='window', maxsplit=0)
@cmdutils.argument('count', count=True)
def download_open(self, cmdline: str=None, count=0):
def download_open(self, cmdline: str = None, count=0):
"""Open the last/[count]th download.
If no specific command is given, this will use the system's default

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -23,7 +23,7 @@ import functools
import sip
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
from qutebrowser.browser import downloads
from qutebrowser.config import style
@ -75,6 +75,7 @@ class DownloadView(QListView):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self.setStyle(QStyleFactory.create('Fusion'))
style.set_register_stylesheet(self)
self.setResizeMode(QListView.Adjust)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -579,12 +579,10 @@ class HintManager(QObject):
if elems is None:
message.error("There was an error while getting hint elements")
return
filterfunc = webelem.FILTERS.get(self._context.group, lambda e: True)
elems = [e for e in elems if filterfunc(e)]
if not elems:
message.error("No elements found.")
return
strings = self._hint_strings(elems)
log.hints.debug("hints: {}".format(', '.join(strings)))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -19,215 +19,82 @@
"""Simple history which gets written to disk."""
import os
import time
import collections
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
from qutebrowser.commands import cmdutils
from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils,
usertypes, message)
from qutebrowser.config import config
from qutebrowser.misc import lineparser, objects
from qutebrowser.commands import cmdutils, cmdexc
from qutebrowser.utils import (utils, objreg, log, usertypes, message,
debug, standarddir)
from qutebrowser.misc import objects, sql
class Entry:
class CompletionHistory(sql.SqlTable):
"""A single entry in the web history.
"""History which only has the newest entry for each URL."""
Attributes:
atime: The time the page was accessed.
url: The URL which was accessed as QUrl.
redirect: If True, don't save this entry to disk
"""
def __init__(self, atime, url, title, redirect=False):
self.atime = float(atime)
self.url = url
self.title = title
self.redirect = redirect
qtutils.ensure_valid(url)
def __repr__(self):
return utils.get_repr(self, constructor=True, atime=self.atime,
url=self.url_str(), title=self.title,
redirect=self.redirect)
def __str__(self):
atime = str(int(self.atime))
if self.redirect:
atime += '-r' # redirect flag
elems = [atime, self.url_str()]
if self.title:
elems.append(self.title)
return ' '.join(elems)
def __eq__(self, other):
return (self.atime == other.atime and
self.title == other.title and
self.url == other.url and
self.redirect == other.redirect)
def url_str(self):
"""Get the URL as a lossless string."""
return self.url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
@classmethod
def from_str(cls, line):
"""Parse a history line like '12345 http://example.com title'."""
data = line.split(maxsplit=2)
if len(data) == 2:
atime, url = data
title = ""
elif len(data) == 3:
atime, url, title = data
else:
raise ValueError("2 or 3 fields expected")
url = QUrl(url)
if not url.isValid():
raise ValueError("Invalid URL: {}".format(url.errorString()))
# https://github.com/qutebrowser/qutebrowser/issues/670
atime = atime.lstrip('\0')
if '-' in atime:
atime, flags = atime.split('-')
else:
flags = ''
if not set(flags).issubset('r'):
raise ValueError("Invalid flags {!r}".format(flags))
redirect = 'r' in flags
return cls(atime, url, title, redirect=redirect)
def __init__(self, parent=None):
super().__init__("CompletionHistory", ['url', 'title', 'last_atime'],
constraints={'url': 'PRIMARY KEY'}, parent=parent)
self.create_index('CompletionHistoryAtimeIndex', 'last_atime')
class WebHistory(QObject):
class WebHistory(sql.SqlTable):
"""The global history of visited pages.
"""The global history of visited pages."""
This is a little more complex as you'd expect so the history can be read
from disk async while new history is already arriving.
def __init__(self, parent=None):
super().__init__("History", ['url', 'title', 'atime', 'redirect'],
parent=parent)
self.completion = CompletionHistory(parent=self)
self.create_index('HistoryIndex', 'url')
self.create_index('HistoryAtimeIndex', 'atime')
self._contains_query = self.contains_query('url')
self._between_query = sql.Query('SELECT * FROM History '
'where not redirect '
'and not url like "qute://%" '
'and atime > :earliest '
'and atime <= :latest '
'ORDER BY atime desc')
self.history_dict is the main place where the history is stored, in an
OrderedDict (sorted by time) of URL strings mapped to Entry objects.
While reading from disk is still ongoing, the history is saved in
self._temp_history instead, and then appended to self.history_dict once
that's fully populated.
All history which is new in this session (rather than read from disk from a
previous browsing session) is also stored in self._new_history.
self._saved_count tracks how many of those entries were already written to
disk, so we can always append to the existing data.
Attributes:
history_dict: An OrderedDict of URLs read from the on-disk history.
_lineparser: The AppendLineParser used to save the history.
_new_history: A list of Entry items of the current session.
_saved_count: How many HistoryEntries have been written to disk.
_initial_read_started: Whether async_read was called.
_initial_read_done: Whether async_read has completed.
_temp_history: OrderedDict of temporary history entries before
async_read was called.
Signals:
add_completion_item: Emitted before a new Entry is added.
Used to sync with the completion.
arg: The new Entry.
item_added: Emitted after a new Entry is added.
Used to tell the savemanager that the history is dirty.
arg: The new Entry.
cleared: Emitted after the history is cleared.
"""
add_completion_item = pyqtSignal(Entry)
item_added = pyqtSignal(Entry)
cleared = pyqtSignal()
async_read_done = pyqtSignal()
def __init__(self, hist_dir, hist_name, parent=None):
super().__init__(parent)
self._initial_read_started = False
self._initial_read_done = False
self._lineparser = lineparser.AppendLineParser(hist_dir, hist_name,
parent=self)
self.history_dict = collections.OrderedDict()
self._temp_history = collections.OrderedDict()
self._new_history = []
self._saved_count = 0
objreg.get('save-manager').add_saveable(
'history', self.save, self.item_added)
self._before_query = sql.Query('SELECT * FROM History '
'where not redirect '
'and not url like "qute://%" '
'and atime <= :latest '
'ORDER BY atime desc '
'limit :limit offset :offset')
def __repr__(self):
return utils.get_repr(self, length=len(self))
def __iter__(self):
return iter(self.history_dict.values())
def __len__(self):
return len(self.history_dict)
def async_read(self):
"""Read the initial history."""
if self._initial_read_started:
log.init.debug("Ignoring async_read() because reading is started.")
return
self._initial_read_started = True
with self._lineparser.open():
for line in self._lineparser:
yield
line = line.rstrip()
if not line:
continue
try:
entry = Entry.from_str(line)
except ValueError as e:
log.init.warning("Invalid history entry {!r}: {}!".format(
line, e))
continue
# This de-duplicates history entries; only the latest
# entry for each URL is kept. If you want to keep
# information about previous hits change the items in
# old_urls to be lists or change Entry to have a
# list of atimes.
self._add_entry(entry)
self._initial_read_done = True
self.async_read_done.emit()
for entry in self._temp_history.values():
self._add_entry(entry)
self._new_history.append(entry)
if not entry.redirect:
self.add_completion_item.emit(entry)
self._temp_history.clear()
def _add_entry(self, entry, target=None):
"""Add an entry to self.history_dict or another given OrderedDict."""
if target is None:
target = self.history_dict
url_str = entry.url_str()
target[url_str] = entry
target.move_to_end(url_str)
def __contains__(self, url):
return self._contains_query.run(val=url).value()
def get_recent(self):
"""Get the most recent history entries."""
old = self._lineparser.get_recent()
return old + [str(e) for e in self._new_history]
return self.select(sort_by='atime', sort_order='desc', limit=100)
def save(self):
"""Save the history to disk."""
new = (str(e) for e in self._new_history[self._saved_count:])
self._lineparser.new_data = new
self._lineparser.save()
self._saved_count = len(self._new_history)
def entries_between(self, earliest, latest):
"""Iterate non-redirect, non-qute entries between two timestamps.
Args:
earliest: Omit timestamps earlier than this.
latest: Omit timestamps later than this.
"""
self._between_query.run(earliest=earliest, latest=latest)
return iter(self._between_query)
def entries_before(self, latest, limit, offset):
"""Iterate non-redirect, non-qute entries occurring before a timestamp.
Args:
latest: Omit timestamps more recent than this.
limit: Max number of entries to include.
offset: Number of entries to skip.
"""
self._before_query.run(latest=latest, limit=limit, offset=offset)
return iter(self._before_query)
@cmdutils.register(name='history-clear', instance='web-history')
def clear(self, force=False):
@ -247,16 +114,27 @@ class WebHistory(QObject):
"history?")
def _do_clear(self):
self._lineparser.clear()
self.history_dict.clear()
self._temp_history.clear()
self._new_history.clear()
self._saved_count = 0
self.cleared.emit()
self.delete_all()
self.completion.delete_all()
def delete_url(self, url):
"""Remove all history entries with the given url.
Args:
url: URL string to delete.
"""
self.delete('url', url)
self.completion.delete('url', url)
@pyqtSlot(QUrl, QUrl, str)
def add_from_tab(self, url, requested_url, title):
"""Add a new history entry as slot, called from a BrowserTab."""
if url.scheme() == 'data' or requested_url.scheme() == 'data':
return
if url.isEmpty():
# things set via setHtml
return
no_formatting = QUrl.UrlFormattingOption(0)
if (requested_url.isValid() and
not requested_url.matches(url, no_formatting)):
@ -274,23 +152,135 @@ class WebHistory(QObject):
(hidden in completion)
atime: Override the atime used to add the entry
"""
if config.get('general', 'private-browsing'):
return
if not url.isValid():
if not url.isValid(): # pragma: no cover
# the no cover pragma is a WORKAROUND for this not being covered in
# old Qt versions.
log.misc.warning("Ignoring invalid URL being added to history")
return
if atime is None:
atime = time.time()
entry = Entry(atime, url, title, redirect=redirect)
if self._initial_read_done:
self._add_entry(entry)
self._new_history.append(entry)
self.item_added.emit(entry)
if not entry.redirect:
self.add_completion_item.emit(entry)
atime = int(atime) if (atime is not None) else int(time.time())
url_str = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
self.insert({'url': url_str,
'title': title,
'atime': atime,
'redirect': redirect})
if not redirect:
self.completion.insert({'url': url_str,
'title': title,
'last_atime': atime},
replace=True)
def _parse_entry(self, line):
"""Parse a history line like '12345 http://example.com title'."""
if not line or line.startswith('#'):
return None
data = line.split(maxsplit=2)
if len(data) == 2:
atime, url = data
title = ""
elif len(data) == 3:
atime, url, title = data
else:
self._add_entry(entry, target=self._temp_history)
raise ValueError("2 or 3 fields expected")
# http://xn--pple-43d.com/ with
# https://bugreports.qt.io/browse/QTBUG-60364
if url in ['http://.com/', 'https://www..com/']:
return None
url = QUrl(url)
if not url.isValid():
raise ValueError("Invalid URL: {}".format(url.errorString()))
# https://github.com/qutebrowser/qutebrowser/issues/2646
if url.scheme() == 'data':
return None
# https://github.com/qutebrowser/qutebrowser/issues/670
atime = atime.lstrip('\0')
if '-' in atime:
atime, flags = atime.split('-')
else:
flags = ''
if not set(flags).issubset('r'):
raise ValueError("Invalid flags {!r}".format(flags))
redirect = 'r' in flags
return (url, title, int(atime), redirect)
def import_txt(self):
"""Import a history text file into sqlite if it exists.
In older versions of qutebrowser, history was stored in a text format.
This converts that file into the new sqlite format and moves it to a
backup location.
"""
path = os.path.join(standarddir.data(), 'history')
if not os.path.isfile(path):
return
def action():
with debug.log_time(log.init, 'Import old history file to sqlite'):
try:
self._read(path)
except ValueError as ex:
message.error('Failed to import history: {}'.format(ex))
else:
bakpath = path + '.bak'
message.info('History import complete. Moving {} to {}'
.format(path, bakpath))
os.rename(path, bakpath)
# delay to give message time to appear before locking down for import
message.info('Converting {} to sqlite...'.format(path))
QTimer.singleShot(100, action)
def _read(self, path):
"""Import a text file into the sql database."""
with open(path, 'r', encoding='utf-8') as f:
data = {'url': [], 'title': [], 'atime': [], 'redirect': []}
completion_data = {'url': [], 'title': [], 'last_atime': []}
for (i, line) in enumerate(f):
try:
parsed = self._parse_entry(line.strip())
if parsed is None:
continue
url, title, atime, redirect = parsed
data['url'].append(url)
data['title'].append(title)
data['atime'].append(atime)
data['redirect'].append(redirect)
if not redirect:
completion_data['url'].append(url)
completion_data['title'].append(title)
completion_data['last_atime'].append(atime)
except ValueError as ex:
raise ValueError('Failed to parse line #{} of {}: "{}"'
.format(i, path, ex))
self.insert_batch(data)
self.completion.insert_batch(completion_data, replace=True)
@cmdutils.register(instance='web-history', debug=True)
def debug_dump_history(self, dest):
"""Dump the history to a file in the old pre-SQL format.
Args:
dest: Where to write the file to.
"""
dest = os.path.expanduser(dest)
lines = ('{}{} {} {}'
.format(int(x.atime), '-r' * x.redirect, x.url, x.title)
for x in self.select(sort_by='atime', sort_order='asc'))
try:
with open(dest, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines))
message.info("Dumped history to {}".format(dest))
except OSError as e:
raise cmdexc.CommandError('Could not write history: {}', e)
def init(parent=None):
@ -299,8 +289,7 @@ def init(parent=None):
Args:
parent: The parent to use for WebHistory.
"""
history = WebHistory(hist_dir=standarddir.data(), hist_name='history',
parent=parent)
history = WebHistory(parent=parent)
objreg.register('web-history', history)
if objects.backend == usertypes.Backend.QtWebKit:

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -79,8 +79,7 @@ def _find_prevnext(prev, elems):
return e
# Then check for regular links/buttons.
filterfunc = webelem.FILTERS[webelem.Group.prevnext]
elems = [e for e in elems if e.tag_name() != 'link' and filterfunc(e)]
elems = [e for e in elems if e.tag_name() != 'link']
option = 'prev-regexes' if prev else 'next-regexes'
if not elems:
return None
@ -128,20 +127,21 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
return
qtutils.ensure_valid(url)
cur_tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
if window:
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow()
new_window = mainwindow.MainWindow(
private=cur_tabbed_browser.private)
new_window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=new_window.win_id)
tabbed_browser.tabopen(url, background=False)
elif tab:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
tabbed_browser.tabopen(url, background=background)
cur_tabbed_browser.tabopen(url, background=background)
else:
browsertab.openurl(url)
selector = ', '.join([webelem.SELECTORS[webelem.Group.links],
webelem.SELECTORS[webelem.Group.prevnext]])
browsertab.elements.find_css(selector, _prevnext_cb)
browsertab.elements.find_css(webelem.SELECTORS[webelem.Group.links],
_prevnext_cb)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -312,4 +312,4 @@ class PACFetcher(QObject):
# Later NetworkManager.createRequest will detect this and display
# an error message.
error_host = "pac-resolve-error.qutebrowser.invalid"
return QNetworkProxy(QNetworkProxy.HttpProxy, error_host, 9)
return [QNetworkProxy(QNetworkProxy.HttpProxy, error_host, 9)]

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 Daniel Schadt
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -27,6 +27,7 @@ import collections
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.browser import downloads
from qutebrowser.browser.webkit import http
@ -273,7 +274,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
if self.fileobj is None or self._reply is None:
# No filename has been set yet (so we don't empty the buffer) or we
# got a readyRead after the reply was finished (which happens on
# qute:log for example).
# qute://log for example).
return
if not self._reply.isOpen():
raise OSError("Reply is closed!")
@ -366,7 +367,8 @@ class DownloadManager(downloads.AbstractDownloadManager):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._networkmanager = networkmanager.NetworkManager(
win_id, None, self)
win_id=win_id, tab_id=None,
private=config.get('general', 'private-browsing'), parent=self)
@pyqtSlot('QUrl')
def get(self, url, *, user_agent=None, **kwargs):
@ -410,7 +412,8 @@ class DownloadManager(downloads.AbstractDownloadManager):
mhtml.start_download_checked, tab=tab))
message.global_bridge.ask(question, blocking=False)
def get_request(self, request, *, target=None, **kwargs):
def get_request(self, request, *, target=None,
suggested_fn=None, **kwargs):
"""Start a download with a QNetworkRequest.
Args:
@ -426,7 +429,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
QNetworkRequest.AlwaysNetwork)
if request.url().scheme().lower() != 'data':
if suggested_fn is not None:
pass
elif request.url().scheme().lower() != 'data':
suggested_fn = urlutils.filename_from_url(request.url())
else:
# We might be downloading a binary blob embedded on a page or even

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Backend-independent qute:* code.
"""Backend-independent qute://* code.
Module attributes:
pyeval_output: The output of the last :pyeval command.
@ -26,16 +26,17 @@ Module attributes:
import json
import os
import sys
import time
import urllib.parse
import datetime
import pkg_resources
from PyQt5.QtCore import QUrlQuery
from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
from qutebrowser.config import config
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg)
objreg, usertypes, qtutils)
from qutebrowser.misc import objects
@ -77,12 +78,25 @@ class QuteSchemeError(Exception):
super().__init__(errorstring)
class add_handler: # pylint: disable=invalid-name
class Redirect(Exception):
"""Decorator to register a qute:* URL handler.
"""Exception to signal a redirect should happen.
Attributes:
_name: The 'foo' part of qute:foo
url: The URL to redirect to, as a QUrl.
"""
def __init__(self, url):
super().__init__(url.toDisplayString())
self.url = url
class add_handler: # pylint: disable=invalid-name
"""Decorator to register a qute://* URL handler.
Attributes:
_name: The 'foo' part of qute://foo
backend: Limit which backends the handler can run with.
"""
@ -105,7 +119,7 @@ class add_handler: # 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",
title="Error while opening qute://url",
url=url.toDisplayString(),
error='{} is not available with this '
'backend'.format(url.toDisplayString()),
@ -127,13 +141,19 @@ def data_for_url(url):
# A url like "qute:foo" is split as "scheme:path", not "scheme:host".
log.misc.debug("url: {}, path: {}, host {}".format(
url.toDisplayString(), path, host))
if path and not host:
new_url = QUrl()
new_url.setScheme('qute')
new_url.setHost(path)
new_url.setPath('/')
if new_url.host(): # path was a valid host
raise Redirect(new_url)
try:
handler = _HANDLERS[path]
handler = _HANDLERS[host]
except KeyError:
try:
handler = _HANDLERS[host]
except KeyError:
raise NoHandlerFound(url)
raise NoHandlerFound(url)
try:
mimetype, data = handler(url)
except OSError as e:
@ -152,7 +172,7 @@ def data_for_url(url):
@add_handler('bookmarks')
def qute_bookmarks(_url):
"""Handler for qute:bookmarks. Display all quickmarks / bookmarks."""
"""Handler for qute://bookmarks. Display all quickmarks / bookmarks."""
bookmarks = sorted(objreg.get('bookmark-manager').marks.items(),
key=lambda x: x[1]) # Sort by title
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
@ -165,68 +185,36 @@ def qute_bookmarks(_url):
return 'text/html', html
@add_handler('history') # noqa
def history_data(start_time, offset=None):
"""Return history data.
Arguments:
start_time: select history starting from this timestamp.
offset: number of items to skip
"""
# history atimes are stored as ints, ensure start_time is not a float
start_time = int(start_time)
hist = objreg.get('web-history')
if offset is not None:
entries = hist.entries_before(start_time, limit=1000, offset=offset)
else:
# end is 24hrs earlier than start
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]
@add_handler('history')
def qute_history(url):
"""Handler for qute:history. Display and serve history."""
def history_iter(start_time, reverse=False):
"""Iterate through the history and get items we're interested.
Arguments:
reverse -- whether to reverse the history_dict before iterating.
start_time -- select history starting from this timestamp.
"""
history = objreg.get('web-history').history_dict.values()
if reverse:
history = reversed(history)
end_time = start_time - 24*60*60 # end is 24hrs earlier than start
# when history_dict is not reversed, we need to keep track of last item
# so that we can yield its atime
last_item = None
for item in history:
# Skip redirects
# Skip qute:// links
if item.redirect or item.url.scheme() == 'qute':
continue
# Skip items out of time window
item_newer = item.atime > start_time
item_older = item.atime <= end_time
if reverse:
# history_dict is reversed, we are going back in history.
# so:
# abort if item is older than start_time+24hr
# skip if item is newer than start
if item_older:
yield {"next": int(item.atime)}
return
if item_newer:
continue
else:
# history_dict isn't reversed, we are going forward in history.
# so:
# abort if item is newer than start_time
# skip if item is older than start_time+24hrs
if item_older:
last_item = item
continue
if item_newer:
yield {"next": int(last_item.atime if last_item else -1)}
return
# Use item's url as title if there's no title.
item_url = item.url.toDisplayString()
item_title = item.title if item.title else item_url
item_time = int(item.atime * 1000)
yield {"url": item_url, "title": item_title, "time": item_time}
# if we reached here, we had reached the end of history
yield {"next": int(last_item.atime if last_item else -1)}
"""Handler for qute://history. Display and serve history."""
if url.path() == '/data':
try:
offset = QUrlQuery(url).queryItemValue("offset")
offset = int(offset) if offset else None
except ValueError as e:
raise QuteSchemeError("Query parameter offset is invalid", e)
# Use start_time in query or current time.
try:
start_time = QUrlQuery(url).queryItemValue("start_time")
@ -234,26 +222,57 @@ def qute_history(url):
except ValueError as e:
raise QuteSchemeError("Query parameter start_time is invalid", e)
if sys.hexversion >= 0x03050000:
# On Python >= 3.5 we can reverse the ordereddict in-place and thus
# apply an additional performance improvement in history_iter.
# On my machine, this gets us down from 550ms to 72us with 500k old
# items.
history = history_iter(start_time, reverse=True)
else:
# On Python 3.4, we can't do that, so we'd need to copy the entire
# history to a list. There, filter first and then reverse it here.
history = reversed(list(history_iter(start_time, reverse=False)))
return 'text/html', json.dumps(list(history))
return 'text/html', json.dumps(history_data(start_time, offset))
else:
return 'text/html', jinja.render('history.html', title='History',
session_interval=config.get('ui', 'history-session-interval'))
if (
config.get('content', 'allow-javascript') and
(objects.backend == usertypes.Backend.QtWebEngine or
qtutils.is_qtwebkit_ng())
):
return 'text/html', jinja.render(
'history.html',
title='History',
session_interval=config.get('ui', 'history-session-interval')
)
else:
# Get current date from query parameter, if not given choose today.
curr_date = datetime.date.today()
try:
query_date = QUrlQuery(url).queryItemValue("date")
if query_date:
curr_date = datetime.datetime.strptime(query_date,
"%Y-%m-%d").date()
except ValueError:
log.misc.debug("Invalid date passed to qute:history: " +
query_date)
one_day = datetime.timedelta(days=1)
next_date = curr_date + one_day
prev_date = curr_date - one_day
# start_time is the last second of curr_date
start_time = time.mktime(next_date.timetuple()) - 1
history = [
(i["url"], i["title"],
datetime.datetime.fromtimestamp(i["time"]),
QUrl(i["url"]).host())
for i in history_data(start_time)
]
return 'text/html', jinja.render(
'history_nojs.html',
title='History',
history=history,
curr_date=curr_date,
next_date=next_date,
prev_date=prev_date,
today=datetime.date.today(),
)
@add_handler('javascript')
def qute_javascript(url):
"""Handler for qute:javascript.
"""Handler for qute://javascript.
Return content of file given as query parameter.
"""
@ -267,7 +286,7 @@ def qute_javascript(url):
@add_handler('pyeval')
def qute_pyeval(_url):
"""Handler for qute:pyeval."""
"""Handler for qute://pyeval."""
html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', html
@ -275,7 +294,7 @@ def qute_pyeval(_url):
@add_handler('version')
@add_handler('verizon')
def qute_version(_url):
"""Handler for qute:version."""
"""Handler for qute://version."""
html = jinja.render('version.html', title='Version info',
version=version.version(),
copyright=qutebrowser.__copyright__)
@ -284,7 +303,7 @@ def qute_version(_url):
@add_handler('plainlog')
def qute_plainlog(url):
"""Handler for qute:plainlog.
"""Handler for qute://plainlog.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
@ -304,7 +323,7 @@ def qute_plainlog(url):
@add_handler('log')
def qute_log(url):
"""Handler for qute:log.
"""Handler for qute://log.
An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors.
@ -325,13 +344,13 @@ def qute_log(url):
@add_handler('gpl')
def qute_gpl(_url):
"""Handler for qute:gpl. Return HTML content as string."""
"""Handler for qute://gpl. Return HTML content as string."""
return 'text/html', utils.read_file('html/COPYING.html')
@add_handler('help')
def qute_help(url):
"""Handler for qute:help."""
"""Handler for qute://help."""
try:
utils.read_file('html/doc/index.html')
except OSError:
@ -360,3 +379,14 @@ def qute_help(url):
else:
data = utils.read_file(path)
return 'text/html', data
@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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -216,8 +216,10 @@ def get_tab(win_id, target):
win_id = win_id
bg_tab = True
elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow()
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
win_id = window.win_id
bg_tab = False

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2016 Antoni Boucher <bouanto@zoho.com>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Antoni Boucher <bouanto@zoho.com>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -22,9 +22,6 @@
Module attributes:
Group: Enum for different kinds of groups.
SELECTORS: CSS selectors for different groups of elements.
FILTERS: A dictionary of filter functions for the modes.
The filter for "links" filters javascript:-links and a-tags
without "href".
"""
import collections.abc
@ -37,18 +34,16 @@ from qutebrowser.keyinput import modeman
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'prevnext',
'inputs'])
Group = usertypes.enum('Group', ['all', 'links', 'images', 'url', 'inputs'])
SELECTORS = {
Group.all: ('a, area, textarea, select, input:not([type=hidden]), button, '
'frame, iframe, link, [onclick], [onmousedown], [role=link], '
'[role=option], [role=button], img'),
Group.links: 'a, area, link, [role=link]',
Group.links: 'a[href], area[href], link[href], [role=link][href]',
Group.images: 'img',
Group.url: '[src], [href]',
Group.prevnext: 'a, area, button, link, [role=button]',
Group.inputs: ('input[type=text], input[type=email], input[type=url], '
'input[type=tel], input[type=number], '
'input[type=password], input[type=search], '
@ -56,16 +51,6 @@ SELECTORS = {
}
def filter_links(elem):
return 'href' in elem and QUrl(elem['href']).scheme() != 'javascript'
FILTERS = {
Group.links: filter_links,
Group.prevnext: filter_links,
}
class Error(Exception):
"""Base class for WebElement errors."""
@ -306,6 +291,11 @@ class AbstractWebElement(collections.abc.MutableMapping):
qtutils.ensure_valid(url)
return url
def is_link(self):
"""Return True if this AbstractWebElement is a link."""
href_tags = ['a', 'area', 'link']
return self.tag_name() in href_tags and 'href' in self
def _mouse_pos(self):
"""Get the position to click/hover."""
# Click the center of the largest square fitting into the top/left
@ -374,15 +364,16 @@ class AbstractWebElement(collections.abc.MutableMapping):
self._click_fake_event(click_target)
return
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
if click_target in [usertypes.ClickTarget.tab,
usertypes.ClickTarget.tab_bg]:
background = click_target == usertypes.ClickTarget.tab_bg
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._tab.win_id)
tabbed_browser.tabopen(url, background=background)
elif click_target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow()
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
window.tabbed_browser.tabopen(url)
else:
@ -403,9 +394,8 @@ class AbstractWebElement(collections.abc.MutableMapping):
self._click_fake_event(click_target)
return
href_tags = ['a', 'area', 'link']
if click_target == usertypes.ClickTarget.normal:
if self.tag_name() in href_tags:
if self.is_link():
log.webelem.debug("Clicking via JS click()")
self._click_js(click_target)
elif self.is_editable(strict=True):
@ -418,7 +408,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
elif click_target in [usertypes.ClickTarget.tab,
usertypes.ClickTarget.tab_bg,
usertypes.ClickTarget.window]:
if self.tag_name() in href_tags:
if self.is_link():
self._click_href(click_target)
else:
self._click_fake_event(click_target)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -19,9 +19,7 @@
"""Wrapper over a QWebEngineCertificateError."""
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineCertificateError
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.utils import usertypes, utils, debug

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -19,9 +19,7 @@
"""A request interceptor taking care of adblocking and custom headers."""
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInterceptor
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.config import config
from qutebrowser.browser import shared
@ -57,8 +55,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor):
info: QWebEngineUrlRequestInfo &info
"""
# FIXME:qtwebengine only block ads for NavigationTypeOther?
if (bytes(info.requestMethod()) == b'GET' and
self._host_blocker.is_blocked(info.requestUrl())):
if self._host_blocker.is_blocked(info.requestUrl()):
log.webview.info("Request to {} blocked by host blocker.".format(
info.requestUrl().host()))
info.block(True)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -25,9 +25,7 @@ import urllib
import functools
from PyQt5.QtCore import pyqtSlot, Qt
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import downloads
from qutebrowser.utils import debug, usertypes, message, log, qtutils
@ -79,7 +77,12 @@ class DownloadItem(downloads.AbstractDownloadItem):
elif state == QWebEngineDownloadItem.DownloadInterrupted:
self.successful = False
# https://bugreports.qt.io/browse/QTBUG-56839
self._die("Download failed")
try:
reason = self._qt_item.interruptReasonString()
except AttributeError:
# Qt < 5.9
reason = "Download failed"
self._die(reason)
else:
raise ValueError("_on_state_changed was called with unknown state "
"{}".format(state_name))
@ -100,7 +103,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
def _get_open_filename(self):
return self._filename
def _set_fileobj(self, fileobj):
def _set_fileobj(self, fileobj, *,
autoclose=True): # pylint: disable=unused-argument
raise downloads.UnsupportedOperationError
def _set_tempfile(self, fileobj):
@ -146,7 +150,7 @@ def _get_suggested_filename(path):
"""
filename = os.path.basename(path)
filename = re.sub(r'\([0-9]+\)(?=\.|$)', '', filename)
if not qtutils.version_check('5.8.1'):
if not qtutils.version_check('5.9'):
# https://bugreports.qt.io/browse/QTBUG-58155
filename = urllib.parse.unquote(filename)
# Doing basename a *second* time because there could be a %2F in

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -18,16 +18,14 @@
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# FIXME:qtwebengine remove this once the stubs are gone
# pylint: disable=unused-variable
# pylint: disable=unused-argument
"""QtWebEngine specific part of the web element API."""
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.utils import log, javascript
from qutebrowser.browser import webelem
@ -39,6 +37,38 @@ class WebEngineElement(webelem.AbstractWebElement):
def __init__(self, js_dict, tab):
super().__init__(tab)
# Do some sanity checks on the data we get from JS
js_dict_types = {
'id': int,
'text': str,
'value': (str, int, float),
'tag_name': str,
'outer_xml': str,
'class_name': str,
'rects': list,
'attributes': dict,
}
assert set(js_dict.keys()).issubset(js_dict_types.keys())
for name, typ in js_dict_types.items():
if name in js_dict and not isinstance(js_dict[name], typ):
raise TypeError("Got {} for {} from JS but expected {}: "
"{}".format(type(js_dict[name]), name, typ,
js_dict))
for name, value in js_dict['attributes'].items():
if not isinstance(name, str):
raise TypeError("Got {} ({}) for attribute name from JS: "
"{}".format(name, type(name), js_dict))
if not isinstance(value, str):
raise TypeError("Got {} ({}) for attribute {} from JS: "
"{}".format(value, type(value), name, js_dict))
for rect in js_dict['rects']:
assert set(rect.keys()) == {'top', 'right', 'bottom', 'left',
'height', 'width'}, rect.keys()
for value in rect.values():
if not isinstance(value, (int, float)):
raise TypeError("Got {} ({}) for rect from JS: "
"{}".format(value, type(value), js_dict))
self._id = js_dict['id']
self._js_dict = js_dict
@ -88,7 +118,9 @@ class WebEngineElement(webelem.AbstractWebElement):
The returned name will always be lower-case.
"""
return self._js_dict['tag_name'].lower()
tag = self._js_dict['tag_name']
assert isinstance(tag, str), tag
return tag.lower()
def outer_xml(self):
"""Get the full HTML representation of this element."""
@ -158,21 +190,19 @@ class WebEngineElement(webelem.AbstractWebElement):
def _click_editable(self, click_target):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
# pylint doesn't know about Qt.MouseEventSynthesizedBySystem
# because it was added in Qt 5.6, but we can be sure we use that with
# QtWebEngine.
# pylint: disable=no-member,useless-suppression
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
Qt.NoModifier, Qt.MouseEventSynthesizedBySystem)
# pylint: enable=no-member,useless-suppression
self._tab.send_event(ev)
# This actually "clicks" the element by calling focus() on it in JS.
self._js_call('focus')
self._move_text_cursor()
def _click_js(self, _click_target):
settings = QWebEngineSettings.globalSettings()
# FIXME:qtwebengine Have a proper API for this
# pylint: disable=protected-access
settings = self._tab._widget.settings()
# pylint: enable=protected-access
attribute = QWebEngineSettings.JavascriptCanOpenWindows
could_open_windows = settings.testAttribute(attribute)
settings.setAttribute(attribute, True)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -22,9 +22,7 @@
import os
from PyQt5.QtCore import QUrl
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineView
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import inspector

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -17,24 +17,22 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""QtWebEngine specific qute:* handlers and glue code."""
"""QtWebEngine specific qute://* handlers and glue code."""
from PyQt5.QtCore import QBuffer, QIODevice
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineCore import (QWebEngineUrlSchemeHandler,
QWebEngineUrlRequestJob)
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import qutescheme
from qutebrowser.utils import log
from qutebrowser.utils import log, qtutils
class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
"""Handle qute:* requests on QtWebEngine."""
"""Handle qute://* requests on QtWebEngine."""
def install(self, profile):
"""Install the handler for qute: URLs on the given profile."""
"""Install the handler for qute:// URLs on the given profile."""
profile.installUrlSchemeHandler(b'qute', self)
def requestStarted(self, job):
@ -58,12 +56,15 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
except qutescheme.QuteSchemeOSError:
# FIXME:qtwebengine how do we show a better error here?
log.misc.exception("OSError while handling qute:* URL")
log.misc.exception("OSError while handling qute://* URL")
job.fail(QWebEngineUrlRequestJob.UrlNotFound)
except qutescheme.QuteSchemeError:
# FIXME:qtwebengine how do we show a better error here?
log.misc.exception("Error while handling qute:* URL")
log.misc.exception("Error while handling qute://* URL")
job.fail(QWebEngineUrlRequestJob.RequestFailed)
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
job.redirect(e.url)
else:
log.misc.debug("Returning {} data".format(mimetype))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# We get various "abstract but not overridden" warnings
# pylint: disable=abstract-method
"""Bridge from QWebEngineSettings to our own settings.
Module attributes:
@ -25,81 +28,96 @@ Module attributes:
"""
import os
import logging
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtGui import QFont
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
QWebEngineScript)
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import shared
from qutebrowser.config import config, websettings
from qutebrowser.utils import objreg, utils, standarddir, javascript, log
from qutebrowser.utils import objreg, utils, standarddir, javascript, qtutils
class Attribute(websettings.Attribute):
# The default QWebEngineProfile
default_profile = None
# The QWebEngineProfile used for private (off-the-record) windows
private_profile = None
class Base(websettings.Base):
"""Base settings class with appropriate _get_global_settings."""
def _get_global_settings(self):
return [default_profile.settings(), private_profile.settings()]
class Attribute(Base, websettings.Attribute):
"""A setting set via QWebEngineSettings::setAttribute."""
GLOBAL_SETTINGS = QWebEngineSettings.globalSettings
ENUM_BASE = QWebEngineSettings
class Setter(websettings.Setter):
class Setter(Base, websettings.Setter):
"""A setting set via QWebEngineSettings getter/setter methods."""
"""A setting set via a QWebEngineSettings setter method."""
GLOBAL_SETTINGS = QWebEngineSettings.globalSettings
pass
class NullStringSetter(websettings.NullStringSetter):
class FontFamilySetter(Base, websettings.FontFamilySetter):
"""A setter for settings requiring a null QString as default."""
"""A setter for a font family.
GLOBAL_SETTINGS = QWebEngineSettings.globalSettings
Gets the default value from QFont.
"""
def __init__(self, font):
# Mapping from WebEngineSettings::initDefaults in
# qtwebengine/src/core/web_engine_settings.cpp
font_to_qfont = {
QWebEngineSettings.StandardFont: QFont.Serif,
QWebEngineSettings.FixedFont: QFont.Monospace,
QWebEngineSettings.SerifFont: QFont.Serif,
QWebEngineSettings.SansSerifFont: QFont.SansSerif,
QWebEngineSettings.CursiveFont: QFont.Cursive,
QWebEngineSettings.FantasyFont: QFont.Fantasy,
}
super().__init__(setter=QWebEngineSettings.setFontFamily, font=font,
qfont=font_to_qfont[font])
class StaticSetter(websettings.StaticSetter):
"""A setting set via static QWebEngineSettings getter/setter methods."""
GLOBAL_SETTINGS = QWebEngineSettings.globalSettings
class ProfileSetter(websettings.Base):
class DefaultProfileSetter(websettings.Base):
"""A setting set on the QWebEngineProfile."""
def __init__(self, getter, setter):
super().__init__()
self._getter = getter
def __init__(self, setter, default=websettings.UNSET):
super().__init__(default)
self._setter = setter
def get(self, settings=None):
utils.unused(settings)
getter = getattr(QWebEngineProfile.defaultProfile(), self._getter)
return getter()
def __repr__(self):
return utils.get_repr(self, setter=self._setter, constructor=True)
def _set(self, value, settings=None):
utils.unused(settings)
setter = getattr(QWebEngineProfile.defaultProfile(), self._setter)
if settings is not None:
raise ValueError("'settings' may not be set with "
"DefaultProfileSetters!")
setter = getattr(default_profile, self._setter)
setter(value)
class PersistentCookiePolicy(ProfileSetter):
class PersistentCookiePolicy(DefaultProfileSetter):
"""The cookies -> store setting is different from other settings."""
def __init__(self):
super().__init__(getter='persistentCookiesPolicy',
setter='setPersistentCookiesPolicy')
def get(self, settings=None):
utils.unused(settings)
return config.get('content', 'cookies-store')
super().__init__('setPersistentCookiesPolicy')
def _set(self, value, settings=None):
utils.unused(settings)
if settings is not None:
raise ValueError("'settings' may not be set with "
"PersistentCookiePolicy!")
setter = getattr(QWebEngineProfile.defaultProfile(), self._setter)
setter(
QWebEngineProfile.AllowPersistentCookies if value else
@ -113,9 +131,6 @@ def _init_stylesheet(profile):
Mostly inspired by QupZilla:
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/app/mainapplication.cpp#L1063-L1101
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/tools/scripts.cpp#L119-L132
FIXME:qtwebengine Use QWebEngineStyleSheet once that's available
https://codereview.qt-project.org/#/c/148671/
"""
old_script = profile.scripts().findScript('_qute_stylesheet')
if not old_script.isNull():
@ -140,19 +155,43 @@ def _init_stylesheet(profile):
profile.scripts().insert(script)
def _init_profile(profile):
"""Initialize settings set on the QWebEngineProfile."""
profile.setCachePath(os.path.join(standarddir.cache(), 'webengine'))
profile.setPersistentStoragePath(
os.path.join(standarddir.data(), 'webengine'))
def _set_user_agent(profile):
"""Set the user agent for the given profile.
We override this per request in the URL interceptor (to allow for
per-domain user agents), but this one still gets used for things like
window.navigator.userAgent in JS.
"""
user_agent = config.get('network', 'user-agent')
profile.setHttpUserAgent(user_agent)
def update_settings(section, option):
"""Update global settings when qwebsettings changed."""
websettings.update_mappings(MAPPINGS, section, option)
profile = QWebEngineProfile.defaultProfile()
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
_init_stylesheet(profile)
_init_stylesheet(default_profile)
_init_stylesheet(private_profile)
elif section == 'network' and option == 'user-agent':
_set_user_agent(default_profile)
_set_user_agent(private_profile)
def _init_profiles():
"""Init the two used QWebEngineProfiles."""
global default_profile, private_profile
default_profile = QWebEngineProfile.defaultProfile()
default_profile.setCachePath(
os.path.join(standarddir.cache(), 'webengine'))
default_profile.setPersistentStoragePath(
os.path.join(standarddir.data(), 'webengine'))
_init_stylesheet(default_profile)
_set_user_agent(default_profile)
private_profile = QWebEngineProfile()
assert private_profile.isOffTheRecord()
_init_stylesheet(private_profile)
_set_user_agent(private_profile)
def init(args):
@ -160,25 +199,12 @@ def init(args):
if args.enable_webengine_inspector:
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
# Workaround for a black screen with some setups
# https://github.com/spyder-ide/spyder/issues/3226
if not os.environ.get('QUTE_NO_OPENGL_WORKAROUND'):
# Hide "No OpenGL_accelerate module loaded: ..." message
logging.getLogger('OpenGL.acceleratesupport').propagate = False
try:
from OpenGL import GL # pylint: disable=unused-variable
except ImportError:
pass
else:
log.misc.debug("Imported PyOpenGL as workaround")
_init_profiles()
profile = QWebEngineProfile.defaultProfile()
_init_profile(profile)
_init_stylesheet(profile)
# We need to do this here as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58650
PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
if not qtutils.version_check('5.9'):
PersistentCookiePolicy().set(config.get('content', 'cookies-store'))
Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
websettings.init_mappings(MAPPINGS)
@ -199,7 +225,6 @@ def shutdown():
# - AllowRunningInsecureContent (5.8)
#
# Missing QtWebEngine fonts:
# - FantasyFont
# - PictographFont
@ -221,9 +246,6 @@ MAPPINGS = {
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
'local-content-can-access-file-urls':
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
# https://bugreports.qt.io/browse/QTBUG-58650
# 'cookies-store':
# PersistentCookiePolicy(),
'webgl':
Attribute(QWebEngineSettings.WebGLEnabled),
},
@ -235,44 +257,28 @@ MAPPINGS = {
},
'fonts': {
'web-family-standard':
Setter(getter=QWebEngineSettings.fontFamily,
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.StandardFont]),
FontFamilySetter(QWebEngineSettings.StandardFont),
'web-family-fixed':
Setter(getter=QWebEngineSettings.fontFamily,
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.FixedFont]),
FontFamilySetter(QWebEngineSettings.FixedFont),
'web-family-serif':
Setter(getter=QWebEngineSettings.fontFamily,
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.SerifFont]),
FontFamilySetter(QWebEngineSettings.SerifFont),
'web-family-sans-serif':
Setter(getter=QWebEngineSettings.fontFamily,
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.SansSerifFont]),
FontFamilySetter(QWebEngineSettings.SansSerifFont),
'web-family-cursive':
Setter(getter=QWebEngineSettings.fontFamily,
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.CursiveFont]),
FontFamilySetter(QWebEngineSettings.CursiveFont),
'web-family-fantasy':
Setter(getter=QWebEngineSettings.fontFamily,
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.FantasyFont]),
FontFamilySetter(QWebEngineSettings.FantasyFont),
'web-size-minimum':
Setter(getter=QWebEngineSettings.fontSize,
setter=QWebEngineSettings.setFontSize,
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumFontSize]),
'web-size-minimum-logical':
Setter(getter=QWebEngineSettings.fontSize,
setter=QWebEngineSettings.setFontSize,
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumLogicalFontSize]),
'web-size-default':
Setter(getter=QWebEngineSettings.fontSize,
setter=QWebEngineSettings.setFontSize,
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFontSize]),
'web-size-default-fixed':
Setter(getter=QWebEngineSettings.fontSize,
setter=QWebEngineSettings.setFontSize,
Setter(QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFixedFontSize]),
},
'ui': {
@ -283,15 +289,14 @@ MAPPINGS = {
'local-storage':
Attribute(QWebEngineSettings.LocalStorageEnabled),
'cache-size':
ProfileSetter(getter='httpCacheMaximumSize',
setter='setHttpCacheMaximumSize')
# 0: automatically managed by QtWebEngine
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
},
'general': {
'xss-auditing':
Attribute(QWebEngineSettings.XSSAuditingEnabled),
'default-encoding':
Setter(getter=QWebEngineSettings.defaultTextEncoding,
setter=QWebEngineSettings.setDefaultTextEncoding),
Setter(QWebEngineSettings.setDefaultTextEncoding),
}
}
@ -301,3 +306,8 @@ try:
except AttributeError:
# Added in Qt 5.8
pass
if qtutils.version_check('5.9'):
# https://bugreports.qt.io/browse/QTBUG-58650
MAPPINGS['content']['cookies-store'] = PersistentCookiePolicy()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -17,30 +17,27 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# FIXME:qtwebengine remove this once the stubs are gone
# pylint: disable=unused-variable
"""Wrapper over a QWebEngineView."""
import os
import math
import functools
import sip
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtNetwork import QAuthenticator
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineScript,
QWebEngineProfile)
# pylint: enable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
from qutebrowser.browser import browsertab, mouse, shared
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
interceptor, webenginequtescheme,
webenginedownloads)
webenginedownloads,
webenginesettings)
from qutebrowser.misc import miscwidgets
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
objreg, jinja)
objreg, jinja, debug, version)
_qute_scheme_handler = None
@ -53,21 +50,31 @@ def init():
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
global _qute_scheme_handler
app = QApplication.instance()
profile = QWebEngineProfile.defaultProfile()
log.init.debug("Initializing qute:* handler...")
software_rendering = (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or
'QT_XCB_FORCE_SOFTWARE_OPENGL' in os.environ)
if version.opengl_vendor() == 'nouveau' and not software_rendering:
# FIXME:qtwebengine display something more sophisticated here
raise browsertab.WebTabError(
"QtWebEngine is not supported with Nouveau graphics (unless "
"QT_XCB_FORCE_SOFTWARE_OPENGL is set as environment variable).")
log.init.debug("Initializing qute://* handler...")
_qute_scheme_handler = webenginequtescheme.QuteSchemeHandler(parent=app)
_qute_scheme_handler.install(profile)
_qute_scheme_handler.install(webenginesettings.default_profile)
_qute_scheme_handler.install(webenginesettings.private_profile)
log.init.debug("Initializing request interceptor...")
host_blocker = objreg.get('host-blocker')
req_interceptor = interceptor.RequestInterceptor(
host_blocker, parent=app)
req_interceptor.install(profile)
req_interceptor.install(webenginesettings.default_profile)
req_interceptor.install(webenginesettings.private_profile)
log.init.debug("Initializing QtWebEngine downloads...")
download_manager = webenginedownloads.DownloadManager(parent=app)
download_manager.install(profile)
download_manager.install(webenginesettings.default_profile)
download_manager.install(webenginesettings.private_profile)
objreg.register('webengine-download-manager', download_manager)
@ -82,17 +89,17 @@ _JS_WORLD_MAP = {
class WebEngineAction(browsertab.AbstractAction):
"""QtWebKit implementations related to web actions."""
"""QtWebEngine implementations related to web actions."""
def _action(self, action):
self._widget.triggerPageAction(action)
action_class = QWebEnginePage
action_base = QWebEnginePage.WebAction
def exit_fullscreen(self):
self._action(QWebEnginePage.ExitFullScreen)
self._widget.triggerPageAction(QWebEnginePage.ExitFullScreen)
def save_page(self):
"""Save the current page."""
self._action(QWebEnginePage.SavePage)
self._widget.triggerPageAction(QWebEnginePage.SavePage)
class WebEnginePrinting(browsertab.AbstractPrinting):
@ -128,12 +135,23 @@ class WebEngineSearch(browsertab.AbstractSearch):
super().__init__(parent)
self._flags = QWebEnginePage.FindFlags(0)
def _find(self, text, flags, cb=None):
"""Call findText on the widget with optional callback."""
if cb is None:
self._widget.findText(text, flags)
else:
self._widget.findText(text, flags, cb)
def _find(self, text, flags, callback, caller):
"""Call findText on the widget."""
self.search_displayed = True
def wrapped_callback(found):
"""Wrap the callback to do debug logging."""
found_text = 'found' if found else "didn't find"
if flags:
flag_text = 'with flags {}'.format(debug.qflags_key(
QWebEnginePage, flags, klass=QWebEnginePage.FindFlag))
else:
flag_text = ''
log.webview.debug(' '.join([caller, found_text, text, flag_text])
.strip())
if callback is not None:
callback(found)
self._widget.findText(text, flags, wrapped_callback)
def search(self, text, *, ignore_case=False, reverse=False,
result_cb=None):
@ -148,9 +166,10 @@ class WebEngineSearch(browsertab.AbstractSearch):
self.text = text
self._flags = flags
self._find(text, flags, result_cb)
self._find(text, flags, result_cb, 'search')
def clear(self):
self.search_displayed = False
self._widget.findText('')
def prev_result(self, *, result_cb=None):
@ -160,10 +179,10 @@ class WebEngineSearch(browsertab.AbstractSearch):
flags &= ~QWebEnginePage.FindBackward
else:
flags |= QWebEnginePage.FindBackward
self._find(self.text, flags, result_cb)
self._find(self.text, flags, result_cb, 'prev_result')
def next_result(self, *, result_cb=None):
self._find(self.text, self._flags, result_cb)
self._find(self.text, self._flags, result_cb, 'next_result')
class WebEngineCaret(browsertab.AbstractCaret):
@ -237,8 +256,47 @@ class WebEngineCaret(browsertab.AbstractCaret):
raise browsertab.UnsupportedOperationError
return self._widget.selectedText()
def _follow_selected_cb(self, js_elem, tab=False):
"""Callback for javascript which clicks the selected element.
Args:
js_elem: The element serialized from javascript.
tab: Open in a new tab.
"""
if js_elem is None:
return
assert isinstance(js_elem, dict), js_elem
elem = webengineelem.WebEngineElement(js_elem, tab=self._tab)
if tab:
click_type = usertypes.ClickTarget.tab
else:
click_type = usertypes.ClickTarget.normal
# Only click if we see a link
if elem.is_link():
log.webview.debug("Found link in selection, clicking. ClickTarget "
"{}, elem {}".format(click_type, elem))
elem.click(click_type)
def follow_selected(self, *, tab=False):
log.stub()
if self._tab.search.search_displayed:
# We are currently in search mode.
# let's click the link via a fake-click
# https://bugreports.qt.io/browse/QTBUG-60673
self._tab.search.clear()
log.webview.debug("Clicking a searched link via fake key press.")
# send a fake enter, clicking the orange selection box
if tab:
self._tab.key_press(Qt.Key_Enter, modifier=Qt.ControlModifier)
else:
self._tab.key_press(Qt.Key_Enter)
else:
# click an existing blue selection
js_code = javascript.assemble('webelem', 'find_selected_link')
self._tab.run_js_async(js_code, lambda jsret:
self._follow_selected_cb(jsret, tab))
class WebEngineScroller(browsertab.AbstractScroller):
@ -256,13 +314,10 @@ class WebEngineScroller(browsertab.AbstractScroller):
page = widget.page()
page.scrollPositionChanged.connect(self._update_pos)
def _key_press(self, key, count=1):
def _repeated_key_press(self, key, count=1, modifier=Qt.NoModifier):
"""Send count fake key presses to this scroller's WebEngineTab."""
for _ in range(min(count, 5000)):
press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0)
release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier,
0, 0, 0)
self._tab.send_event(press_evt)
self._tab.send_event(release_evt)
self._tab.key_press(key, modifier)
@pyqtSlot()
def _update_pos(self):
@ -289,7 +344,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
else:
perc_y = min(100, round(100 / dy * jsret['px']['y']))
self._at_bottom = dy >= jsret['px']['y']
self._at_bottom = math.ceil(jsret['px']['y']) >= dy
self._pos_perc = perc_x, perc_y
self.perc_changed.emit(*self._pos_perc)
@ -319,28 +374,28 @@ class WebEngineScroller(browsertab.AbstractScroller):
self._tab.run_js_async(js_code)
def up(self, count=1):
self._key_press(Qt.Key_Up, count)
self._repeated_key_press(Qt.Key_Up, count)
def down(self, count=1):
self._key_press(Qt.Key_Down, count)
self._repeated_key_press(Qt.Key_Down, count)
def left(self, count=1):
self._key_press(Qt.Key_Left, count)
self._repeated_key_press(Qt.Key_Left, count)
def right(self, count=1):
self._key_press(Qt.Key_Right, count)
self._repeated_key_press(Qt.Key_Right, count)
def top(self):
self._key_press(Qt.Key_Home)
self._tab.key_press(Qt.Key_Home)
def bottom(self):
self._key_press(Qt.Key_End)
self._tab.key_press(Qt.Key_End)
def page_up(self, count=1):
self._key_press(Qt.Key_PageUp, count)
self._repeated_key_press(Qt.Key_PageUp, count)
def page_down(self, count=1):
self._key_press(Qt.Key_PageDown, count)
self._repeated_key_press(Qt.Key_PageDown, count)
def at_top(self):
return self.pos_px().y() == 0
@ -356,29 +411,28 @@ class WebEngineHistory(browsertab.AbstractHistory):
def current_idx(self):
return self._history.currentItemIndex()
def back(self):
self._history.back()
def forward(self):
self._history.forward()
def can_go_back(self):
return self._history.canGoBack()
def can_go_forward(self):
return self._history.canGoForward()
def _item_at(self, i):
return self._history.itemAt(i)
def _go_to_item(self, item):
return self._history.goToItem(item)
def serialize(self):
# WORKAROUND (remove this when we bump the requirements to 5.9)
# https://bugreports.qt.io/browse/QTBUG-59599
if self._history.count() == 0:
raise browsertab.WebTabError("Can't serialize page without "
"history!")
# WORKAROUND (FIXME: remove this when we bump the requirements to 5.9?)
# https://github.com/qutebrowser/qutebrowser/issues/2289
scheme = self._history.currentItem().url().scheme()
if scheme in ['view-source', 'chrome']:
raise browsertab.WebTabError("Can't serialize special URL!")
if not qtutils.version_check('5.9'):
# WORKAROUND for
# https://github.com/qutebrowser/qutebrowser/issues/2289
# Don't use the history's currentItem here, because of
# https://bugreports.qt.io/browse/QTBUG-59599 and because it doesn't
# contain view-source.
scheme = self._tab.url().scheme()
if scheme in ['view-source', 'chrome']:
raise browsertab.WebTabError("Can't serialize special URL!")
return qtutils.serialize(self._history)
def deserialize(self, data):
@ -448,18 +502,18 @@ class WebEngineElements(browsertab.AbstractElements):
callback(elem)
def find_css(self, selector, callback, *, only_visible=False):
js_code = javascript.assemble('webelem', 'find_all', selector,
js_code = javascript.assemble('webelem', 'find_css', selector,
only_visible)
js_cb = functools.partial(self._js_cb_multiple, callback)
self._tab.run_js_async(js_code, js_cb)
def find_id(self, elem_id, callback):
js_code = javascript.assemble('webelem', 'element_by_id', elem_id)
js_code = javascript.assemble('webelem', 'find_id', elem_id)
js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb)
def find_focused(self, callback):
js_code = javascript.assemble('webelem', 'focus_element')
js_code = javascript.assemble('webelem', 'find_focused')
js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb)
@ -467,7 +521,7 @@ class WebEngineElements(browsertab.AbstractElements):
assert pos.x() >= 0
assert pos.y() >= 0
pos /= self._tab.zoom.factor()
js_code = javascript.assemble('webelem', 'element_at_pos',
js_code = javascript.assemble('webelem', 'find_at_pos',
pos.x(), pos.y())
js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb)
@ -477,10 +531,11 @@ class WebEngineTab(browsertab.AbstractTab):
"""A QtWebEngine tab in the browser."""
def __init__(self, win_id, mode_manager, parent=None):
def __init__(self, *, win_id, mode_manager, private, parent=None):
super().__init__(win_id=win_id, mode_manager=mode_manager,
parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id)
private=private, parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
private=private)
self.history = WebEngineHistory(self)
self.scroller = WebEngineScroller(self, parent=self)
self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager,
@ -561,9 +616,11 @@ class WebEngineTab(browsertab.AbstractTab):
def shutdown(self):
self.shutting_down.emit()
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58563
self.search.clear()
self.action.exit_fullscreen()
if qtutils.version_check('5.8', exact=True):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58563
self.search.clear()
self._widget.shutdown()
def reload(self, *, force=False):
@ -582,14 +639,12 @@ class WebEngineTab(browsertab.AbstractTab):
def icon(self):
return self._widget.icon()
def set_html(self, html, base_url=None):
def set_html(self, html, base_url=QUrl()):
# FIXME:qtwebengine
# check this and raise an exception if too big:
# Warning: The content will be percent encoded before being sent to the
# renderer via IPC. This may increase its size. The maximum size of the
# percent encoded content is 2 megabytes minus 30 bytes.
if base_url is None:
base_url = QUrl()
self._widget.setHtml(html, base_url)
def networkaccessmanager(self):
@ -601,6 +656,13 @@ class WebEngineTab(browsertab.AbstractTab):
def clear_ssl_errors(self):
raise browsertab.UnsupportedOperationError
def key_press(self, key, modifier=Qt.NoModifier):
press_evt = QKeyEvent(QEvent.KeyPress, key, modifier, 0, 0, 0)
release_evt = QKeyEvent(QEvent.KeyRelease, key, modifier,
0, 0, 0)
self.send_event(press_evt)
self.send_event(release_evt)
@pyqtSlot()
def _on_history_trigger(self):
url = self.url()
@ -644,12 +706,24 @@ class WebEngineTab(browsertab.AbstractTab):
def _on_fullscreen_requested(self, request):
request.accept()
on = request.toggleOn()
self.data.fullscreen = on
self.fullscreen_requested.emit(on)
if on:
notification = miscwidgets.FullscreenNotification(self)
notification.show()
notification.set_timeout(3000)
@pyqtSlot()
def _on_load_started(self):
"""Clear search when a new load is started if needed."""
if (qtutils.version_check('5.9') and
not qtutils.version_check('5.9.2')):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
self.search.clear()
super()._on_load_started()
@pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int)
def _on_render_process_terminated(self, status, exitcode):
"""Show an error when the renderer process terminated."""

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -23,12 +23,10 @@ import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import shared
from qutebrowser.browser.webengine import certificateerror
from qutebrowser.browser.webengine import certificateerror, webenginesettings
from qutebrowser.config import config
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
objreg)
@ -38,13 +36,19 @@ class WebEngineView(QWebEngineView):
"""Custom QWebEngineView subclass with qutebrowser-specific features."""
def __init__(self, tabdata, win_id, parent=None):
def __init__(self, *, tabdata, win_id, private, parent=None):
super().__init__(parent)
self._win_id = win_id
self._tabdata = tabdata
theme_color = self.style().standardPalette().color(QPalette.Base)
page = WebEnginePage(theme_color=theme_color, parent=self)
if private:
profile = webenginesettings.private_profile
assert profile.isOffTheRecord()
else:
profile = webenginesettings.default_profile
page = WebEnginePage(theme_color=theme_color, profile=profile,
parent=self)
self.setPage(page)
def shutdown(self):
@ -124,8 +128,8 @@ class WebEnginePage(QWebEnginePage):
certificate_error = pyqtSignal()
shutting_down = pyqtSignal()
def __init__(self, theme_color, parent=None):
super().__init__(parent)
def __init__(self, *, theme_color, profile, parent=None):
super().__init__(profile, parent)
self._is_shutting_down = False
self.featurePermissionRequested.connect(
self._on_feature_permission_requested)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -21,161 +21,35 @@
import os.path
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
from PyQt5.QtNetwork import QNetworkDiskCache
from qutebrowser.config import config
from qutebrowser.utils import utils, objreg
from qutebrowser.utils import utils, objreg, qtutils
class DiskCache(QNetworkDiskCache):
"""Disk cache which sets correct cache dir and size.
Attributes:
_activated: Whether the cache should be used.
_cache_dir: The base directory for cache files (standarddir.cache())
"""
"""Disk cache which sets correct cache dir and size."""
def __init__(self, cache_dir, parent=None):
super().__init__(parent)
self._cache_dir = cache_dir
self._maybe_activate()
objreg.get('config').changed.connect(self.on_config_changed)
self.setCacheDirectory(os.path.join(cache_dir, 'http'))
self._set_cache_size()
objreg.get('config').changed.connect(self._set_cache_size)
def __repr__(self):
return utils.get_repr(self, size=self.cacheSize(),
maxsize=self.maximumCacheSize(),
path=self.cacheDirectory())
@config.change_filter('storage', 'cache-size')
def _set_cache_size(self):
"""Set the cache size based on the config."""
size = config.get('storage', 'cache-size')
if size is None:
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
if (qtutils.version_check('5.7.1') and
not qtutils.version_check('5.9')): # pragma: no cover
size = 0
self.setMaximumCacheSize(size)
def _maybe_activate(self):
"""Activate/deactivate the cache based on the config."""
if config.get('general', 'private-browsing'):
self._activated = False
else:
self._activated = True
self.setCacheDirectory(os.path.join(self._cache_dir, 'http'))
self._set_cache_size()
@pyqtSlot(str, str)
def on_config_changed(self, section, option):
"""Update cache size/activated if the config was changed."""
if (section, option) == ('storage', 'cache-size'):
self._set_cache_size()
elif (section, option) == ('general', # pragma: no branch
'private-browsing'):
self._maybe_activate()
def cacheSize(self):
"""Return the current size taken up by the cache.
Return:
An int.
"""
if self._activated:
return super().cacheSize()
else:
return 0
def fileMetaData(self, filename):
"""Return the QNetworkCacheMetaData for the cache file filename.
Args:
filename: The file name as a string.
Return:
A QNetworkCacheMetaData object.
"""
if self._activated:
return super().fileMetaData(filename)
else:
return QNetworkCacheMetaData()
def data(self, url):
"""Return the data associated with url.
Args:
url: A QUrl.
return:
A QIODevice or None.
"""
if self._activated:
return super().data(url)
else:
return None
def insert(self, device):
"""Insert the data in device and the prepared meta data into the cache.
Args:
device: A QIODevice.
"""
if self._activated:
super().insert(device)
else:
return None
def metaData(self, url):
"""Return the meta data for the url url.
Args:
url: A QUrl.
Return:
A QNetworkCacheMetaData object.
"""
if self._activated:
return super().metaData(url)
else:
return QNetworkCacheMetaData()
def prepare(self, meta_data):
"""Return the device that should be populated with the data.
Args:
meta_data: A QNetworkCacheMetaData object.
Return:
A QIODevice or None.
"""
if self._activated:
return super().prepare(meta_data)
else:
return None
def remove(self, url):
"""Remove the cache entry for url.
Return:
True on success, False otherwise.
"""
if self._activated:
return super().remove(url)
else:
return False
def updateMetaData(self, meta_data):
"""Update the cache meta date for the meta_data's url to meta_data.
Args:
meta_data: A QNetworkCacheMetaData object.
"""
if self._activated:
super().updateMetaData(meta_data)
else:
return
def clear(self):
"""Remove all items from the cache."""
if self._activated:
super().clear()
else:
return

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -41,7 +41,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
try:
# Qt >= 5.4
return hash(self._error)
except TypeError:
except TypeError: # pragma: no cover
return hash((self._error.certificate().toDer(),
self._error.error()))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Daniel Schadt
# Copyright 2015-2017 Daniel Schadt
#
# This file is part of qutebrowser.
#

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2016 Antoni Boucher (antoyo) <bouanto@zoho.com>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Antoni Boucher (antoyo) <bouanto@zoho.com>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -121,13 +121,13 @@ class NetworkManager(QNetworkAccessManager):
reparented to the DownloadManager. This counts the
still running downloads, so the QNAM can clean
itself up when this reaches zero again.
_requests: Pending requests.
_scheme_handlers: A dictionary (scheme -> handler) of supported custom
schemes.
_win_id: The window ID this NetworkManager is associated with.
_tab_id: The tab ID this NetworkManager is associated with.
_rejected_ssl_errors: A {QUrl: [SslError]} dict of rejected errors.
_accepted_ssl_errors: A {QUrl: [SslError]} dict of accepted errors.
_private: Whether we're in private browsing mode.
Signals:
shutting_down: Emitted when the QNAM is shutting down.
@ -135,7 +135,7 @@ class NetworkManager(QNetworkAccessManager):
shutting_down = pyqtSignal()
def __init__(self, win_id, tab_id, parent=None):
def __init__(self, *, win_id, tab_id, private, parent=None):
log.init.debug("Initializing NetworkManager")
with log.disable_qt_msghandler():
# WORKAROUND for a hang when a message is printed - See:
@ -145,12 +145,12 @@ class NetworkManager(QNetworkAccessManager):
self.adopted_downloads = 0
self._win_id = win_id
self._tab_id = tab_id
self._requests = []
self._private = private
self._scheme_handlers = {
'qute': webkitqutescheme.QuteSchemeHandler(win_id),
'file': filescheme.FileSchemeHandler(win_id),
}
self._set_cookiejar(private=config.get('general', 'private-browsing'))
self._set_cookiejar()
self._set_cache()
self.sslErrors.connect(self.on_ssl_errors)
self._rejected_ssl_errors = collections.defaultdict(list)
@ -158,15 +158,10 @@ class NetworkManager(QNetworkAccessManager):
self.authenticationRequired.connect(self.on_authentication_required)
self.proxyAuthenticationRequired.connect(
self.on_proxy_authentication_required)
objreg.get('config').changed.connect(self.on_config_changed)
def _set_cookiejar(self, private=False):
"""Set the cookie jar of the NetworkManager correctly.
Args:
private: Whether we're currently in private browsing mode.
"""
if private:
def _set_cookiejar(self):
"""Set the cookie jar of the NetworkManager correctly."""
if self._private:
cookie_jar = objreg.get('ram-cookie-jar')
else:
cookie_jar = objreg.get('cookie-jar')
@ -178,11 +173,9 @@ class NetworkManager(QNetworkAccessManager):
cookie_jar.setParent(app)
def _set_cache(self):
"""Set the cache of the NetworkManager correctly.
We can't switch the whole cache in private mode because QNAM would
delete the old cache.
"""
"""Set the cache of the NetworkManager correctly."""
if self._private:
return
# We have a shared cache - we restore its parent so we don't take
# ownership of it.
app = QCoreApplication.instance()
@ -206,9 +199,6 @@ class NetworkManager(QNetworkAccessManager):
def shutdown(self):
"""Abort all running requests."""
self.setNetworkAccessible(QNetworkAccessManager.NotAccessible)
for request in self._requests:
request.abort()
request.deleteLater()
self.shutting_down.emit()
# No @pyqtSlot here, see
@ -324,17 +314,6 @@ class NetworkManager(QNetworkAccessManager):
authenticator.setPassword(answer.password)
_proxy_auth_cache[proxy_id] = answer
@config.change_filter('general', 'private-browsing')
def on_config_changed(self):
"""Set cookie jar when entering/leaving private browsing mode."""
private_browsing = config.get('general', 'private-browsing')
if private_browsing:
# switched from normal mode to private mode
self._set_cookiejar(private=True)
else:
# switched from private mode to normal mode
self._set_cookiejar()
@pyqtSlot()
def on_adopted_download_destroyed(self):
"""Check if we can clean up if an adopted download was destroyed.
@ -386,9 +365,6 @@ class NetworkManager(QNetworkAccessManager):
def createRequest(self, op, req, outgoing_data):
"""Return a new QNetworkReply object.
Extend QNetworkAccessManager::createRequest to save requests in
self._requests and handle custom schemes.
Args:
op: Operation op
req: const QNetworkRequest & req
@ -416,8 +392,7 @@ class NetworkManager(QNetworkAccessManager):
req.setRawHeader(header, value)
host_blocker = objreg.get('host-blocker')
if (op == QNetworkAccessManager.GetOperation and
host_blocker.is_blocked(req.url())):
if host_blocker.is_blocked(req.url()):
log.webview.info("Request to {} blocked by host blocker.".format(
req.url().host()))
return networkreply.ErrorNetworkReply(
@ -454,6 +429,4 @@ class NetworkManager(QNetworkAccessManager):
reply = super().createRequest(op, req, outgoing_data)
else:
reply = super().createRequest(op, req, outgoing_data)
self._requests.append(reply)
reply.destroyed.connect(self._requests.remove)
return reply

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>
@ -19,6 +19,10 @@
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
#
# For some reason, a segfault will be triggered if the unnecessary lambdas in
# this file aren't there.
# pylint: disable=unnecessary-lambda
"""Special network replies.."""
@ -114,9 +118,6 @@ class ErrorNetworkReply(QNetworkReply):
# the device to avoid getting a warning.
self.setOpenMode(QIODevice.ReadOnly)
self.setError(error, errorstring)
# For some reason, a segfault will be triggered if these lambdas aren't
# there.
# pylint: disable=unnecessary-lambda
QTimer.singleShot(0, lambda: self.error.emit(error))
QTimer.singleShot(0, lambda: self.finished.emit())
@ -137,3 +138,20 @@ class ErrorNetworkReply(QNetworkReply):
def isRunning(self):
return False
class RedirectNetworkReply(QNetworkReply):
"""A reply which redirects to the given URL."""
def __init__(self, new_url, parent=None):
super().__init__(parent)
self.setAttribute(QNetworkRequest.RedirectionTargetAttribute, new_url)
QTimer.singleShot(0, lambda: self.finished.emit())
def abort(self):
"""Called when there's e.g. a redirection limit."""
pass
def readData(self, _maxlen):
return bytes()

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de>

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""QtWebKit specific qute:* handlers and glue code."""
"""QtWebKit specific qute://* handlers and glue code."""
import mimetypes
import functools
@ -28,13 +28,13 @@ from PyQt5.QtNetwork import QNetworkReply
from qutebrowser.browser import pdfjs, qutescheme
from qutebrowser.browser.webkit.network import schemehandler, networkreply
from qutebrowser.utils import jinja, log, message, objreg, usertypes
from qutebrowser.utils import jinja, log, message, objreg, usertypes, qtutils
from qutebrowser.config import configexc, configdata
class QuteSchemeHandler(schemehandler.SchemeHandler):
"""Scheme handler for qute: URLs."""
"""Scheme handler for qute:// URLs."""
def createRequest(self, _op, request, _outgoing_data):
"""Create a new request.
@ -62,6 +62,9 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
except qutescheme.QuteSchemeError as e:
return networkreply.ErrorNetworkReply(request, e.errorstring,
e.error, self.parent())
except qutescheme.Redirect as e:
qtutils.ensure_valid(e.url)
return networkreply.RedirectNetworkReply(e.url, self.parent())
return networkreply.FixedDataNetworkReply(request, data, mimetype,
self.parent())
@ -69,15 +72,15 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
class JSBridge(QObject):
"""Javascript-bridge for special qute:... pages."""
"""Javascript-bridge for special qute://... pages."""
@pyqtSlot(str, str, str)
def set(self, sectname, optname, value):
"""Slot to set a setting from qute:settings."""
"""Slot to set a setting from qute://settings."""
# https://github.com/qutebrowser/qutebrowser/issues/727
if ((sectname, optname) == ('content', 'allow-javascript') and
value == 'false'):
message.error("Refusing to disable javascript via qute:settings "
message.error("Refusing to disable javascript via qute://settings "
"as it needs javascript support.")
return
try:
@ -88,7 +91,7 @@ class JSBridge(QObject):
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)
def qute_settings(_url):
"""Handler for qute:settings. View/change qute configuration."""
"""Handler for qute://settings. View/change qute configuration."""
config_getter = functools.partial(objreg.get('config').get, raw=True)
html = jinja.render('settings.html', title='settings', config=configdata,
confget=config_getter)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -286,9 +286,6 @@ def normalize_ws(text):
def parse_headers(content_disposition):
"""Build a _ContentDisposition from header values."""
# WORKAROUND for https://bitbucket.org/logilab/pylint/issue/492/
# pylint: disable=no-member
# We allow non-ascii here (it will only be parsed inside of qdtext, and
# rejected by the grammar if it appears in other places), although parsing
# it can be ambiguous. Parsing it ensures that a non-ambiguous filename*

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -21,7 +21,6 @@
from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
from PyQt5.QtWebKit import qWebKitVersion
from qutebrowser.utils import qtutils
@ -181,7 +180,7 @@ def serialize(items):
else:
current_idx = 0
if qtutils.is_qtwebkit_ng(qWebKitVersion()):
if qtutils.is_qtwebkit_ng():
_serialize_ng(items, current_idx, stream)
else:
_serialize_old(items, current_idx, stream)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2014-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2014-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -112,7 +112,9 @@ class WebKitElement(webelem.AbstractWebElement):
def value(self):
self._check_vanished()
return self._elem.evaluateJavaScript('this.value')
val = self._elem.evaluateJavaScript('this.value')
assert isinstance(val, (int, float, str, type(None))), val
return val
def set_value(self, value):
self._check_vanished()
@ -283,8 +285,7 @@ class WebKitElement(webelem.AbstractWebElement):
for _ in range(5):
if elem is None:
break
tag = elem.tag_name()
if tag == 'a' or tag == 'area':
if elem.is_link():
if elem.get('target', None) == '_blank':
elem['target'] = '_top'
break

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -19,9 +19,12 @@
"""QtWebKit specific part of history."""
import functools
from PyQt5.QtWebKit import QWebHistoryInterface
from qutebrowser.utils import debug
class WebHistoryInterface(QWebHistoryInterface):
@ -34,11 +37,13 @@ class WebHistoryInterface(QWebHistoryInterface):
def __init__(self, webhistory, parent=None):
super().__init__(parent)
self._history = webhistory
self._history.changed.connect(self.historyContains.cache_clear)
def addHistoryEntry(self, url_string):
"""Required for a QWebHistoryInterface impl, obsoleted by add_url."""
pass
@functools.lru_cache(maxsize=32768)
def historyContains(self, url_string):
"""Called by WebKit to determine if a URL is contained in the history.
@ -48,7 +53,8 @@ class WebHistoryInterface(QWebHistoryInterface):
Return:
True if the url is in the history, False otherwise.
"""
return url_string in self._history.history_dict
with debug.log_time('sql', 'historyContains'):
return url_string in self._history
def init(history):

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# We get various "abstract but not overridden" warnings
# pylint: disable=abstract-method
"""Bridge from QWebSettings to our own settings.
Module attributes:
@ -26,43 +29,66 @@ Module attributes:
import os.path
from PyQt5.QtWebKit import QWebSettings, qWebKitVersion
from PyQt5.QtGui import QFont
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.config import config, websettings
from qutebrowser.utils import standarddir, objreg, urlutils, qtutils, message
from qutebrowser.utils import standarddir, objreg, urlutils, qtutils
from qutebrowser.browser import shared
class Attribute(websettings.Attribute):
class Base(websettings.Base):
"""Base settings class with appropriate _get_global_settings."""
def _get_global_settings(self):
return [QWebSettings.globalSettings()]
class Attribute(Base, websettings.Attribute):
"""A setting set via QWebSettings::setAttribute."""
GLOBAL_SETTINGS = QWebSettings.globalSettings
ENUM_BASE = QWebSettings
class Setter(websettings.Setter):
class Setter(Base, websettings.Setter):
"""A setting set via QWebSettings getter/setter methods."""
"""A setting set via a QWebSettings setter method."""
GLOBAL_SETTINGS = QWebSettings.globalSettings
pass
class NullStringSetter(websettings.NullStringSetter):
class StaticSetter(Base, websettings.StaticSetter):
"""A setter for settings requiring a null QString as default."""
"""A setting set via a static QWebSettings setter method."""
GLOBAL_SETTINGS = QWebSettings.globalSettings
pass
class StaticSetter(websettings.StaticSetter):
class FontFamilySetter(Base, websettings.FontFamilySetter):
"""A setting set via static QWebSettings getter/setter methods."""
"""A setter for a font family.
GLOBAL_SETTINGS = QWebSettings.globalSettings
Gets the default value from QFont.
"""
def __init__(self, font):
# Mapping from QWebSettings::QWebSettings() in
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
font_to_qfont = {
QWebSettings.StandardFont: QFont.Serif,
QWebSettings.FixedFont: QFont.Monospace,
QWebSettings.SerifFont: QFont.Serif,
QWebSettings.SansSerifFont: QFont.SansSerif,
QWebSettings.CursiveFont: QFont.Cursive,
QWebSettings.FantasyFont: QFont.Fantasy,
}
super().__init__(setter=QWebSettings.setFontFamily, font=font,
qfont=font_to_qfont[font])
class CookiePolicy(websettings.Base):
class CookiePolicy(Base):
"""The ThirdPartyCookiePolicy setting is different from other settings."""
@ -73,12 +99,9 @@ class CookiePolicy(websettings.Base):
'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies,
}
def get(self, settings=None):
return config.get('content', 'cookies-accept')
def _set(self, value, settings=None):
QWebSettings.globalSettings().setThirdPartyCookiePolicy(
self.MAPPING[value])
for obj in self._get_settings(settings):
obj.setThirdPartyCookiePolicy(self.MAPPING[value])
def _set_user_stylesheet():
@ -88,21 +111,9 @@ def _set_user_stylesheet():
QWebSettings.globalSettings().setUserStyleSheetUrl(url)
def _init_private_browsing():
if config.get('general', 'private-browsing'):
if qtutils.is_qtwebkit_ng(qWebKitVersion()):
message.warning("Private browsing is not fully implemented by "
"QtWebKit-NG!")
QWebSettings.setIconDatabasePath('')
else:
QWebSettings.setIconDatabasePath(standarddir.cache())
def update_settings(section, option):
"""Update global settings when qwebsettings changed."""
if (section, option) == ('general', 'private-browsing'):
_init_private_browsing()
elif section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
_set_user_stylesheet()
websettings.update_mappings(MAPPINGS, section, option)
@ -113,8 +124,7 @@ def init(_args):
cache_path = standarddir.cache()
data_path = standarddir.data()
_init_private_browsing()
QWebSettings.setIconDatabasePath(standarddir.cache())
QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(cache_path, 'application-cache'))
QWebSettings.globalSettings().setLocalStoragePath(
@ -122,6 +132,13 @@ def init(_args):
QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage'))
if (config.get('general', 'private-browsing') and
not qtutils.version_check('5.4.2')):
# WORKAROUND for https://codereview.qt-project.org/#/c/108936/
# Won't work when private browsing is not enabled globally, but that's
# the best we can do...
QWebSettings.setIconDatabasePath('')
websettings.init_mappings(MAPPINGS)
_set_user_stylesheet()
objreg.get('config').changed.connect(update_settings)
@ -146,14 +163,10 @@ MAPPINGS = {
Attribute(QWebSettings.JavascriptCanCloseWindows),
'javascript-can-access-clipboard':
Attribute(QWebSettings.JavascriptCanAccessClipboard),
#'allow-java':
# Attribute(QWebSettings.JavaEnabled),
'allow-plugins':
Attribute(QWebSettings.PluginsEnabled),
'webgl':
Attribute(QWebSettings.WebGLEnabled),
'css-regions':
Attribute(QWebSettings.CSSRegionsEnabled),
'hyperlink-auditing':
Attribute(QWebSettings.HyperlinkAuditingEnabled),
'local-content-can-access-remote-urls':
@ -175,44 +188,28 @@ MAPPINGS = {
},
'fonts': {
'web-family-standard':
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.StandardFont]),
FontFamilySetter(QWebSettings.StandardFont),
'web-family-fixed':
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.FixedFont]),
FontFamilySetter(QWebSettings.FixedFont),
'web-family-serif':
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.SerifFont]),
FontFamilySetter(QWebSettings.SerifFont),
'web-family-sans-serif':
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.SansSerifFont]),
FontFamilySetter(QWebSettings.SansSerifFont),
'web-family-cursive':
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.CursiveFont]),
FontFamilySetter(QWebSettings.CursiveFont),
'web-family-fantasy':
Setter(getter=QWebSettings.fontFamily,
setter=QWebSettings.setFontFamily,
args=[QWebSettings.FantasyFont]),
FontFamilySetter(QWebSettings.FantasyFont),
'web-size-minimum':
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
Setter(QWebSettings.setFontSize,
args=[QWebSettings.MinimumFontSize]),
'web-size-minimum-logical':
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
Setter(QWebSettings.setFontSize,
args=[QWebSettings.MinimumLogicalFontSize]),
'web-size-default':
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
Setter(QWebSettings.setFontSize,
args=[QWebSettings.DefaultFontSize]),
'web-size-default-fixed':
Setter(getter=QWebSettings.fontSize,
setter=QWebSettings.setFontSize,
Setter(QWebSettings.setFontSize,
args=[QWebSettings.DefaultFixedFontSize]),
},
'ui': {
@ -221,9 +218,6 @@ MAPPINGS = {
'frame-flattening':
Attribute(QWebSettings.FrameFlatteningEnabled),
# user-stylesheet is handled separately
'css-media-type':
NullStringSetter(getter=QWebSettings.cssMediaType,
setter=QWebSettings.setCSSMediaType),
'smooth-scrolling':
Attribute(QWebSettings.ScrollAnimatorEnabled),
#'accelerated-compositing':
@ -232,40 +226,22 @@ MAPPINGS = {
# Attribute(QWebSettings.TiledBackingStoreEnabled),
},
'storage': {
'offline-storage-database':
Attribute(QWebSettings.OfflineStorageDatabaseEnabled),
'offline-web-application-storage':
'offline-web-application-cache':
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
'local-storage':
Attribute(QWebSettings.LocalStorageEnabled),
Attribute(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
'maximum-pages-in-cache':
StaticSetter(getter=QWebSettings.maximumPagesInCache,
setter=QWebSettings.setMaximumPagesInCache),
'object-cache-capacities':
StaticSetter(getter=None,
setter=QWebSettings.setObjectCacheCapacities,
unpack=True),
'offline-storage-default-quota':
StaticSetter(getter=QWebSettings.offlineStorageDefaultQuota,
setter=QWebSettings.setOfflineStorageDefaultQuota),
'offline-web-application-cache-quota':
StaticSetter(
getter=QWebSettings.offlineWebApplicationCacheQuota,
setter=QWebSettings.setOfflineWebApplicationCacheQuota),
StaticSetter(QWebSettings.setMaximumPagesInCache),
},
'general': {
'private-browsing':
Attribute(QWebSettings.PrivateBrowsingEnabled),
'developer-extras':
Attribute(QWebSettings.DeveloperExtrasEnabled),
'print-element-backgrounds':
Attribute(QWebSettings.PrintElementBackgrounds),
'xss-auditing':
Attribute(QWebSettings.XSSAuditingEnabled),
'site-specific-quirks':
Attribute(QWebSettings.SiteSpecificQuirksEnabled),
'default-encoding':
Setter(getter=QWebSettings.defaultTextEncoding,
setter=QWebSettings.setDefaultTextEncoding),
Setter(QWebSettings.setDefaultTextEncoding),
}
}

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