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 build: off
environment: environment:
PYTHONUNBUFFERED: 1 PYTHONUNBUFFERED: 1
PYTHON: C:\Python36\python.exe
matrix: matrix:
- TESTENV: py34
- TESTENV: py36-pyqt58 - TESTENV: py36-pyqt58
PYTHON: C:\Python36\python.exe
- TESTENV: unittests-frozen
- TESTENV: pylint - TESTENV: pylint
install: install:
- C:\Python27\python -u scripts\dev\ci\appveyor_install.py - '%PYTHON% -m pip install -U pip'
- set PATH=%PATH%;C:\Python36 - '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt'
- 'set PATH=%PATH%;C:\Python36'
test_script: 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__) # (for pytest's __tracebackhide__)
# F401: Unused import # F401: Unused import
# N802: function name should be lowercase # N802: function name should be lowercase
# N806: variable in function should be lowercase
# P101: format string does contain unindexed parameters # P101: format string does contain unindexed parameters
# P102: docstring does contain unindexed parameters # P102: docstring does contain unindexed parameters
# P103: other string does contain unindexed parameters # P103: other string does contain unindexed parameters
@ -35,10 +36,10 @@ max-complexity = 12
putty-auto-ignore = True putty-auto-ignore = True
putty-ignore = putty-ignore =
/# pylint: disable=invalid-name/ : +N801,N806 /# pylint: disable=invalid-name/ : +N801,N806
/# pylint: disable=wildcard-import/ : +F403
/# pragma: no mccabe/ : +C901 /# pragma: no mccabe/ : +C901
tests/*/test_*.py : +D100,D101,D401 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/helpers/fixtures.py : +N806
tests/unit/browser/webkit/http/test_content_disposition.py : +D400 tests/unit/browser/webkit/http/test_content_disposition.py : +D400
scripts/dev/ci/appveyor_install.py : +FI53 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 <!-- 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, broad-except,
bare-except, bare-except,
eval-used, eval-used,
exec-used,
file-ignored,
wrong-import-order,
ungrouped-imports, ungrouped-imports,
redefined-variable-type,
suppressed-message, suppressed-message,
too-many-return-statements, too-many-return-statements,
duplicate-code, duplicate-code,
@ -53,12 +49,9 @@ no-docstring-rgx=(^_|^main$)
[FORMAT] [FORMAT]
max-line-length=79 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 expected-line-ending-format=LF
[SIMILARITIES]
min-similarity-lines=8
[VARIABLES] [VARIABLES]
dummy-variables-rgx=_.* dummy-variables-rgx=_.*
@ -69,12 +62,10 @@ max-args=10
valid-metaclass-classmethod-first-arg=cls valid-metaclass-classmethod-first-arg=cls
[TYPECHECK] [TYPECHECK]
# WORKAROUND for https://github.com/PyCQA/astroid/pull/357 ignored-modules=PyQt5,PyQt5.QtWebKit
ignored-modules=pytest
# MsgType added as WORKAROUND for [IMPORTS]
# https://bitbucket.org/logilab/pylint/issues/690/ # WORKAROUND
# UnsetObject because pylint infers any objreg.get(...) as UnsetObject. # For some reason, pylint doesn't know about some Python 3 modules on
ignored-classes=qutebrowser.utils.objreg.UnsetObject, # AppVeyor...
qutebrowser.browser.webkit.webelem.WebElementWrapper, known-standard-library=faulthandler,http,enum,tokenize,posixpath,importlib,types
scripts.dev.check_coverage.MsgType,
qutebrowser.browser.downloads.UnsupportedAttribute

1
.pyup.yml Normal file
View File

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

View File

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

View File

@ -14,37 +14,189 @@ This project adheres to http://semver.org/[Semantic Versioning].
// `Fixed` for any bug fixes. // `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities. // `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 Added
~~~~~ ~~~~~
- New `:clear-messages` command to clear shown messages. - New back/forward indicator in the statusbar
- 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.
Changed Changed
~~~~~~~ ~~~~~~~
- When using QtWebEngine, the underlying Chromium version is now shown in the - Upgrading qutebrowser with a version older than v0.4.0 still running now won't
version info. work properly anymore.
- Improved `qute:history` page with lazy loading - Using `:download` now uses the page's title as filename.
- Messages are now hidden when clicked - Using `:back` or `:forward` with a count now skips intermediate pages.
- Paths like `C:Downloads` are now treated as absolute paths on Windows. - When there are multiple messages shown, the timeout is increased.
- PAC on QtWebKit now supports SOCK5 as type. - `: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 Fixed
~~~~~ ~~~~~
- Added a workaround for a black screen with QtWebEngine with some setups - The macOS .dmg is now built against Qt 5.9 which fixes various
(the workaround requires PyOpenGL to be installed, but it's optional) important issues (such as not being able to type dead keys).
- Crash when trying to retry downloads with QtWebEngine - Fixed crash with `:download` on PyQt 5.9.
- Crash when cloning page without history - Cloning a page without history doesn't crash anymore.
- Continuing a search after clearing it - When a download results in a HTTP error, it now shows the error correctly
- Crash when downloading a download resulting in a HTTP error instead of crashing.
- Various rare crashes - 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 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 - 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 - `: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 - `: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 Changed
~~~~~~~ ~~~~~~~
@ -105,6 +257,18 @@ Changed
- `network -> proxy` can also be set to `pac+file://...` now to - `network -> proxy` can also be set to `pac+file://...` now to
use a local proxy autoconfig file (on QtWebKit) 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 Fixed
~~~~~ ~~~~~
@ -379,7 +543,7 @@ Fixed
- Fix crash when pressing enter without a command - Fix crash when pressing enter without a command
- Adjust error message to point out QtWebEngine is unsupported with the OS - Adjust error message to point out QtWebEngine is unsupported with the OS
X .app currently. X .app currently.
- Hide Harfbuzz warning with the OS X .app - Hide Harfbuzz warning with the macOS .app
v0.8.0 v0.8.0
------ ------
@ -742,7 +906,7 @@ Fixed
- Fixed scrolling to the very left/right with `:scroll-perc`. - Fixed scrolling to the very left/right with `:scroll-perc`.
- Using an external editor should now work correctly with some funny chars - Using an external editor should now work correctly with some funny chars
(U+2028/U+2029/BOM). (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 upgrade from earlier config versions.
- Fixed crash when killing a running userscript. - Fixed crash when killing a running userscript.
- Fixed characters being passed through when shifted with - Fixed characters being passed through when shifted with
@ -817,7 +981,7 @@ Changed
- The completion widget doesn't show a border anymore. - The completion widget doesn't show a border anymore.
- The tabbar doesn't display ugly arrows anymore if there isn't enough space - The tabbar doesn't display ugly arrows anymore if there isn't enough space
for all tabs. 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. - Better support for Qt 5.5 and Python 3.5.
Fixed Fixed
@ -928,7 +1092,7 @@ Fixed
- Fixed AssertionError when closing many windows quickly. - Fixed AssertionError when closing many windows quickly.
- Various fixes for deprecated key bindings and auto-migrations. - 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). - 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 crash when downloading a URL without filename (e.g. magnet links) via "Save as...".
- Fixed exception when starting qutebrowser with `:set` as argument. - Fixed exception when starting qutebrowser with `:set` as argument.
- Fixed horrible completion performance when the `shrink` option was set. - 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. - 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. - Various improvements to documentation, logging, and the crash reporter.
- Expand `~` to the users home directory with `:run-userscript`. - 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. - Add luakit-like `gt`/`gT` keybindings to cycle through tabs.
- Show default value for config values in the completion. - Show default value for config values in the completion.
- Clone tab icon, tab text and zoom level when cloning tabs. - 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. * `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] * The tests now use http://pytest.org/[pytest]
* Many new tests added * 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]. * Coverage recording via http://nedbatchelder.com/code/coverage/[coverage.py].
* New `--pdb-postmortem argument` to drop into the pdb debugger on exceptions. * 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. * 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 rare exception when a key is pressed shortly after opening a window
* Fix exception with certain invalid URLs like `http:foo:0` * 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` * Fix exception when a local files can't be read in `:adblock-update`
* Hide 2 more Qt warnings. * Hide 2 more Qt warnings.
* Add `!important` to hint CSS so websites don't override the hint look * 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`/`=`. * Set zoom to default instead of 100% with `:zoom`/`=`.
* Adjust page zoom if default zoom changed. * Adjust page zoom if default zoom changed.
* Force tabs to be focused on `:undo`. * 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. * Allow min-/maximizing of print preview on Windows.
* Various documentation improvements. * Various documentation improvements.
* Various other small improvements and cleanups. * Various other small improvements and cleanups.

View File

@ -5,6 +5,12 @@ The Compiler <mail@qutebrowser.org>
:data-uri: :data-uri:
:toc: :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! I `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
This document contains guidelines for contributing to qutebrowser, as well as 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 * https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which
require little/no coding] 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: 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. * 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://pygments.org/docs/[pygments]
* http://fdik.org/pyPEG/index.html[pyPEG2] * http://fdik.org/pyPEG/index.html[pyPEG2]
* http://pythonhosted.org/setuptools/[setuptools] * 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] * https://pypi.python.org/pypi/colorama[colorama]
Related RFCs and standards: Related RFCs and standards:
@ -546,6 +558,28 @@ Rebuilding the website
If you want to rebuild the website, run `./scripts/asciidoc2html.py --website <outputdir>`. 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 Style conventions
----------------- -----------------
@ -654,8 +688,9 @@ qutebrowser release
* Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version` * Add newest config to `tests/unit/config/old_configs` and update `test_upgrade_version`
- `python -m qutebrowser --basedir conf :quit` - `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` - `rm -r conf`
- git add
- commit - commit
* Adjust `__version_info__` in `qutebrowser/__init__.py`. * Adjust `__version_info__` in `qutebrowser/__init__.py`.
* Update changelog (remove *(unreleased)*) * Update changelog (remove *(unreleased)*)
@ -670,8 +705,8 @@ qutebrowser release
as closed. as closed.
* Linux: Run `python3 scripts/dev/build_release.py --upload v0.$x.$y` * 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) * 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)
* OS X: Run `python3 scripts/dev/build_release.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) * 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 * Update `qutebrowser-git` PKGBUILD if dependencies/install changed
* Announce to qutebrowser and qutebrowser-announce mailinglist * 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 http://www.reddit.com/r/programming/comments/25j41u/adblock_pluss_effect_on_firefoxs_memory_usage/chhpomw[big
impact] on browsing speed and impact] on browsing speed and
https://blog.mozilla.org/nnethercote/2014/05/14/adblock-pluss-effect-on-firefoxs-memory-usage/[RAM 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 usage], so implementing support for AdBlockPlus-like lists is currently not
for v0.1 if at all. a priority.
How do I play Youtube videos with mpv?:: How do I play Youtube videos with mpv?::
You can easily add a key binding to play youtube videos inside a real video 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, `:open foodrecipes`, you will see a list of all the food recipe sites,
without having to remember the exact website title or address. 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 == Troubleshooting
Configuration not saved after modifying config.:: Configuration not saved after modifying config.::

View File

@ -1,6 +1,8 @@
Installing qutebrowser Installing qutebrowser
====================== ======================
toc::[]
On Debian / Ubuntu 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. 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 Using the packages
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get: 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 Get the qutebrowser package from the
https://github.com/qutebrowser/qutebrowser/releases[release page] and download https://github.com/qutebrowser/qutebrowser/releases[release page] and download
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package]. 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: 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: 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.
# 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
----
To generate the documentation for the `:help` command, when using the git To generate the documentation for the `:help` command, when using the git
repository (rather than a release): repository (rather than a release):
@ -102,6 +85,9 @@ qutebrowser is available in the official repositories for Fedora 22 and newer.
# dnf install qutebrowser # dnf install qutebrowser
---- ----
It's also recommended to install `qt5-qtwebengine` and start with `--backend
webengine` to use the new backend.
On Archlinux On Archlinux
------------ ------------
@ -111,6 +97,10 @@ qutebrowser is available in the official [community] repository.
# pacman -S qutebrowser # 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: There is also a -git version available in the AUR:
https://aur.archlinux.org/packages/qutebrowser-git/[qutebrowser-git]. 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 (`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary. 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: If video or sound don't seem to work, try installing the gstreamer plugins:
---- ----
@ -192,6 +186,10 @@ with:
# xbps-install qutebrowser # 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 On NixOS
-------- --------
@ -202,6 +200,9 @@ it with:
$ nix-env -i qutebrowser $ nix-env -i qutebrowser
---- ----
It's recommended to install `qt5.qtwebengine` and start with
`--backend webengine` to use the new backend.
On openSUSE On openSUSE
----------- -----------
@ -222,19 +223,19 @@ On OpenBSD
qutebrowser is in http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/www/qutebrowser/[OpenBSD ports]. 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 # cd /usr/ports/www/qutebrowser
# make install # make install
---- ----
Or alternatively if you're using `-current` (or OpenBSD 6.1 once it's been released):
----
# pkg_add qutebrowser
----
On Windows On Windows
---------- ----------
@ -243,7 +244,7 @@ There are different ways to install qutebrowser on Windows:
Prebuilt binaries Prebuilt binaries
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
Prebuilt standalone packages and MSI installers Prebuilt standalone packages and installers
https://github.com/qutebrowser/qutebrowser/releases[are built] for every https://github.com/qutebrowser/qutebrowser/releases[are built] for every
release. release.
@ -276,13 +277,13 @@ $ pip install tox
Then <<tox,install qutebrowser via tox>>. Then <<tox,install qutebrowser via tox>>.
On OS X On macOS
------- --------
Prebuilt binary 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 files from the
https://github.com/qutebrowser/qutebrowser/releases[release page]. 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]: 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.
---- Alternatively, you can use `tox -e mkvenv` (without `-pypi`) to symlink your
$ tox -e mkvenv-win 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 On Windows, run `tox -e 'mkvenv-win' instead, however make sure that ONLY
system-wide Qt5/PyQt5 installations are symlinked into the virtual environment. 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 You can then create a simple wrapper script to start qutebrowser somewhere in
your `$PATH` (e.g. `/usr/local/bin/qutebrowser` or `~/bin/qutebrowser`): 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 "$@" ~/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 Updating
~~~~~~~~ ~~~~~~~~
@ -382,5 +381,5 @@ virtualenv. Thus it's recommended to run the following command to recreate the
virtualenv: virtualenv:
---- ----
$ tox -r -e mkvenv $ tox -r -e mkvenv-pypi
---- ----

View File

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

View File

@ -36,11 +36,8 @@ Downloads
--------- ---------
See the https://github.com/qutebrowser/qutebrowser/releases[github releases See the https://github.com/qutebrowser/qutebrowser/releases[github releases
page] for available downloads (currently a source archive, and standalone page] for available downloads and the link:INSTALL.asciidoc[INSTALL] file for
packages as well as MSI installers for Windows). detailed instructions on how to get qutebrowser running on various platforms.
See link:INSTALL.asciidoc[INSTALL] for detailed instructions on how to get
qutebrowser running for various platforms.
Documentation Documentation
------------- -------------
@ -71,7 +68,11 @@ https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
mailto:qutebrowser@lists.qutebrowser.org[]. mailto:qutebrowser@lists.qutebrowser.org[].
There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] 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 Contributions / Bugs
-------------------- --------------------
@ -97,26 +98,36 @@ Requirements
The following software and libraries are required to run qutebrowser: The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.4 or newer * http://www.python.org/[Python] 3.4 or newer (3.6 recommended) - note that
* http://qt.io/[Qt] 5.2.0 or newer (5.5.1 recommended) support for Python 3.4
* QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG) or QtWebEngine 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 * 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] * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2] * http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2] * http://jinja.pocoo.org/[jinja2]
* http://pygments.org/[pygments] * http://pygments.org/[pygments]
* http://pyyaml.org/wiki/PyYAML[PyYAML] * 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] * http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml`
with QtWebKit)
To generate the documentation for the `:help` command, when using the git * On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log
repository (rather than a release), http://asciidoc.org/[asciidoc] is needed. output.
* http://asciidoc.org/[asciidoc] to generate the documentation for the `:help`
On Windows, https://pypi.python.org/pypi/colorama/[colorama] is needed to command, when using the git repository (rather than a release).
display colored log output.
See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser See link:INSTALL.asciidoc[INSTALL] for directions on how to install qutebrowser
and its dependencies. and its dependencies.
@ -140,209 +151,59 @@ get in touch!
Authors 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 Additionally, the following people have contributed graphics:
* 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:
* Jad/link:http://yelostudio.com[yelo] (new icon) * Jad/link:http://yelostudio.com[yelo] (new icon)
* WOFall (original icon) * WOFall (original icon)
* regines (key binding cheatsheet) * 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: Similar projects
----------------
* 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)
Many projects with a similar goal as qutebrowser exist.
Most of them were inspirations for qutebrowser in some way, thanks for that! 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 Active
problems and helpful hints: ~~~~~~
* http://eric-ide.python-projects.org/[eric5] / Detlev Offenbach * https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2)
* https://code.google.com/p/devicenzo/[devicenzo] * https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2)
* portix * http://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2)
* seir * http://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2)
* nitroxleecher * 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]. * https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1,
* Everyone who had the patience to test qutebrowser before v0.1. https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] -
* Everyone triaging/fixing my bugs in the main inspiration for qutebrowser)
https://bugreports.qt.io/secure/Dashboard.jspa[Qt bugtracker] * http://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with
* Everyone answering my questions on http://stackoverflow.com/[Stack Overflow] WebKit1)
and in IRC. * http://pwmt.org/projects/jumanji/[jumanji] (C, GTK+ with WebKit1)
* All the projects which were a great help while developing qutebrowser. * 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 License
------- -------

View File

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

View File

@ -1,13 +1,135 @@
Crowdfunding backers 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 Mid-2016, qutebrowser did run a http://igg.me/at/qutebrowser[crowdfunding] for
QtWebEngine support in qutebrowser. QtWebEngine support in qutebrowser.
Thanks a lot to the following people who contributed to it: Thanks a lot to the following people who contributed to it:
Gold sponsors Gold sponsors
------------- ~~~~~~~~~~~~~
- Chris Salzberg - Chris Salzberg
- Clayton Craft - Clayton Craft
@ -16,7 +138,7 @@ Gold sponsors
- 1 Anonymous - 1 Anonymous
Day sponsors Day sponsors
------------ ~~~~~~~~~~~~
- Agent 42 - Agent 42
- Iggy Jackson - Iggy Jackson
@ -28,7 +150,7 @@ Day sponsors
- 4 Anonymous - 4 Anonymous
Other sponsors Other sponsors
-------------- ~~~~~~~~~~~~~~
- AP M - AP M
- Alessandro Balzano - Alessandro Balzano

View File

@ -1,5 +1,5 @@
// DO NOT EDIT THIS FILE DIRECTLY! // DO NOT EDIT THIS FILE DIRECTLY!
// It is autogenerated from docstrings by running: // It is autogenerated by running:
// $ python3 scripts/dev/src2asciidoc.py // $ python3 scripts/dev/src2asciidoc.py
= Commands = 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-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-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-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. |<<tab-prev,tab-prev>>|Switch to the previous tab, or switch [count] tabs back.
|<<unbind,unbind>>|Unbind a keychain. |<<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. |<<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. |<<window-only,window-only>>|Close all windows except for the current one.
|<<wq,wq>>|Save open pages and quit. |<<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]]
=== 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. 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. * +*-t*+, +*--tab*+: Open in a new tab.
* +*-w*+, +*--window*+: Open in a new window. * +*-w*+, +*--window*+: Open in a new window.
* +*-s*+, +*--secure*+: Force HTTPS. * +*-s*+, +*--secure*+: Force HTTPS.
* +*-p*+, +*--private*+: Open a new window in private browsing mode.
==== count ==== count
The tab index to open the URL in. The tab index to open the URL in.
@ -743,6 +746,7 @@ Load a session.
[[session-save]] [[session-save]]
=== session-save === session-save
Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*] Syntax: +:session-save [*--current*] [*--quiet*] [*--force*] [*--only-active-window*]
[*--with-private*]
['name']+ ['name']+
Save a session. Save a session.
@ -756,6 +760,7 @@ Save a session.
* +*-q*+, +*--quiet*+: Don't show confirmation message. * +*-q*+, +*--quiet*+: Don't show confirmation message.
* +*-f*+, +*--force*+: Force saving internal sessions (starting with an underline). * +*-f*+, +*--force*+: Force saving internal sessions (starting with an underline).
* +*-o*+, +*--only-active-window*+: Saves only tabs of the currently active window. * +*-o*+, +*--only-active-window*+: Saves only tabs of the currently active window.
* +*-p*+, +*--with-private*+: Include private windows.
[[set]] [[set]]
=== set === set
@ -831,7 +836,7 @@ Duplicate the current tab.
[[tab-close]] [[tab-close]]
=== tab-close === tab-close
Syntax: +:tab-close [*--prev*] [*--next*] [*--opposite*]+ Syntax: +:tab-close [*--prev*] [*--next*] [*--opposite*] [*--force*]+
Close the current/[count]th tab. 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. * +*-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'. * +*-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 ==== count
The tab index to close The tab index to close
@ -892,13 +898,23 @@ How many tabs to switch forward.
[[tab-only]] [[tab-only]]
=== tab-only === tab-only
Syntax: +:tab-only [*--prev*] [*--next*]+ Syntax: +:tab-only [*--prev*] [*--next*] [*--force*]+
Close all tabs except for the current one. Close all tabs except for the current one.
==== optional arguments ==== optional arguments
* +*-p*+, +*--prev*+: Keep tabs before the current. * +*-p*+, +*--prev*+: Keep tabs before the current.
* +*-n*+, +*--next*+: Keep tabs after 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]]
=== tab-prev === tab-prev
@ -920,7 +936,7 @@ Unbind a keychain.
[[undo]] [[undo]]
=== undo === undo
Re-open a closed tab (optionally skipping [count] closed tabs). Re-open a closed tab.
[[view-source]] [[view-source]]
=== 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-clear-ssl-errors,debug-clear-ssl-errors>>|Clear remembered SSL error answers.
|<<debug-console,debug-console>>|Show the debugging console. |<<debug-console,debug-console>>|Show the debugging console.
|<<debug-crash,debug-crash>>|Crash for debugging purposes. |<<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-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-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. |<<debug-log-filter,debug-log-filter>>|Change the log filter for console logging.
@ -1571,6 +1588,15 @@ Crash for debugging purposes.
==== positional arguments ==== positional arguments
* +'typ'+: either 'exception' or 'segfault'. * +'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]]
=== debug-dump-page === debug-dump-page
Syntax: +:debug-dump-page [*--plain*] 'dest'+ Syntax: +:debug-dump-page [*--plain*] 'dest'+
@ -1599,7 +1625,8 @@ Syntax: +:debug-log-filter 'filters'+
Change the log filter for console logging. Change the log filter for console logging.
==== positional arguments ==== 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]]
=== debug-log-level === debug-log-level
@ -1654,7 +1681,7 @@ Syntax: +:debug-webaction 'action'+
Execute a webaction. 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 ==== positional arguments
* +'action'+: The action to execute, e.g. MoveToNextChar. * +'action'+: The action to execute, e.g. MoveToNextChar.

View File

@ -1,5 +1,5 @@
// DO NOT EDIT THIS FILE DIRECTLY! // DO NOT EDIT THIS FILE DIRECTLY!
// It is autogenerated from docstrings by running: // It is autogenerated by running:
// $ python3 scripts/dev/src2asciidoc.py // $ python3 scripts/dev/src2asciidoc.py
= Settings = Settings
@ -18,11 +18,10 @@
|<<general-auto-save-interval,auto-save-interval>>|How often (in milliseconds) to auto-save config/cookies/etc. |<<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,editor>>|The editor (and arguments) to use for the `open-editor` command.
|<<general-editor-encoding,editor-encoding>>|Encoding to use for editor. |<<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-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-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-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-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,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. |<<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-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-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-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-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-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. |<<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-close-mouse-button,close-mouse-button>>|On which mouse button to close tabs.
|<<tabs-position,position>>|The position of the tab bar. |<<tabs-position,position>>|The position of the tab bar.
|<<tabs-show-favicons,show-favicons>>|Whether to show favicons in 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-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-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-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,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-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-mousewheel-tab-switching,mousewheel-tab-switching>>|Switch between tabs using the mouse wheel.
|<<tabs-padding,padding>>|Padding for tabs (top, bottom, left, right). |<<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-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-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-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-web-application-cache,offline-web-application-cache>>|Whether support for the HTML 5 web application cache feature is enabled.
|<<storage-offline-storage-default-quota,offline-storage-default-quota>>|Default quota for new offline storage databases. |<<storage-local-storage,local-storage>>|Whether support for HTML 5 local storage and Web SQL is enabled.
|<<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-cache-size,cache-size>>|Size of the HTTP network cache. Empty to use the default value. |<<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-javascript,allow-javascript>>|Enables or disables the running of JavaScript programs.
|<<content-allow-plugins,allow-plugins>>|Enables or disables plugins in Web pages. |<<content-allow-plugins,allow-plugins>>|Enables or disables plugins in Web pages.
|<<content-webgl,webgl>>|Enables or disables WebGL. |<<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-hyperlink-auditing,hyperlink-auditing>>|Enable or disable hyperlink auditing (<a ping>).
|<<content-geolocation,geolocation>>|Allow websites to request geolocations. |<<content-geolocation,geolocation>>|Allow websites to request geolocations.
|<<content-notifications,notifications>>|Allow websites to show notifications. |<<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-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-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-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-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-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. |<<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-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.fg,statusbar.fg>>|Foreground color of the statusbar.
|<<colors-statusbar.bg,statusbar.bg>>|Background 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.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.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.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.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.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.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 |<<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]] [[general-private-browsing]]
=== 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: Valid values:
@ -403,8 +403,6 @@ Valid values:
Default: +pass:[false]+ Default: +pass:[false]+
This setting is only available with the QtWebKit backend.
[[general-developer-extras]] [[general-developer-extras]]
=== developer-extras === developer-extras
Enable extra tools for Web developers. Enable extra tools for Web developers.
@ -445,26 +443,13 @@ Valid values:
Default: +pass:[false]+ 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]] [[general-default-encoding]]
=== default-encoding === default-encoding
Default encoding to use for websites. 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]] [[general-new-instance-open-target]]
=== new-instance-open-target === new-instance-open-target
@ -581,6 +566,7 @@ Default: +pass:[bottom]+
[[ui-message-timeout]] [[ui-message-timeout]]
=== message-timeout === message-timeout
Time (in ms) to show messages in the statusbar for. Time (in ms) to show messages in the statusbar for.
Set to 0 to never clear messages.
Default: +pass:[2000]+ Default: +pass:[2000]+
@ -653,14 +639,6 @@ Valid values:
Default: +pass:[true]+ 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]] [[ui-smooth-scrolling]]
=== smooth-scrolling === smooth-scrolling
Whether to enable smooth scrolling for web pages. Note smooth scrolling does not work with the :scroll-px command. 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. * `{scroll_pos}`: The page scroll position.
* `{host}`: The host of the current web page. * `{host}`: The host of the current web page.
* `{backend}`: Either 'webkit' or 'webengine' * `{backend}`: Either 'webkit' or 'webengine'
* `{private}` : Indicates when private mode is enabled.
Default: +pass:[{perc}{title}{title_sep}qutebrowser]+ 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. 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: Valid values:
* +system+: Use the system wide proxy. * +system+: Use the system wide proxy.
@ -921,7 +899,7 @@ How many URLs to show in the web history.
0: no history / -1: unlimited 0: no history / -1: unlimited
Default: +pass:[1000]+ Default: +pass:[-1]+
[[completion-quick-complete]] [[completion-quick-complete]]
=== quick-complete === quick-complete
@ -1205,12 +1183,24 @@ Valid values:
Default: +pass:[true]+ 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]] [[tabs-width]]
=== width === width
The width of the tab bar if it's vertical, in px or as percentage of the window. The width of the tab bar if it's vertical, in px or as percentage of the window.
Default: +pass:[20%]+ 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]] [[tabs-indicator-width]]
=== indicator-width === indicator-width
Width of the progress indicator (0 to disable). 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. * `{scroll_pos}`: The page scroll position.
* `{host}`: The host of the current web page. * `{host}`: The host of the current web page.
* `{backend}`: Either 'webkit' or 'webengine' * `{backend}`: Either 'webkit' or 'webengine'
* `{private}` : Indicates when private mode is enabled.
Default: +pass:[{index}: {title}]+ 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]] [[tabs-title-alignment]]
=== title-alignment === title-alignment
Alignment of the text inside of tabs 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/ 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. This setting is only available with the QtWebKit backend.
[[storage-object-cache-capacities]] [[storage-offline-web-application-cache]]
=== object-cache-capacities === offline-web-application-cache
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
Whether support for the HTML 5 web application cache feature is enabled. 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. 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]] [[storage-local-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: Valid values:
@ -1449,19 +1404,6 @@ Valid values:
Default: +pass:[true]+ 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]] [[content-hyperlink-auditing]]
=== hyperlink-auditing === hyperlink-auditing
Enable or disable hyperlink auditing (<a ping>). Enable or disable hyperlink auditing (<a ping>).
@ -1608,7 +1550,7 @@ This setting is only available with the QtWebKit backend.
[[content-cookies-store]] [[content-cookies-store]]
=== 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: Valid values:
@ -1898,6 +1840,18 @@ Background color of the statusbar.
Default: +pass:[black]+ 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]] [[colors-statusbar.fg.insert]]
=== statusbar.fg.insert === statusbar.fg.insert
Foreground color of the statusbar in insert mode. Foreground color of the statusbar in insert mode.
@ -1922,6 +1876,18 @@ Background color of the statusbar in command mode.
Default: +pass:[${statusbar.bg}]+ 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]] [[colors-statusbar.fg.caret]]
=== statusbar.fg.caret === statusbar.fg.caret
Foreground color of the statusbar in caret mode. Foreground color of the statusbar in caret mode.
@ -2266,7 +2232,7 @@ Fonts used for the UI, with optional style/weight/size.
=== _monospace === _monospace
Default monospace fonts. 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]] [[fonts-completion]]
=== completion === completion
@ -2350,25 +2316,25 @@ Default: empty
=== web-size-minimum === web-size-minimum
The hard minimum font size. The hard minimum font size.
Default: empty Default: +pass:[0]+
[[fonts-web-size-minimum-logical]] [[fonts-web-size-minimum-logical]]
=== web-size-minimum-logical === web-size-minimum-logical
The minimum logical font size that is applied when zooming out. The minimum logical font size that is applied when zooming out.
Default: empty Default: +pass:[6]+
[[fonts-web-size-default]] [[fonts-web-size-default]]
=== web-size-default === web-size-default
The default font size for regular text. The default font size for regular text.
Default: empty Default: +pass:[16]+
[[fonts-web-size-default-fixed]] [[fonts-web-size-default-fixed]]
=== web-size-default-fixed === web-size-default-fixed
The default font size for fixed-pitch text. The default font size for fixed-pitch text.
Default: empty Default: +pass:[13]+
[[fonts-keyhint]] [[fonts-keyhint]]
=== 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. * Run `:adblock-update` to download adblock lists and activate adblocking.
* If you just cloned the repository, you'll need to run * If you just cloned the repository, you'll need to run
`scripts/asciidoc2html.py` to generate the documentation. `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 * Subscribe to
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist]. 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. How URLs should be opened if there is already a qutebrowser instance running.
*--backend* '{webkit,webengine}':: *--backend* '{webkit,webengine}'::
Which backend to use (webengine backend is EXPERIMENTAL!). Which backend to use.
*--enable-webengine-inspector*:: *--enable-webengine-inspector*::
Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details. Enable the web inspector for QtWebEngine. Note that this is a SECURITY RISK and you should not visit untrusted websites with the inspector turned on. See https://bugreports.qt.io/browse/QTBUG-50725 for more details.
@ -93,12 +93,6 @@ show it.
*--nowindow*:: *--nowindow*::
Don't show the main window. Don't show the main window.
*--debug-exit*::
Turn on debugging of late exit.
*--pdb-postmortem*::
Drop into pdb on exceptions.
*--temp-basedir*:: *--temp-basedir*::
Use a temporary basedir. Use a temporary basedir.
@ -110,6 +104,9 @@ show it.
*--qt-flag* 'QT_FLAG':: *--qt-flag* 'QT_FLAG'::
Pass an argument to Qt as flag. Pass an argument to Qt as flag.
*--debug-flag* 'DEBUG_FLAGS'::
Pass name of debugging feature to be turned on.
// QUTE_OPTIONS_END // QUTE_OPTIONS_END
== FILES == FILES

View File

@ -60,7 +60,7 @@ Sending commands
Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be Normal qutebrowser commands can be written to `$QUTE_FIFO` and will be
executed. 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. immediately.
On Windows, this is a regular file, and the commands in it will be executed as 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', '') ('../qutebrowser/git-commit-id', '')
] ]
if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')): # if os.path.exists(os.path.join('qutebrowser', '3rdparty', 'pdfjs')):
data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs')) # data_files.append(('../qutebrowser/3rdparty/pdfjs', '3rdparty/pdfjs'))
else: # else:
print("Warning: excluding pdfjs as it's not present!") # print("Warning: excluding pdfjs as it's not present!")
return data_files return data_files
@ -41,10 +41,10 @@ a = Analysis(['../qutebrowser/__main__.py'],
pathex=['misc'], pathex=['misc'],
binaries=None, binaries=None,
datas=get_data_files(), datas=get_data_files(),
hiddenimports=[], hiddenimports=['PyQt5.QtOpenGL', 'PyQt5._QOpenGLFunctions_2_0'],
hookspath=[], hookspath=[],
runtime_hooks=[], runtime_hooks=[],
excludes=[], excludes=['tkinter'],
win_no_prefer_redirects=False, win_no_prefer_redirects=False,
win_private_assemblies=False, win_private_assemblies=False,
cipher=block_cipher) cipher=block_cipher)

View File

@ -1,5 +1,9 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
codecov==2.0.5 certifi==2017.4.17
coverage==4.3.4 chardet==3.0.4
requests==2.13.0 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==2.6.2 # rq.filter: < 3.0.0
flake8-copyright==0.2.0 flake8-copyright==0.2.0
flake8-debugger==1.4.0 # rq.filter: != 2.0.0 flake8-debugger==1.4.0 # rq.filter: != 2.0.0
flake8-deprecated==1.1 flake8-deprecated==1.2
flake8-docstrings==1.0.3 flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
flake8-future-import==0.4.3 flake8-future-import==0.4.3
flake8-mock==0.3 flake8-mock==0.3
flake8-pep3101==1.0 flake8-pep3101==1.0 # rq.filter: < 1.1
flake8-polyfill==1.0.1 flake8-polyfill==1.0.1
flake8-putty==0.4.0 flake8-putty==0.4.0
flake8-string-format==0.2.3 flake8-string-format==0.2.3
flake8-tidy-imports==1.0.6 flake8-tidy-imports==1.1.0
flake8-tuple==0.2.12 flake8-tuple==0.2.13
mccabe==0.6.1 mccabe==0.6.1
packaging==16.8
pep8-naming==0.4.1 pep8-naming==0.4.1
pycodestyle==2.3.1 pycodestyle==2.3.1
pydocstyle==1.1.1 pydocstyle==1.1.1 # rq.filter: < 2.0.0
pyflakes==1.5.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-copyright
flake8-debugger!=2.0.0 flake8-debugger!=2.0.0
flake8-deprecated flake8-deprecated
flake8-docstrings flake8-docstrings<1.1.0
flake8-future-import flake8-future-import
flake8-mock flake8-mock
flake8-pep3101 flake8-pep3101<1.1
flake8-putty flake8-putty
flake8-string-format flake8-string-format
flake8-tidy-imports flake8-tidy-imports
flake8-tuple flake8-tuple
pep8-naming pep8-naming
pydocstyle pydocstyle<2.0.0
pyflakes pyflakes
# Pinned to 2.0.0 otherwise # Pinned to 2.0.0 otherwise
@ -21,6 +21,9 @@ mccabe==0.6.1
# Waiting until flake8-putty updated # Waiting until flake8-putty updated
#@ filter: flake8 < 3.0.0 #@ 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 # https://github.com/JBKahn/flake8-debugger/issues/5
#@ filter: flake8-debugger != 2.0.0 #@ filter: flake8-debugger != 2.0.0

View File

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

View File

@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # 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 # 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 # This file is automatically generated by scripts/dev/recompile_requirements.py
-e git+https://github.com/PyCQA/astroid.git#egg=astroid -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 github3.py==0.9.6
isort==4.2.5 idna==2.5
lazy-object-proxy==1.2.2 isort==4.2.15
lazy-object-proxy==1.3.1
mccabe==0.6.1 mccabe==0.6.1
-e git+https://github.com/PyCQA/pylint.git#egg=pylint -e git+https://github.com/PyCQA/pylint.git#egg=pylint
./scripts/dev/pylint_checkers ./scripts/dev/pylint_checkers
requests==2.13.0 requests==2.18.1
six==1.10.0
uritemplate==3.0.0 uritemplate==3.0.0
uritemplate.py==3.0.2 uritemplate.py==3.0.2
urllib3==1.21.1
wrapt==1.10.10 wrapt==1.10.10

View File

@ -1,13 +1,18 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # 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 github3.py==0.9.6
isort==4.2.5 idna==2.5
lazy-object-proxy==1.2.2 isort==4.2.15
lazy-object-proxy==1.3.1
mccabe==0.6.1 mccabe==0.6.1
pylint==1.6.5 pylint==1.7.2
./scripts/dev/pylint_checkers ./scripts/dev/pylint_checkers
requests==2.13.0 requests==2.18.1
six==1.10.0
uritemplate==3.0.0 uritemplate==3.0.0
uritemplate.py==3.0.2 uritemplate.py==3.0.2
urllib3==1.21.1
wrapt==1.10.10 wrapt==1.10.10

View File

@ -1,4 +1,4 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # This file is automatically generated by scripts/dev/recompile_requirements.py
PyQt5==5.8.2 PyQt5==5.9
sip==4.19.2 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-repeat.git
git+https://github.com/pytest-dev/pytest-rerunfailures.git git+https://github.com/pytest-dev/pytest-rerunfailures.git
git+https://github.com/abusalimov/pytest-travis-fold.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 git+https://github.com/The-Compiler/pytest-xvfb.git
hg+https://bitbucket.org/gutworth/six hg+https://bitbucket.org/gutworth/six
hg+https://bitbucket.org/jendrikseipp/vulture hg+https://bitbucket.org/jendrikseipp/vulture

View File

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

View File

@ -2,6 +2,7 @@ beautifulsoup4
cheroot cheroot
coverage coverage
Flask Flask
hunter
httpbin httpbin
hypothesis hypothesis
pytest pytest
@ -16,8 +17,7 @@ pytest-qt
pytest-repeat pytest-repeat
pytest-rerunfailures pytest-rerunfailures
pytest-travis-fold pytest-travis-fold
pytest-warnings
pytest-xvfb pytest-xvfb
vulture 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 # This file is automatically generated by scripts/dev/recompile_requirements.py
pluggy==0.4.0 pluggy==0.4.0
py==1.4.33 py==1.4.34
tox==2.6.0 tox==2.7.0
virtualenv==15.1.0 virtualenv==15.1.0

View File

@ -1,3 +1,3 @@
# This file is automatically generated by scripts/dev/recompile_requirements.py # 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 #!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com> # Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

View File

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2015 jnphilipp <me@jnphilipp.org> # Copyright 2015 jnphilipp <me@jnphilipp.org>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # 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 $blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
WARNING: the passwords are stored in qutebrowser's 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 $blink!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$reset
Usage: run as a userscript form qutebrowser, e.g.: Usage: run as a userscript form qutebrowser, e.g.:

View File

@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright 2015 Zach-Button <zachrey.button@gmail.com> # Copyright 2015 Zach-Button <zachrey.button@gmail.com>
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# #
# This file is part of qutebrowser. # 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. # 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: # Usage:
# :spawn --userscript readability # :spawn --userscript readability
# #
from __future__ import absolute_import from __future__ import absolute_import
import codecs, os import codecs, os
from readability.readability import Document
tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html') tmpfile=os.path.expanduser('~/.local/share/qutebrowser/userscripts/readability.html')
if not os.path.exists(os.path.dirname(tmpfile)): if not os.path.exists(os.path.dirname(tmpfile)):
os.makedirs(os.path.dirname(tmpfile)) os.makedirs(os.path.dirname(tmpfile))
with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source: with codecs.open(os.environ['QUTE_HTML'], 'r', 'utf-8') as source:
doc = Document(source.read()) data = source.read()
content = doc.summary().replace('<html>', '<html><head><title>%s</title></head>' % doc.title())
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: with codecs.open(tmpfile, 'w', 'utf-8') as target:
target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />') target.write('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')

View File

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

View File

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

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -21,7 +21,7 @@
import itertools 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.QtGui import QIcon
from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtWidgets import QWidget, QApplication
@ -35,11 +35,12 @@ from qutebrowser.browser import mouse, hints
tab_id_gen = itertools.count(0) 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. """Get a QtWebKit/QtWebEngine tab object.
Args: Args:
win_id: The window ID where the tab will be shown. 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. parent: The Qt parent to set.
""" """
# Importing modules here so we don't depend on QtWebEngine without the # Importing modules here so we don't depend on QtWebEngine without the
@ -51,7 +52,8 @@ def create(win_id, parent=None):
else: else:
from qutebrowser.browser.webkit import webkittab from qutebrowser.browser.webkit import webkittab
tab_class = webkittab.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(): def init():
@ -94,6 +96,8 @@ class TabData:
viewing_source: Set if we're currently showing a source view. viewing_source: Set if we're currently showing a source view.
override_target: Override for open_target for fake clicks (like hints). override_target: Override for open_target for fake clicks (like hints).
Only used for QtWebKit. Only used for QtWebKit.
pinned: Flag to pin the tab.
fullscreen: Whether the tab has a video shown fullscreen currently.
""" """
def __init__(self): def __init__(self):
@ -101,11 +105,21 @@ class TabData:
self.viewing_source = False self.viewing_source = False
self.inspector = None self.inspector = None
self.override_target = None self.override_target = None
self.pinned = False
self.fullscreen = False
class AbstractAction: 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): def __init__(self):
self._widget = None self._widget = None
@ -118,6 +132,13 @@ class AbstractAction:
"""Save the current page.""" """Save the current page."""
raise NotImplementedError 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: class AbstractPrinting:
@ -155,6 +176,8 @@ class AbstractSearch(QObject):
Attributes: Attributes:
text: The last thing this view was searched for. 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). _flags: The flags of the last search (needs to be set by subclasses).
_widget: The underlying WebView widget. _widget: The underlying WebView widget.
""" """
@ -163,6 +186,7 @@ class AbstractSearch(QObject):
super().__init__(parent) super().__init__(parent)
self._widget = None self._widget = None
self.text = None self.text = None
self.search_displayed = False
def search(self, text, *, ignore_case=False, reverse=False, def search(self, text, *, ignore_case=False, reverse=False,
result_cb=None): result_cb=None):
@ -441,11 +465,21 @@ class AbstractHistory:
def current_idx(self): def current_idx(self):
raise NotImplementedError raise NotImplementedError
def back(self): def back(self, count=1):
raise NotImplementedError 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): def forward(self, count=1):
raise NotImplementedError 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): def can_go_back(self):
raise NotImplementedError raise NotImplementedError
@ -453,6 +487,12 @@ class AbstractHistory:
def can_go_forward(self): def can_go_forward(self):
raise NotImplementedError raise NotImplementedError
def _item_at(self, i):
raise NotImplementedError
def _go_to_item(self, item):
raise NotImplementedError
def serialize(self): def serialize(self):
"""Serialize into an opaque format understood by self.deserialize.""" """Serialize into an opaque format understood by self.deserialize."""
raise NotImplementedError raise NotImplementedError
@ -524,6 +564,7 @@ class AbstractTab(QWidget):
Attributes: Attributes:
history: The AbstractHistory for the current tab. history: The AbstractHistory for the current tab.
registry: The ObjectRegistry associated with this 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 _load_status: loading status of this page
Accessible via load_status() method. Accessible via load_status() method.
@ -563,7 +604,8 @@ class AbstractTab(QWidget):
fullscreen_requested = pyqtSignal(bool) fullscreen_requested = pyqtSignal(bool)
renderer_process_terminated = pyqtSignal(TerminationStatus, int) 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.win_id = win_id
self.tab_id = next(tab_id_gen) self.tab_id = next(tab_id_gen)
super().__init__(parent) super().__init__(parent)
@ -740,6 +782,10 @@ class AbstractTab(QWidget):
def clear_ssl_errors(self): def clear_ssl_errors(self):
raise NotImplementedError 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): def dump_async(self, callback, *, plain=False):
"""Dump the current page to a file ascync. """Dump the current page to a file ascync.
@ -771,7 +817,7 @@ class AbstractTab(QWidget):
def icon(self): def icon(self):
raise NotImplementedError raise NotImplementedError
def set_html(self, html, base_url): def set_html(self, html, base_url=QUrl()):
raise NotImplementedError raise NotImplementedError
def networkaccessmanager(self): def networkaccessmanager(self):

View File

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

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -19,215 +19,82 @@
"""Simple history which gets written to disk.""" """Simple history which gets written to disk."""
import os
import time 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.commands import cmdutils, cmdexc
from qutebrowser.utils import (utils, objreg, standarddir, log, qtutils, from qutebrowser.utils import (utils, objreg, log, usertypes, message,
usertypes, message) debug, standarddir)
from qutebrowser.config import config from qutebrowser.misc import objects, sql
from qutebrowser.misc import lineparser, objects
class Entry: class CompletionHistory(sql.SqlTable):
"""A single entry in the web history. """History which only has the newest entry for each URL."""
Attributes: def __init__(self, parent=None):
atime: The time the page was accessed. super().__init__("CompletionHistory", ['url', 'title', 'last_atime'],
url: The URL which was accessed as QUrl. constraints={'url': 'PRIMARY KEY'}, parent=parent)
redirect: If True, don't save this entry to disk self.create_index('CompletionHistoryAtimeIndex', 'last_atime')
"""
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)
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 def __init__(self, parent=None):
from disk async while new history is already arriving. 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 self._before_query = sql.Query('SELECT * FROM History '
OrderedDict (sorted by time) of URL strings mapped to Entry objects. 'where not redirect '
'and not url like "qute://%" '
While reading from disk is still ongoing, the history is saved in 'and atime <= :latest '
self._temp_history instead, and then appended to self.history_dict once 'ORDER BY atime desc '
that's fully populated. 'limit :limit offset :offset')
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)
def __repr__(self): def __repr__(self):
return utils.get_repr(self, length=len(self)) return utils.get_repr(self, length=len(self))
def __iter__(self): def __contains__(self, url):
return iter(self.history_dict.values()) return self._contains_query.run(val=url).value()
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 get_recent(self): def get_recent(self):
"""Get the most recent history entries.""" """Get the most recent history entries."""
old = self._lineparser.get_recent() return self.select(sort_by='atime', sort_order='desc', limit=100)
return old + [str(e) for e in self._new_history]
def save(self): def entries_between(self, earliest, latest):
"""Save the history to disk.""" """Iterate non-redirect, non-qute entries between two timestamps.
new = (str(e) for e in self._new_history[self._saved_count:])
self._lineparser.new_data = new Args:
self._lineparser.save() earliest: Omit timestamps earlier than this.
self._saved_count = len(self._new_history) 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') @cmdutils.register(name='history-clear', instance='web-history')
def clear(self, force=False): def clear(self, force=False):
@ -247,16 +114,27 @@ class WebHistory(QObject):
"history?") "history?")
def _do_clear(self): def _do_clear(self):
self._lineparser.clear() self.delete_all()
self.history_dict.clear() self.completion.delete_all()
self._temp_history.clear()
self._new_history.clear() def delete_url(self, url):
self._saved_count = 0 """Remove all history entries with the given url.
self.cleared.emit()
Args:
url: URL string to delete.
"""
self.delete('url', url)
self.completion.delete('url', url)
@pyqtSlot(QUrl, QUrl, str) @pyqtSlot(QUrl, QUrl, str)
def add_from_tab(self, url, requested_url, title): def add_from_tab(self, url, requested_url, title):
"""Add a new history entry as slot, called from a BrowserTab.""" """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) no_formatting = QUrl.UrlFormattingOption(0)
if (requested_url.isValid() and if (requested_url.isValid() and
not requested_url.matches(url, no_formatting)): not requested_url.matches(url, no_formatting)):
@ -274,23 +152,135 @@ class WebHistory(QObject):
(hidden in completion) (hidden in completion)
atime: Override the atime used to add the entry atime: Override the atime used to add the entry
""" """
if config.get('general', 'private-browsing'): if not url.isValid(): # pragma: no cover
return # the no cover pragma is a WORKAROUND for this not being covered in
if not url.isValid(): # old Qt versions.
log.misc.warning("Ignoring invalid URL being added to history") log.misc.warning("Ignoring invalid URL being added to history")
return return
if atime is None: atime = int(atime) if (atime is not None) else int(time.time())
atime = time.time() url_str = url.toString(QUrl.FullyEncoded | QUrl.RemovePassword)
entry = Entry(atime, url, title, redirect=redirect) self.insert({'url': url_str,
if self._initial_read_done: 'title': title,
self._add_entry(entry) 'atime': atime,
self._new_history.append(entry) 'redirect': redirect})
self.item_added.emit(entry) if not redirect:
if not entry.redirect: self.completion.insert({'url': url_str,
self.add_completion_item.emit(entry) '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: 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): def init(parent=None):
@ -299,8 +289,7 @@ def init(parent=None):
Args: Args:
parent: The parent to use for WebHistory. parent: The parent to use for WebHistory.
""" """
history = WebHistory(hist_dir=standarddir.data(), hist_name='history', history = WebHistory(parent=parent)
parent=parent)
objreg.register('web-history', history) objreg.register('web-history', history)
if objects.backend == usertypes.Backend.QtWebKit: if objects.backend == usertypes.Backend.QtWebKit:

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -312,4 +312,4 @@ class PACFetcher(QObject):
# Later NetworkManager.createRequest will detect this and display # Later NetworkManager.createRequest will detect this and display
# an error message. # an error message.
error_host = "pac-resolve-error.qutebrowser.invalid" 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: # 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. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -27,6 +27,7 @@ import collections
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from qutebrowser.config import config
from qutebrowser.utils import message, usertypes, log, urlutils, utils from qutebrowser.utils import message, usertypes, log, urlutils, utils
from qutebrowser.browser import downloads from qutebrowser.browser import downloads
from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit import http
@ -273,7 +274,7 @@ class DownloadItem(downloads.AbstractDownloadItem):
if self.fileobj is None or self._reply is None: 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 # 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 # got a readyRead after the reply was finished (which happens on
# qute:log for example). # qute://log for example).
return return
if not self._reply.isOpen(): if not self._reply.isOpen():
raise OSError("Reply is closed!") raise OSError("Reply is closed!")
@ -366,7 +367,8 @@ class DownloadManager(downloads.AbstractDownloadManager):
def __init__(self, win_id, parent=None): def __init__(self, win_id, parent=None):
super().__init__(parent) super().__init__(parent)
self._networkmanager = networkmanager.NetworkManager( 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') @pyqtSlot('QUrl')
def get(self, url, *, user_agent=None, **kwargs): def get(self, url, *, user_agent=None, **kwargs):
@ -410,7 +412,8 @@ class DownloadManager(downloads.AbstractDownloadManager):
mhtml.start_download_checked, tab=tab)) mhtml.start_download_checked, tab=tab))
message.global_bridge.ask(question, blocking=False) 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. """Start a download with a QNetworkRequest.
Args: Args:
@ -426,7 +429,9 @@ class DownloadManager(downloads.AbstractDownloadManager):
request.setAttribute(QNetworkRequest.CacheLoadControlAttribute, request.setAttribute(QNetworkRequest.CacheLoadControlAttribute,
QNetworkRequest.AlwaysNetwork) 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()) suggested_fn = urlutils.filename_from_url(request.url())
else: else:
# We might be downloading a binary blob embedded on a page or even # 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: # 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. # This file is part of qutebrowser.
# #
@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Backend-independent qute:* code. """Backend-independent qute://* code.
Module attributes: Module attributes:
pyeval_output: The output of the last :pyeval command. pyeval_output: The output of the last :pyeval command.
@ -26,16 +26,17 @@ Module attributes:
import json import json
import os import os
import sys
import time import time
import urllib.parse import urllib.parse
import datetime
import pkg_resources
from PyQt5.QtCore import QUrlQuery from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser import qutebrowser
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import (version, utils, jinja, log, message, docutils, from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg) objreg, usertypes, qtutils)
from qutebrowser.misc import objects from qutebrowser.misc import objects
@ -77,12 +78,25 @@ class QuteSchemeError(Exception):
super().__init__(errorstring) 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: 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. 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): def wrong_backend_handler(self, url):
"""Show an error page about using the invalid backend.""" """Show an error page about using the invalid backend."""
html = jinja.render('error.html', html = jinja.render('error.html',
title="Error while opening qute:url", title="Error while opening qute://url",
url=url.toDisplayString(), url=url.toDisplayString(),
error='{} is not available with this ' error='{} is not available with this '
'backend'.format(url.toDisplayString()), '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". # A url like "qute:foo" is split as "scheme:path", not "scheme:host".
log.misc.debug("url: {}, path: {}, host {}".format( log.misc.debug("url: {}, path: {}, host {}".format(
url.toDisplayString(), path, host)) 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: try:
handler = _HANDLERS[path] handler = _HANDLERS[host]
except KeyError: except KeyError:
try: raise NoHandlerFound(url)
handler = _HANDLERS[host]
except KeyError:
raise NoHandlerFound(url)
try: try:
mimetype, data = handler(url) mimetype, data = handler(url)
except OSError as e: except OSError as e:
@ -152,7 +172,7 @@ def data_for_url(url):
@add_handler('bookmarks') @add_handler('bookmarks')
def qute_bookmarks(_url): 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(), bookmarks = sorted(objreg.get('bookmark-manager').marks.items(),
key=lambda x: x[1]) # Sort by title key=lambda x: x[1]) # Sort by title
quickmarks = sorted(objreg.get('quickmark-manager').marks.items(), quickmarks = sorted(objreg.get('quickmark-manager').marks.items(),
@ -165,68 +185,36 @@ def qute_bookmarks(_url):
return 'text/html', html 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): def qute_history(url):
"""Handler for qute:history. Display and serve history.""" """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)}
if url.path() == '/data': 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. # Use start_time in query or current time.
try: try:
start_time = QUrlQuery(url).queryItemValue("start_time") start_time = QUrlQuery(url).queryItemValue("start_time")
@ -234,26 +222,57 @@ def qute_history(url):
except ValueError as e: except ValueError as e:
raise QuteSchemeError("Query parameter start_time is invalid", e) raise QuteSchemeError("Query parameter start_time is invalid", e)
if sys.hexversion >= 0x03050000: return 'text/html', json.dumps(history_data(start_time, offset))
# 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))
else: else:
return 'text/html', jinja.render('history.html', title='History', if (
session_interval=config.get('ui', 'history-session-interval')) 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') @add_handler('javascript')
def qute_javascript(url): def qute_javascript(url):
"""Handler for qute:javascript. """Handler for qute://javascript.
Return content of file given as query parameter. Return content of file given as query parameter.
""" """
@ -267,7 +286,7 @@ def qute_javascript(url):
@add_handler('pyeval') @add_handler('pyeval')
def qute_pyeval(_url): def qute_pyeval(_url):
"""Handler for qute:pyeval.""" """Handler for qute://pyeval."""
html = jinja.render('pre.html', title='pyeval', content=pyeval_output) html = jinja.render('pre.html', title='pyeval', content=pyeval_output)
return 'text/html', html return 'text/html', html
@ -275,7 +294,7 @@ def qute_pyeval(_url):
@add_handler('version') @add_handler('version')
@add_handler('verizon') @add_handler('verizon')
def qute_version(_url): def qute_version(_url):
"""Handler for qute:version.""" """Handler for qute://version."""
html = jinja.render('version.html', title='Version info', html = jinja.render('version.html', title='Version info',
version=version.version(), version=version.version(),
copyright=qutebrowser.__copyright__) copyright=qutebrowser.__copyright__)
@ -284,7 +303,7 @@ def qute_version(_url):
@add_handler('plainlog') @add_handler('plainlog')
def qute_plainlog(url): def qute_plainlog(url):
"""Handler for qute:plainlog. """Handler for qute://plainlog.
An optional query parameter specifies the minimum log level to print. An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors. For example, qute://log?level=warning prints warnings and errors.
@ -304,7 +323,7 @@ def qute_plainlog(url):
@add_handler('log') @add_handler('log')
def qute_log(url): def qute_log(url):
"""Handler for qute:log. """Handler for qute://log.
An optional query parameter specifies the minimum log level to print. An optional query parameter specifies the minimum log level to print.
For example, qute://log?level=warning prints warnings and errors. For example, qute://log?level=warning prints warnings and errors.
@ -325,13 +344,13 @@ def qute_log(url):
@add_handler('gpl') @add_handler('gpl')
def qute_gpl(_url): 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') return 'text/html', utils.read_file('html/COPYING.html')
@add_handler('help') @add_handler('help')
def qute_help(url): def qute_help(url):
"""Handler for qute:help.""" """Handler for qute://help."""
try: try:
utils.read_file('html/doc/index.html') utils.read_file('html/doc/index.html')
except OSError: except OSError:
@ -360,3 +379,14 @@ def qute_help(url):
else: else:
data = utils.read_file(path) data = utils.read_file(path)
return 'text/html', data 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: # 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. # This file is part of qutebrowser.
# #
@ -216,8 +216,10 @@ def get_tab(win_id, target):
win_id = win_id win_id = win_id
bg_tab = True bg_tab = True
elif target == usertypes.ClickTarget.window: elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
from qutebrowser.mainwindow import mainwindow from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow() window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show() window.show()
win_id = window.win_id win_id = window.win_id
bg_tab = False bg_tab = False

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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>
# Copyright 2015-2016 Antoni Boucher <bouanto@zoho.com> # Copyright 2015-2017 Antoni Boucher <bouanto@zoho.com>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -19,9 +19,7 @@
"""Wrapper over a QWebEngineCertificateError.""" """Wrapper over a QWebEngineCertificateError."""
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineCertificateError from PyQt5.QtWebEngineWidgets import QWebEngineCertificateError
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.utils import usertypes, utils, debug from qutebrowser.utils import usertypes, utils, debug

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -25,9 +25,7 @@ import urllib
import functools import functools
from PyQt5.QtCore import pyqtSlot, Qt from PyQt5.QtCore import pyqtSlot, Qt
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem from PyQt5.QtWebEngineWidgets import QWebEngineDownloadItem
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import downloads from qutebrowser.browser import downloads
from qutebrowser.utils import debug, usertypes, message, log, qtutils from qutebrowser.utils import debug, usertypes, message, log, qtutils
@ -79,7 +77,12 @@ class DownloadItem(downloads.AbstractDownloadItem):
elif state == QWebEngineDownloadItem.DownloadInterrupted: elif state == QWebEngineDownloadItem.DownloadInterrupted:
self.successful = False self.successful = False
# https://bugreports.qt.io/browse/QTBUG-56839 # 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: else:
raise ValueError("_on_state_changed was called with unknown state " raise ValueError("_on_state_changed was called with unknown state "
"{}".format(state_name)) "{}".format(state_name))
@ -100,7 +103,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
def _get_open_filename(self): def _get_open_filename(self):
return self._filename return self._filename
def _set_fileobj(self, fileobj): def _set_fileobj(self, fileobj, *,
autoclose=True): # pylint: disable=unused-argument
raise downloads.UnsupportedOperationError raise downloads.UnsupportedOperationError
def _set_tempfile(self, fileobj): def _set_tempfile(self, fileobj):
@ -146,7 +150,7 @@ def _get_suggested_filename(path):
""" """
filename = os.path.basename(path) filename = os.path.basename(path)
filename = re.sub(r'\([0-9]+\)(?=\.|$)', '', filename) 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 # https://bugreports.qt.io/browse/QTBUG-58155
filename = urllib.parse.unquote(filename) filename = urllib.parse.unquote(filename)
# Doing basename a *second* time because there could be a %2F in # 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: # 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. # This file is part of qutebrowser.
# #
@ -18,16 +18,14 @@
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# FIXME:qtwebengine remove this once the stubs are gone # 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.""" """QtWebEngine specific part of the web element API."""
from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop from PyQt5.QtCore import QRect, Qt, QPoint, QEventLoop
from PyQt5.QtGui import QMouseEvent from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineSettings from PyQt5.QtWebEngineWidgets import QWebEngineSettings
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.utils import log, javascript from qutebrowser.utils import log, javascript
from qutebrowser.browser import webelem from qutebrowser.browser import webelem
@ -39,6 +37,38 @@ class WebEngineElement(webelem.AbstractWebElement):
def __init__(self, js_dict, tab): def __init__(self, js_dict, tab):
super().__init__(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._id = js_dict['id']
self._js_dict = js_dict self._js_dict = js_dict
@ -88,7 +118,9 @@ class WebEngineElement(webelem.AbstractWebElement):
The returned name will always be lower-case. 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): def outer_xml(self):
"""Get the full HTML representation of this element.""" """Get the full HTML representation of this element."""
@ -158,21 +190,19 @@ class WebEngineElement(webelem.AbstractWebElement):
def _click_editable(self, click_target): def _click_editable(self, click_target):
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515 # 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), ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton, QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
Qt.NoModifier, Qt.MouseEventSynthesizedBySystem) Qt.NoModifier, Qt.MouseEventSynthesizedBySystem)
# pylint: enable=no-member,useless-suppression
self._tab.send_event(ev) self._tab.send_event(ev)
# This actually "clicks" the element by calling focus() on it in JS. # This actually "clicks" the element by calling focus() on it in JS.
self._js_call('focus') self._js_call('focus')
self._move_text_cursor() self._move_text_cursor()
def _click_js(self, _click_target): 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 attribute = QWebEngineSettings.JavascriptCanOpenWindows
could_open_windows = settings.testAttribute(attribute) could_open_windows = settings.testAttribute(attribute)
settings.setAttribute(attribute, True) settings.setAttribute(attribute, True)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -22,9 +22,7 @@
import os import os
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtWebEngineWidgets import QWebEngineView
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import inspector from qutebrowser.browser import inspector

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # 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. """Bridge from QWebEngineSettings to our own settings.
Module attributes: Module attributes:
@ -25,81 +28,96 @@ Module attributes:
""" """
import os 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, from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
QWebEngineScript) QWebEngineScript)
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import shared from qutebrowser.browser import shared
from qutebrowser.config import config, websettings 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.""" """A setting set via QWebEngineSettings::setAttribute."""
GLOBAL_SETTINGS = QWebEngineSettings.globalSettings
ENUM_BASE = QWebEngineSettings 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): class DefaultProfileSetter(websettings.Base):
"""A setting set via static QWebEngineSettings getter/setter methods."""
GLOBAL_SETTINGS = QWebEngineSettings.globalSettings
class ProfileSetter(websettings.Base):
"""A setting set on the QWebEngineProfile.""" """A setting set on the QWebEngineProfile."""
def __init__(self, getter, setter): def __init__(self, setter, default=websettings.UNSET):
super().__init__() super().__init__(default)
self._getter = getter
self._setter = setter self._setter = setter
def get(self, settings=None): def __repr__(self):
utils.unused(settings) return utils.get_repr(self, setter=self._setter, constructor=True)
getter = getattr(QWebEngineProfile.defaultProfile(), self._getter)
return getter()
def _set(self, value, settings=None): def _set(self, value, settings=None):
utils.unused(settings) if settings is not None:
setter = getattr(QWebEngineProfile.defaultProfile(), self._setter) raise ValueError("'settings' may not be set with "
"DefaultProfileSetters!")
setter = getattr(default_profile, self._setter)
setter(value) setter(value)
class PersistentCookiePolicy(ProfileSetter): class PersistentCookiePolicy(DefaultProfileSetter):
"""The cookies -> store setting is different from other settings.""" """The cookies -> store setting is different from other settings."""
def __init__(self): def __init__(self):
super().__init__(getter='persistentCookiesPolicy', super().__init__('setPersistentCookiesPolicy')
setter='setPersistentCookiesPolicy')
def get(self, settings=None):
utils.unused(settings)
return config.get('content', 'cookies-store')
def _set(self, value, settings=None): 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 = getattr(QWebEngineProfile.defaultProfile(), self._setter)
setter( setter(
QWebEngineProfile.AllowPersistentCookies if value else QWebEngineProfile.AllowPersistentCookies if value else
@ -113,9 +131,6 @@ def _init_stylesheet(profile):
Mostly inspired by QupZilla: 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/app/mainapplication.cpp#L1063-L1101
https://github.com/QupZilla/qupzilla/blob/v2.0/src/lib/tools/scripts.cpp#L119-L132 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') old_script = profile.scripts().findScript('_qute_stylesheet')
if not old_script.isNull(): if not old_script.isNull():
@ -140,19 +155,43 @@ def _init_stylesheet(profile):
profile.scripts().insert(script) profile.scripts().insert(script)
def _init_profile(profile): def _set_user_agent(profile):
"""Initialize settings set on the QWebEngineProfile.""" """Set the user agent for the given profile.
profile.setCachePath(os.path.join(standarddir.cache(), 'webengine'))
profile.setPersistentStoragePath( We override this per request in the URL interceptor (to allow for
os.path.join(standarddir.data(), 'webengine')) 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): def update_settings(section, option):
"""Update global settings when qwebsettings changed.""" """Update global settings when qwebsettings changed."""
websettings.update_mappings(MAPPINGS, section, option) websettings.update_mappings(MAPPINGS, section, option)
profile = QWebEngineProfile.defaultProfile()
if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']: 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): def init(args):
@ -160,25 +199,12 @@ def init(args):
if args.enable_webengine_inspector: if args.enable_webengine_inspector:
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port()) os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
# Workaround for a black screen with some setups _init_profiles()
# 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")
profile = QWebEngineProfile.defaultProfile()
_init_profile(profile)
_init_stylesheet(profile)
# We need to do this here as a WORKAROUND for # We need to do this here as a WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58650 # 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) Attribute(QWebEngineSettings.FullScreenSupportEnabled).set(True)
websettings.init_mappings(MAPPINGS) websettings.init_mappings(MAPPINGS)
@ -199,7 +225,6 @@ def shutdown():
# - AllowRunningInsecureContent (5.8) # - AllowRunningInsecureContent (5.8)
# #
# Missing QtWebEngine fonts: # Missing QtWebEngine fonts:
# - FantasyFont
# - PictographFont # - PictographFont
@ -221,9 +246,6 @@ MAPPINGS = {
Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls), Attribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls),
'local-content-can-access-file-urls': 'local-content-can-access-file-urls':
Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls), Attribute(QWebEngineSettings.LocalContentCanAccessFileUrls),
# https://bugreports.qt.io/browse/QTBUG-58650
# 'cookies-store':
# PersistentCookiePolicy(),
'webgl': 'webgl':
Attribute(QWebEngineSettings.WebGLEnabled), Attribute(QWebEngineSettings.WebGLEnabled),
}, },
@ -235,44 +257,28 @@ MAPPINGS = {
}, },
'fonts': { 'fonts': {
'web-family-standard': 'web-family-standard':
Setter(getter=QWebEngineSettings.fontFamily, FontFamilySetter(QWebEngineSettings.StandardFont),
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.StandardFont]),
'web-family-fixed': 'web-family-fixed':
Setter(getter=QWebEngineSettings.fontFamily, FontFamilySetter(QWebEngineSettings.FixedFont),
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.FixedFont]),
'web-family-serif': 'web-family-serif':
Setter(getter=QWebEngineSettings.fontFamily, FontFamilySetter(QWebEngineSettings.SerifFont),
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.SerifFont]),
'web-family-sans-serif': 'web-family-sans-serif':
Setter(getter=QWebEngineSettings.fontFamily, FontFamilySetter(QWebEngineSettings.SansSerifFont),
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.SansSerifFont]),
'web-family-cursive': 'web-family-cursive':
Setter(getter=QWebEngineSettings.fontFamily, FontFamilySetter(QWebEngineSettings.CursiveFont),
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.CursiveFont]),
'web-family-fantasy': 'web-family-fantasy':
Setter(getter=QWebEngineSettings.fontFamily, FontFamilySetter(QWebEngineSettings.FantasyFont),
setter=QWebEngineSettings.setFontFamily,
args=[QWebEngineSettings.FantasyFont]),
'web-size-minimum': 'web-size-minimum':
Setter(getter=QWebEngineSettings.fontSize, Setter(QWebEngineSettings.setFontSize,
setter=QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumFontSize]), args=[QWebEngineSettings.MinimumFontSize]),
'web-size-minimum-logical': 'web-size-minimum-logical':
Setter(getter=QWebEngineSettings.fontSize, Setter(QWebEngineSettings.setFontSize,
setter=QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.MinimumLogicalFontSize]), args=[QWebEngineSettings.MinimumLogicalFontSize]),
'web-size-default': 'web-size-default':
Setter(getter=QWebEngineSettings.fontSize, Setter(QWebEngineSettings.setFontSize,
setter=QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFontSize]), args=[QWebEngineSettings.DefaultFontSize]),
'web-size-default-fixed': 'web-size-default-fixed':
Setter(getter=QWebEngineSettings.fontSize, Setter(QWebEngineSettings.setFontSize,
setter=QWebEngineSettings.setFontSize,
args=[QWebEngineSettings.DefaultFixedFontSize]), args=[QWebEngineSettings.DefaultFixedFontSize]),
}, },
'ui': { 'ui': {
@ -283,15 +289,14 @@ MAPPINGS = {
'local-storage': 'local-storage':
Attribute(QWebEngineSettings.LocalStorageEnabled), Attribute(QWebEngineSettings.LocalStorageEnabled),
'cache-size': 'cache-size':
ProfileSetter(getter='httpCacheMaximumSize', # 0: automatically managed by QtWebEngine
setter='setHttpCacheMaximumSize') DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
}, },
'general': { 'general': {
'xss-auditing': 'xss-auditing':
Attribute(QWebEngineSettings.XSSAuditingEnabled), Attribute(QWebEngineSettings.XSSAuditingEnabled),
'default-encoding': 'default-encoding':
Setter(getter=QWebEngineSettings.defaultTextEncoding, Setter(QWebEngineSettings.setDefaultTextEncoding),
setter=QWebEngineSettings.setDefaultTextEncoding),
} }
} }
@ -301,3 +306,8 @@ try:
except AttributeError: except AttributeError:
# Added in Qt 5.8 # Added in Qt 5.8
pass 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: # 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. # This file is part of qutebrowser.
# #
@ -17,30 +17,27 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # 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.""" """Wrapper over a QWebEngineView."""
import os
import math
import functools import functools
import sip import sip
from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QUrl, QTimer
from PyQt5.QtGui import QKeyEvent from PyQt5.QtGui import QKeyEvent
from PyQt5.QtNetwork import QAuthenticator from PyQt5.QtNetwork import QAuthenticator
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import (QWebEnginePage, QWebEngineScript, from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
QWebEngineProfile)
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import browsertab, mouse, shared from qutebrowser.browser import browsertab, mouse, shared
from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory,
interceptor, webenginequtescheme, interceptor, webenginequtescheme,
webenginedownloads) webenginedownloads,
webenginesettings)
from qutebrowser.misc import miscwidgets from qutebrowser.misc import miscwidgets
from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils,
objreg, jinja) objreg, jinja, debug, version)
_qute_scheme_handler = None _qute_scheme_handler = None
@ -53,21 +50,31 @@ def init():
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html # https://www.riverbankcomputing.com/pipermail/pyqt/2016-September/038075.html
global _qute_scheme_handler global _qute_scheme_handler
app = QApplication.instance() 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 = 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...") log.init.debug("Initializing request interceptor...")
host_blocker = objreg.get('host-blocker') host_blocker = objreg.get('host-blocker')
req_interceptor = interceptor.RequestInterceptor( req_interceptor = interceptor.RequestInterceptor(
host_blocker, parent=app) 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...") log.init.debug("Initializing QtWebEngine downloads...")
download_manager = webenginedownloads.DownloadManager(parent=app) 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) objreg.register('webengine-download-manager', download_manager)
@ -82,17 +89,17 @@ _JS_WORLD_MAP = {
class WebEngineAction(browsertab.AbstractAction): class WebEngineAction(browsertab.AbstractAction):
"""QtWebKit implementations related to web actions.""" """QtWebEngine implementations related to web actions."""
def _action(self, action): action_class = QWebEnginePage
self._widget.triggerPageAction(action) action_base = QWebEnginePage.WebAction
def exit_fullscreen(self): def exit_fullscreen(self):
self._action(QWebEnginePage.ExitFullScreen) self._widget.triggerPageAction(QWebEnginePage.ExitFullScreen)
def save_page(self): def save_page(self):
"""Save the current page.""" """Save the current page."""
self._action(QWebEnginePage.SavePage) self._widget.triggerPageAction(QWebEnginePage.SavePage)
class WebEnginePrinting(browsertab.AbstractPrinting): class WebEnginePrinting(browsertab.AbstractPrinting):
@ -128,12 +135,23 @@ class WebEngineSearch(browsertab.AbstractSearch):
super().__init__(parent) super().__init__(parent)
self._flags = QWebEnginePage.FindFlags(0) self._flags = QWebEnginePage.FindFlags(0)
def _find(self, text, flags, cb=None): def _find(self, text, flags, callback, caller):
"""Call findText on the widget with optional callback.""" """Call findText on the widget."""
if cb is None: self.search_displayed = True
self._widget.findText(text, flags)
else: def wrapped_callback(found):
self._widget.findText(text, flags, cb) """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, def search(self, text, *, ignore_case=False, reverse=False,
result_cb=None): result_cb=None):
@ -148,9 +166,10 @@ class WebEngineSearch(browsertab.AbstractSearch):
self.text = text self.text = text
self._flags = flags self._flags = flags
self._find(text, flags, result_cb) self._find(text, flags, result_cb, 'search')
def clear(self): def clear(self):
self.search_displayed = False
self._widget.findText('') self._widget.findText('')
def prev_result(self, *, result_cb=None): def prev_result(self, *, result_cb=None):
@ -160,10 +179,10 @@ class WebEngineSearch(browsertab.AbstractSearch):
flags &= ~QWebEnginePage.FindBackward flags &= ~QWebEnginePage.FindBackward
else: else:
flags |= QWebEnginePage.FindBackward 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): 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): class WebEngineCaret(browsertab.AbstractCaret):
@ -237,8 +256,47 @@ class WebEngineCaret(browsertab.AbstractCaret):
raise browsertab.UnsupportedOperationError raise browsertab.UnsupportedOperationError
return self._widget.selectedText() 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): 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): class WebEngineScroller(browsertab.AbstractScroller):
@ -256,13 +314,10 @@ class WebEngineScroller(browsertab.AbstractScroller):
page = widget.page() page = widget.page()
page.scrollPositionChanged.connect(self._update_pos) 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)): for _ in range(min(count, 5000)):
press_evt = QKeyEvent(QEvent.KeyPress, key, Qt.NoModifier, 0, 0, 0) self._tab.key_press(key, modifier)
release_evt = QKeyEvent(QEvent.KeyRelease, key, Qt.NoModifier,
0, 0, 0)
self._tab.send_event(press_evt)
self._tab.send_event(release_evt)
@pyqtSlot() @pyqtSlot()
def _update_pos(self): def _update_pos(self):
@ -289,7 +344,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
else: else:
perc_y = min(100, round(100 / dy * jsret['px']['y'])) 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._pos_perc = perc_x, perc_y
self.perc_changed.emit(*self._pos_perc) self.perc_changed.emit(*self._pos_perc)
@ -319,28 +374,28 @@ class WebEngineScroller(browsertab.AbstractScroller):
self._tab.run_js_async(js_code) self._tab.run_js_async(js_code)
def up(self, count=1): 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): 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): 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): def right(self, count=1):
self._key_press(Qt.Key_Right, count) self._repeated_key_press(Qt.Key_Right, count)
def top(self): def top(self):
self._key_press(Qt.Key_Home) self._tab.key_press(Qt.Key_Home)
def bottom(self): def bottom(self):
self._key_press(Qt.Key_End) self._tab.key_press(Qt.Key_End)
def page_up(self, count=1): 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): 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): def at_top(self):
return self.pos_px().y() == 0 return self.pos_px().y() == 0
@ -356,29 +411,28 @@ class WebEngineHistory(browsertab.AbstractHistory):
def current_idx(self): def current_idx(self):
return self._history.currentItemIndex() return self._history.currentItemIndex()
def back(self):
self._history.back()
def forward(self):
self._history.forward()
def can_go_back(self): def can_go_back(self):
return self._history.canGoBack() return self._history.canGoBack()
def can_go_forward(self): def can_go_forward(self):
return self._history.canGoForward() 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): def serialize(self):
# WORKAROUND (remove this when we bump the requirements to 5.9) if not qtutils.version_check('5.9'):
# https://bugreports.qt.io/browse/QTBUG-59599 # WORKAROUND for
if self._history.count() == 0: # https://github.com/qutebrowser/qutebrowser/issues/2289
raise browsertab.WebTabError("Can't serialize page without " # Don't use the history's currentItem here, because of
"history!") # https://bugreports.qt.io/browse/QTBUG-59599 and because it doesn't
# WORKAROUND (FIXME: remove this when we bump the requirements to 5.9?) # contain view-source.
# https://github.com/qutebrowser/qutebrowser/issues/2289 scheme = self._tab.url().scheme()
scheme = self._history.currentItem().url().scheme() if scheme in ['view-source', 'chrome']:
if scheme in ['view-source', 'chrome']: raise browsertab.WebTabError("Can't serialize special URL!")
raise browsertab.WebTabError("Can't serialize special URL!")
return qtutils.serialize(self._history) return qtutils.serialize(self._history)
def deserialize(self, data): def deserialize(self, data):
@ -448,18 +502,18 @@ class WebEngineElements(browsertab.AbstractElements):
callback(elem) callback(elem)
def find_css(self, selector, callback, *, only_visible=False): 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) only_visible)
js_cb = functools.partial(self._js_cb_multiple, callback) js_cb = functools.partial(self._js_cb_multiple, callback)
self._tab.run_js_async(js_code, js_cb) self._tab.run_js_async(js_code, js_cb)
def find_id(self, elem_id, callback): 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) js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb) self._tab.run_js_async(js_code, js_cb)
def find_focused(self, callback): 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) js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb) self._tab.run_js_async(js_code, js_cb)
@ -467,7 +521,7 @@ class WebEngineElements(browsertab.AbstractElements):
assert pos.x() >= 0 assert pos.x() >= 0
assert pos.y() >= 0 assert pos.y() >= 0
pos /= self._tab.zoom.factor() 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()) pos.x(), pos.y())
js_cb = functools.partial(self._js_cb_single, callback) js_cb = functools.partial(self._js_cb_single, callback)
self._tab.run_js_async(js_code, js_cb) self._tab.run_js_async(js_code, js_cb)
@ -477,10 +531,11 @@ class WebEngineTab(browsertab.AbstractTab):
"""A QtWebEngine tab in the browser.""" """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, super().__init__(win_id=win_id, mode_manager=mode_manager,
parent=parent) private=private, parent=parent)
widget = webview.WebEngineView(tabdata=self.data, win_id=win_id) widget = webview.WebEngineView(tabdata=self.data, win_id=win_id,
private=private)
self.history = WebEngineHistory(self) self.history = WebEngineHistory(self)
self.scroller = WebEngineScroller(self, parent=self) self.scroller = WebEngineScroller(self, parent=self)
self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager, self.caret = WebEngineCaret(win_id=win_id, mode_manager=mode_manager,
@ -561,9 +616,11 @@ class WebEngineTab(browsertab.AbstractTab):
def shutdown(self): def shutdown(self):
self.shutting_down.emit() self.shutting_down.emit()
# WORKAROUND for self.action.exit_fullscreen()
# https://bugreports.qt.io/browse/QTBUG-58563 if qtutils.version_check('5.8', exact=True):
self.search.clear() # WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58563
self.search.clear()
self._widget.shutdown() self._widget.shutdown()
def reload(self, *, force=False): def reload(self, *, force=False):
@ -582,14 +639,12 @@ class WebEngineTab(browsertab.AbstractTab):
def icon(self): def icon(self):
return self._widget.icon() return self._widget.icon()
def set_html(self, html, base_url=None): def set_html(self, html, base_url=QUrl()):
# FIXME:qtwebengine # FIXME:qtwebengine
# check this and raise an exception if too big: # check this and raise an exception if too big:
# Warning: The content will be percent encoded before being sent to the # 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 # renderer via IPC. This may increase its size. The maximum size of the
# percent encoded content is 2 megabytes minus 30 bytes. # percent encoded content is 2 megabytes minus 30 bytes.
if base_url is None:
base_url = QUrl()
self._widget.setHtml(html, base_url) self._widget.setHtml(html, base_url)
def networkaccessmanager(self): def networkaccessmanager(self):
@ -601,6 +656,13 @@ class WebEngineTab(browsertab.AbstractTab):
def clear_ssl_errors(self): def clear_ssl_errors(self):
raise browsertab.UnsupportedOperationError 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() @pyqtSlot()
def _on_history_trigger(self): def _on_history_trigger(self):
url = self.url() url = self.url()
@ -644,12 +706,24 @@ class WebEngineTab(browsertab.AbstractTab):
def _on_fullscreen_requested(self, request): def _on_fullscreen_requested(self, request):
request.accept() request.accept()
on = request.toggleOn() on = request.toggleOn()
self.data.fullscreen = on
self.fullscreen_requested.emit(on) self.fullscreen_requested.emit(on)
if on: if on:
notification = miscwidgets.FullscreenNotification(self) notification = miscwidgets.FullscreenNotification(self)
notification.show() notification.show()
notification.set_timeout(3000) 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) @pyqtSlot(QWebEnginePage.RenderProcessTerminationStatus, int)
def _on_render_process_terminated(self, status, exitcode): def _on_render_process_terminated(self, status, exitcode):
"""Show an error when the renderer process terminated.""" """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: # 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. # This file is part of qutebrowser.
# #
@ -23,12 +23,10 @@ import functools
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
from PyQt5.QtGui import QPalette from PyQt5.QtGui import QPalette
# pylint: disable=no-name-in-module,import-error,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
# pylint: enable=no-name-in-module,import-error,useless-suppression
from qutebrowser.browser import shared 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.config import config
from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message, from qutebrowser.utils import (log, debug, usertypes, jinja, urlutils, message,
objreg) objreg)
@ -38,13 +36,19 @@ class WebEngineView(QWebEngineView):
"""Custom QWebEngineView subclass with qutebrowser-specific features.""" """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) super().__init__(parent)
self._win_id = win_id self._win_id = win_id
self._tabdata = tabdata self._tabdata = tabdata
theme_color = self.style().standardPalette().color(QPalette.Base) 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) self.setPage(page)
def shutdown(self): def shutdown(self):
@ -124,8 +128,8 @@ class WebEnginePage(QWebEnginePage):
certificate_error = pyqtSignal() certificate_error = pyqtSignal()
shutting_down = pyqtSignal() shutting_down = pyqtSignal()
def __init__(self, theme_color, parent=None): def __init__(self, *, theme_color, profile, parent=None):
super().__init__(parent) super().__init__(profile, parent)
self._is_shutting_down = False self._is_shutting_down = False
self.featurePermissionRequested.connect( self.featurePermissionRequested.connect(
self._on_feature_permission_requested) self._on_feature_permission_requested)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -21,161 +21,35 @@
import os.path import os.path
from PyQt5.QtCore import pyqtSlot from PyQt5.QtNetwork import QNetworkDiskCache
from PyQt5.QtNetwork import QNetworkDiskCache, QNetworkCacheMetaData
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import utils, objreg from qutebrowser.utils import utils, objreg, qtutils
class DiskCache(QNetworkDiskCache): class DiskCache(QNetworkDiskCache):
"""Disk cache which sets correct cache dir and size. """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())
"""
def __init__(self, cache_dir, parent=None): def __init__(self, cache_dir, parent=None):
super().__init__(parent) super().__init__(parent)
self._cache_dir = cache_dir self.setCacheDirectory(os.path.join(cache_dir, 'http'))
self._maybe_activate() self._set_cache_size()
objreg.get('config').changed.connect(self.on_config_changed) objreg.get('config').changed.connect(self._set_cache_size)
def __repr__(self): def __repr__(self):
return utils.get_repr(self, size=self.cacheSize(), return utils.get_repr(self, size=self.cacheSize(),
maxsize=self.maximumCacheSize(), maxsize=self.maximumCacheSize(),
path=self.cacheDirectory()) path=self.cacheDirectory())
@config.change_filter('storage', 'cache-size')
def _set_cache_size(self): def _set_cache_size(self):
"""Set the cache size based on the config.""" """Set the cache size based on the config."""
size = config.get('storage', 'cache-size') size = config.get('storage', 'cache-size')
if size is None: if size is None:
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate 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) 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: # 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. # This file is part of qutebrowser.
# #
@ -41,7 +41,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
try: try:
# Qt >= 5.4 # Qt >= 5.4
return hash(self._error) return hash(self._error)
except TypeError: except TypeError: # pragma: no cover
return hash((self._error.certificate().toDer(), return hash((self._error.certificate().toDer(),
self._error.error())) self._error.error()))

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,7 +1,7 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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>
# Copyright 2015-2016 Antoni Boucher (antoyo) <bouanto@zoho.com> # Copyright 2015-2017 Antoni Boucher (antoyo) <bouanto@zoho.com>
# #
# This file is part of qutebrowser. # This file is part of qutebrowser.
# #

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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, # Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # 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 # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # 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..""" """Special network replies.."""
@ -114,9 +118,6 @@ class ErrorNetworkReply(QNetworkReply):
# the device to avoid getting a warning. # the device to avoid getting a warning.
self.setOpenMode(QIODevice.ReadOnly) self.setOpenMode(QIODevice.ReadOnly)
self.setError(error, errorstring) 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.error.emit(error))
QTimer.singleShot(0, lambda: self.finished.emit()) QTimer.singleShot(0, lambda: self.finished.emit())
@ -137,3 +138,20 @@ class ErrorNetworkReply(QNetworkReply):
def isRunning(self): def isRunning(self):
return False 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: # 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, # Based on the Eric5 helpviewer,
# Copyright (c) 2009 - 2014 Detlev Offenbach <detlev@die-offenbachs.de> # 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: # 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. # This file is part of qutebrowser.
# #
@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # 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 mimetypes
import functools import functools
@ -28,13 +28,13 @@ from PyQt5.QtNetwork import QNetworkReply
from qutebrowser.browser import pdfjs, qutescheme from qutebrowser.browser import pdfjs, qutescheme
from qutebrowser.browser.webkit.network import schemehandler, networkreply 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 from qutebrowser.config import configexc, configdata
class QuteSchemeHandler(schemehandler.SchemeHandler): class QuteSchemeHandler(schemehandler.SchemeHandler):
"""Scheme handler for qute: URLs.""" """Scheme handler for qute:// URLs."""
def createRequest(self, _op, request, _outgoing_data): def createRequest(self, _op, request, _outgoing_data):
"""Create a new request. """Create a new request.
@ -62,6 +62,9 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
except qutescheme.QuteSchemeError as e: except qutescheme.QuteSchemeError as e:
return networkreply.ErrorNetworkReply(request, e.errorstring, return networkreply.ErrorNetworkReply(request, e.errorstring,
e.error, self.parent()) 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, return networkreply.FixedDataNetworkReply(request, data, mimetype,
self.parent()) self.parent())
@ -69,15 +72,15 @@ class QuteSchemeHandler(schemehandler.SchemeHandler):
class JSBridge(QObject): class JSBridge(QObject):
"""Javascript-bridge for special qute:... pages.""" """Javascript-bridge for special qute://... pages."""
@pyqtSlot(str, str, str) @pyqtSlot(str, str, str)
def set(self, sectname, optname, value): 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 # https://github.com/qutebrowser/qutebrowser/issues/727
if ((sectname, optname) == ('content', 'allow-javascript') and if ((sectname, optname) == ('content', 'allow-javascript') and
value == 'false'): 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.") "as it needs javascript support.")
return return
try: try:
@ -88,7 +91,7 @@ class JSBridge(QObject):
@qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit) @qutescheme.add_handler('settings', backend=usertypes.Backend.QtWebKit)
def qute_settings(_url): 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) config_getter = functools.partial(objreg.get('config').get, raw=True)
html = jinja.render('settings.html', title='settings', config=configdata, html = jinja.render('settings.html', title='settings', config=configdata,
confget=config_getter) confget=config_getter)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -286,9 +286,6 @@ def normalize_ws(text):
def parse_headers(content_disposition): def parse_headers(content_disposition):
"""Build a _ContentDisposition from header values.""" """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 # 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 # rejected by the grammar if it appears in other places), although parsing
# it can be ambiguous. Parsing it ensures that a non-ambiguous filename* # 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: # 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. # This file is part of qutebrowser.
# #
@ -21,7 +21,6 @@
from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
from PyQt5.QtWebKit import qWebKitVersion
from qutebrowser.utils import qtutils from qutebrowser.utils import qtutils
@ -181,7 +180,7 @@ def serialize(items):
else: else:
current_idx = 0 current_idx = 0
if qtutils.is_qtwebkit_ng(qWebKitVersion()): if qtutils.is_qtwebkit_ng():
_serialize_ng(items, current_idx, stream) _serialize_ng(items, current_idx, stream)
else: else:
_serialize_old(items, current_idx, stream) _serialize_old(items, current_idx, stream)

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -112,7 +112,9 @@ class WebKitElement(webelem.AbstractWebElement):
def value(self): def value(self):
self._check_vanished() 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): def set_value(self, value):
self._check_vanished() self._check_vanished()
@ -283,8 +285,7 @@ class WebKitElement(webelem.AbstractWebElement):
for _ in range(5): for _ in range(5):
if elem is None: if elem is None:
break break
tag = elem.tag_name() if elem.is_link():
if tag == 'a' or tag == 'area':
if elem.get('target', None) == '_blank': if elem.get('target', None) == '_blank':
elem['target'] = '_top' elem['target'] = '_top'
break break

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #

View File

@ -1,6 +1,6 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # 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. # This file is part of qutebrowser.
# #
@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. # 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. """Bridge from QWebSettings to our own settings.
Module attributes: Module attributes:
@ -26,43 +29,66 @@ Module attributes:
import os.path 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.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 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.""" """A setting set via QWebSettings::setAttribute."""
GLOBAL_SETTINGS = QWebSettings.globalSettings
ENUM_BASE = QWebSettings 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.""" """The ThirdPartyCookiePolicy setting is different from other settings."""
@ -73,12 +99,9 @@ class CookiePolicy(websettings.Base):
'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies, 'no-unknown-3rdparty': QWebSettings.AllowThirdPartyWithExistingCookies,
} }
def get(self, settings=None):
return config.get('content', 'cookies-accept')
def _set(self, value, settings=None): def _set(self, value, settings=None):
QWebSettings.globalSettings().setThirdPartyCookiePolicy( for obj in self._get_settings(settings):
self.MAPPING[value]) obj.setThirdPartyCookiePolicy(self.MAPPING[value])
def _set_user_stylesheet(): def _set_user_stylesheet():
@ -88,21 +111,9 @@ def _set_user_stylesheet():
QWebSettings.globalSettings().setUserStyleSheetUrl(url) 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): def update_settings(section, option):
"""Update global settings when qwebsettings changed.""" """Update global settings when qwebsettings changed."""
if (section, option) == ('general', 'private-browsing'): if section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
_init_private_browsing()
elif section == 'ui' and option in ['hide-scrollbar', 'user-stylesheet']:
_set_user_stylesheet() _set_user_stylesheet()
websettings.update_mappings(MAPPINGS, section, option) websettings.update_mappings(MAPPINGS, section, option)
@ -113,8 +124,7 @@ def init(_args):
cache_path = standarddir.cache() cache_path = standarddir.cache()
data_path = standarddir.data() data_path = standarddir.data()
_init_private_browsing() QWebSettings.setIconDatabasePath(standarddir.cache())
QWebSettings.setOfflineWebApplicationCachePath( QWebSettings.setOfflineWebApplicationCachePath(
os.path.join(cache_path, 'application-cache')) os.path.join(cache_path, 'application-cache'))
QWebSettings.globalSettings().setLocalStoragePath( QWebSettings.globalSettings().setLocalStoragePath(
@ -122,6 +132,13 @@ def init(_args):
QWebSettings.setOfflineStoragePath( QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage')) 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) websettings.init_mappings(MAPPINGS)
_set_user_stylesheet() _set_user_stylesheet()
objreg.get('config').changed.connect(update_settings) objreg.get('config').changed.connect(update_settings)
@ -146,14 +163,10 @@ MAPPINGS = {
Attribute(QWebSettings.JavascriptCanCloseWindows), Attribute(QWebSettings.JavascriptCanCloseWindows),
'javascript-can-access-clipboard': 'javascript-can-access-clipboard':
Attribute(QWebSettings.JavascriptCanAccessClipboard), Attribute(QWebSettings.JavascriptCanAccessClipboard),
#'allow-java':
# Attribute(QWebSettings.JavaEnabled),
'allow-plugins': 'allow-plugins':
Attribute(QWebSettings.PluginsEnabled), Attribute(QWebSettings.PluginsEnabled),
'webgl': 'webgl':
Attribute(QWebSettings.WebGLEnabled), Attribute(QWebSettings.WebGLEnabled),
'css-regions':
Attribute(QWebSettings.CSSRegionsEnabled),
'hyperlink-auditing': 'hyperlink-auditing':
Attribute(QWebSettings.HyperlinkAuditingEnabled), Attribute(QWebSettings.HyperlinkAuditingEnabled),
'local-content-can-access-remote-urls': 'local-content-can-access-remote-urls':
@ -175,44 +188,28 @@ MAPPINGS = {
}, },
'fonts': { 'fonts': {
'web-family-standard': 'web-family-standard':
Setter(getter=QWebSettings.fontFamily, FontFamilySetter(QWebSettings.StandardFont),
setter=QWebSettings.setFontFamily,
args=[QWebSettings.StandardFont]),
'web-family-fixed': 'web-family-fixed':
Setter(getter=QWebSettings.fontFamily, FontFamilySetter(QWebSettings.FixedFont),
setter=QWebSettings.setFontFamily,
args=[QWebSettings.FixedFont]),
'web-family-serif': 'web-family-serif':
Setter(getter=QWebSettings.fontFamily, FontFamilySetter(QWebSettings.SerifFont),
setter=QWebSettings.setFontFamily,
args=[QWebSettings.SerifFont]),
'web-family-sans-serif': 'web-family-sans-serif':
Setter(getter=QWebSettings.fontFamily, FontFamilySetter(QWebSettings.SansSerifFont),
setter=QWebSettings.setFontFamily,
args=[QWebSettings.SansSerifFont]),
'web-family-cursive': 'web-family-cursive':
Setter(getter=QWebSettings.fontFamily, FontFamilySetter(QWebSettings.CursiveFont),
setter=QWebSettings.setFontFamily,
args=[QWebSettings.CursiveFont]),
'web-family-fantasy': 'web-family-fantasy':
Setter(getter=QWebSettings.fontFamily, FontFamilySetter(QWebSettings.FantasyFont),
setter=QWebSettings.setFontFamily,
args=[QWebSettings.FantasyFont]),
'web-size-minimum': 'web-size-minimum':
Setter(getter=QWebSettings.fontSize, Setter(QWebSettings.setFontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.MinimumFontSize]), args=[QWebSettings.MinimumFontSize]),
'web-size-minimum-logical': 'web-size-minimum-logical':
Setter(getter=QWebSettings.fontSize, Setter(QWebSettings.setFontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.MinimumLogicalFontSize]), args=[QWebSettings.MinimumLogicalFontSize]),
'web-size-default': 'web-size-default':
Setter(getter=QWebSettings.fontSize, Setter(QWebSettings.setFontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.DefaultFontSize]), args=[QWebSettings.DefaultFontSize]),
'web-size-default-fixed': 'web-size-default-fixed':
Setter(getter=QWebSettings.fontSize, Setter(QWebSettings.setFontSize,
setter=QWebSettings.setFontSize,
args=[QWebSettings.DefaultFixedFontSize]), args=[QWebSettings.DefaultFixedFontSize]),
}, },
'ui': { 'ui': {
@ -221,9 +218,6 @@ MAPPINGS = {
'frame-flattening': 'frame-flattening':
Attribute(QWebSettings.FrameFlatteningEnabled), Attribute(QWebSettings.FrameFlatteningEnabled),
# user-stylesheet is handled separately # user-stylesheet is handled separately
'css-media-type':
NullStringSetter(getter=QWebSettings.cssMediaType,
setter=QWebSettings.setCSSMediaType),
'smooth-scrolling': 'smooth-scrolling':
Attribute(QWebSettings.ScrollAnimatorEnabled), Attribute(QWebSettings.ScrollAnimatorEnabled),
#'accelerated-compositing': #'accelerated-compositing':
@ -232,40 +226,22 @@ MAPPINGS = {
# Attribute(QWebSettings.TiledBackingStoreEnabled), # Attribute(QWebSettings.TiledBackingStoreEnabled),
}, },
'storage': { 'storage': {
'offline-storage-database': 'offline-web-application-cache':
Attribute(QWebSettings.OfflineStorageDatabaseEnabled),
'offline-web-application-storage':
Attribute(QWebSettings.OfflineWebApplicationCacheEnabled), Attribute(QWebSettings.OfflineWebApplicationCacheEnabled),
'local-storage': 'local-storage':
Attribute(QWebSettings.LocalStorageEnabled), Attribute(QWebSettings.LocalStorageEnabled,
QWebSettings.OfflineStorageDatabaseEnabled),
'maximum-pages-in-cache': 'maximum-pages-in-cache':
StaticSetter(getter=QWebSettings.maximumPagesInCache, StaticSetter(QWebSettings.setMaximumPagesInCache),
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),
}, },
'general': { 'general': {
'private-browsing':
Attribute(QWebSettings.PrivateBrowsingEnabled),
'developer-extras': 'developer-extras':
Attribute(QWebSettings.DeveloperExtrasEnabled), Attribute(QWebSettings.DeveloperExtrasEnabled),
'print-element-backgrounds': 'print-element-backgrounds':
Attribute(QWebSettings.PrintElementBackgrounds), Attribute(QWebSettings.PrintElementBackgrounds),
'xss-auditing': 'xss-auditing':
Attribute(QWebSettings.XSSAuditingEnabled), Attribute(QWebSettings.XSSAuditingEnabled),
'site-specific-quirks':
Attribute(QWebSettings.SiteSpecificQuirksEnabled),
'default-encoding': '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