Merge branch 'drop-legacy'

This commit is contained in:
Florian Bruhin 2017-09-18 23:07:58 +02:00
commit 43ff79be0b
76 changed files with 426 additions and 1301 deletions

View File

@ -6,18 +6,12 @@ python: 3.6
matrix:
include:
- os: linux
env: DOCKER=debian-jessie
services: docker
- os: linux
env: DOCKER=archlinux
services: docker
- os: linux
env: DOCKER=archlinux-webengine QUTE_BDD_WEBENGINE=true
services: docker
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
- os: linux
env: TESTENV=py36-pyqt571
- os: linux

View File

@ -99,11 +99,8 @@ Requirements
The following software and libraries are required to run qutebrowser:
* http://www.python.org/[Python] 3.4 or newer (3.6 recommended) - note that
support for Python 3.4
https://github.com/qutebrowser/qutebrowser/issues/2742[will be dropped soon].
* http://qt.io/[Qt] 5.2.0 or newer (5.9 recommended - note that support for Qt
< 5.7.1 will be dropped soon) with the following modules:
* http://www.python.org/[Python] 3.5 or newer (3.6 recommended)
* http://qt.io/[Qt] 5.7.1 or newer with the following modules:
- QtCore / qtbase
- QtQuick (part of qtbase in some distributions)
- QtSQL (part of qtbase in some distributions)
@ -111,9 +108,8 @@ The following software and libraries are required to run qutebrowser:
- QtWebKit (old or link:https://github.com/annulen/webkit/wiki[reloaded]/NG).
Note that support for legacy QtWebKit (before 5.212) will be
dropped soon.
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.2.0 or newer
(5.9 recommended) for Python 3. Note that support for PyQt < 5.7 will be
dropped soon.
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
(5.9 recommended) for Python 3.
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
* http://fdik.org/pyPEG/[pyPEG2]
* http://jinja.pocoo.org/[jinja2]

View File

@ -22,8 +22,8 @@ Breaking changes
- (TODO) Support for legacy QtWebKit (before 5.212 which is distributed
independently from Qt) is dropped.
- (TODO) Support for Python 3.4 is dropped.
- (TODO) Support for Qt before 5.7 is dropped.
- Support for Python 3.4 is dropped.
- Support for Qt before 5.7.1 and PyQt before 5.7 is dropped.
- (TODO) New dependency on ruamel.yaml; dropped PyYAML dependency.
- (TODO) The QtWebEngine backend is now used by default if available.
- New dependency on the QtSql module and Qt sqlite support.
@ -36,6 +36,7 @@ Breaking changes
work properly anymore.
- Various documentation files got moved to the doc/ subfolder,
`qutebrowser.desktop` got moved to misc/.
- The `--harfbuzz` commandline argument got dropped
Major changes
~~~~~~~~~~~~~
@ -50,6 +51,9 @@ Added
- New back/forward indicator in the statusbar
- New `bindings.key_mappings` setting to map keys to other keys
- New `qt_args` setting to pass additional arguments to Qt/Chromium
- New `backend` setting to select the backend to use (auto/webengine/webkit).
Together with the previous setting, this should make wrapper scripts
unnecessary.
Changed
~~~~~~~

View File

@ -180,7 +180,7 @@ In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
and shows a graphical representation of what takes how much time.
It uses the built-in Python
https://docs.python.org/3.4/library/profile.html[cProfile] module and can show
https://docs.python.org/3.6/library/profile.html[cProfile] module and can show
the output in four different ways:
* Raw profile file (`--profile-tool=none`)
@ -535,11 +535,11 @@ ____
Setting up a Windows Development Environment
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Install https://www.python.org/downloads/release/python-344/[Python 3.4]
* Install https://sourceforge.net/projects/pyqt/files/PyQt5/PyQt-5.5.1/[PyQt 5.5]
* Create a file at `C:\Windows\system32\python3.bat` with the following content:
`@C:\Python34\python %*`
This will make the Python 3.4 interpreter available as `python3`, which is used by various development scripts.
* Install https://www.python.org/downloads/release/python-362/[Python 3.6].
* Install PyQt via `pip install PyQt5`
* Create a file at `C:\Windows\system32\python3.bat` with the following content (adjust the path as necessary):
`@C:\Python36\python %*`
This will make the Python 3.6 interpreter available as `python3`, which is used by various development scripts.
* Install git from the https://git-scm.com/download/win[git-scm downloads page]
Try not to enable `core.autocrlf`, since that will cause `flake8` to complain a lot. Use an editor that can deal with plain line feeds instead.
* Clone your favourite qutebrowser repository.

View File

@ -205,26 +205,6 @@ Experiencing freezing on sites like duckduckgo and youtube.::
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
for more details.
Experiencing segfaults (crashes) on Debian systems.::
For Debian it's highly recommended to install the `gstreamer0.10-plugins-base` package.
This is a workaround for a bug in Qt, it has been fixed upstream in Qt 5.4
More details can be found
https://bugs.webkit.org/show_bug.cgi?id=119951[here].
Segfaults on Facebook, Medium, Amazon, ...::
If you are on a Debian or Ubuntu based system, you might experience some crashes
visiting these sites. This is caused by various bugs in Qt which have been
fixed in Qt 5.4. However Debian and Ubuntu are slow to adopt or upgrade
some packages. On Debian Jessie, it's recommended to use the experimental
repos as described in https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc#on-debian--ubuntu[the documentation].
+
Since Ubuntu Trusty (using Qt 5.2.1),
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.3.0%2C%20%225.3.0%20Alpha%22%2C%20%225.3.0%20Beta1%22%2C%20%225.3.0%20RC1%22%2C%205.3.1%2C%205.3.2%2C%205.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[over
70 important bugs] have been fixed in QtWebKit. For Debian Jessie (using Qt 5.3.2)
it's still
https://bugreports.qt.io/browse/QTBUG-42417?jql=component%20%3D%20WebKit%20and%20resolution%20%3D%20Done%20and%20fixVersion%20in%20(5.4.0%2C%20%225.4.0%20Alpha%22%2C%20%225.4.0%20Beta%22%2C%20%225.4.0%20RC%22)%20and%20priority%20in%20(%22P2%3A%20Important%22%2C%20%22P1%3A%20Critical%22%2C%20%22P0%3A%20Blocker%22)[nearly
20 important bugs].
When using QtWebEngine, qutebrowser reports "Render Process Crashed" and the console prints a traceback on Gentoo Linux or another Source-Based Distro::
As stated in https://gcc.gnu.org/gcc-6/changes.html[GCC's Website] GCC 6 has introduced some optimizations that could break non-conforming codebases, like QtWebEngine. +
As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +

View File

@ -12,6 +12,7 @@
|<<aliases,aliases>>|Aliases for commands.
|<<auto_save.interval,auto_save.interval>>|How often (in milliseconds) to auto-save config/cookies/etc.
|<<auto_save.session,auto_save.session>>|Always restore open sites when qutebrowser is reopened.
|<<backend,backend>>|The backend to use to display websites.
|<<bindings.commands,bindings.commands>>|Keybindings mapping keys to commands in different modes.
|<<bindings.default,bindings.default>>|Default keybindings. If you want to add bindings, modify `bindings.commands` instead.
|<<bindings.key_mappings,bindings.key_mappings>>|This setting can be used to map keys to other keys.
@ -285,6 +286,23 @@ Valid values:
Default: empty
[[backend]]
=== backend
The backend to use to display websites.
qutebrowser supports two different web rendering engines / backends, QtWebKit and QtWebEngine.
QtWebKit is based on WebKit (similar to Safari). It was discontinued by the Qt project with Qt 5.6, but picked up as a well maintained fork: https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
QtWebEngine is Qt's official successor to QtWebKit and based on the Chromium project. It's slightly more resource hungry that QtWebKit and has a couple of missing features in qutebrowser, but is generally the preferred choice.
Type: <<types,String>>
Valid values:
* +auto+: Automatically select either QtWebEngine or QtWebKit
* +webkit+: Force QtWebKit
* +webengine+: Force QtWebEngine
Default: +pass:[auto]+
[[bindings.commands]]
=== bindings.commands
Keybindings mapping keys to commands in different modes.

View File

@ -3,39 +3,50 @@ Installing qutebrowser
toc::[]
NOTE: qutebrowser recently had some bigger dependency changes for v1.0.0, which
means those instructions might be out of date in some places.
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[Please help]
updating them if you notice something being broken!
On Debian / Ubuntu
------------------
qutebrowser should run on these systems:
How to install qutebrowser depends a lot on the version of Debian/Ubuntu you're
running.
* Debian jessie or newer
* Ubuntu Trusty (14.04 LTS) or newer
* Any other distribution based on these (e.g. Linux Mint 17+)
Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is
still relatively easy!
Those distributions only have Python 3.4 and a too old Qt version available,
while qutebrowser requires Python 3.5 and Qt 5.7.1 or newer.
You can use packages that are built for every release or build it yourself from git.
It should be possible to install Python 3.5 e.g. from the
https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa[deadsnakes PPA] or via_ipca
https://github.com/pyenv/pyenv[pyenv], but nobody tried that yet.
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.
If you get qutebrowser running on those distributions, please
https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
to update this documentation!
Using the packages
~~~~~~~~~~~~~~~~~~
Ubuntu 16.04 LTS / Linux Mint 18
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ubuntu 16.04 doesn't come with an up-to-date engine (a new enough QtWebKit, or
QtWebEngine). However, it comes with Python 3.5, so you can
<<tox,install qutebrowser via tox>>.
Debian Stretch / Ubuntu 17.04 and newer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Those versions come with QtWebEngine in the repositories. This makes it possible
to install qutebrowser via the Debian package.
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 python3-pyqt5.qtsql libqt5sql5-sqlite
# apt install python-tox python3-{lxml,pyqt5,sip,jinja2,pygments,yaml} python3-pyqt5.qt{webengine,quick,opengl,sql} libqt5sql5-sqlite
----
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to use the
newer QtWebEngine backend.
To do so, install `python3-pyqt5.qtwebengine` and `python3-pyqt5.qtopengl`, then
start qutebrowser with `--backend webengine`.
Get the qutebrowser package from the
https://github.com/qutebrowser/qutebrowser/releases[release page] and download
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
@ -47,35 +58,27 @@ Install the packages:
# dpkg -i qutebrowser_*_all.deb
----
Build it from git
~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
Some additional hints:
- Alternatively, you can <<tox,install qutebrowser via tox>> to get a newer
QtWebEngine version.
- If running from git, run the following to generate the documentation for the
`:help` command:
+
----
# apt-get install python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python-tox python3-sip python3-dev python3-pyqt5.qtsql libqt5sql5-sqlite
----
On Debian Stretch or Ubuntu 17.04 or later, it's also recommended to install
`python3-pyqt5.qtwebengine` and start qutebrowser with `--backend webengine` in
order to use the new backend.
To generate the documentation for the `:help` command, when using the git
repository (rather than a release):
----
# apt-get install asciidoc source-highlight
# apt-get install --no-install-recommends asciidoc source-highlight
$ python3 scripts/asciidoc2html.py
----
If video or sound don't seem to work, try installing the gstreamer plugins:
- If you prefer using QtWebKit, there's an up-to-date version available in
Debian experimental, or from http://repo.paretje.be/unstable/[this repository]
for Debian Stretch.
- If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
+
----
# apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
----
Then <<tox,install qutebrowser via tox>>.
On Fedora
---------
@ -116,7 +119,7 @@ $ rm -r qutebrowser-git
or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`.
If video or sound don't seem to work, try installing the gstreamer plugins:
If video or sound don't work with QtWebKit, try installing the gstreamer plugins:
----
# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav
@ -125,6 +128,8 @@ If video or sound don't seem to work, try installing the gstreamer plugins:
On Gentoo
---------
WARNING: The Gentoo packages (even the live version) are lagging behind a lot, which means those instructions probably won't work anymore. Until things are looking better, it's recommended to <<tox,install qutebrowser via tox>>.
A version of qutebrowser is available in the main repository and can be installed with:
----
@ -161,15 +166,11 @@ To update to the last Live version, remember to do
To include qutebrowser among the updates.
Make sure you have `python3_4` in your `PYTHON_TARGETS`
(`/etc/portage/make.conf`) and rebuild your system (`emerge -uDNav @world`) if
necessary.
You'll also need to install `dev-qt/qtwebengine` or a newer QtWebKit using
https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71[this ebuild].
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 work with QtWebKit, try installing the gstreamer
plugins:
----
# emerge -av gst-plugins-{base,good,bad,ugly,libav}
@ -214,6 +215,8 @@ On openSUSE
There are prebuilt RPMs available at https://software.opensuse.org/download.html?project=network&package=qutebrowser[OBS].
To use the QtWebEngine backend, install `libqt5-qtwebengine`.
On OpenBSD
----------

View File

@ -84,9 +84,6 @@ show it.
*--force-color*::
Force colored logging
*--harfbuzz* '{old,new,system,auto}'::
HarfBuzz engine version to use. Default: auto.
*--relaxed-config*::
Silently remove unknown config options.

View File

@ -19,15 +19,12 @@ markers =
qtwebengine_todo: Features still missing with QtWebEngine
qtwebengine_skip: Tests not applicable with QtWebEngine
qtwebkit_skip: Tests not applicable with QtWebKit
qtwebkit_ng_xfail: Tests failing with QtWebKit-NG
qtwebkit_ng_skip: Tests skipped with QtWebKit-NG
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
js_prompt: Tests needing to display a javascript prompt
this: Used to mark tests during development
no_invalid_lines: Don't fail on unparseable lines in end2end tests
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478
qt55: Tests only running on Qt 5.5 or later
qt_log_level_fail = WARNING
qt_log_ignore =
^SpellCheck: .*

View File

@ -52,11 +52,14 @@ from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.keyinput import macros
from qutebrowser.mainwindow import mainwindow, prompt
from qutebrowser.misc import (readline, ipc, savemanager, sessions,
crashsignal, earlyinit, objects, sql, cmdhistory)
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir, error)
# We import utilcmds to run the cmdutils.register decorators.
crashsignal, earlyinit, sql, cmdhistory)
from qutebrowser.utils import (log, version, message, utils, urlutils, objreg,
usertypes, standarddir, error)
# pylint: disable=unused-import
# We import those to run the cmdutils.register decorators.
from qutebrowser.mainwindow.statusbar import command
from qutebrowser.misc import utilcmds
# pylint: enable=unused-import
qApp = None
@ -74,7 +77,7 @@ def run(args):
standarddir.init(args)
log.init.debug("Initializing config...")
config.early_init()
config.early_init(args)
global qApp
qApp = Application(args)
@ -329,17 +332,6 @@ def _open_special_pages(args):
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window='last-focused')
# Legacy QtWebKit warning
needs_warning = (objects.backend == usertypes.Backend.QtWebKit and
not qtutils.is_qtwebkit_ng())
warning_shown = general_sect.get('backend-warning-shown') == '1'
if not warning_shown and needs_warning:
tabbed_browser.tabopen(QUrl('qute://backend-warning'),
background=False)
general_sect['backend-warning-shown'] = '1'
# Quickstart page
quickstart_done = general_sect.get('quickstart-done') == '1'

View File

@ -24,6 +24,7 @@ import sys
import os.path
import shlex
import functools
import typing
from PyQt5.QtWidgets import QApplication, QTabBar, QDialog
from PyQt5.QtCore import Qt, QUrl, QEvent, QUrlQuery
@ -39,10 +40,11 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
webelem, downloads)
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils, typing, debug)
objreg, utils, debug)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import urlmodel, miscmodels
from qutebrowser.mainwindow import mainwindow
class CommandDispatcher:
@ -70,7 +72,6 @@ class CommandDispatcher:
def _new_tabbed_browser(self, private):
"""Get a tabbed-browser from a new window."""
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow(private=private)
new_window.show()
return new_window.tabbed_browser

View File

@ -24,6 +24,7 @@ import posixpath
from qutebrowser.browser import webelem
from qutebrowser.config import config
from qutebrowser.utils import objreg, urlutils, log, message, qtutils
from qutebrowser.mainwindow import mainwindow
class Error(Exception):
@ -134,7 +135,6 @@ def prevnext(*, browsertab, win_id, baseurl, prev=False,
window=win_id)
if window:
from qutebrowser.mainwindow import mainwindow
new_window = mainwindow.MainWindow(
private=cur_tabbed_browser.private)
new_window.show()

View File

@ -28,7 +28,6 @@ import json
import os
import time
import urllib.parse
import datetime
import textwrap
import pkg_resources
@ -37,7 +36,7 @@ from PyQt5.QtCore import QUrlQuery, QUrl
import qutebrowser
from qutebrowser.config import config, configdata, configexc, configdiff
from qutebrowser.utils import (version, utils, jinja, log, message, docutils,
objreg, usertypes, qtutils)
objreg)
from qutebrowser.misc import objects
@ -224,50 +223,13 @@ def qute_history(url):
return 'text/html', json.dumps(history_data(start_time, offset))
else:
if (
config.val.content.javascript.enabled and
(objects.backend == usertypes.Backend.QtWebEngine or
qtutils.is_qtwebkit_ng())
):
return 'text/html', jinja.render(
'history.html',
title='History',
gap_interval=config.val.history_gap_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(),
)
if not config.val.content.javascript.enabled:
return 'text/plain', b'JavaScript is required for qute://history'
return 'text/html', jinja.render(
'history.html',
title='History',
gap_interval=config.val.history_gap_interval
)
@add_handler('javascript')

View File

@ -23,6 +23,7 @@ import html
from qutebrowser.config import config
from qutebrowser.utils import usertypes, message, log, objreg, jinja
from qutebrowser.mainwindow import mainwindow
class CallSuper(Exception):
@ -234,7 +235,6 @@ def get_tab(win_id, target):
elif target == usertypes.ClickTarget.window:
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=win_id)
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
win_id = window.win_id

View File

@ -31,6 +31,7 @@ from PyQt5.QtGui import QMouseEvent
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.mainwindow import mainwindow
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg
@ -372,7 +373,6 @@ class AbstractWebElement(collections.abc.MutableMapping):
background = click_target == usertypes.ClickTarget.tab_bg
tabbed_browser.tabopen(url, background=background)
elif click_target == usertypes.ClickTarget.window:
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow(private=tabbed_browser.private)
window.show()
window.tabbed_browser.tabopen(url)

View File

@ -49,7 +49,6 @@ class DiskCache(QNetworkDiskCache):
if size is None:
size = 1024 * 1024 * 50 # default from QNetworkDiskCachePrivate
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-59909
if (qtutils.version_check('5.7.1') and
not qtutils.version_check('5.9')): # pragma: no cover
if not qtutils.version_check('5.9'): # pragma: no cover
size = 0
self.setMaximumCacheSize(size)

View File

@ -38,12 +38,7 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
string=str(self))
def __hash__(self):
try:
# Qt >= 5.4
return hash(self._error)
except TypeError:
return hash((self._error.certificate().toDer(),
self._error.error()))
return hash(self._error)
def __eq__(self, other):
return self._error == other._error # pylint: disable=protected-access

View File

@ -24,13 +24,12 @@ import collections
import netrc
import html
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
QUrl, QByteArray)
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
QByteArray)
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
urlutils)
from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
from qutebrowser.browser import shared
from qutebrowser.browser.webkit import certificateerror
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
@ -88,15 +87,9 @@ def _is_secure_cipher(cipher):
def init():
"""Disable insecure SSL ciphers on old Qt versions."""
if qtutils.version_check('5.3.0'):
default_ciphers = QSslSocket.defaultCiphers()
log.init.debug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
else:
# https://codereview.qt-project.org/#/c/75943/
default_ciphers = QSslSocket.supportedCiphers()
log.init.debug("Supported Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
default_ciphers = QSslSocket.defaultCiphers()
log.init.debug("Default Qt ciphers: {}".format(
', '.join(c.name() for c in default_ciphers)))
good_ciphers = []
bad_ciphers = []
@ -409,24 +402,11 @@ class NetworkManager(QNetworkAccessManager):
tab = objreg.get('tab', scope='tab', window=self._win_id,
tab=self._tab_id)
current_url = tab.url()
except (KeyError, RuntimeError, TypeError):
except (KeyError, RuntimeError):
# https://github.com/qutebrowser/qutebrowser/issues/889
# Catching RuntimeError and TypeError because we could be in
# the middle of the webpage shutdown here.
# Catching RuntimeError because we could be in the middle of
# the webpage shutdown here.
current_url = QUrl()
self.set_referer(req, current_url)
if PYQT_VERSION < 0x050301:
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
#
# If we don't disable our message handler, we get a freeze if a
# warning is printed due to a PyQt bug, e.g. when clicking a
# currency on http://ch.mouser.com/localsites/
#
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034420.html
with log.disable_qt_msghandler():
reply = super().createRequest(op, req, outgoing_data)
else:
reply = super().createRequest(op, req, outgoing_data)
return reply
return super().createRequest(op, req, outgoing_data)

View File

@ -25,13 +25,7 @@ from PyQt5.QtCore import QByteArray, QDataStream, QIODevice, QUrl
from qutebrowser.utils import qtutils
def _encode_url(url):
"""Encode a QUrl suitable to pass to QWebHistory."""
data = bytes(QUrl.toPercentEncoding(url.toString(), b':/#?&+=@%*'))
return data.decode('ascii')
def _serialize_ng(items, current_idx, stream):
def _serialize_items(items, current_idx, stream):
# {'currentItemIndex': 0,
# 'history': [{'children': [],
# 'documentSequenceNumber': 1485030525573123,
@ -47,13 +41,13 @@ def _serialize_ng(items, current_idx, stream):
# 'urlString': 'about:blank'}]}
data = {'currentItemIndex': current_idx, 'history': []}
for item in items:
data['history'].append(_serialize_item_ng(item))
data['history'].append(_serialize_item(item))
stream.writeInt(3) # history stream version
stream.writeQVariantMap(data)
def _serialize_item_ng(item):
def _serialize_item(item):
data = {
'originalURLString': item.original_url.toString(QUrl.FullyEncoded),
'scrollPosition': {'x': 0, 'y': 0},
@ -68,82 +62,6 @@ def _serialize_item_ng(item):
return data
def _serialize_old(items, current_idx, stream):
### Source/WebKit/qt/Api/qwebhistory.cpp operator<<
stream.writeInt(2) # history stream version
stream.writeInt(len(items))
stream.writeInt(current_idx)
for i, item in enumerate(items):
_serialize_item_old(i, item, stream)
def _serialize_item_old(i, item, stream):
"""Serialize a single WebHistoryItem into a QDataStream.
Args:
i: The index of the current item.
item: The WebHistoryItem to write.
stream: The QDataStream to write to.
"""
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
## urlString
stream.writeQString(_encode_url(item.url))
## title
stream.writeQString(item.title)
## originalURLString
stream.writeQString(_encode_url(item.original_url))
### Source/WebCore/history/HistoryItem.cpp decodeBackForwardTree
## backForwardTreeEncodingVersion
stream.writeUInt32(2)
## size (recursion stack)
stream.writeUInt64(0)
## node->m_documentSequenceNumber
# If two HistoryItems have the same document sequence number, then they
# refer to the same instance of a document. Traversing history from one
# such HistoryItem to another preserves the document.
stream.writeInt64(i + 1)
## size (node->m_documentState)
stream.writeUInt64(0)
## node->m_formContentType
# info used to repost form data
stream.writeQString(None)
## hasFormData
stream.writeBool(False)
## node->m_itemSequenceNumber
# If two HistoryItems have the same item sequence number, then they are
# clones of one another. Traversing history from one such HistoryItem to
# another is a no-op. HistoryItem clones are created for parent and
# sibling frames when only a subframe navigates.
stream.writeInt64(i + 1)
## node->m_referrer
stream.writeQString(None)
## node->m_scrollPoint (x)
try:
stream.writeInt32(item.user_data['scroll-pos'].x())
except (KeyError, TypeError):
stream.writeInt32(0)
## node->m_scrollPoint (y)
try:
stream.writeInt32(item.user_data['scroll-pos'].y())
except (KeyError, TypeError):
stream.writeInt32(0)
## node->m_pageScaleFactor
stream.writeFloat(1)
## hasStateObject
# Support for HTML5 History
stream.writeBool(False)
## node->m_target
stream.writeQString(None)
### Source/WebCore/history/qt/HistoryItemQt.cpp restoreState
## validUserData
# We could restore the user data here, but we prefer to use the
# QWebHistoryItem API for that.
stream.writeBool(False)
def serialize(items):
"""Serialize a list of QWebHistoryItems to a data stream.
@ -180,10 +98,7 @@ def serialize(items):
else:
current_idx = 0
if qtutils.is_qtwebkit_ng():
_serialize_ng(items, current_idx, stream)
else:
_serialize_old(items, current_idx, stream)
_serialize_items(items, current_idx, stream)
user_data += [item.user_data for item in items]

View File

@ -292,9 +292,6 @@ class WebKitElement(webelem.AbstractWebElement):
elem = elem._parent() # pylint: disable=protected-access
def _move_text_cursor(self):
if self is None:
# old PyQt versions call the slot after the element is deleted.
return
if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document()

View File

@ -33,7 +33,7 @@ from PyQt5.QtGui import QFont
from PyQt5.QtWebKit import QWebSettings
from qutebrowser.config import config, websettings
from qutebrowser.utils import standarddir, urlutils, qtutils
from qutebrowser.utils import standarddir, urlutils
from qutebrowser.browser import shared
@ -131,13 +131,6 @@ def init(_args):
QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage'))
if (config.val.content.private_browsing and
not qtutils.version_check('5.4.2')):
# WORKAROUND for https://codereview.qt-project.org/#/c/108936/
# Won't work when private browsing is not enabled globally, but that's
# the best we can do...
QWebSettings.setIconDatabasePath('')
websettings.init_mappings(MAPPINGS)
_set_user_stylesheet()
config.instance.changed.connect(_update_settings)

View File

@ -55,20 +55,14 @@ class WebKitPrinting(browsertab.AbstractPrinting):
"""QtWebKit implementations related to printing."""
def _do_check(self):
if not qtutils.check_print_compat():
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
raise browsertab.WebTabError(
"Printing on Qt < 5.3.0 on Windows is broken, please upgrade!")
def check_pdf_support(self):
self._do_check()
pass
def check_printer_support(self):
self._do_check()
pass
def check_preview_support(self):
self._do_check()
pass
def to_pdf(self, filename):
printer = QPrinter()

View File

@ -22,7 +22,7 @@
import html
import functools
from PyQt5.QtCore import pyqtSlot, pyqtSignal, PYQT_VERSION, Qt, QUrl, QPoint
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import QFileDialog
@ -33,8 +33,8 @@ from qutebrowser.config import config
from qutebrowser.browser import pdfjs, shared
from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils,
objreg, debug, urlutils)
from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug,
urlutils)
class BrowserPage(QWebPage):
@ -87,22 +87,16 @@ class BrowserPage(QWebPage):
self.restoreFrameStateRequested.connect(
self.on_restore_frame_state_requested)
if PYQT_VERSION > 0x050300:
# WORKAROUND (remove this when we bump the requirements to 5.3.1)
# We can't override javaScriptPrompt with older PyQt-versions because
# of a bug in PyQt.
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html
def javaScriptPrompt(self, frame, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts."""
if self._is_shutting_down:
return (False, "")
try:
return shared.javascript_prompt(frame.url(), js_msg, default,
abort_on=[self.loadStarted,
self.shutting_down])
except shared.CallSuper:
return super().javaScriptPrompt(frame, js_msg, default)
def javaScriptPrompt(self, frame, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts."""
if self._is_shutting_down:
return (False, "")
try:
return shared.javascript_prompt(frame.url(), js_msg, default,
abort_on=[self.loadStarted,
self.shutting_down])
except shared.CallSuper:
return super().javaScriptPrompt(frame, js_msg, default)
def _handle_errorpage(self, info, errpage):
"""Display an error page if needed.
@ -225,10 +219,6 @@ class BrowserPage(QWebPage):
def on_print_requested(self, frame):
"""Handle printing when requested via javascript."""
if not qtutils.check_print_compat():
message.error("Printing on Qt < 5.3.0 on Windows is broken, "
"please upgrade!")
return
printdiag = QPrintDialog()
printdiag.setAttribute(Qt.WA_DeleteOnClose)
printdiag.open(lambda: frame.print(printdiag.printer()))
@ -350,15 +340,7 @@ class BrowserPage(QWebPage):
frame: The QWebFrame which gets saved.
item: The QWebHistoryItem to be saved.
"""
try:
if frame != self.mainFrame():
return
except RuntimeError:
# With Qt 5.2.1 (Ubuntu Trusty) we get this when closing a tab:
# RuntimeError: wrapped C/C++ object of type BrowserPage has
# been deleted
# Since the information here isn't that important for closing web
# views anyways, we ignore this error.
if frame != self.mainFrame():
return
data = {
'zoom': frame.zoomFactor(),
@ -401,9 +383,6 @@ class BrowserPage(QWebPage):
"""
return ext in self._extension_handlers
# WORKAROUND for:
# http://www.riverbankcomputing.com/pipermail/pyqt/2014-August/034722.html
@utils.prevent_exceptions(False, PYQT_VERSION < 0x50302)
def extension(self, ext, opt, out):
"""Override QWebPage::extension to provide error pages.

View File

@ -29,7 +29,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.utils import log, usertypes, utils, qtutils, objreg, debug
from qutebrowser.utils import log, usertypes, utils, objreg, debug
from qutebrowser.browser.webkit import webpage
@ -57,7 +57,7 @@ class WebView(QWebView):
def __init__(self, *, win_id, tab_id, tab, private, parent=None):
super().__init__(parent)
if sys.platform == 'darwin' and qtutils.version_check('5.4'):
if sys.platform == 'darwin':
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
# See https://github.com/qutebrowser/qutebrowser/issues/462
self.setStyle(QStyleFactory.create('Fusion'))
@ -74,13 +74,9 @@ class WebView(QWebView):
page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
tabdata=tab.data, private=private,
parent=self)
try:
page.setVisibilityState(
QWebPage.VisibilityStateVisible if self.isVisible()
else QWebPage.VisibilityStateHidden)
except AttributeError:
pass
page.setVisibilityState(
QWebPage.VisibilityStateVisible if self.isVisible()
else QWebPage.VisibilityStateHidden)
self.setPage(page)
@ -240,12 +236,8 @@ class WebView(QWebView):
Return:
The superclass event return value.
"""
try:
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
except AttributeError:
pass
super().showEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
def hideEvent(self, e):
"""Extend hideEvent to set the page visibility state to hidden.
@ -256,12 +248,8 @@ class WebView(QWebView):
Return:
The superclass event return value.
"""
try:
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
except AttributeError:
pass
super().hideEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
def mousePressEvent(self, e):
"""Set the tabdata ClickTarget on a mousepress.

View File

@ -22,10 +22,11 @@
import inspect
import collections
import traceback
import typing
from qutebrowser.commands import cmdexc, argparser
from qutebrowser.utils import (log, utils, message, docutils, objreg,
usertypes, typing)
usertypes)
from qutebrowser.utils import debug as debug_utils
from qutebrowser.misc import objects
@ -415,10 +416,7 @@ class Command:
# We also can't use isinstance here because typing.Union doesn't
# support that.
# pylint: disable=no-member,useless-suppression
try:
types = list(typ.__union_params__)
except AttributeError:
types = list(typ.__args__)
types = list(typ.__args__)
# pylint: enable=no-member,useless-suppression
if param.default is not inspect.Parameter.empty:
types.append(type(param.default))

View File

@ -28,9 +28,10 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtWidgets import QMessageBox
from qutebrowser.config import configdata, configexc, configtypes, configfiles
from qutebrowser.utils import utils, objreg, message, log, usertypes
from qutebrowser.misc import objects, msgbox
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import (utils, objreg, message, log, usertypes, jinja,
qtutils)
from qutebrowser.misc import objects, msgbox, earlyinit
from qutebrowser.commands import cmdexc, cmdutils, runners
from qutebrowser.completion.models import configmodel
# An easy way to access the config from other code via config.val.foo
@ -176,8 +177,6 @@ class KeyConfig:
def bind(self, key, command, *, mode, force=False, save_yaml=False):
"""Add a new binding from key to command."""
# Doing this here to work around a Python 3.4 circular import
from qutebrowser.commands import runners
key = self._prepare(key, mode)
parser = runners.CommandParser()
@ -395,7 +394,7 @@ class Config(QObject):
def _set_value(self, opt, value):
"""Set the given option to the given value."""
if objects.backend is not None:
if not isinstance(objects.backend, objects.NoBackend):
if objects.backend not in opt.backends:
raise configexc.BackendError(objects.backend)
@ -594,8 +593,6 @@ def set_register_stylesheet(obj, *, stylesheet=None, update=True):
@functools.lru_cache()
def _render_stylesheet(stylesheet):
"""Render the given stylesheet jinja template."""
# Imported here to avoid a Python 3.4 circular import
from qutebrowser.utils import jinja
with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet)
return template.render(conf=val)
@ -645,7 +642,7 @@ class StyleSheetObserver(QObject):
instance.changed.connect(self._update_stylesheet)
def early_init():
def early_init(args):
"""Initialize the part of the config which works without a QApplication."""
configdata.init()
@ -690,6 +687,33 @@ def early_init():
configfiles.init()
objects.backend = get_backend(args)
earlyinit.init_with_backend(objects.backend)
def get_backend(args):
"""Find out what backend to use based on available libraries."""
try:
import PyQt5.QtWebKit # pylint: disable=unused-variable
except ImportError:
webkit_available = False
else:
webkit_available = qtutils.is_new_qtwebkit()
str_to_backend = {
'webkit': usertypes.Backend.QtWebKit,
'webengine': usertypes.Backend.QtWebEngine,
}
if args.backend is not None:
return str_to_backend[args.backend]
elif val.backend != 'auto':
return str_to_backend[val.backend]
elif webkit_available:
return usertypes.Backend.QtWebKit
else:
return usertypes.Backend.QtWebEngine
def late_init(save_manager):
"""Initialize the rest of the config after the QApplication is created."""

View File

@ -101,6 +101,28 @@ qt_args:
https://peter.sh/experiments/chromium-command-line-switches/ for a list)
will work.
backend:
type:
name: String
valid_values:
- auto: Automatically select either QtWebEngine or QtWebKit
- webkit: Force QtWebKit
- webengine: Force QtWebEngine
default: auto
desc: >-
The backend to use to display websites.
qutebrowser supports two different web rendering engines / backends,
QtWebKit and QtWebEngine.
QtWebKit is based on WebKit (similar to Safari). It was discontinued by the
Qt project with Qt 5.6, but picked up as a well maintained fork:
https://github.com/annulen/webkit/wiki - qutebrowser only supports the fork.
QtWebEngine is Qt's official successor to QtWebKit and based on the Chromium
project. It's slightly more resource hungry that QtWebKit and has a couple
of missing features in qutebrowser, but is generally the preferred choice.
## auto_save
auto_save.interval:

View File

@ -19,7 +19,7 @@
"""Exceptions related to config parsing."""
from qutebrowser.utils import utils
from qutebrowser.utils import utils, jinja
class Error(Exception):
@ -108,7 +108,6 @@ class ConfigFileErrors(Error):
def to_html(self):
"""Get the error texts as a HTML snippet."""
from qutebrowser.utils import jinja
template = jinja.environment.from_string("""
Errors occurred while reading {{ basename }}:

View File

@ -30,7 +30,7 @@ import yaml
from PyQt5.QtCore import QSettings
import qutebrowser
from qutebrowser.config import configexc
from qutebrowser.config import configexc, config
from qutebrowser.utils import standarddir, utils, qtutils
@ -51,8 +51,10 @@ class StateConfig(configparser.ConfigParser):
self.add_section(sect)
except configparser.DuplicateSectionError:
pass
# See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
self['general'].pop('fooled', None)
deleted_keys = ['fooled', 'backend-warning-shown']
for key in deleted_keys:
self['general'].pop(key, None)
def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly.
@ -152,8 +154,8 @@ class ConfigAPI:
errors: Errors which occurred while setting options.
"""
def __init__(self, config, keyconfig):
self._config = config
def __init__(self, conf, keyconfig):
self._config = conf
self._keyconfig = keyconfig
self.load_autoconfig = True
self.errors = []
@ -189,7 +191,6 @@ class ConfigAPI:
def read_config_py(filename=None):
"""Read a config.py file."""
from qutebrowser.config import config
api = ConfigAPI(config.instance, config.key_instance)
if filename is None:

View File

@ -59,9 +59,9 @@ from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtWidgets import QTabWidget, QTabBar
from qutebrowser.commands import cmdutils
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import configexc
from qutebrowser.utils import standarddir, utils, qtutils
from qutebrowser.utils import standarddir, utils, qtutils, urlutils
SYSTEM_PROXY = object() # Return value for Proxy type
@ -791,7 +791,6 @@ class Command(BaseType):
if not Command.unvalidated:
Command.unvalidated = True
try:
from qutebrowser.commands import runners, cmdexc
parser = runners.CommandParser()
try:
parser.parse_all(value)
@ -1287,7 +1286,6 @@ class Proxy(BaseType):
('none', "Don't use any proxy"))
def to_py(self, value):
from qutebrowser.utils import urlutils
self._basic_py_validation(value, str)
if not value:
return None
@ -1352,7 +1350,6 @@ class FuzzyUrl(BaseType):
"""A URL which gets interpreted as search if needed."""
def to_py(self, value):
from qutebrowser.utils import urlutils
self._basic_py_validation(value, str)
if not value:
return None

View File

@ -1,100 +0,0 @@
{% extends "styled.html" %}
{% block style %}
{{super()}}
.note {
font-size: smaller;
color: grey;
}
.mono {
font-family: monospace;
}
{% endblock %}
{% block content %}
<h1>Legacy QtWebKit backend</h1>
<span class="note">Note this warning will only appear once. Use <span class="mono">:open
qute://backend-warning</span> to show it again at a later time.</span>
<p>
You're using qutebrowser with the legacy QtWebKit backend. It's still the
default until a few remaining issues are sorted out. If you can, it's
strongly suggested to switch earlier, as legacy QtWebKit has known security
issues and also breaks things on various websites.
</p>
<h2>Using QtWebEngine instead</h2>
<span class="note">This is usually the better choice if you aren't using Nouveau graphics, and
don't need any features which are currently unavailable with QtWebEngine (like
the <span class="mono">qute://settings</span> page or caret browsing).</span>
{% macro install_webengine(package) -%}
You should be able to install <span class="mono">{{ package }}</span> and start qutebrowser with <span class="mono">--backend webengine</span> to use the new backend.
{%- endmacro %}
{% macro please_open_issue() -%}
If you know more, please <a href="https://github.com/qutebrowser/qutebrowser/issues/new">open an issue</a>!
{%- endmacro %}
{% macro unknown_system() -%}
There's no information available for your system. {{ please_open_issue() }}
{%- endmacro %}
<p>
{% if distribution.parsed == Distribution.ubuntu %}
{% if distribution.version == none %}
{{ unknown_system() }}
{% elif distribution.version >= version('17.04') %}
{{ install_webengine('python3-pyqt5.qtwebengine') }}
{% elif distribution.version >= version('16.04') %}
QtWebEngine is only available in Ubuntu's repositories since 17.04, but you can <a href="https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc#installing-qutebrowser-with-tox">install qutebrowser via tox</a> with <span class="mono">tox -e mkvenv-pypi</span> to use the new backend.
{% else %}
Unfortunately, no easy way is known to install QtWebEngine on Ubuntu &lt; 16.04. {{ please_open_issue() }}
{% endif %}
{% elif distribution.parsed == Distribution.debian %}
{% if distribution.version == none %}
{{ unknown_system() }}
{% elif distribution.version >= version('9') %}
{{ install_webengine('python3-pyqt5.qtwebengine') }}
{% else %}
Unfortunately, no easy way is known to install QtWebEngine on Debian &lt; 9. {{ please_open_issue() }}
{% endif %}
{% elif distribution.parsed in [Distribution.arch, Distribution.manjaro] %}
{{ install_webengine('qt5-webengine') }}
{% elif distribution.parsed == Distribution.void %}
{{ install_webengine('python-PyQt5-webengine') }}
{% elif distribution.parsed == Distribution.fedora %}
{{ install_webengine('qt5-qtwebengine') }}
{% elif distribution.parsed == Distribution.opensuse %}
{{ install_webengine('libqt5-qtwebengine') }}
{% elif distribution.parsed == Distribution.gentoo %}
{{ install_webengine('dev-qt/qtwebengine') }}
{% else %}
{{ unknown_system() }}
{% endif %}
</p>
<h2>Using QtWebKit-NG instead</h2>
<span class="note">This is a drop-in replacement for legacy QtWebKit.</span>
<p>
{% if distribution.parsed == Distribution.debian and distribution.version != none and distribution.version >= version('9') %}
There are unofficial QtWebKit-NG packages <a href="http://repo.paretje.be/unstable/">available</a>.
{% elif distribution.parsed in [Distribution.ubuntu, Distribution.debian] %}
No easy way is known to install QtWebKit-NG on your system.
There are unofficial QtWebKit-NG packages <a href="http://repo.paretje.be/unstable/">available</a>, but they are intended for Debian Unstable.
{{ please_open_issue() }}
{% elif distribution.parsed in [Distribution.arch, Distribution.manjaro] %}
With an updated <span class="mono">qt5-webkit</span> package, you should already get QtWebKit-NG.
{% elif distribution.parsed == Distribution.gentoo %}
There's an unofficial <a href="https://gist.github.com/annulen/309569fb61e5d64a703c055c1e726f71">ebuild</a> available.
{% else %}
{{ unknown_system() }}
{% endif %}
</p>
{% endblock %}

View File

@ -1,61 +0,0 @@
{% extends "styled.html" %}
{% block style %}
{{super()}}
body {
max-width: 1440px;
}
td.title {
word-break: break-all;
}
td.time {
color: #555;
text-align: right;
white-space: nowrap;
}
table {
margin-bottom: 30px;
}
.date {
color: #555;
font-size: 12pt;
padding-bottom: 15px;
font-weight: bold;
text-align: left;
}
.pagination-link {
color: #555;
font-weight: bold;
margn-bottom: 15px;
text-decoration: none;
}
{% endblock %}
{% block content %}
<h1>Browsing history</h1>
<table>
<caption class="date">{{curr_date.strftime("%a, %d %B %Y")}}</caption>
<tbody>
{% for url, title, time, host in history %}
<tr>
<td class="title">
<a href="{{url}}">{{title}}</a>
<span class="hostname">{{host}}</span>
</td>
<td class="time">{{time.strftime("%X")}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<span class="pagination-link"><a href="qute://history/?date={{prev_date.strftime("%Y-%m-%d")}}" rel="prev">Previous</a></span>
{% if today >= next_date %}
<span class="pagination-link"><a href="qute://history/?date={{next_date.strftime("%Y-%m-%d")}}" rel="next">Next</a></span>
{% endif %}
{% endblock %}

View File

@ -31,8 +31,7 @@ from qutebrowser.commands import runners, cmdutils
from qutebrowser.config import config, configfiles
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
jinja, debug)
from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt
from qutebrowser.mainwindow.statusbar import bar
from qutebrowser.mainwindow import messageview, prompt
from qutebrowser.completion import completionwidget, completer
from qutebrowser.keyinput import modeman
from qutebrowser.browser import (commands, downloadview, hints,
@ -140,6 +139,11 @@ class MainWindow(QWidget):
parent: The parent the window should get.
"""
super().__init__(parent)
# Late import to avoid a circular dependency
# - browsertab -> hints -> webelem -> mainwindow -> bar -> browsertab
from qutebrowser.mainwindow import tabbedbrowser
from qutebrowser.mainwindow.statusbar import bar
self.setAttribute(Qt.WA_DeleteOnClose)
self._commandrunner = None
self._overlays = []

View File

@ -61,10 +61,6 @@ class Progress(QProgressBar):
def on_tab_changed(self, tab):
"""Set the correct value when the current tab changed."""
if self is None: # pragma: no branch
# This should never happen, but for some weird reason it does
# sometimes.
return # pragma: no cover
self.setValue(tab.progress())
if tab.load_status() == usertypes.LoadStatus.loading:
self.show()

View File

@ -28,7 +28,7 @@ from PyQt5.QtGui import QIcon
from qutebrowser.config import config
from qutebrowser.keyinput import modeman
from qutebrowser.mainwindow import tabwidget
from qutebrowser.mainwindow import tabwidget, mainwindow
from qutebrowser.browser import signalfilter, browsertab
from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg,
urlutils, message, jinja)
@ -432,7 +432,6 @@ class TabbedBrowser(tabwidget.TabWidget):
if (config.val.tabs.tabs_are_windows and self.count() > 0 and
not ignore_tabs_are_windows):
from qutebrowser.mainwindow import mainwindow
window = mainwindow.MainWindow(private=self.private)
window.show()
tabbed_browser = objreg.get('tabbed-browser', scope='window',

View File

@ -70,9 +70,6 @@ class TabWidget(QTabWidget):
@config.change_filter('tabs')
def _init_config(self):
"""Initialize attributes based on the config."""
if self is None: # pragma: no cover
# WORKAROUND for PyQt 5.2
return
tabbar = self.tabBar()
self.setMovable(True)
self.setTabsClosable(False)
@ -744,24 +741,17 @@ class TabBarStyle(QCommonStyle):
log.misc.warning("Could not get layouts for tab!")
return QRect()
return layouts.text
elif sr == QStyle.SE_TabWidgetTabBar:
elif sr in [QStyle.SE_TabWidgetTabBar,
QStyle.SE_TabBarScrollLeftButton]:
# Handling SE_TabBarScrollLeftButton so the left scroll button is
# aligned properly. Otherwise, empty space will be shown after the
# last tab even though the button width is set to 0
#
# Need to use super() because we also use super() to render
# element in drawControl(); otherwise, we may get bit by
# style differences...
rct = super().subElementRect(sr, opt, widget)
return rct
return super().subElementRect(sr, opt, widget)
else:
try:
# We need this so the left scroll button is aligned properly.
# Otherwise, empty space will be shown after the last tab even
# though the button width is set to 0
#
# QStyle.SE_TabBarScrollLeftButton was added in Qt 5.7
if sr == QStyle.SE_TabBarScrollLeftButton:
return super().subElementRect(sr, opt, widget)
except AttributeError:
pass
return self._style.subElementRect(sr, opt, widget)
def _tab_layout(self, opt):

View File

@ -43,12 +43,12 @@ except ImportError: # pragma: no cover
# to stderr.
def check_python_version():
"""Check if correct python version is run."""
if sys.hexversion < 0x03040000:
if sys.hexversion < 0x03050000:
# We don't use .format() and print_function here just in case someone
# still has < 2.6 installed.
# pylint: disable=bad-builtin
version_str = '.'.join(map(str, sys.version_info[:3]))
text = ("At least Python 3.4 is required to run qutebrowser, but " +
text = ("At least Python 3.5 is required to run qutebrowser, but " +
version_str + " is installed!\n")
if Tk and '--no-err-windows' not in sys.argv: # pragma: no cover
root = Tk()

View File

@ -19,7 +19,7 @@
"""Things which need to be done really early (e.g. before importing Qt).
At this point we can be sure we have all python 3.4 features available.
At this point we can be sure we have all python 3.5 features available.
"""
try:
@ -29,7 +29,6 @@ try:
except ImportError:
hunter = None
import os
import sys
import faulthandler
import traceback
@ -41,8 +40,6 @@ try:
except ImportError:
tkinter = None
import pkg_resources
# NOTE: No qutebrowser or PyQt import should be done here, as some early
# initialization needs to take place before that!
@ -68,7 +65,7 @@ def _missing_str(name, *, windows=None, pip=None, webengine=False):
if not webengine:
lines = ['<b>If you installed a qutebrowser package for your '
'distribution, please report this as a bug.</b>']
blocks.append('<br />'.join(lines))
blocks.append('<br />'.join(lines))
if windows is not None:
lines = ["<b>On Windows:</b>"]
lines += windows.splitlines()
@ -140,73 +137,6 @@ def init_faulthandler(fileobj=sys.__stderr__):
faulthandler.register(signal.SIGUSR1)
def _qt_version():
"""Get the running Qt version.
Needs to be in a function so we can do a local import easily (to not import
from QtCore too early) but can patch this out easily for tests.
"""
from PyQt5.QtCore import qVersion
return pkg_resources.parse_version(qVersion())
def fix_harfbuzz(args):
"""Fix harfbuzz issues.
This switches to the most stable harfbuzz font rendering engine available
on the platform instead of using the system wide one.
This fixes crashes on various sites.
- On Qt 5.2 (and probably earlier) the new engine probably has more
crashes and is also experimental.
e.g. https://bugreports.qt.io/browse/QTBUG-36099
- On Qt 5.3.0 there's a bug that affects a lot of websites:
https://bugreports.qt.io/browse/QTBUG-39278
So the new engine will be more stable.
- On Qt 5.3.1 this bug is fixed and the old engine will be the more stable
one again.
- On Qt 5.4 the new engine is the default and most bugs are taken care of.
IMPORTANT: This needs to be done before QWidgets is imported in any way!
WORKAROUND (remove this when we bump the requirements to 5.3.1)
Args:
args: The argparse namespace.
"""
from qutebrowser.utils import log
if 'PyQt5.QtWidgets' in sys.modules:
msg = "Harfbuzz fix attempted but QtWidgets is already imported!"
if getattr(sys, 'frozen', False):
log.init.debug(msg)
else:
log.init.warning(msg)
if sys.platform.startswith('linux') and args.harfbuzz == 'auto':
if _qt_version() == pkg_resources.parse_version('5.3.0'):
log.init.debug("Using new harfbuzz engine (auto)")
os.environ['QT_HARFBUZZ'] = 'new'
elif _qt_version() < pkg_resources.parse_version('5.4.0'):
log.init.debug("Using old harfbuzz engine (auto)")
os.environ['QT_HARFBUZZ'] = 'old'
else:
log.init.debug("Using system harfbuzz engine (auto)")
elif args.harfbuzz in ['old', 'new']:
# forced harfbuzz variant
# FIXME looking at the Qt code, 'new' isn't a valid value, but leaving
# it empty and using new yields different behavior...
# (probably irrelevant when workaround gets removed)
log.init.debug("Using {} harfbuzz engine (forced)".format(
args.harfbuzz))
os.environ['QT_HARFBUZZ'] = args.harfbuzz
else:
log.init.debug("Using system harfbuzz engine")
def check_pyqt_core():
"""Check if PyQt core is installed."""
try:
@ -233,26 +163,6 @@ def check_pyqt_core():
sys.exit(1)
def get_backend(args):
"""Find out what backend to use based on available libraries.
Note this function returns the backend as a string so we don't have to
import qutebrowser.utils.usertypes yet.
"""
try:
import PyQt5.QtWebKit # pylint: disable=unused-variable
webkit_available = True
except ImportError:
webkit_available = False
if args.backend is not None:
return args.backend
elif webkit_available:
return 'webkit'
else:
return 'webengine'
def qt_version(qversion=None, qt_version_str=None):
"""Get a Qt version string based on the runtime/compiled versions."""
if qversion is None:
@ -268,50 +178,65 @@ def qt_version(qversion=None, qt_version_str=None):
return qversion
def check_qt_version(backend):
def check_qt_version():
"""Check if the Qt version is recent enough."""
from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR
from qutebrowser.utils import qtutils
if (not qtutils.version_check('5.2.0', strict=True) or
if (not qtutils.version_check('5.7.1', strict=True) or
PYQT_VERSION < 0x050200):
text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but Qt {} / "
"PyQt {} is installed.".format(qt_version(),
PYQT_VERSION_STR))
_die(text)
elif (backend == 'webengine' and (
not qtutils.version_check('5.7.1', strict=True) or
PYQT_VERSION < 0x050700)):
text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required for "
"QtWebEngine support, but Qt {} / PyQt {} is installed."
.format(qt_version(), PYQT_VERSION_STR))
text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required, "
"but Qt {} / PyQt {} is installed.".format(qt_version(),
PYQT_VERSION_STR))
_die(text)
def check_ssl_support(backend):
def check_ssl_support():
"""Check if SSL support is available."""
from qutebrowser.utils import log
# pylint: disable=unused-variable
try:
from PyQt5.QtNetwork import QSslSocket
except ImportError:
_die("Fatal error: Your Qt is built without SSL support.")
def check_backend_ssl_support(backend):
"""Check for full SSL availability when we know the backend."""
from PyQt5.QtNetwork import QSslSocket
from qutebrowser.utils import log, usertypes
text = ("Could not initialize QtNetwork SSL support. If you use "
"OpenSSL 1.1 with a PyQt package from PyPI (e.g. on Archlinux "
"or Debian Stretch), you need to set LD_LIBRARY_PATH to the path "
"of OpenSSL 1.0.")
if backend == 'webengine':
text += " This only affects downloads."
"of OpenSSL 1.0. This only affects downloads.")
if not QSslSocket.supportsSsl():
if backend == 'webkit':
if backend == usertypes.Backend.QtWebKit:
_die("Could not initialize SSL support.")
else:
assert backend == 'webengine'
assert backend == usertypes.Backend.QtWebEngine
log.init.warning(text)
def check_libraries(backend):
def _check_modules(modules):
"""Make sure the given modules are available."""
from qutebrowser.utils import log
for name, text in modules.items():
try:
# https://github.com/pallets/jinja/pull/628
# https://bitbucket.org/birkenfeld/pygments-main/issues/1314/
# https://github.com/pallets/jinja/issues/646
# https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e
messages = ['invalid escape sequence',
'Flags not at the start of the expression']
with log.ignore_py_warnings(
category=DeprecationWarning,
message=r'({})'.format('|'.join(messages))):
importlib.import_module(name)
except ImportError as e:
_die(text, e)
def check_libraries():
"""Check if all needed Python libraries are installed."""
modules = {
'pkg_resources':
@ -339,32 +264,37 @@ def check_libraries(backend):
'PyQt5.QtQml': _missing_str("PyQt5.QtQml"),
'PyQt5.QtSql': _missing_str("PyQt5.QtSql"),
}
if backend == 'webengine':
modules['PyQt5.QtWebEngineWidgets'] = _missing_str("QtWebEngine",
webengine=True)
modules['PyQt5.QtOpenGL'] = _missing_str("PyQt5.QtOpenGL")
_check_modules(modules)
def check_backend_libraries(backend):
"""Make sure the libraries needed by the given backend are available.
Args:
backend: The backend as usertypes.Backend member.
"""
from qutebrowser.utils import usertypes
if backend == usertypes.Backend.QtWebEngine:
modules = {
'PyQt5.QtWebEngineWidgets':
_missing_str("QtWebEngine", webengine=True),
'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"),
}
else:
assert backend == 'webkit'
modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit")
modules['PyQt5.QtWebKitWidgets'] = _missing_str(
"PyQt5.QtWebKitWidgets")
assert backend == usertypes.Backend.QtWebKit, backend
modules = {
'PyQt5.QtWebKit': _missing_str("PyQt5.QtWebKit"),
'PyQt5.QtWebKitWidgets': _missing_str("PyQt5.QtWebKitWidgets"),
}
_check_modules(modules)
from qutebrowser.utils import log
for name, text in modules.items():
try:
# https://github.com/pallets/jinja/pull/628
# https://bitbucket.org/birkenfeld/pygments-main/issues/1314/
# https://github.com/pallets/jinja/issues/646
# https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e
messages = ['invalid escape sequence',
'Flags not at the start of the expression']
with log.ignore_py_warnings(
category=DeprecationWarning,
message=r'({})'.format('|'.join(messages))):
importlib.import_module(name)
except ImportError as e:
_die(text, e)
def check_new_webkit(backend):
"""Make sure we use QtWebEngine or a new QtWebKit."""
from qutebrowser.utils import usertypes, qtutils
if backend == usertypes.Backend.QtWebKit and not qtutils.is_new_qtwebkit():
_die("qutebrowser does not support legacy QtWebKit versions anymore, "
"see the installation docs for details.")
def remove_inputhook():
@ -395,18 +325,7 @@ def check_optimize_flag():
"unexpected behavior may occur.")
def set_backend(backend):
"""Set the objects.backend global to the given backend (as string)."""
from qutebrowser.misc import objects
from qutebrowser.utils import usertypes
backends = {
'webkit': usertypes.Backend.QtWebKit,
'webengine': usertypes.Backend.QtWebEngine,
}
objects.backend = backends[backend]
def earlyinit(args):
def early_init(args):
"""Do all needed early initialization.
Note that it's vital the other earlyinit functions get called in the right
@ -423,15 +342,23 @@ def earlyinit(args):
check_pyqt_core()
# Init logging as early as possible
init_log(args)
# Now the faulthandler is enabled we fix the Qt harfbuzzing library, before
# importing QtWidgets.
fix_harfbuzz(args)
# Now we can be sure QtCore is available, so we can print dialogs on
# errors, so people only using the GUI notice them as well.
backend = get_backend(args)
check_qt_version(backend)
check_libraries()
check_qt_version()
remove_inputhook()
check_libraries(backend)
check_ssl_support(backend)
check_ssl_support()
check_optimize_flag()
set_backend(backend)
def init_with_backend(backend):
"""Do later stages of init when we know the backend.
Args:
backend: The backend as usertypes.Backend member.
"""
assert not isinstance(backend, str), backend
assert backend is not None
check_backend_libraries(backend)
check_backend_ssl_support(backend)
check_new_webkit(backend)

View File

@ -22,5 +22,14 @@
# NOTE: We need to be careful with imports here, as this is imported from
# earlyinit.
class NoBackend:
"""Special object when there's no backend set so we notice that."""
def __eq__(self, other):
raise AssertionError("No backend set!")
# A usertypes.Backend member
backend = None
backend = NoBackend()

View File

@ -32,6 +32,7 @@ from qutebrowser.utils import (standarddir, objreg, qtutils, log, message,
from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.config import config, configfiles
from qutebrowser.completion.models import miscmodels
from qutebrowser.mainwindow import mainwindow
default = object() # Sentinel value
@ -371,7 +372,6 @@ class SessionManager(QObject):
name: The name of the session to load.
temp: If given, don't set the current session.
"""
from qutebrowser.mainwindow import mainwindow
path = self._get_session_path(name, check_exists=True)
try:
with open(path, encoding='utf-8') as f:

View File

@ -95,9 +95,6 @@ def get_argparser():
action='store_false', dest='color')
debug.add_argument('--force-color', help="Force colored logging",
action='store_true')
debug.add_argument('--harfbuzz', choices=['old', 'new', 'system', 'auto'],
default='auto', help="HarfBuzz engine version to use. "
"Default: auto.")
debug.add_argument('--relaxed-config', action='store_true',
help="Silently remove unknown config options.")
debug.add_argument('--nowindow', action='store_true', help="Don't show "
@ -170,8 +167,8 @@ def main():
# from json.
data = json.loads(args.json_args)
args = argparse.Namespace(**data)
earlyinit.earlyinit(args)
earlyinit.early_init(args)
# We do this imports late as earlyinit needs to be run first (because of
# the harfbuzz fix and version checking).
# version checking and other early initialization)
from qutebrowser import app
return app.run(args)

View File

@ -345,7 +345,7 @@ def qt_message_handler(msg_type, context, msg):
try:
qt_to_logging[QtCore.QtInfoMsg] = logging.INFO
except AttributeError:
# Qt < 5.5
# While we don't support Qt < 5.5 anymore, logging still needs to work
pass
# Change levels of some well-known messages to debug so they don't get

View File

@ -105,13 +105,9 @@ class ObjectRegistry(collections.UserDict):
func = partial_objs[name]
try:
self[name].destroyed.disconnect(func)
except (RuntimeError, TypeError):
except RuntimeError:
# If C++ has deleted the object, the slot is already
# disconnected.
#
# With older PyQt-versions (5.2.1) we'll get a "TypeError:
# pyqtSignal must be bound to a QObject" instead:
# https://github.com/qutebrowser/qutebrowser/issues/257
pass
del partial_objs[name]
@ -145,7 +141,7 @@ class ObjectRegistry(collections.UserDict):
for name, obj in self.data.items():
try:
obj_repr = repr(obj)
except (RuntimeError, TypeError):
except RuntimeError:
# Underlying object deleted probably
obj_repr = '<deleted>'
lines.append("{}: {}".format(name, obj_repr))

View File

@ -28,10 +28,10 @@ Module attributes:
import io
import os
import operator
import contextlib
import pkg_resources
from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray,
QIODevice, QSaveFile, QT_VERSION_STR)
try:
@ -39,17 +39,6 @@ try:
except ImportError: # pragma: no cover
qWebKitVersion = None
from qutebrowser.utils import log
with log.ignore_py_warnings(category=PendingDeprecationWarning, module='imp'):
with log.ignore_py_warnings(category=ImportWarning):
# This imports 'imp' and gives us a PendingDeprecationWarning on
# Debian Jessie.
#
# On Archlinux, we get ImportWarning from
# importlib/_bootstrap_external.py for modules with missing __init__.
import pkg_resources
MAXVALS = {
'int': 2 ** 31 - 1,
@ -102,8 +91,8 @@ def version_check(version, exact=False, strict=False):
return result
def is_qtwebkit_ng():
"""Check if the given version is QtWebKit-NG."""
def is_new_qtwebkit():
"""Check if the given version is a new QtWebKit."""
assert qWebKitVersion is not None
return (pkg_resources.parse_version(qWebKitVersion()) >
pkg_resources.parse_version('538.1'))
@ -137,15 +126,6 @@ def check_overflow(arg, ctype, fatal=True):
return arg
def check_print_compat():
"""Check if printing should work in the given Qt version."""
# WORKAROUND (remove this when we bump the requirements to 5.3.0)
if os.name == 'nt':
return version_check('5.3')
else:
return True
def ensure_valid(obj):
"""Ensure a Qt object with an .isValid() method is valid."""
if not obj.isValid():

View File

@ -1,68 +0,0 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
# pylint: disable=unused-import,bad-mcs-method-argument
"""Wrapper for Python 3.5's typing module.
This wrapper is needed as both Python 3.5 and typing for PyPI isn't commonly
packaged yet. As we don't actually need anything from the typing module at
runtime, we instead mock the typing classes (using objects to make things
easier) so the typing module isn't a hard dependency.
"""
# Those are defined here to make them testable easily
class FakeTypingMeta(type):
"""Fake typing metaclass like typing.TypingMeta."""
def __init__(self, *args, # pylint: disable=super-init-not-called
**_kwds):
pass
class FakeUnionMeta(FakeTypingMeta):
"""Fake union metaclass metaclass like typing.UnionMeta."""
def __new__(cls, name, bases, namespace, parameters=None):
if parameters is None:
return super().__new__(cls, name, bases, namespace)
self = super().__new__(cls, name, bases, {})
self.__union_params__ = tuple(parameters)
return self
def __getitem__(self, parameters):
return self.__class__(self.__name__, self.__bases__,
dict(self.__dict__), parameters=parameters)
class FakeUnion(metaclass=FakeUnionMeta):
"""Fake Union type like typing.Union."""
__union_params__ = None
try:
from typing import Union
except ImportError: # pragma: no cover
Union = FakeUnion

View File

@ -355,14 +355,8 @@ class Question(QObject):
log.misc.debug("Question was already aborted")
return
self.is_aborted = True
try:
self.aborted.emit()
self.completed.emit()
except TypeError:
# WORKAROUND
# We seem to get "pyqtSignal must be bound to a QObject, not
# 'Question' here, which makes no sense at all..."
log.misc.exception("Error while aborting question")
self.aborted.emit()
self.completed.emit()
class Timer(QTimer):

View File

@ -42,7 +42,7 @@ except ImportError: # pragma: no cover
from yaml import SafeLoader as YamlLoader, SafeDumper as YamlDumper
import qutebrowser
from qutebrowser.utils import qtutils, log
from qutebrowser.utils import qtutils, log, debug
fake_clipboard = None
@ -425,14 +425,13 @@ class KeyInfo:
self.text = text
def __repr__(self):
# Meh, dependency cycle...
from qutebrowser.utils.debug import qenum_key
if self.modifiers is None:
modifiers = None
else:
#modifiers = qflags_key(Qt, self.modifiers)
modifiers = hex(int(self.modifiers))
return get_repr(self, constructor=True, key=qenum_key(Qt, self.key),
return get_repr(self, constructor=True,
key=debug.qenum_key(Qt, self.key),
modifiers=modifiers, text=self.text)
def __eq__(self, other):
@ -814,10 +813,12 @@ def open_file(filename, cmdline=None):
the filename is appended to the cmdline.
"""
# Import late to avoid circular imports:
# utils -> config -> configdata -> configtypes -> cmdutils -> command ->
# utils
from qutebrowser.misc import guiprocess
# - usertypes -> utils -> guiprocess -> message -> usertypes
# - usertypes -> utils -> config -> configdata -> configtypes ->
# cmdutils -> command -> message -> usertypes
from qutebrowser.config import config
from qutebrowser.misc import guiprocess
# the default program to open downloads with - will be empty string
# if we want to use the default
override = config.val.downloads.open_dispatcher

View File

@ -44,7 +44,7 @@ except ImportError: # pragma: no cover
QWebEngineProfile = None
import qutebrowser
from qutebrowser.utils import log, utils, standarddir, usertypes, qtutils
from qutebrowser.utils import log, utils, standarddir, usertypes
from qutebrowser.misc import objects, earlyinit, sql
from qutebrowser.browser import pdfjs
@ -190,7 +190,6 @@ def _module_versions():
('pygments', ['__version__']),
('yaml', ['__version__']),
('cssutils', ['__version__']),
('typing', []),
('PyQt5.QtWebEngineWidgets', []),
('PyQt5.QtWebKitWidgets', []),
])
@ -304,9 +303,7 @@ def _chromium_version():
def _backend():
"""Get the backend line with relevant information."""
if objects.backend == usertypes.Backend.QtWebKit:
return '{} (WebKit {})'.format(
'QtWebKit-NG' if qtutils.is_qtwebkit_ng() else 'legacy QtWebKit',
qWebKitVersion())
return 'new QtWebKit (WebKit {})'.format(qWebKitVersion())
else:
webengine = usertypes.Backend.QtWebEngine
assert objects.backend == webengine, objects.backend

View File

@ -149,8 +149,6 @@ PERFECT_FILES = [
'utils/jinja.py'),
('tests/unit/utils/test_error.py',
'utils/error.py'),
('tests/unit/utils/test_typing.py',
'utils/typing.py'),
('tests/unit/utils/test_javascript.py',
'utils/javascript.py'),
@ -291,7 +289,7 @@ def main_check_all():
tests.
This runs pytest with the used executable, so check_coverage.py should be
called with something like ./.tox/py34/bin/python.
called with something like ./.tox/py36/bin/python.
"""
for test_file, src_file in PERFECT_FILES:
if test_file is None:

View File

@ -5,10 +5,6 @@
#
case $TESTENV in
py34-cov)
exe=/usr/bin/python3.4
full=full
;;
py3*-pyqt*)
exe=$(readlink -f .tox/$TESTENV/bin/python)
full=

View File

@ -121,7 +121,8 @@ setupdata = {
'Operating System :: Microsoft :: Windows :: Windows 7',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Topic :: Internet',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Browsers',

View File

@ -62,7 +62,6 @@ def _apply_platform_markers(config, item):
('no_ci', 'CI' in os.environ, "Skipped on CI."),
('issue2478', os.name == 'nt' and config.webengine,
"Broken with QtWebEngine on Windows"),
('qt55', not qtutils.version_check('5.5'), "Requires Qt 5.5 or newer"),
]
for searched_marker, condition, default_reason in markers:
@ -128,12 +127,9 @@ def pytest_collection_modifyitems(config, items):
item.add_marker(pytest.mark.xfail(run=False))
if item.get_marker('js_prompt'):
if config.webengine:
js_prompt_pyqt_version = 0x050700
else:
js_prompt_pyqt_version = 0x050300
item.add_marker(pytest.mark.skipif(
PYQT_VERSION <= js_prompt_pyqt_version,
reason='JS prompts are not supported with this PyQt version'))
item.add_marker(pytest.mark.skipif(
PYQT_VERSION <= 0x050700,
reason='JS prompts are not supported with PyQt 5.7'))
if deselected:
deselected_items.append(item)

View File

@ -109,8 +109,6 @@ def _get_backend_tag(tag):
'qtwebengine_todo': pytest.mark.qtwebengine_todo,
'qtwebengine_skip': pytest.mark.qtwebengine_skip,
'qtwebkit_skip': pytest.mark.qtwebkit_skip,
'qtwebkit_ng_xfail': pytest.mark.qtwebkit_ng_xfail,
'qtwebkit_ng_skip': pytest.mark.qtwebkit_ng_skip,
}
if not any(tag.startswith(t + ':') for t in pytest_marks):
return None
@ -143,10 +141,6 @@ def pytest_collection_modifyitems(config, items):
config.webengine),
('qtwebkit_skip', 'Skipped with QtWebKit', pytest.mark.skipif,
not config.webengine),
('qtwebkit_ng_xfail', 'Failing with QtWebKit-NG', pytest.mark.xfail,
not config.webengine and qtutils.is_qtwebkit_ng()),
('qtwebkit_ng_skip', 'Skipped with QtWebKit-NG', pytest.mark.skipif,
not config.webengine and qtutils.is_qtwebkit_ng()),
('qtwebengine_flaky', 'Flaky with QtWebEngine', pytest.mark.skipif,
config.webengine),
('qtwebengine_mac_xfail', 'Fails on macOS with QtWebEngine',

View File

@ -6,11 +6,7 @@
var my_window;
function open_modal() {
if (window.showModalDialog) {
window.showModalDialog();
} else {
window.open('about:blank', 'window', 'modal');
}
window.open('about:blank', 'window', 'modal');
}
function open_normal() {

View File

@ -103,13 +103,4 @@ Feature: Page history
And I open qute:history without waiting
And I wait until qute://history is loaded
Then the page should contain the plaintext "3.txt"
Then the page should contain the plaintext "4.txt"
## Bugs
@qtwebengine_skip @qtwebkit_ng_skip
Scenario: Opening a valid URL which turns out invalid
When I set url.auto_search to naive
And I run :open http://foo%40bar@baz
Then "QFSFileEngine::open: No file name specified" should be logged
And "Error while loading : Host not found" should be logged
Then the page should contain the plaintext "4.txt"

View File

@ -17,7 +17,7 @@ Feature: Javascript stuff
And I run :click-element id close-normal
Then "Focus object changed: *" should be logged
@qtwebkit_ng_skip
@qtwebkit_skip
Scenario: Opening/closing a modal window via JS
When I open data/javascript/window_open.html
And I run :tab-only
@ -26,7 +26,6 @@ Feature: Javascript stuff
And I run :tab-focus 1
And I run :click-element id close-normal
Then "Focus object changed: *" should be logged
# WebModalDialog with QtWebKit, WebDialog with QtWebEngine
And "Web*Dialog requested, but we don't support that!" should be logged
# https://github.com/qutebrowser/qutebrowser/issues/906

View File

@ -42,7 +42,6 @@ Feature: Using private browsing
## https://github.com/qutebrowser/qutebrowser/issues/1219
@qtwebkit_ng_skip: private browsing is not implemented yet
Scenario: Sharing cookies with private browsing
When I open cookies/set?qute-test=42 without waiting in a private window
And I wait until cookies is loaded

View File

@ -21,8 +21,6 @@ import pytest
import pytest_bdd as bdd
from PyQt5.QtCore import PYQT_VERSION
bdd.scenarios('yankpaste.feature')
@ -34,10 +32,6 @@ def init_fake_clipboard(quteproc):
@bdd.when(bdd.parsers.parse('I insert "{value}" into the text field'))
def set_text_field(request, quteproc, value):
if request.config.webengine and PYQT_VERSION >= 0x50700:
cmd = ":jseval --world=0 set_text('{}')".format(value)
else:
cmd = ":jseval set_text('{}')".format(value)
quteproc.send_cmd(cmd)
def set_text_field(quteproc, value):
quteproc.send_cmd(":jseval --world=0 set_text('{}')".format(value))
quteproc.wait_for_js('textarea set to: ' + value)

View File

@ -29,8 +29,6 @@ import pytest
from PyQt5.QtCore import QProcess
from qutebrowser.utils import qtutils
def _base_args(config):
"""Get the arguments to pass with every invocation."""
@ -188,8 +186,6 @@ def test_version(request):
assert re.search(r'^qutebrowser\s+v\d+(\.\d+)', stdout) is not None
@pytest.mark.skipif(not qtutils.version_check('5.3'),
reason="Does not work on Qt 5.2")
def test_qt_arg(request, quteproc_new, tmpdir):
"""Test --qt-arg."""
args = (['--temp-basedir', '--qt-arg', 'stylesheet',
@ -269,7 +265,7 @@ def test_launching_with_python2():
pytest.skip("python2 not found")
_stdout, stderr = proc.communicate()
assert proc.returncode == 1
error = "At least Python 3.4 is required to run qutebrowser"
error = "At least Python 3.5 is required to run qutebrowser"
assert stderr.decode('ascii').startswith(error)

View File

@ -43,7 +43,7 @@ from qutebrowser.browser.webkit import cookies
from qutebrowser.misc import savemanager, sql
from qutebrowser.keyinput import modeman
from PyQt5.QtCore import PYQT_VERSION, pyqtSignal, QEvent, QSize, Qt, QObject
from PyQt5.QtCore import pyqtSignal, QEvent, QSize, Qt, QObject
from PyQt5.QtGui import QKeyEvent
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtNetwork import QNetworkCookieJar
@ -156,8 +156,6 @@ def tab_registry(win_registry):
@pytest.fixture
def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
"""Fixture providing the FakeWebTab *class*."""
if PYQT_VERSION < 0x050600:
pytest.skip('Causes segfaults, see #1638')
return stubs.FakeWebTab
@ -417,8 +415,9 @@ def fake_save_manager():
@pytest.fixture
def fake_args():
def fake_args(request):
ns = types.SimpleNamespace()
ns.backend = 'webengine' if request.config.webengine else 'webkit'
objreg.register('args', ns)
yield ns
objreg.delete('args')

View File

@ -19,8 +19,6 @@
import pytest
from PyQt5.QtCore import PYQT_VERSION
from qutebrowser.browser import browsertab
pytestmark = pytest.mark.usefixtures('redirect_webengine_data')
@ -48,9 +46,6 @@ def view(qtbot, config_stub, request):
@pytest.fixture(params=['webkit', 'webengine'])
def tab(request, qtbot, tab_registry, cookiejar_and_cache, mode_manager):
if PYQT_VERSION < 0x050600:
pytest.skip('Causes segfaults, see #1638')
if request.param == 'webkit':
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab_class = webkittab.WebKitTab

View File

@ -20,10 +20,9 @@
import http.server
import threading
import logging
import sys
import pytest
from PyQt5.QtCore import QUrl, QT_VERSION_STR
from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import (QNetworkProxy, QNetworkProxyQuery, QHostInfo,
QHostAddress)
@ -206,14 +205,6 @@ def test_secret_url(url, has_secret, from_file):
res.resolve(QNetworkProxyQuery(QUrl(url)), from_file=from_file)
# See https://github.com/qutebrowser/qutebrowser/pull/1891#issuecomment-259222615
try:
from PyQt5 import QtWebEngineWidgets
except ImportError:
QtWebEngineWidgets = None
def fetcher_test(test_str):
class PACHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
@ -244,10 +235,6 @@ def fetcher_test(test_str):
return fetcher
@pytest.mark.skipif(QT_VERSION_STR.startswith('5.7') and
QtWebEngineWidgets is not None and
sys.platform == "linux",
reason="Segfaults when run with QtWebEngine tests on Linux")
def test_fetch_success():
test_str = """
function FindProxyForURL(domain, host) {
@ -260,10 +247,6 @@ def test_fetch_success():
assert len(proxies) == 3
@pytest.mark.skipif(QT_VERSION_STR.startswith('5.7') and
QtWebEngineWidgets is not None and
sys.platform == "linux",
reason="Segfaults when run with QtWebEngine tests on Linux")
def test_fetch_evalerror(caplog):
test_str = """
function FindProxyForURL(domain, host) {

View File

@ -24,11 +24,12 @@
import sys
import logging
import types
import typing
import pytest
from qutebrowser.commands import cmdutils, cmdexc, argparser, command
from qutebrowser.utils import usertypes, typing
from qutebrowser.utils import usertypes
@pytest.fixture(autouse=True)

View File

@ -877,6 +877,9 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
monkeypatch.setattr(config, 'key_instance', None)
monkeypatch.setattr(config, '_change_filters', [])
monkeypatch.setattr(config, '_init_errors', [])
# Make sure we get no SSL warning
monkeypatch.setattr(config.earlyinit, 'check_backend_ssl_support',
lambda _backend: None)
yield
try:
objreg.delete('config-commands')
@ -888,7 +891,7 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
@pytest.mark.parametrize('config_py', [True, 'error', False])
@pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', False])
# pylint: disable=too-many-branches
def test_early_init(init_patch, config_tmpdir, caplog,
def test_early_init(init_patch, config_tmpdir, caplog, fake_args,
load_autoconfig, config_py, invalid_yaml):
# Prepare files
autoconfig_file = config_tmpdir / 'autoconfig.yml'
@ -914,7 +917,7 @@ def test_early_init(init_patch, config_tmpdir, caplog,
'utf-8', ensure=True)
with caplog.at_level(logging.ERROR):
config.early_init()
config.early_init(fake_args)
# Check error messages
expected_errors = []
@ -954,15 +957,16 @@ def test_early_init(init_patch, config_tmpdir, caplog,
assert config.instance._values == {'colors.hints.fg': 'magenta'}
def test_early_init_invalid_change_filter(init_patch):
def test_early_init_invalid_change_filter(init_patch, fake_args):
config.change_filter('foobar')
with pytest.raises(configexc.NoOptionError):
config.early_init()
config.early_init(fake_args)
@pytest.mark.parametrize('errors', [True, False])
def test_late_init(init_patch, monkeypatch, fake_save_manager, mocker, errors):
config.early_init()
def test_late_init(init_patch, monkeypatch, fake_save_manager, fake_args,
mocker, errors):
config.early_init(fake_args)
if errors:
err = configexc.ConfigErrorDesc("Error text", Exception("Exception"))
errs = configexc.ConfigFileErrors("config.py", [err])

View File

@ -28,7 +28,7 @@ import pytest
from qutebrowser.misc import checkpyver
TEXT = (r"At least Python 3.4 is required to run qutebrowser, but "
TEXT = (r"At least Python 3.5 is required to run qutebrowser, but "
r"\d+\.\d+\.\d+ is installed!\n")
@ -60,7 +60,7 @@ def test_patched_no_errwindow(capfd, monkeypatch):
"""Test with a patched sys.hexversion and --no-err-windows."""
monkeypatch.setattr(checkpyver.sys, 'argv',
[sys.argv[0], '--no-err-windows'])
monkeypatch.setattr(checkpyver.sys, 'hexversion', 0x03000000)
monkeypatch.setattr(checkpyver.sys, 'hexversion', 0x03040000)
monkeypatch.setattr(checkpyver.sys, 'exit', lambda status: None)
checkpyver.check_python_version()
stdout, stderr = capfd.readouterr()
@ -70,7 +70,7 @@ def test_patched_no_errwindow(capfd, monkeypatch):
def test_patched_errwindow(capfd, mocker, monkeypatch):
"""Test with a patched sys.hexversion and a fake Tk."""
monkeypatch.setattr(checkpyver.sys, 'hexversion', 0x03000000)
monkeypatch.setattr(checkpyver.sys, 'hexversion', 0x03040000)
monkeypatch.setattr(checkpyver.sys, 'exit', lambda status: None)
try:

View File

@ -19,11 +19,7 @@
"""Test qutebrowser.misc.earlyinit."""
import os
import sys
import types
import logging
import pkg_resources
import pytest
@ -35,94 +31,3 @@ def test_init_faulthandler_stderr_none(monkeypatch, attr):
"""Make sure init_faulthandler works when sys.stderr/__stderr__ is None."""
monkeypatch.setattr(sys, attr, None)
earlyinit.init_faulthandler()
class TestFixHarfbuzz:
@pytest.fixture(autouse=True)
def clear_harfbuzz(self):
"""Clear QT_HARFBUZZ before/after tests."""
old_harfbuzz = os.environ.pop('QT_HARFBUZZ', None)
yield
if old_harfbuzz is None:
os.environ.pop('QT_HARFBUZZ', None)
else:
os.environ['QT_HARFBUZZ'] = old_harfbuzz
@pytest.fixture
def args(self):
"""Get a fake argparse namespace."""
return types.SimpleNamespace(harfbuzz='auto')
@pytest.mark.parametrize('harfbuzz, qt_version, platform, expected', [
('auto', '5.2.1', 'linux', 'old'),
('auto', '5.3.0', 'linux', 'new'),
('auto', '5.3.2', 'linux', 'old'),
('auto', '5.4.0', 'linux', None),
('auto', '5.2.1', 'windows', None),
('old', '5.3.0', 'linux', 'old'),
('old', '5.4.0', 'linux', 'old'),
('new', '5.2.1', 'linux', 'new'),
('new', '5.3.2', 'linux', 'new'),
('new', '5.4.0', 'linux', 'new'),
])
def test_fix_harfbuzz(self, clear_harfbuzz, args, monkeypatch, caplog,
harfbuzz, qt_version, platform, expected):
"""Check the QT_HARFBUZZ env var."""
args.harfbuzz = harfbuzz
monkeypatch.setattr(earlyinit, '_qt_version',
lambda: pkg_resources.parse_version(qt_version))
monkeypatch.setattr(earlyinit.sys, 'platform', platform)
with caplog.at_level(logging.WARNING):
# Because QtWidgets is already imported
earlyinit.fix_harfbuzz(args)
assert os.environ.get('QT_HARFBUZZ', None) == expected
@pytest.mark.parametrize('frozen, level', [
(True, logging.DEBUG),
(False, logging.WARNING),
])
def test_widgets_warning(self, args, monkeypatch, caplog, frozen, level):
"""Make sure fix_harfbuzz warns when QtWidgets is imported."""
# Make sure QtWidgets is in sys.modules
from PyQt5 import QtWidgets # pylint: disable=unused-variable
if frozen:
monkeypatch.setattr(earlyinit.sys, 'frozen', True, raising=False)
else:
monkeypatch.delattr(earlyinit.sys, 'frozen', raising=False)
with caplog.at_level(level):
earlyinit.fix_harfbuzz(args)
record = caplog.records[0]
assert record.levelno == level
msg = "Harfbuzz fix attempted but QtWidgets is already imported!"
assert record.message == msg
def test_no_warning(self, args, monkeypatch):
"""Without QtWidgets in sys.modules, no warning should be shown."""
monkeypatch.setattr(earlyinit.sys, 'modules', {})
earlyinit.fix_harfbuzz(args)
@pytest.mark.parametrize('same', [True, False])
def test_qt_version(same):
if same:
qt_version_str = '5.4.0'
expected = '5.4.0'
else:
qt_version_str = '5.3.0'
expected = '5.4.0 (compiled 5.3.0)'
actual = earlyinit.qt_version(qversion='5.4.0',
qt_version_str=qt_version_str)
assert actual == expected
def test_qt_version_no_args():
"""Make sure qt_version without arguments at least works."""
earlyinit.qt_version()

View File

@ -35,7 +35,7 @@ from PyQt5.QtTest import QSignalSpy
import qutebrowser
from qutebrowser.misc import ipc
from qutebrowser.utils import objreg, qtutils, standarddir
from qutebrowser.utils import objreg, standarddir
from helpers import stubs
@ -778,26 +778,7 @@ def test_connect_inexistent(qlocalsocket):
assert qlocalsocket.error() == QLocalSocket.ServerNotFoundError
def test_socket_options_listen_problem(qlocalserver, short_tmpdir):
"""In earlier versions of Qt, listening fails when using socketOptions.
With this test, we verify that this bug exists in the Qt version/OS
combinations we expect it to, and doesn't exist in other versions.
"""
servername = str(short_tmpdir / 'x')
qlocalserver.setSocketOptions(QLocalServer.UserAccessOption)
ok = qlocalserver.listen(servername)
if os.name == 'nt' or qtutils.version_check('5.4'):
assert ok
else:
assert not ok
assert qlocalserver.serverError() == QAbstractSocket.HostNotFoundError
assert qlocalserver.errorString() == 'QLocalServer::listen: Name error'
@pytest.mark.posix
@pytest.mark.skipif(not qtutils.version_check('5.4'),
reason="setSocketOptions is even more broken on Qt < 5.4.")
def test_socket_options_address_in_use_problem(qlocalserver, short_tmpdir):
"""Qt seems to ignore AddressInUseError when using socketOptions.

View File

@ -0,0 +1,33 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
import pytest
from qutebrowser.misc import objects
from qutebrowser.utils import usertypes
@pytest.mark.parametrize('func', [
lambda: objects.NoBackend() == usertypes.Backend.QtWebEngine,
lambda: objects.NoBackend() != usertypes.Backend.QtWebEngine,
lambda: objects.NoBackend() in [usertypes.Backend.QtWebEngine],
])
def test_no_backend(func):
with pytest.raises(AssertionError, match='No backend set!'):
func()

View File

@ -19,13 +19,9 @@
"""Tests for qutebrowser.utils.javascript."""
import binascii
import os.path
import pytest
import hypothesis
import hypothesis.strategies
from PyQt5.QtCore import PYQT_VERSION
from qutebrowser.utils import javascript
@ -61,65 +57,22 @@ class TestStringEscape:
"""Test javascript escaping with some expected outcomes."""
assert javascript.string_escape(before) == after
def _test_escape(self, text, qtbot, webframe):
"""Helper function for test_real_escape*."""
try:
self._test_escape_simple(text, webframe)
except AssertionError:
# Try another method if the simple method failed.
#
# See _test_escape_hexlified documentation on why this is
# necessary.
self._test_escape_hexlified(text, qtbot, webframe)
def _test_escape_hexlified(self, text, qtbot, webframe):
"""Test conversion by hexlifying in javascript.
Since the conversion of QStrings to Python strings is broken in some
older PyQt versions in some corner cases, we load an HTML file which
generates an MD5 of the escaped text and use that for comparisons.
"""
escaped = javascript.string_escape(text)
path = os.path.join(os.path.dirname(__file__),
'test_javascript_string_escape.html')
with open(path, encoding='utf-8') as f:
html_source = f.read().replace('%INPUT%', escaped)
with qtbot.waitSignal(webframe.loadFinished) as blocker:
webframe.setHtml(html_source)
assert blocker.args == [True]
result = webframe.evaluateJavaScript('window.qute_test_result')
assert result is not None
assert '|' in result
result_md5, result_text = result.split('|', maxsplit=1)
text_md5 = binascii.hexlify(text.encode('utf-8')).decode('ascii')
assert result_md5 == text_md5, result_text
def _test_escape_simple(self, text, webframe):
def _test_escape(self, text, webframe):
"""Test conversion by using evaluateJavaScript."""
escaped = javascript.string_escape(text)
result = webframe.evaluateJavaScript('"{}";'.format(escaped))
assert result == text
@pytest.mark.parametrize('text', sorted(TESTS), ids=repr)
def test_real_escape(self, webframe, qtbot, text):
def test_real_escape(self, webframe, text):
"""Test javascript escaping with a real QWebPage."""
self._test_escape(text, qtbot, webframe)
self._test_escape(text, webframe)
@pytest.mark.qt_log_ignore('^OpenType support missing for script')
@hypothesis.given(hypothesis.strategies.text())
def test_real_escape_hypothesis(self, webframe, qtbot, text):
def test_real_escape_hypothesis(self, webframe, text):
"""Test javascript escaping with a real QWebPage and hypothesis."""
# We can't simply use self._test_escape because of this:
# https://github.com/pytest-dev/pytest-qt/issues/69
# self._test_escape(text, qtbot, webframe)
try:
self._test_escape_simple(text, webframe)
except AssertionError:
if PYQT_VERSION >= 0x050300:
self._test_escape_hexlified(text, qtbot, webframe)
self._test_escape(text, webframe)
@pytest.mark.parametrize('arg, expected', [

View File

@ -1,68 +0,0 @@
<!--
Helper file for string_escape() in test_javascript.py.
Since the conversion from QStrings to Python strings is broken in some corner
cases in PyQt < 5.4 we hexlify the string we got in javascript here and test
that in the test.
-->
<html>
<head>
<script type="text/javascript">
//<![CDATA[
/*
* hexlify() and str2rstr_utf8() are based on:
*
* JavaScript MD5 1.0.1
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
'use strict';
function hexlify(input) {
var hex_tab = '0123456789abcdef';
var output = '';
var x;
var i;
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt(x & 0x0F);
}
return output;
}
function encode_utf8(input) {
return unescape(encodeURIComponent(input));
}
function set_text() {
var elems = document.getElementsByTagName("p");
var hexlified = hexlify(encode_utf8("%INPUT%"));
var result = hexlified + "|" + "%INPUT%";
elems[0].innerHTML = result
window.qute_test_result = result;
}
//]]>
</script>
</head>
<body onload="set_text()">
<p>set_text() not called...</p>
</html>

View File

@ -82,14 +82,14 @@ def test_version_check(monkeypatch, qversion, compiled, version, exact,
assert qtutils.version_check(version, exact, strict=strict) == expected
@pytest.mark.parametrize('version, ng', [
@pytest.mark.parametrize('version, is_new', [
('537.21', False), # QtWebKit 5.1
('538.1', False), # Qt 5.8
('602.1', True) # QtWebKit-NG TP5
('602.1', True) # new QtWebKit TP5, 5.212 Alpha
])
def test_is_qtwebkit_ng(monkeypatch, version, ng):
def test_is_new_qtwebkit(monkeypatch, version, is_new):
monkeypatch.setattr(qtutils, 'qWebKitVersion', lambda: version)
assert qtutils.is_qtwebkit_ng() == ng
assert qtutils.is_new_qtwebkit() == is_new
class TestCheckOverflow:
@ -118,26 +118,6 @@ class TestCheckOverflow:
assert newval == repl
@pytest.mark.parametrize('os_name, qversion, expected', [
('linux', '5.2.1', True), # unaffected OS
('linux', '5.4.1', True), # unaffected OS
('nt', '5.2.1', False),
('nt', '5.3.0', True), # unaffected Qt version
('nt', '5.4.1', True), # unaffected Qt version
])
def test_check_print_compat(os_name, qversion, expected, monkeypatch):
"""Test check_print_compat.
Args:
os_name: The fake os.name to set.
qversion: The fake qVersion() to set.
expected: The expected return value.
"""
monkeypatch.setattr(qtutils.os, 'name', os_name)
monkeypatch.setattr(qtutils, 'qVersion', lambda: qversion)
assert qtutils.check_print_compat() == expected
class QtObject:
"""Fake Qt object for test_ensure."""

View File

@ -87,8 +87,6 @@ def test_fake_mac_config(tmpdir, monkeypatch):
assert standarddir.config() == expected
# FIXME:conf needs AppDataLocation
@pytest.mark.qt55
@pytest.mark.parametrize('what', ['data', 'config', 'cache'])
@pytest.mark.not_mac
def test_fake_windows(tmpdir, monkeypatch, what):
@ -354,8 +352,6 @@ class TestSystemData:
assert standarddir.data(system=True) == standarddir.data()
# FIXME:conf needs AppDataLocation
@pytest.mark.qt55
class TestMoveWindowsAndMacOS:
"""Test other invocations of _move_data."""

View File

@ -1,54 +0,0 @@
# Copyright 2016-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
"""Tests for qutebrowser.utils.typing."""
import pytest
from qutebrowser.utils import typing
@pytest.fixture
def pytyping():
"""A fixture to get the python 3.5+ typing module."""
pytyping = pytest.importorskip('typing')
return pytyping
class TestUnion:
def test_python_subclass(self, pytyping):
assert (type(pytyping.Union[str, int]) is # flake8: disable=E721
type(pytyping.Union))
def test_qute_subclass(self):
assert (type(typing.FakeUnion[str, int]) is # flake8: disable=E721
type(typing.FakeUnion))
def test_python_params(self, pytyping):
union = pytyping.Union[str, int]
try:
assert union.__union_params__ == (str, int)
except AttributeError:
assert union.__args__ == (str, int)
def test_qute_params(self):
union = typing.FakeUnion[str, int]
try:
assert union.__union_params__ == (str, int)
except AttributeError:
assert union.__args__ == (str, int)

View File

@ -328,9 +328,8 @@ def test_get_search_url_invalid(url):
(False, True, True, 'deadbeef'),
(False, True, True, 'hello.'),
(False, True, False, 'site:cookies.com oatmeal raisin'),
# no DNS because bogus-IP
(False, True, False, '31c3'),
(False, True, False, 'foo::bar'), # no DNS because of no host
# no DNS because there is no host
(False, True, False, 'foo::bar'),
# Valid search term with autosearch
(False, False, False, 'test foo'),
# autosearch = False
@ -350,11 +349,6 @@ def test_is_url(config_stub, fake_dns,
url: The URL to test, as a string.
auto_search: With which auto_search setting to test
"""
if (url == '31c3' and
auto_search == 'dns' and
qtutils.version_check('5.6.1')):
pytest.xfail("Qt behavior changed")
config_stub.val.url.auto_search = auto_search
if auto_search == 'dns':
if uses_dns:

View File

@ -35,7 +35,7 @@ import pkg_resources
import pytest
import qutebrowser
from qutebrowser.utils import version, usertypes, qtutils
from qutebrowser.utils import version, usertypes
from qutebrowser.browser import pdfjs
@ -515,12 +515,10 @@ class ImportFake:
('pygments', True),
('yaml', True),
('cssutils', True),
('typing', True),
('PyQt5.QtWebEngineWidgets', True),
('PyQt5.QtWebKitWidgets', True),
])
self.no_version_attribute = ['sip', 'typing',
'PyQt5.QtWebEngineWidgets',
self.no_version_attribute = ['sip', 'PyQt5.QtWebEngineWidgets',
'PyQt5.QtWebKitWidgets']
self.version_attribute = '__version__'
self.version = '1.2.3'
@ -588,7 +586,6 @@ class TestModuleVersions:
@pytest.mark.parametrize('module, idx, expected', [
('colorama', 1, 'colorama: no'),
('cssutils', 6, 'cssutils: no'),
('typing', 7, 'typing: no'),
])
def test_missing_module(self, module, idx, expected, import_fake):
"""Test with a module missing.
@ -839,7 +836,6 @@ class VersionParams:
VersionParams('frozen', frozen=True),
VersionParams('no-style', style=False),
VersionParams('no-webkit', with_webkit=False),
VersionParams('webkit-ng', with_webkit='ng'),
VersionParams('unknown-dist', known_distribution=False),
VersionParams('no-ssl', ssl_support=False),
], ids=lambda param: param.name)
@ -884,13 +880,7 @@ def test_version_output(params, stubs, monkeypatch):
patches['qWebKitVersion'] = lambda: 'WEBKIT VERSION'
patches['objects.backend'] = usertypes.Backend.QtWebKit
patches['QWebEngineProfile'] = None
if params.with_webkit == 'ng':
backend = 'QtWebKit-NG'
patches['qtutils.is_qtwebkit_ng'] = lambda: True
else:
backend = 'legacy QtWebKit'
patches['qtutils.is_qtwebkit_ng'] = lambda: False
substitutions['backend'] = backend + ' (WebKit WEBKIT VERSION)'
substitutions['backend'] = 'new QtWebKit (WebKit WEBKIT VERSION)'
else:
monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False)
patches['objects.backend'] = usertypes.Backend.QtWebEngine
@ -946,8 +936,6 @@ def test_version_output(params, stubs, monkeypatch):
assert version.version() == expected
@pytest.mark.skipif(not qtutils.version_check('5.4'),
reason="Needs Qt >= 5.4.")
def test_opengl_vendor():
"""Simply call version.opengl_vendor() and see if it doesn't crash."""
pytest.importorskip("PyQt5.QtOpenGL")

View File

@ -19,8 +19,6 @@
"""Tests for usertypes.Question."""
import logging
import pytest
from qutebrowser.utils import usertypes
@ -82,15 +80,6 @@ def test_abort(question, qtbot):
assert question.is_aborted
def test_abort_typeerror(question, qtbot, mocker, caplog):
"""Test Question.abort() with .emit() raising a TypeError."""
signal_mock = mocker.patch('qutebrowser.utils.usertypes.Question.aborted')
signal_mock.emit.side_effect = TypeError
with caplog.at_level(logging.ERROR, 'misc'):
question.abort()
assert caplog.records[0].message == 'Error while aborting question'
def test_abort_twice(question, qtbot):
"""Abort a question twice."""
with qtbot.wait_signal(question.aborted):