Drop support for Qt < 5.7.1

See #2742
This commit is contained in:
Florian Bruhin 2017-09-18 09:10:32 +02:00
parent 2b4304908a
commit 852baaa8c3
39 changed files with 142 additions and 683 deletions

View File

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

View File

@ -23,7 +23,7 @@ Breaking changes
- (TODO) Support for legacy QtWebKit (before 5.212 which is distributed - (TODO) Support for legacy QtWebKit (before 5.212 which is distributed
independently from Qt) is dropped. independently from Qt) is dropped.
- (TODO) Support for Python 3.4 is dropped. - (TODO) Support for Python 3.4 is dropped.
- (TODO) Support for Qt before 5.7 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) New dependency on ruamel.yaml; dropped PyYAML dependency.
- (TODO) The QtWebEngine backend is now used by default if available. - (TODO) The QtWebEngine backend is now used by default if available.
- New dependency on the QtSql module and Qt sqlite support. - New dependency on the QtSql module and Qt sqlite support.
@ -36,6 +36,7 @@ Breaking changes
work properly anymore. work properly anymore.
- Various documentation files got moved to the doc/ subfolder, - Various documentation files got moved to the doc/ subfolder,
`qutebrowser.desktop` got moved to misc/. `qutebrowser.desktop` got moved to misc/.
- The `--harfbuzz` commandline argument got dropped
Major changes Major changes
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -205,26 +205,6 @@ Experiencing freezing on sites like duckduckgo and youtube.::
See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357] See https://github.com/qutebrowser/qutebrowser/issues/357[Issue #357]
for more details. 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:: 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 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. + As a workaround, you can disable the nullpointer check optimization by adding the -fno-delete-null-pointer-checks flag while compiling. +

View File

@ -3,39 +3,50 @@ Installing qutebrowser
toc::[] 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 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 Debian Jessie / Ubuntu 14.04 LTS / Linux Mint < 18
* Ubuntu Trusty (14.04 LTS) or newer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Any other distribution based on these (e.g. Linux Mint 17+)
Unfortunately there is no Debian package in the official repos yet, but installing qutebrowser is Those distributions only have Python 3.4 and a too old Qt version available. A
still relatively easy! newer Qt isn't easily installable on Python 3.4, unfortunately.
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>> If you get qutebrowser running on those distributions, please
instead in order to be able to use the new QtWebEngine backend. Newer versions https://github.com/qutebrowser/qutebrowser/blob/master/doc/contributing.asciidoc[contribute]
have a QtWebEngine package in the repositories. 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: 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 Get the qutebrowser package from the
https://github.com/qutebrowser/qutebrowser/releases[release page] and download https://github.com/qutebrowser/qutebrowser/releases[release page] and download
the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package]. the https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb[PyPEG2 package].
@ -47,35 +58,27 @@ Install the packages:
# dpkg -i qutebrowser_*_all.deb # dpkg -i qutebrowser_*_all.deb
---- ----
Build it from git Some additional hints:
~~~~~~~~~~~~~~~~~
Install the dependencies via apt-get:
- 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 # apt-get install --no-install-recommends asciidoc source-highlight
----
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
$ python3 scripts/asciidoc2html.py $ 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} # apt-get install gstreamer1.0-plugins-{bad,base,good,ugly}
---- ----
Then <<tox,install qutebrowser via tox>>.
On Fedora On Fedora
--------- ---------
@ -116,7 +119,7 @@ $ rm -r qutebrowser-git
or you could use an AUR helper, e.g. `yaourt -S 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 # 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 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: A version of qutebrowser is available in the main repository and can be installed with:
---- ----

View File

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

View File

@ -27,7 +27,6 @@ markers =
this: Used to mark tests during development this: Used to mark tests during development
no_invalid_lines: Don't fail on unparseable lines in end2end tests no_invalid_lines: Don't fail on unparseable lines in end2end tests
issue2478: Tests which are broken on Windows with QtWebEngine, https://github.com/qutebrowser/qutebrowser/issues/2478 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_level_fail = WARNING
qt_log_ignore = qt_log_ignore =
^SpellCheck: .* ^SpellCheck: .*

View File

@ -19,9 +19,6 @@
"""The ListView to display downloads in.""" """The ListView to display downloads in."""
import functools
import sip
from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer
from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory
@ -30,29 +27,6 @@ from qutebrowser.config import config
from qutebrowser.utils import qtutils, utils, objreg from qutebrowser.utils import qtutils, utils, objreg
def update_geometry(obj):
"""Weird WORKAROUND for some weird PyQt bug (probably).
This actually should be a method of DownloadView, but for some reason the
rowsInserted/rowsRemoved signals don't get disconnected from this method
when the DownloadView is deleted from Qt (e.g. by closing a window).
Here we check if obj ("self") was deleted and just ignore the event if so.
Original bug: https://github.com/qutebrowser/qutebrowser/issues/167
Workaround bug: https://github.com/qutebrowser/qutebrowser/issues/171
"""
def _update_geometry():
"""Actually update the geometry if the object still exists."""
if sip.isdeleted(obj):
return
obj.updateGeometry()
# If we don't use a singleShot QTimer, the geometry isn't updated correctly
# and won't include the new item.
QTimer.singleShot(0, _update_geometry)
class DownloadView(QListView): class DownloadView(QListView):
"""QListView which shows currently running downloads as a bar. """QListView which shows currently running downloads as a bar.
@ -85,9 +59,12 @@ class DownloadView(QListView):
self.setSpacing(1) self.setSpacing(1)
self._menu = None self._menu = None
model = objreg.get('download-model', scope='window', window=win_id) model = objreg.get('download-model', scope='window', window=win_id)
model.rowsInserted.connect(functools.partial(update_geometry, self)) model.rowsInserted.connect(lambda:
model.rowsRemoved.connect(functools.partial(update_geometry, self)) QTimer.singleShot(0, self.updateGeometry))
model.dataChanged.connect(functools.partial(update_geometry, self)) model.rowsRemoved.connect(lambda:
QTimer.singleShot(0, self.updateGeometry))
model.dataChanged.connect(lambda:
QTimer.singleShot(0, self.updateGeometry))
self.setModel(model) self.setModel(model)
self.setWrapping(True) self.setWrapping(True)
self.setContextMenuPolicy(Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)

View File

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

View File

@ -37,14 +37,6 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper):
self, error=debug.qenum_key(QSslError, self._error.error()), self, error=debug.qenum_key(QSslError, self._error.error()),
string=str(self)) 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()))
def __eq__(self, other): def __eq__(self, other):
return self._error == other._error # pylint: disable=protected-access return self._error == other._error # pylint: disable=protected-access

View File

@ -24,13 +24,12 @@ import collections
import netrc import netrc
import html import html
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication, from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QCoreApplication, QUrl,
QUrl, QByteArray) QByteArray)
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslSocket
from qutebrowser.config import config from qutebrowser.config import config
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils, from qutebrowser.utils import message, log, usertypes, utils, objreg, urlutils
urlutils)
from qutebrowser.browser import shared from qutebrowser.browser import shared
from qutebrowser.browser.webkit import certificateerror from qutebrowser.browser.webkit import certificateerror
from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply, from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply,
@ -88,15 +87,9 @@ def _is_secure_cipher(cipher):
def init(): def init():
"""Disable insecure SSL ciphers on old Qt versions.""" """Disable insecure SSL ciphers on old Qt versions."""
if qtutils.version_check('5.3.0'): default_ciphers = QSslSocket.defaultCiphers()
default_ciphers = QSslSocket.defaultCiphers() log.init.debug("Default Qt ciphers: {}".format(
log.init.debug("Default Qt ciphers: {}".format( ', '.join(c.name() for c in default_ciphers)))
', '.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)))
good_ciphers = [] good_ciphers = []
bad_ciphers = [] bad_ciphers = []
@ -409,24 +402,11 @@ class NetworkManager(QNetworkAccessManager):
tab = objreg.get('tab', scope='tab', window=self._win_id, tab = objreg.get('tab', scope='tab', window=self._win_id,
tab=self._tab_id) tab=self._tab_id)
current_url = tab.url() current_url = tab.url()
except (KeyError, RuntimeError, TypeError): except (KeyError, RuntimeError):
# https://github.com/qutebrowser/qutebrowser/issues/889 # https://github.com/qutebrowser/qutebrowser/issues/889
# Catching RuntimeError and TypeError because we could be in # Catching RuntimeError because we could be in the middle of the
# the middle of the webpage shutdown here. # webpage shutdown here.
current_url = QUrl() current_url = QUrl()
self.set_referer(req, current_url) self.set_referer(req, current_url)
return super().createRequest(op, req, outgoing_data)
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

View File

@ -292,9 +292,6 @@ class WebKitElement(webelem.AbstractWebElement):
elem = elem._parent() # pylint: disable=protected-access elem = elem._parent() # pylint: disable=protected-access
def _move_text_cursor(self): 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(): if self.is_text_input() and self.is_editable():
self._tab.caret.move_to_end_of_document() 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 PyQt5.QtWebKit import QWebSettings
from qutebrowser.config import config, websettings from qutebrowser.config import config, websettings
from qutebrowser.utils import standarddir, urlutils, qtutils from qutebrowser.utils import standarddir, urlutils
from qutebrowser.browser import shared from qutebrowser.browser import shared
@ -131,13 +131,6 @@ def init(_args):
QWebSettings.setOfflineStoragePath( QWebSettings.setOfflineStoragePath(
os.path.join(data_path, 'offline-storage')) 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) websettings.init_mappings(MAPPINGS)
_set_user_stylesheet() _set_user_stylesheet()
config.instance.changed.connect(_update_settings) config.instance.changed.connect(_update_settings)

View File

@ -55,20 +55,14 @@ class WebKitPrinting(browsertab.AbstractPrinting):
"""QtWebKit implementations related to printing.""" """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): def check_pdf_support(self):
self._do_check() pass
def check_printer_support(self): def check_printer_support(self):
self._do_check() pass
def check_preview_support(self): def check_preview_support(self):
self._do_check() pass
def to_pdf(self, filename): def to_pdf(self, filename):
printer = QPrinter() printer = QPrinter()

View File

@ -22,7 +22,7 @@
import html import html
import functools 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.QtGui import QDesktopServices
from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
from PyQt5.QtWidgets import QFileDialog from PyQt5.QtWidgets import QFileDialog
@ -33,8 +33,8 @@ from qutebrowser.config import config
from qutebrowser.browser import pdfjs, shared from qutebrowser.browser import pdfjs, shared
from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit import http
from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.browser.webkit.network import networkmanager
from qutebrowser.utils import (message, usertypes, log, jinja, qtutils, utils, from qutebrowser.utils import (message, usertypes, log, jinja, objreg, debug,
objreg, debug, urlutils) urlutils)
class BrowserPage(QWebPage): class BrowserPage(QWebPage):
@ -87,22 +87,16 @@ class BrowserPage(QWebPage):
self.restoreFrameStateRequested.connect( self.restoreFrameStateRequested.connect(
self.on_restore_frame_state_requested) self.on_restore_frame_state_requested)
if PYQT_VERSION > 0x050300: def javaScriptPrompt(self, frame, js_msg, default):
# WORKAROUND (remove this when we bump the requirements to 5.3.1) """Override javaScriptPrompt to use qutebrowser prompts."""
# We can't override javaScriptPrompt with older PyQt-versions because if self._is_shutting_down:
# of a bug in PyQt. return (False, "")
# See http://www.riverbankcomputing.com/pipermail/pyqt/2014-June/034385.html try:
return shared.javascript_prompt(frame.url(), js_msg, default,
def javaScriptPrompt(self, frame, js_msg, default): abort_on=[self.loadStarted,
"""Override javaScriptPrompt to use qutebrowser prompts.""" self.shutting_down])
if self._is_shutting_down: except shared.CallSuper:
return (False, "") return super().javaScriptPrompt(frame, js_msg, default)
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): def _handle_errorpage(self, info, errpage):
"""Display an error page if needed. """Display an error page if needed.
@ -225,10 +219,6 @@ class BrowserPage(QWebPage):
def on_print_requested(self, frame): def on_print_requested(self, frame):
"""Handle printing when requested via javascript.""" """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 = QPrintDialog()
printdiag.setAttribute(Qt.WA_DeleteOnClose) printdiag.setAttribute(Qt.WA_DeleteOnClose)
printdiag.open(lambda: frame.print(printdiag.printer())) printdiag.open(lambda: frame.print(printdiag.printer()))
@ -350,15 +340,7 @@ class BrowserPage(QWebPage):
frame: The QWebFrame which gets saved. frame: The QWebFrame which gets saved.
item: The QWebHistoryItem to be saved. item: The QWebHistoryItem to be saved.
""" """
try: if frame != self.mainFrame():
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.
return return
data = { data = {
'zoom': frame.zoomFactor(), 'zoom': frame.zoomFactor(),
@ -401,9 +383,6 @@ class BrowserPage(QWebPage):
""" """
return ext in self._extension_handlers 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): def extension(self, ext, opt, out):
"""Override QWebPage::extension to provide error pages. """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.config import config
from qutebrowser.keyinput import modeman 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 from qutebrowser.browser.webkit import webpage
@ -57,7 +57,7 @@ class WebView(QWebView):
def __init__(self, *, win_id, tab_id, tab, private, parent=None): def __init__(self, *, win_id, tab_id, tab, private, parent=None):
super().__init__(parent) 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 # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-42948
# See https://github.com/qutebrowser/qutebrowser/issues/462 # See https://github.com/qutebrowser/qutebrowser/issues/462
self.setStyle(QStyleFactory.create('Fusion')) 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, page = webpage.BrowserPage(win_id=self.win_id, tab_id=self._tab_id,
tabdata=tab.data, private=private, tabdata=tab.data, private=private,
parent=self) parent=self)
page.setVisibilityState(
try: QWebPage.VisibilityStateVisible if self.isVisible()
page.setVisibilityState( else QWebPage.VisibilityStateHidden)
QWebPage.VisibilityStateVisible if self.isVisible()
else QWebPage.VisibilityStateHidden)
except AttributeError:
pass
self.setPage(page) self.setPage(page)
@ -240,12 +236,8 @@ class WebView(QWebView):
Return: Return:
The superclass event return value. The superclass event return value.
""" """
try:
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
except AttributeError:
pass
super().showEvent(e) super().showEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateVisible)
def hideEvent(self, e): def hideEvent(self, e):
"""Extend hideEvent to set the page visibility state to hidden. """Extend hideEvent to set the page visibility state to hidden.
@ -256,12 +248,8 @@ class WebView(QWebView):
Return: Return:
The superclass event return value. The superclass event return value.
""" """
try:
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
except AttributeError:
pass
super().hideEvent(e) super().hideEvent(e)
self.page().setVisibilityState(QWebPage.VisibilityStateHidden)
def mousePressEvent(self, e): def mousePressEvent(self, e):
"""Set the tabdata ClickTarget on a mousepress. """Set the tabdata ClickTarget on a mousepress.

View File

@ -61,10 +61,6 @@ class Progress(QProgressBar):
def on_tab_changed(self, tab): def on_tab_changed(self, tab):
"""Set the correct value when the current tab changed.""" """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()) self.setValue(tab.progress())
if tab.load_status() == usertypes.LoadStatus.loading: if tab.load_status() == usertypes.LoadStatus.loading:
self.show() self.show()

View File

@ -70,9 +70,6 @@ class TabWidget(QTabWidget):
@config.change_filter('tabs') @config.change_filter('tabs')
def _init_config(self): def _init_config(self):
"""Initialize attributes based on the config.""" """Initialize attributes based on the config."""
if self is None: # pragma: no cover
# WORKAROUND for PyQt 5.2
return
tabbar = self.tabBar() tabbar = self.tabBar()
self.setMovable(True) self.setMovable(True)
self.setTabsClosable(False) self.setTabsClosable(False)
@ -744,24 +741,17 @@ class TabBarStyle(QCommonStyle):
log.misc.warning("Could not get layouts for tab!") log.misc.warning("Could not get layouts for tab!")
return QRect() return QRect()
return layouts.text 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 # Need to use super() because we also use super() to render
# element in drawControl(); otherwise, we may get bit by # element in drawControl(); otherwise, we may get bit by
# style differences... # style differences...
rct = super().subElementRect(sr, opt, widget) return super().subElementRect(sr, opt, widget)
return rct
else: 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) return self._style.subElementRect(sr, opt, widget)
def _tab_layout(self, opt): def _tab_layout(self, opt):

View File

@ -29,7 +29,6 @@ try:
except ImportError: except ImportError:
hunter = None hunter = None
import os
import sys import sys
import faulthandler import faulthandler
import traceback import traceback
@ -41,8 +40,6 @@ try:
except ImportError: except ImportError:
tkinter = None tkinter = None
import pkg_resources
# NOTE: No qutebrowser or PyQt import should be done here, as some early # NOTE: No qutebrowser or PyQt import should be done here, as some early
# initialization needs to take place before that! # initialization needs to take place before that!
@ -140,73 +137,6 @@ def init_faulthandler(fileobj=sys.__stderr__):
faulthandler.register(signal.SIGUSR1) 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(): def check_pyqt_core():
"""Check if PyQt core is installed.""" """Check if PyQt core is installed."""
try: try:
@ -268,22 +198,15 @@ def qt_version(qversion=None, qt_version_str=None):
return qversion return qversion
def check_qt_version(backend): def check_qt_version():
"""Check if the Qt version is recent enough.""" """Check if the Qt version is recent enough."""
from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR from PyQt5.QtCore import PYQT_VERSION, PYQT_VERSION_STR
from qutebrowser.utils import qtutils 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): PYQT_VERSION < 0x050200):
text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but Qt {} / " text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required, "
"PyQt {} is installed.".format(qt_version(), "but Qt {} / PyQt {} is installed.".format(qt_version(),
PYQT_VERSION_STR)) 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))
_die(text) _die(text)
@ -423,13 +346,10 @@ def earlyinit(args):
check_pyqt_core() check_pyqt_core()
# Init logging as early as possible # Init logging as early as possible
init_log(args) 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 # 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. # errors, so people only using the GUI notice them as well.
backend = get_backend(args) backend = get_backend(args)
check_qt_version(backend) check_qt_version()
remove_inputhook() remove_inputhook()
check_libraries(backend) check_libraries(backend)
check_ssl_support(backend) check_ssl_support(backend)

View File

@ -139,8 +139,6 @@ class IPCServer(QObject):
_server: A QLocalServer to accept new connections. _server: A QLocalServer to accept new connections.
_socket: The QLocalSocket we're currently connected to. _socket: The QLocalSocket we're currently connected to.
_socketname: The socketname to use. _socketname: The socketname to use.
_socketopts_ok: Set if using setSocketOptions is working with this
OS/Qt version.
_atime_timer: Timer to update the atime of the socket regularly. _atime_timer: Timer to update the atime of the socket regularly.
Signals: Signals:
@ -182,14 +180,6 @@ class IPCServer(QObject):
self._socket = None self._socket = None
self._old_socket = None self._old_socket = None
self._socketopts_ok = os.name == 'nt'
if self._socketopts_ok: # pragma: no cover
# If we use setSocketOptions on Unix with Qt < 5.4, we get a
# NameError while listening...
log.ipc.debug("Calling setSocketOptions")
self._server.setSocketOptions(QLocalServer.UserAccessOption)
else: # pragma: no cover
log.ipc.debug("Not calling setSocketOptions")
def _remove_server(self): def _remove_server(self):
"""Remove an existing server.""" """Remove an existing server."""
@ -210,22 +200,21 @@ class IPCServer(QObject):
raise AddressInUseError(self._server) raise AddressInUseError(self._server)
else: else:
raise ListenError(self._server) raise ListenError(self._server)
if not self._socketopts_ok: # pragma: no cover
# If we use setSocketOptions on Unix with Qt < 5.4, we get a # If we use setSocketOptions on Unix with Qt < 5.4, we get a NameError
# NameError while listening. # while listening. (see b135569d5c6e68c735ea83f42e4baf51f7972281)
# (see b135569d5c6e68c735ea83f42e4baf51f7972281) #
# # Also, we don't get an AddressInUseError with Qt 5.5:
# Also, we don't get an AddressInUseError with Qt 5.5: # https://bugreports.qt.io/browse/QTBUG-48635
# https://bugreports.qt.io/browse/QTBUG-48635 #
# # This means we don't use it at all.
# This means we only use setSocketOption on Windows... try:
try: os.chmod(self._server.fullServerName(), 0o700)
os.chmod(self._server.fullServerName(), 0o700) except FileNotFoundError:
except FileNotFoundError: # https://github.com/qutebrowser/qutebrowser/issues/1530
# https://github.com/qutebrowser/qutebrowser/issues/1530 # The server doesn't actually exist even if ok was reported as
# The server doesn't actually exist even if ok was reported as # True, so report this as an error.
# True, so report this as an error. raise ListenError(self._server)
raise ListenError(self._server)
@pyqtSlot('QLocalSocket::LocalSocketError') @pyqtSlot('QLocalSocket::LocalSocketError')
def on_error(self, err): def on_error(self, err):

View File

@ -95,9 +95,6 @@ def get_argparser():
action='store_false', dest='color') action='store_false', dest='color')
debug.add_argument('--force-color', help="Force colored logging", debug.add_argument('--force-color', help="Force colored logging",
action='store_true') 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', debug.add_argument('--relaxed-config', action='store_true',
help="Silently remove unknown config options.") help="Silently remove unknown config options.")
debug.add_argument('--nowindow', action='store_true', help="Don't show " debug.add_argument('--nowindow', action='store_true', help="Don't show "
@ -172,6 +169,6 @@ def main():
args = argparse.Namespace(**data) args = argparse.Namespace(**data)
earlyinit.earlyinit(args) earlyinit.earlyinit(args)
# We do this imports late as earlyinit needs to be run first (because of # 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 from qutebrowser import app
return app.run(args) return app.run(args)

View File

@ -345,7 +345,7 @@ def qt_message_handler(msg_type, context, msg):
try: try:
qt_to_logging[QtCore.QtInfoMsg] = logging.INFO qt_to_logging[QtCore.QtInfoMsg] = logging.INFO
except AttributeError: except AttributeError:
# Qt < 5.5 # While we don't support Qt < 5.5 anymore, logging still needs to work
pass pass
# Change levels of some well-known messages to debug so they don't get # 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] func = partial_objs[name]
try: try:
self[name].destroyed.disconnect(func) self[name].destroyed.disconnect(func)
except (RuntimeError, TypeError): except RuntimeError:
# If C++ has deleted the object, the slot is already # If C++ has deleted the object, the slot is already
# disconnected. # 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 pass
del partial_objs[name] del partial_objs[name]
@ -145,7 +141,7 @@ class ObjectRegistry(collections.UserDict):
for name, obj in self.data.items(): for name, obj in self.data.items():
try: try:
obj_repr = repr(obj) obj_repr = repr(obj)
except (RuntimeError, TypeError): except RuntimeError:
# Underlying object deleted probably # Underlying object deleted probably
obj_repr = '<deleted>' obj_repr = '<deleted>'
lines.append("{}: {}".format(name, obj_repr)) lines.append("{}: {}".format(name, obj_repr))

View File

@ -28,7 +28,6 @@ Module attributes:
import io import io
import os
import operator import operator
import contextlib import contextlib
@ -137,15 +136,6 @@ def check_overflow(arg, ctype, fatal=True):
return arg 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): def ensure_valid(obj):
"""Ensure a Qt object with an .isValid() method is valid.""" """Ensure a Qt object with an .isValid() method is valid."""
if not obj.isValid(): if not obj.isValid():

View File

@ -355,14 +355,8 @@ class Question(QObject):
log.misc.debug("Question was already aborted") log.misc.debug("Question was already aborted")
return return
self.is_aborted = True self.is_aborted = True
try: self.aborted.emit()
self.aborted.emit() self.completed.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")
class Timer(QTimer): class Timer(QTimer):

View File

@ -62,7 +62,6 @@ def _apply_platform_markers(config, item):
('no_ci', 'CI' in os.environ, "Skipped on CI."), ('no_ci', 'CI' in os.environ, "Skipped on CI."),
('issue2478', os.name == 'nt' and config.webengine, ('issue2478', os.name == 'nt' and config.webengine,
"Broken with QtWebEngine on Windows"), "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: 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)) item.add_marker(pytest.mark.xfail(run=False))
if item.get_marker('js_prompt'): if item.get_marker('js_prompt'):
if config.webengine: if config.webengine:
js_prompt_pyqt_version = 0x050700 item.add_marker(pytest.mark.skipif(
else: PYQT_VERSION <= 0x050700,
js_prompt_pyqt_version = 0x050300 reason='JS prompts are not supported with PyQt 5.7'))
item.add_marker(pytest.mark.skipif(
PYQT_VERSION <= js_prompt_pyqt_version,
reason='JS prompts are not supported with this PyQt version'))
if deselected: if deselected:
deselected_items.append(item) deselected_items.append(item)

View File

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

View File

@ -29,8 +29,6 @@ import pytest
from PyQt5.QtCore import QProcess from PyQt5.QtCore import QProcess
from qutebrowser.utils import qtutils
def _base_args(config): def _base_args(config):
"""Get the arguments to pass with every invocation.""" """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 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): def test_qt_arg(request, quteproc_new, tmpdir):
"""Test --qt-arg.""" """Test --qt-arg."""
args = (['--temp-basedir', '--qt-arg', 'stylesheet', args = (['--temp-basedir', '--qt-arg', 'stylesheet',

View File

@ -43,7 +43,7 @@ from qutebrowser.browser.webkit import cookies
from qutebrowser.misc import savemanager, sql from qutebrowser.misc import savemanager, sql
from qutebrowser.keyinput import modeman 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.QtGui import QKeyEvent
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout
from PyQt5.QtNetwork import QNetworkCookieJar from PyQt5.QtNetwork import QNetworkCookieJar
@ -156,8 +156,6 @@ def tab_registry(win_registry):
@pytest.fixture @pytest.fixture
def fake_web_tab(stubs, tab_registry, mode_manager, qapp): def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
"""Fixture providing the FakeWebTab *class*.""" """Fixture providing the FakeWebTab *class*."""
if PYQT_VERSION < 0x050600:
pytest.skip('Causes segfaults, see #1638')
return stubs.FakeWebTab return stubs.FakeWebTab

View File

@ -19,8 +19,6 @@
import pytest import pytest
from PyQt5.QtCore import PYQT_VERSION
from qutebrowser.browser import browsertab from qutebrowser.browser import browsertab
pytestmark = pytest.mark.usefixtures('redirect_webengine_data') pytestmark = pytest.mark.usefixtures('redirect_webengine_data')
@ -48,9 +46,6 @@ def view(qtbot, config_stub, request):
@pytest.fixture(params=['webkit', 'webengine']) @pytest.fixture(params=['webkit', 'webengine'])
def tab(request, qtbot, tab_registry, cookiejar_and_cache, mode_manager): 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': if request.param == 'webkit':
webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab') webkittab = pytest.importorskip('qutebrowser.browser.webkit.webkittab')
tab_class = webkittab.WebKitTab tab_class = webkittab.WebKitTab

View File

@ -20,10 +20,9 @@
import http.server import http.server
import threading import threading
import logging import logging
import sys
import pytest import pytest
from PyQt5.QtCore import QUrl, QT_VERSION_STR from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import (QNetworkProxy, QNetworkProxyQuery, QHostInfo, from PyQt5.QtNetwork import (QNetworkProxy, QNetworkProxyQuery, QHostInfo,
QHostAddress) QHostAddress)
@ -206,14 +205,6 @@ def test_secret_url(url, has_secret, from_file):
res.resolve(QNetworkProxyQuery(QUrl(url)), from_file=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): def fetcher_test(test_str):
class PACHandler(http.server.BaseHTTPRequestHandler): class PACHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
@ -244,10 +235,6 @@ def fetcher_test(test_str):
return fetcher 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(): def test_fetch_success():
test_str = """ test_str = """
function FindProxyForURL(domain, host) { function FindProxyForURL(domain, host) {
@ -260,10 +247,6 @@ def test_fetch_success():
assert len(proxies) == 3 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): def test_fetch_evalerror(caplog):
test_str = """ test_str = """
function FindProxyForURL(domain, host) { function FindProxyForURL(domain, host) {

View File

@ -19,11 +19,7 @@
"""Test qutebrowser.misc.earlyinit.""" """Test qutebrowser.misc.earlyinit."""
import os
import sys import sys
import types
import logging
import pkg_resources
import pytest import pytest
@ -37,79 +33,6 @@ def test_init_faulthandler_stderr_none(monkeypatch, attr):
earlyinit.init_faulthandler() 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]) @pytest.mark.parametrize('same', [True, False])
def test_qt_version(same): def test_qt_version(same):
if same: if same:

View File

@ -35,7 +35,7 @@ from PyQt5.QtTest import QSignalSpy
import qutebrowser import qutebrowser
from qutebrowser.misc import ipc from qutebrowser.misc import ipc
from qutebrowser.utils import objreg, qtutils, standarddir from qutebrowser.utils import objreg, standarddir
from helpers import stubs from helpers import stubs
@ -778,26 +778,7 @@ def test_connect_inexistent(qlocalsocket):
assert qlocalsocket.error() == QLocalSocket.ServerNotFoundError 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.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): def test_socket_options_address_in_use_problem(qlocalserver, short_tmpdir):
"""Qt seems to ignore AddressInUseError when using socketOptions. """Qt seems to ignore AddressInUseError when using socketOptions.

View File

@ -19,13 +19,9 @@
"""Tests for qutebrowser.utils.javascript.""" """Tests for qutebrowser.utils.javascript."""
import binascii
import os.path
import pytest import pytest
import hypothesis import hypothesis
import hypothesis.strategies import hypothesis.strategies
from PyQt5.QtCore import PYQT_VERSION
from qutebrowser.utils import javascript from qutebrowser.utils import javascript
@ -61,65 +57,22 @@ class TestStringEscape:
"""Test javascript escaping with some expected outcomes.""" """Test javascript escaping with some expected outcomes."""
assert javascript.string_escape(before) == after assert javascript.string_escape(before) == after
def _test_escape(self, text, qtbot, webframe): def _test_escape(self, text, 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):
"""Test conversion by using evaluateJavaScript.""" """Test conversion by using evaluateJavaScript."""
escaped = javascript.string_escape(text) escaped = javascript.string_escape(text)
result = webframe.evaluateJavaScript('"{}";'.format(escaped)) result = webframe.evaluateJavaScript('"{}";'.format(escaped))
assert result == text assert result == text
@pytest.mark.parametrize('text', sorted(TESTS), ids=repr) @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.""" """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') @pytest.mark.qt_log_ignore('^OpenType support missing for script')
@hypothesis.given(hypothesis.strategies.text()) @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.""" """Test javascript escaping with a real QWebPage and hypothesis."""
# We can't simply use self._test_escape because of this: self._test_escape(text, webframe)
# 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)
@pytest.mark.parametrize('arg, expected', [ @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

@ -118,26 +118,6 @@ class TestCheckOverflow:
assert newval == repl 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: class QtObject:
"""Fake Qt object for test_ensure.""" """Fake Qt object for test_ensure."""

View File

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

View File

@ -328,9 +328,8 @@ def test_get_search_url_invalid(url):
(False, True, True, 'deadbeef'), (False, True, True, 'deadbeef'),
(False, True, True, 'hello.'), (False, True, True, 'hello.'),
(False, True, False, 'site:cookies.com oatmeal raisin'), (False, True, False, 'site:cookies.com oatmeal raisin'),
# no DNS because bogus-IP # no DNS because there is no host
(False, True, False, '31c3'), (False, True, False, 'foo::bar'),
(False, True, False, 'foo::bar'), # no DNS because of no host
# Valid search term with autosearch # Valid search term with autosearch
(False, False, False, 'test foo'), (False, False, False, 'test foo'),
# autosearch = False # autosearch = False
@ -350,11 +349,6 @@ def test_is_url(config_stub, fake_dns,
url: The URL to test, as a string. url: The URL to test, as a string.
auto_search: With which auto_search setting to test 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 config_stub.val.url.auto_search = auto_search
if auto_search == 'dns': if auto_search == 'dns':
if uses_dns: if uses_dns:

View File

@ -35,7 +35,7 @@ import pkg_resources
import pytest import pytest
import qutebrowser import qutebrowser
from qutebrowser.utils import version, usertypes, qtutils from qutebrowser.utils import version, usertypes
from qutebrowser.browser import pdfjs from qutebrowser.browser import pdfjs
@ -946,8 +946,6 @@ def test_version_output(params, stubs, monkeypatch):
assert version.version() == expected assert version.version() == expected
@pytest.mark.skipif(not qtutils.version_check('5.4'),
reason="Needs Qt >= 5.4.")
def test_opengl_vendor(): def test_opengl_vendor():
"""Simply call version.opengl_vendor() and see if it doesn't crash.""" """Simply call version.opengl_vendor() and see if it doesn't crash."""
pytest.importorskip("PyQt5.QtOpenGL") pytest.importorskip("PyQt5.QtOpenGL")

View File

@ -19,8 +19,6 @@
"""Tests for usertypes.Question.""" """Tests for usertypes.Question."""
import logging
import pytest import pytest
from qutebrowser.utils import usertypes from qutebrowser.utils import usertypes
@ -82,15 +80,6 @@ def test_abort(question, qtbot):
assert question.is_aborted 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): def test_abort_twice(question, qtbot):
"""Abort a question twice.""" """Abort a question twice."""
with qtbot.wait_signal(question.aborted): with qtbot.wait_signal(question.aborted):