diff options
50 files changed, 415 insertions, 279 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index f2424fc94..92a20c0bd 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,15 +5,15 @@ cache: build: off environment: PYTHONUNBUFFERED: 1 - PYTHON: C:\Python36\python.exe + PYTHON: C:\Python36-x64\python.exe matrix: - - TESTENV: py36-pyqt510 + - TESTENV: py36-pyqt511 - TESTENV: pylint install: - '%PYTHON% -m pip install -U pip' - '%PYTHON% -m pip install -r misc\requirements\requirements-tox.txt' - - 'set PATH=%PATH%;C:\Python36' + - 'set PATH=C:\Python36-x64;%PATH' test_script: - '%PYTHON% -m tox -e %TESTENV%' diff --git a/.travis.yml b/.travis.yml index 143938aef..81a5d6fbb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,18 +20,17 @@ matrix: - os: linux env: TESTENV=py36-pyqt59 - os: linux - env: TESTENV=py36-pyqt510-cov - # We need a newer Xvfb as a WORKAROUND for: - # https://bugreports.qt.io/browse/QTBUG-64928 + env: TESTENV=py36-pyqt510 + - os: linux + env: TESTENV=py36-pyqt511-cov + # https://github.com/travis-ci/travis-ci/issues/9069 + - os: linux + python: 3.7 sudo: required - addons: - apt: - sources: - - sourceline: "deb http://us.archive.ubuntu.com/ubuntu/ xenial main universe" - packages: - - xvfb + dist: xenial + env: TESTENV=py37-pyqt511 - os: osx - env: TESTENV=py36 OSX=sierra + env: TESTENV=py37 OSX=sierra osx_image: xcode9.2 language: generic # https://github.com/qutebrowser/qutebrowser/issues/2013 diff --git a/README.asciidoc b/README.asciidoc index db401d49d..2237955dd 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -97,7 +97,7 @@ Requirements The following software and libraries are required to run qutebrowser: * http://www.python.org/[Python] 3.5 or newer (3.6 recommended) -* http://qt.io/[Qt] 5.7.1 or newer (5.10 recommended) with the following modules: +* http://qt.io/[Qt] 5.7.1 or newer (5.11.1 recommended) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) - QtSQL (part of qtbase in some distributions) @@ -107,7 +107,7 @@ The following software and libraries are required to run qutebrowser: link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is supported * http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer - (5.10 recommended) for Python 3 + (5.11.2 recommended) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * http://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index efd451976..79a75f49f 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -21,6 +21,7 @@ v1.4.0 (unreleased) Added ~~~~~ +- Support for the bundled `sip` module in PyQt 5.11. - New `--debug-flag log-requests` to log requests to the debug log for debugging. - New `--first` flag for `:hint` (bound to `gi` for inputs) which automatically @@ -59,14 +60,14 @@ Changed ~~~~~~~ - The following settings now support URL patterns: - - content.headers.do_not_track - - content.headers.custom - - content.headers.accept_language - - content.headers.user_agent - - content.ssl_strict - - content.geolocation - - content.notifications - - content.media_capture + * `content.headers.do_not_track` + * `content.headers.custom` + * `content.headers.accept_language` + * `content.headers.user_agent` + * `content.ssl_strict` + * `content.geolocation` + * `content.notifications` + * `content.media_capture` - The Windows/macOS releases now bundle Qt 5.11.1 which is based on Chromium 65.0.3325.151 with security fixes up to Chromium 67.0.3396.87. - New short flags for commandline arguments: `-B` and `-T` for `--basedir` and @@ -104,6 +105,10 @@ Changed Existing dictionaries are copied over. - If an error while parsing `~/.netrc` occurs, the cause of the error is now logged. +- On Qt 5.9 or newer, certificate errors now show Chromium's detailed error + page. +- Greasemonkey scripts now support a "@qute-js-world" tag to run them in a + different JavaScript context. Fixed ~~~~~ @@ -112,10 +117,16 @@ Fixed - The security fix in v1.3.3 caused URLs with ampersands (`www.example.com?one=1&two=2`) to send the wrong arguments when clicked on the `qute://history` page. +- Crash when opening a PDF page with PDF.js enabled (on QtWebKit), but no + PDF.js installed. Removed ~~~~~~~ +- No prebuilt binaries for 32-bit Windows are supplied anymore. This is due to + Qt removing QtWebEngine support for those upstream. It might be possible to + distribute 32-bit binaries again with Qt 5.12 in December, but that will only + happen if it turns out enough people actually need 32-bit support. - `:tab-detach` which has been deprecated in v1.1.0 has been removed. - The `content.developer_extras` setting got removed. On QtWebKit, developer extras are now automatically enabled when opening the inspector. diff --git a/doc/contributing.asciidoc b/doc/contributing.asciidoc index 07c2dfe25..26ff12a15 100644 --- a/doc/contributing.asciidoc +++ b/doc/contributing.asciidoc @@ -689,8 +689,6 @@ New PyQt release ~~~~~~~~~~~~~~~~ * See above. -* Install new PyQt in Windows VM (32- and 64-bit). -* Download new installer and update PyQt installer path in `ci_install.py`. * Update `tox.ini`/`.travis.yml`/`.appveyor.yml` to test new versions. qutebrowser release @@ -712,8 +710,8 @@ qutebrowser release as closed. * Linux: Run `git checkout v1.$x.$y && ./.venv/bin/python3 scripts/dev/build_release.py --upload v1.$x.$y`. -* Windows: Run `git checkout v1.X.Y; C:\Python36-32\python scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand). -* macOS: Run `git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand). +* Windows: Run `git checkout v1.X.Y; py -3.6 scripts\dev\build_release.py --asciidoc C:\Python27\python C:\asciidoc-8.6.9\asciidoc.py --upload v1.X.Y` (replace X/Y by hand). +* macOS: Run `pyenv shell 3.6.6 && git checkout v1.X.Y && python3 scripts/dev/build_release.py --upload v1.X.Y` (replace X/Y by hand). * On server: - Run `python3 scripts/dev/download_release.py v1.X.Y` (replace X/Y by hand). - Run `git pull github master && sudo python3 scripts/asciidoc2html.py --website /srv/http/qutebrowser` diff --git a/misc/requirements/requirements-pyqt-old.txt b/misc/requirements/requirements-pyqt-old.txt deleted file mode 100644 index f0fa50ad5..000000000 --- a/misc/requirements/requirements-pyqt-old.txt +++ /dev/null @@ -1,4 +0,0 @@ -# This file is automatically generated by scripts/dev/recompile_requirements.py - -PyQt5==5.10 # rq.filter: != 5.10.1 -sip==4.19.8 diff --git a/misc/requirements/requirements-pyqt-old.txt-raw b/misc/requirements/requirements-pyqt-old.txt-raw deleted file mode 100644 index 16b82e0d5..000000000 --- a/misc/requirements/requirements-pyqt-old.txt-raw +++ /dev/null @@ -1,2 +0,0 @@ -PyQt5==5.10.0 -#@ filter: PyQt5 != 5.10.1
\ No newline at end of file diff --git a/misc/requirements/requirements-pyqt.txt b/misc/requirements/requirements-pyqt.txt index 059ff2df7..063f85122 100644 --- a/misc/requirements/requirements-pyqt.txt +++ b/misc/requirements/requirements-pyqt.txt @@ -1,4 +1,4 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py -PyQt5==5.10.1 -sip==4.19.8 +PyQt5==5.11.2 +PyQt5-sip==4.19.11 diff --git a/misc/requirements/requirements-qutebrowser.txt-raw b/misc/requirements/requirements-qutebrowser.txt-raw index c66c65beb..506af17c0 100644 --- a/misc/requirements/requirements-qutebrowser.txt-raw +++ b/misc/requirements/requirements-qutebrowser.txt-raw @@ -1,7 +1,9 @@ Jinja2 Pygments pyPEG2 -PyYAML +PyYAML!=4.1 colorama cssutils attrs + +#@ filter: PyYAML != 4.1 diff --git a/misc/requirements/requirements-tests-git.txt b/misc/requirements/requirements-tests-git.txt index 6681dd15e..ce00cd31c 100644 --- a/misc/requirements/requirements-tests-git.txt +++ b/misc/requirements/requirements-tests-git.txt @@ -35,8 +35,4 @@ git+https://github.com/pallets/markupsafe.git hg+http://bitbucket.org/birkenfeld/pygments-main hg+https://bitbucket.org/fdik/pypeg git+https://github.com/python-attrs/attrs.git - -# Fails to build: -# gcc: error: ext/_yaml.c: No such file or directory -# hg+https://bitbucket.org/xi/pyyaml -PyYAML==3.12 +git+https://github.com/yaml/pyyaml.git diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 334e740c3..4475def05 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -11,7 +11,7 @@ fields==5.0.0 Flask==1.0.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.59.1 +hypothesis==3.65.0 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 @@ -20,9 +20,9 @@ more-itertools==4.2.0 parse==1.8.4 parse-type==0.4.2 pluggy==0.6.0 -py==1.5.3 +py==1.5.4 py-cpuinfo==4.0.0 -pytest==3.6.1 +pytest==3.6.2 pytest-bdd==2.21.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt index e8b3de2b1..64ab8bf68 100644 --- a/misc/requirements/requirements-tox.txt +++ b/misc/requirements/requirements-tox.txt @@ -1,7 +1,7 @@ # This file is automatically generated by scripts/dev/recompile_requirements.py pluggy==0.6.0 -py==1.5.3 +py==1.5.4 six==1.11.0 tox==3.0.0 virtualenv==16.0.0 diff --git a/pytest.ini b/pytest.ini index c897f0be7..1b5016321 100644 --- a/pytest.ini +++ b/pytest.ini @@ -63,4 +63,8 @@ qt_log_ignore = ^inotify_add_watch\(".*"\) failed: "No space left on device" ^QSettings::value: Empty key passed ^Icon theme ".*" not found + ^Error receiving trust for a CA certificate xfail_strict = true +filterwarnings = + # This happens in many qutebrowser dependencies... + ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 7e78b2621..0170b4627 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -22,7 +22,6 @@ import enum import itertools -import sip import attr from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, QObject, QSizeF, Qt from PyQt5.QtGui import QIcon @@ -38,6 +37,7 @@ from qutebrowser.utils import (utils, objreg, usertypes, log, qtutils, urlutils, message) from qutebrowser.misc import miscwidgets, objects from qutebrowser.browser import mouse, hints +from qutebrowser.qt import sip tab_id_gen = itertools.count(0) @@ -893,10 +893,6 @@ class AbstractTab(QWidget): self._progress = perc self.load_progress.emit(perc) - @pyqtSlot() - def _on_ssl_errors(self): - self._has_ssl_errors = True - def url(self, requested=False): raise NotImplementedError diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py index dd112e00a..2e30c26c2 100644 --- a/qutebrowser/browser/downloads.py +++ b/qutebrowser/browser/downloads.py @@ -29,7 +29,6 @@ import pathlib import tempfile import enum -import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QObject, QModelIndex, QTimer, QAbstractListModel, QUrl) @@ -37,6 +36,7 @@ from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config from qutebrowser.utils import (usertypes, standarddir, utils, message, log, qtutils) +from qutebrowser.qt import sip ModelRole = enum.IntEnum('ModelRole', ['item'], start=Qt.UserRole) diff --git a/qutebrowser/browser/downloadview.py b/qutebrowser/browser/downloadview.py index 80da117d2..e90e37509 100644 --- a/qutebrowser/browser/downloadview.py +++ b/qutebrowser/browser/downloadview.py @@ -21,13 +21,13 @@ import functools -import sip from PyQt5.QtCore import pyqtSlot, QSize, Qt, QTimer from PyQt5.QtWidgets import QListView, QSizePolicy, QMenu, QStyleFactory from qutebrowser.browser import downloads from qutebrowser.config import config from qutebrowser.utils import qtutils, utils, objreg +from qutebrowser.qt import sip def update_geometry(obj): diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py index db8246bab..cff45f3ac 100644 --- a/qutebrowser/browser/greasemonkey.py +++ b/qutebrowser/browser/greasemonkey.py @@ -58,6 +58,7 @@ class GreasemonkeyScript: self.run_at = None self.script_meta = None self.runs_on_sub_frames = True + self.jsworld = "main" for name, value in properties: if name == 'name': self.name = value @@ -77,6 +78,8 @@ class GreasemonkeyScript: self.runs_on_sub_frames = False elif name == 'require': self.requires.append(value) + elif name == 'qute-js-world': + self.jsworld = value HEADER_REGEX = r'// ==UserScript==|\n+// ==/UserScript==\n' PROPS_REGEX = r'// @(?P<prop>[^\s]+)\s*(?P<val>.*)' diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index e3483bac0..b3787e5d0 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -34,7 +34,6 @@ import urllib import collections import pkg_resources -import sip from PyQt5.QtCore import QUrlQuery, QUrl import qutebrowser @@ -42,6 +41,7 @@ from qutebrowser.config import config, configdata, configexc, configdiff from qutebrowser.utils import (version, utils, jinja, log, message, docutils, objreg, urlutils) from qutebrowser.misc import objects +from qutebrowser.qt import sip pyeval_output = ":pyeval was never called" diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 01ccab13b..2398ca2e4 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -48,7 +48,8 @@ def custom_headers(url): for header, value in conf_headers.items(): headers[header.encode('ascii')] = value.encode('ascii') - accept_language = config.instance.get('content.headers.accept_language', url=url) + accept_language = config.instance.get('content.headers.accept_language', + url=url) if accept_language is not None: headers[b'Accept-Language'] = accept_language.encode('ascii') diff --git a/qutebrowser/browser/webengine/certificateerror.py b/qutebrowser/browser/webengine/certificateerror.py index 47953d4cc..768f54ec6 100644 --- a/qutebrowser/browser/webengine/certificateerror.py +++ b/qutebrowser/browser/webengine/certificateerror.py @@ -28,6 +28,10 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper): """A wrapper over a QWebEngineCertificateError.""" + def __init__(self, error): + super().__init__(error) + self.ignore = False + def __str__(self): return self._error.errorDescription() @@ -37,5 +41,8 @@ class CertificateErrorWrapper(usertypes.AbstractCertificateErrorWrapper): self._error.error()), string=str(self)) + def url(self): + return self._error.url() + def is_overridable(self): return self._error.isOverridable() diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 23c26ef95..9f840b715 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -25,7 +25,6 @@ import sys import re import html as html_utils -import sip from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF, QUrl, QTimer, QObject, qVersion) from PyQt5.QtGui import QKeyEvent, QIcon @@ -38,10 +37,11 @@ from qutebrowser.browser import browsertab, mouse, shared from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, interceptor, webenginequtescheme, cookies, webenginedownloads, - webenginesettings) + webenginesettings, certificateerror) from qutebrowser.misc import miscwidgets from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, message, objreg, jinja, debug) +from qutebrowser.qt import sip _qute_scheme_handler = None @@ -788,6 +788,7 @@ class _WebEngineScripts(QObject): super().__init__(parent) self._tab = tab self._widget = None + self._greasemonkey = objreg.get('greasemonkey') def connect_signals(self): config.instance.changed.connect(self._on_config_changed) @@ -853,9 +854,16 @@ class _WebEngineScripts(QObject): self._inject_early_js('js', js_code, subframes=True) self._init_stylesheet() - greasemonkey = objreg.get('greasemonkey') - greasemonkey.scripts_reloaded.connect(self._inject_userscripts) - self._inject_userscripts() + # The Greasemonkey metadata block support in QtWebEngine only starts at + # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in + # response to urlChanged. + if not qtutils.version_check('5.8'): + self._tab.url_changed.connect( + self._inject_greasemonkey_scripts_for_url) + else: + self._greasemonkey.scripts_reloaded.connect( + self._inject_all_greasemonkey_scripts) + self._inject_all_greasemonkey_scripts() def _init_stylesheet(self): """Initialize custom stylesheets. @@ -872,40 +880,77 @@ class _WebEngineScripts(QObject): ) self._inject_early_js('stylesheet', js_code, subframes=True) - def _inject_userscripts(self): - """Register user JavaScript files with the global profiles.""" - # The Greasemonkey metadata block support in QtWebEngine only starts at - # Qt 5.8. With 5.7.1, we need to inject the scripts ourselves in - # response to urlChanged. - if not qtutils.version_check('5.8'): + @pyqtSlot(QUrl) + def _inject_greasemonkey_scripts_for_url(self, url): + matching_scripts = self._greasemonkey.scripts_for(url) + self._inject_greasemonkey_scripts( + matching_scripts.start, QWebEngineScript.DocumentCreation, True) + self._inject_greasemonkey_scripts( + matching_scripts.end, QWebEngineScript.DocumentReady, False) + self._inject_greasemonkey_scripts( + matching_scripts.idle, QWebEngineScript.Deferred, False) + + @pyqtSlot() + def _inject_all_greasemonkey_scripts(self): + scripts = self._greasemonkey.all_scripts() + self._inject_greasemonkey_scripts(scripts) + + def _inject_greasemonkey_scripts(self, scripts=None, injection_point=None, + remove_first=True): + """Register user JavaScript files with the current tab. + + Args: + scripts: A list of GreasemonkeyScripts, or None to add all + known by the Greasemonkey subsystem. + injection_point: The QWebEngineScript::InjectionPoint stage + to inject the script into, None to use + auto-detection. + remove_first: Whether to remove all previously injected + scripts before adding these ones. + """ + if sip.isdeleted(self._widget): return - # Since we are inserting scripts into profile.scripts they won't - # just get replaced by new gm scripts like if we were injecting them - # ourselves so we need to remove all gm scripts, while not removing - # any other stuff that might have been added. Like the one for - # stylesheets. - greasemonkey = objreg.get('greasemonkey') - scripts = self._widget.page().scripts() - for script in scripts.toList(): - if script.name().startswith("GM-"): - log.greasemonkey.debug('Removing script: {}' - .format(script.name())) - removed = scripts.remove(script) - assert removed, script.name() - - # Then add the new scripts. - for script in greasemonkey.all_scripts(): - # @run-at (and @include/@exclude/@match) is parsed by - # QWebEngineScript. + # Since we are inserting scripts into a per-tab collection, + # rather than just injecting scripts on page load, we need to + # make sure we replace existing scripts, not just add new ones. + # While, taking care not to remove any other scripts that might + # have been added elsewhere, like the one for stylesheets. + page_scripts = self._widget.page().scripts() + if remove_first: + for script in page_scripts.toList(): + if script.name().startswith("GM-"): + log.greasemonkey.debug('Removing script: {}' + .format(script.name())) + removed = page_scripts.remove(script) + assert removed, script.name() + + if not scripts: + return + + for script in scripts: new_script = QWebEngineScript() - new_script.setWorldId(QWebEngineScript.MainWorld) + try: + world = int(script.jsworld) + except ValueError: + try: + world = _JS_WORLD_MAP[usertypes.JsWorld[ + script.jsworld.lower()]] + except KeyError: + log.greasemonkey.error( + "script {} has invalid value for '@qute-js-world'" + ": {}".format(script.name, script.jsworld)) + continue + new_script.setWorldId(world) new_script.setSourceCode(script.code()) new_script.setName("GM-{}".format(script.name)) new_script.setRunsOnSubFrames(script.runs_on_sub_frames) + # Override the @run-at value parsed by QWebEngineScript if desired. + if injection_point: + new_script.setInjectionPoint(injection_point) log.greasemonkey.debug('adding script: {}' .format(new_script.name())) - scripts.insert(new_script) + page_scripts.insert(new_script) class WebEngineTab(browsertab.AbstractTab): @@ -1227,6 +1272,34 @@ class WebEngineTab(browsertab.AbstractTab): # the old icon is still displayed. self.icon_changed.emit(QIcon()) + @pyqtSlot(certificateerror.CertificateErrorWrapper) + def _on_ssl_errors(self, error): + self._has_ssl_errors = True + + url = error.url() + log.webview.debug("Certificate error: {}".format(error)) + + if error.is_overridable(): + error.ignore = shared.ignore_certificate_errors( + url, [error], abort_on=[self.shutting_down, self.load_started]) + else: + log.webview.error("Non-overridable certificate error: " + "{}".format(error)) + + log.webview.debug("ignore {}, URL {}, requested {}".format( + error.ignore, url, self.url(requested=True))) + + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-56207 + # We can't really know when to show an error page, as the error might + # have happened when loading some resource. + # However, self.url() is not available yet and the requested URL + # might not match the URL we get from the error - so we just apply a + # heuristic here. + if (not qtutils.version_check('5.9') and + not error.ignore and + url.matches(self.url(requested=True), QUrl.RemoveScheme)): + self._show_error_page(url, str(error)) + @pyqtSlot(QUrl) def _on_predicted_navigation(self, url): """If we know we're going to visit an URL soon, change the settings. diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index 7436bc01a..b10cc5f9a 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -19,18 +19,17 @@ """The main browser widget for QtWebEngine.""" -import sip -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION +from PyQt5.QtCore import pyqtSignal, QUrl, PYQT_VERSION from PyQt5.QtGui import QPalette from PyQt5.QtWidgets import QWidget -from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage, - QWebEngineScript) +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from qutebrowser.browser import shared -from qutebrowser.browser.webengine import certificateerror, webenginesettings +from qutebrowser.browser.webengine import webenginesettings, certificateerror from qutebrowser.config import config -from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils +from qutebrowser.utils import log, debug, usertypes, objreg, qtutils from qutebrowser.misc import miscwidgets +from qutebrowser.qt import sip class WebEngineView(QWebEngineView): @@ -152,11 +151,13 @@ class WebEnginePage(QWebEnginePage): Signals: certificate_error: Emitted on certificate errors. + Needs to be directly connected to a slot setting the + 'ignore' attribute. shutting_down: Emitted when the page is shutting down. navigation_request: Emitted on acceptNavigationRequest. """ - certificate_error = pyqtSignal() + certificate_error = pyqtSignal(certificateerror.CertificateErrorWrapper) shutting_down = pyqtSignal() navigation_request = pyqtSignal(usertypes.NavigationRequest) @@ -166,7 +167,6 @@ class WebEnginePage(QWebEnginePage): self._theme_color = theme_color self._set_bg_color() config.instance.changed.connect(self._set_bg_color) - self.urlChanged.connect(self._inject_userjs) @config.change_filter('colors.webpage.bg') def _set_bg_color(self): @@ -181,36 +181,9 @@ class WebEnginePage(QWebEnginePage): def certificateError(self, error): """Handle certificate errors coming from Qt.""" - self.certificate_error.emit() - url = error.url() error = certificateerror.CertificateErrorWrapper(error) - log.webview.debug("Certificate error: {}".format(error)) - - url_string = url.toDisplayString() - error_page = jinja.render( - 'error.html', title="Error loading page: {}".format(url_string), - url=url_string, error=str(error)) - - if error.is_overridable(): - ignore = shared.ignore_certificate_errors( - url, [error], abort_on=[self.loadStarted, self.shutting_down]) - else: - log.webview.error("Non-overridable certificate error: " - "{}".format(error)) - ignore = False - - # We can't really know when to show an error page, as the error might - # have happened when loading some resource. - # However, self.url() is not available yet and self.requestedUrl() - # might not match the URL we get from the error - so we just apply a - # heuristic here. - # See https://bugreports.qt.io/browse/QTBUG-56207 - log.webview.debug("ignore {}, URL {}, requested {}".format( - ignore, url, self.requestedUrl())) - if not ignore and url.matches(self.requestedUrl(), QUrl.RemoveScheme): - self.setHtml(error_page) - - return ignore + self.certificate_error.emit(error) + return error.ignore def javaScriptConfirm(self, url, js_msg): """Override javaScriptConfirm to use qutebrowser prompts.""" @@ -288,43 +261,3 @@ class WebEnginePage(QWebEnginePage): is_main_frame=is_main_frame) self.navigation_request.emit(navigation) return navigation.accepted - - @pyqtSlot('QUrl') - def _inject_userjs(self, url): - """Inject userscripts registered for `url` into the current page.""" - if qtutils.version_check('5.8'): - # Handled in webenginetab with the builtin Greasemonkey - # support. - return - - # Using QWebEnginePage.scripts() to hold the user scripts means - # we don't have to worry ourselves about where to inject the - # page but also means scripts hang around for the tab lifecycle. - # So clear them here. - scripts = self.scripts() - for script in scripts.toList(): - if script.name().startswith("GM-"): - log.greasemonkey.debug("Removing script: {}" - .format(script.name())) - removed = scripts.remove(script) - assert removed, script.name() - - def _add_script(script, injection_point): - new_script = QWebEngineScript() - new_script.setInjectionPoint(injection_point) - new_script.setWorldId(QWebEngineScript.MainWorld) - new_script.setSourceCode(script.code()) - new_script.setName("GM-{}".format(script.name)) - new_script.setRunsOnSubFrames(script.runs_on_sub_frames) - log.greasemonkey.debug("Adding script: {}" - .format(new_script.name())) - scripts.insert(new_script) - - greasemonkey = objreg.get('greasemonkey') - matching_scripts = greasemonkey.scripts_for(url) - for script in matching_scripts.start: - _add_script(script, QWebEngineScript.DocumentCreation) - for script in matching_scripts.end: - _add_script(script, QWebEngineScript.DocumentReady) - for script in matching_scripts.idle: - _add_script(script, QWebEngineScript.Deferred) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index cf158ed2d..31cb82f29 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -23,7 +23,6 @@ import re import functools import xml.etree.ElementTree -import sip from PyQt5.QtCore import (pyqtSlot, Qt, QEvent, QUrl, QPoint, QTimer, QSizeF, QSize) from PyQt5.QtGui import QKeyEvent, QIcon @@ -35,6 +34,7 @@ from qutebrowser.browser import browsertab, shared from qutebrowser.browser.webkit import (webview, tabhistory, webkitelem, webkitsettings) from qutebrowser.utils import qtutils, usertypes, utils, log, debug +from qutebrowser.qt import sip class WebKitAction(browsertab.AbstractAction): @@ -808,6 +808,10 @@ class WebKitTab(browsertab.AbstractTab): if navigation.is_main_frame: self.settings.update_for_url(navigation.url) + @pyqtSlot() + def _on_ssl_errors(self): + self._has_ssl_errors = True + def _connect_signals(self): view = self._widget page = view.page() diff --git a/qutebrowser/browser/webkit/webpage.py b/qutebrowser/browser/webkit/webpage.py index 853ff1b81..4e0701329 100644 --- a/qutebrowser/browser/webkit/webpage.py +++ b/qutebrowser/browser/webkit/webpage.py @@ -22,7 +22,6 @@ import html import functools -import sip from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QPoint from PyQt5.QtGui import QDesktopServices from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest @@ -35,6 +34,7 @@ from qutebrowser.browser import pdfjs, shared from qutebrowser.browser.webkit import http from qutebrowser.browser.webkit.network import networkmanager from qutebrowser.utils import message, usertypes, log, jinja, objreg +from qutebrowser.qt import sip class BrowserPage(QWebPage): @@ -212,7 +212,8 @@ class BrowserPage(QWebPage): page = pdfjs.generate_pdfjs_page(reply.url()) except pdfjs.PDFJSNotFound: page = jinja.render('no_pdfjs.html', - url=reply.url().toDisplayString()) + url=reply.url().toDisplayString(), + title="PDF.js not found") self.mainFrame().setContent(page.encode('utf-8'), 'text/html', reply.url()) reply.deleteLater() diff --git a/qutebrowser/javascript/.eslintrc.yaml b/qutebrowser/javascript/.eslintrc.yaml index 02081f7a7..6a46f96e9 100644 --- a/qutebrowser/javascript/.eslintrc.yaml +++ b/qutebrowser/javascript/.eslintrc.yaml @@ -57,3 +57,4 @@ rules: no-ternary: "off" max-lines: "off" multiline-ternary: ["error", "always-multiline"] + max-lines-per-function: "off" diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index f0cf9c22a..e22e24f77 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -138,6 +138,37 @@ def _key_to_string(key): 'Dead_Hook': 'Hook', 'Dead_Horn': 'Horn', + 'Dead_Stroke': '̵', + 'Dead_Abovecomma': '̓', + 'Dead_Abovereversedcomma': '̔', + 'Dead_Doublegrave': '̏', + 'Dead_Belowring': '̥', + 'Dead_Belowmacron': '̱', + 'Dead_Belowcircumflex': '̭', + 'Dead_Belowtilde': '̰', + 'Dead_Belowbreve': '̮', + 'Dead_Belowdiaeresis': '̤', + 'Dead_Invertedbreve': '̑', + 'Dead_Belowcomma': '̦', + 'Dead_Currency': '¤', + 'Dead_a': 'a', + 'Dead_A': 'A', + 'Dead_e': 'e', + 'Dead_E': 'E', + 'Dead_i': 'i', + 'Dead_I': 'I', + 'Dead_o': 'o', + 'Dead_O': 'O', + 'Dead_u': 'u', + 'Dead_U': 'U', + 'Dead_Small_Schwa': 'ə', + 'Dead_Capital_Schwa': 'Ə', + 'Dead_Greek': 'Greek', + 'Dead_Lowline': '̲', + 'Dead_Aboveverticalline': '̍', + 'Dead_Belowverticalline': '\u0329', + 'Dead_Longsolidusoverlay': '̸', + 'Memo': 'Memo', 'ToDoList': 'To Do List', 'Calendar': 'Calendar', diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index f7af28440..357f63dd7 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -24,7 +24,6 @@ import html import collections import attr -import sip from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, QItemSelectionModel, QObject, QEventLoop) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, @@ -36,6 +35,7 @@ from qutebrowser.config import config from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message from qutebrowser.keyinput import modeman from qutebrowser.commands import cmdutils, cmdexc +from qutebrowser.qt import sip prompt_queue = None diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index 9649d27cc..92e3a9731 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -246,7 +246,7 @@ def configure_pyqt(): from PyQt5.QtCore import pyqtRemoveInputHook pyqtRemoveInputHook() - import sip + from qutebrowser.qt import sip try: # Added in sip 4.19.4 sip.enableoverflowchecking(True) diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index dddf48b05..358ab0c53 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -24,7 +24,6 @@ import os.path import itertools import urllib -import sip from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer from PyQt5.QtWidgets import QApplication import yaml @@ -35,6 +34,7 @@ from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config, configfiles from qutebrowser.completion.models import miscmodels from qutebrowser.mainwindow import mainwindow +from qutebrowser.qt import sip default = object() # Sentinel value diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 5d987afa3..66483e09a 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -29,7 +29,6 @@ try: except ImportError: hunter = None -import sip from PyQt5.QtCore import QUrl # so it's available for :debug-pyeval from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import @@ -40,6 +39,7 @@ from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.config import config, configdata from qutebrowser.misc import consolewidget from qutebrowser.utils.version import pastebin_version +from qutebrowser.qt import sip @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) diff --git a/qutebrowser/qt.py b/qutebrowser/qt.py new file mode 100644 index 000000000..2878bbe98 --- /dev/null +++ b/qutebrowser/qt.py @@ -0,0 +1,28 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org> +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. + +"""Wrappers around Qt/PyQt code.""" + +# pylint: disable=unused-import +# PyQt 5.11 comes with a bundled sip, +# for older PyQt versions it's a separate module. +try: + from PyQt5 import sip +except ImportError: + import sip diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 30e570e16..48711614d 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -209,6 +209,11 @@ def _init_py_warnings(): """Initialize Python warning handling.""" warnings.simplefilter('default') warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning) + # This happens in many qutebrowser dependencies... + warnings.filterwarnings('ignore', category=DeprecationWarning, + message="Using or importing the ABCs from " + "'collections' instead of from 'collections.abc' " + "is deprecated, and in 3.8 it will stop working") @contextlib.contextmanager diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py index dc5168a8d..570d1f887 100644 --- a/qutebrowser/utils/version.py +++ b/qutebrowser/utils/version.py @@ -318,7 +318,7 @@ def _chromium_version(): Qt 5.9: Chromium 56 Qt 5.10: Chromium 61 Qt 5.11: Chromium 65 - Qt 5.12: Chromium 69 (?) + Qt 5.12: Chromium 69 (? - current dev branch: 67) Also see https://www.chromium.org/developers/calendar """ diff --git a/requirements.txt b/requirements.txt index 2695ba55f..4516340d7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,4 @@ Jinja2==2.10 MarkupSafe==1.0 Pygments==2.2.0 pyPEG2==2.15.2 -PyYAML==3.12 +PyYAML==3.13b1 # rq.filter: != 4.1 diff --git a/scripts/dev/build_release.py b/scripts/dev/build_release.py index 146b7a462..254132b3c 100755 --- a/scripts/dev/build_release.py +++ b/scripts/dev/build_release.py @@ -45,7 +45,6 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, import qutebrowser from scripts import utils # from scripts.dev import update_3rdparty -from scripts.dev import gen_versioninfo def call_script(name, *args, python=sys.executable): @@ -239,31 +238,16 @@ def build_windows(): except FileNotFoundError: python_x64 = r'C:\Python{}\python.exe'.format(ver) - try: - reg32_key = winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, - r'SOFTWARE\WOW6432Node\Python\PythonCore' - r'\{}-32\InstallPath'.format(dot_ver)) - python_x86 = winreg.QueryValueEx(reg32_key, 'ExecutablePath')[0] - except FileNotFoundError: - python_x86 = r'C:\Python{}-32\python.exe'.format(ver) - out_pyinstaller = os.path.join('dist', 'qutebrowser') - out_32 = os.path.join('dist', - 'qutebrowser-{}-x86'.format(qutebrowser.__version__)) out_64 = os.path.join('dist', 'qutebrowser-{}-x64'.format(qutebrowser.__version__)) artifacts = [] + from scripts.dev import gen_versioninfo utils.print_title("Updating VersionInfo file") gen_versioninfo.main() - utils.print_title("Running pyinstaller 32bit") - _maybe_remove(out_32) - call_tox('pyinstaller', '-r', python=python_x86) - shutil.move(out_pyinstaller, out_32) - patch_windows(out_32) - utils.print_title("Running pyinstaller 64bit") _maybe_remove(out_64) call_tox('pyinstaller', '-r', python=python_x64) @@ -272,38 +256,21 @@ def build_windows(): utils.print_title("Building installers") subprocess.run(['makensis.exe', - '/DVERSION={}'.format(qutebrowser.__version__), - 'misc/qutebrowser.nsi'], check=True) - subprocess.run(['makensis.exe', '/DX64', '/DVERSION={}'.format(qutebrowser.__version__), 'misc/qutebrowser.nsi'], check=True) - name_32 = 'qutebrowser-{}-win32.exe'.format(qutebrowser.__version__) name_64 = 'qutebrowser-{}-amd64.exe'.format(qutebrowser.__version__) artifacts += [ - (os.path.join('dist', name_32), - 'application/vnd.microsoft.portable-executable', - 'Windows 32bit installer'), (os.path.join('dist', name_64), 'application/vnd.microsoft.portable-executable', 'Windows 64bit installer'), ] - utils.print_title("Running 32bit smoke test") - smoke_test(os.path.join(out_32, 'qutebrowser.exe')) utils.print_title("Running 64bit smoke test") smoke_test(os.path.join(out_64, 'qutebrowser.exe')) - utils.print_title("Zipping 32bit standalone...") - name = 'qutebrowser-{}-windows-standalone-win32'.format( - qutebrowser.__version__) - shutil.make_archive(name, 'zip', 'dist', os.path.basename(out_32)) - artifacts.append(('{}.zip'.format(name), - 'application/zip', - 'Windows 32bit standalone')) - utils.print_title("Zipping 64bit standalone...") name = 'qutebrowser-{}-windows-standalone-amd64'.format( qutebrowser.__version__) diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh index 20aa1c12d..18f5aa9ec 100644 --- a/scripts/dev/ci/travis_install.sh +++ b/scripts/dev/ci/travis_install.sh @@ -43,11 +43,6 @@ travis_retry() { return $result } -brew_install() { - brew update - brew install "$@" -} - pip_install() { travis_retry python3 -m pip install "$@" } @@ -62,7 +57,10 @@ check_pyqt() { python3 <<EOF import sys from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion -from sip import SIP_VERSION_STR +try: + from PyQt.sip import SIP_VERSION_STR +except ModuleNotFoundError: + from sip import SIP_VERSION_STR print("Python {}".format(sys.version)) print("PyQt5 {}".format(PYQT_VERSION_STR)) @@ -84,8 +82,8 @@ elif [[ $TRAVIS_OS_NAME == osx ]]; then brew --version brew update - brew upgrade python - brew install qt5 pyqt5 libyaml + brew upgrade python libyaml + brew install qt5 pyqt5 pip_install -r misc/requirements/requirements-tox.txt python3 -m pip --version diff --git a/tests/conftest.py b/tests/conftest.py index 7df1a1e08..595c2940e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,7 +25,6 @@ import os import sys import warnings -import sip import pytest import hypothesis from PyQt5.QtCore import qVersion, PYQT_VERSION @@ -38,6 +37,7 @@ from helpers.messagemock import message_mock from helpers.fixtures import * # noqa: F403 from qutebrowser.utils import qtutils, standarddir, usertypes, utils, version from qutebrowser.misc import objects +from qutebrowser.qt import sip import qutebrowser.app # To register commands diff --git a/tests/end2end/conftest.py b/tests/end2end/conftest.py index 136d6eb67..3240cc7b8 100644 --- a/tests/end2end/conftest.py +++ b/tests/end2end/conftest.py @@ -85,8 +85,8 @@ def _get_version_tag(tag): do_skip = { '==': not qtutils.version_check(version, exact=True, compiled=False), - '>=': not qtutils.version_check(version), - '<': qtutils.version_check(version), + '>=': not qtutils.version_check(version, compiled=False), + '<': qtutils.version_check(version, compiled=False), '!=': qtutils.version_check(version, exact=True, compiled=False), } return pytest.mark.skipif(do_skip[op], reason='Needs ' + tag) diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature index e33b16f68..3a0cb1da0 100644 --- a/tests/end2end/features/hints.feature +++ b/tests/end2end/features/hints.feature @@ -165,13 +165,13 @@ Feature: Using hints And I hint with args "all run message-info {hint-url}" and follow a Then the message "http://localhost:(port)/data/hello.txt" should be shown - @qt!=5.11.0 + @qt<5.11 Scenario: Clicking an invalid link When I open data/invalid_link.html And I hint with args "all" and follow a Then the error "Invalid link clicked - *" should be shown - @qt!=5.11.0 + @qt<5.11 Scenario: Clicking an invalid link opening in a new tab When I open data/invalid_link.html And I hint with args "all tab" and follow a diff --git a/tests/end2end/features/history.feature b/tests/end2end/features/history.feature index 0432c0705..c3791945e 100644 --- a/tests/end2end/features/history.feature +++ b/tests/end2end/features/history.feature @@ -118,9 +118,11 @@ Feature: Page history And I open qute://history Then the javascript message "XSS" should not be logged + @flaky Scenario: Escaping of URLs in :history When I open query?one=1&two=2 And I open qute://history + And I wait 1s # JS loads the history async And I hint with args "links normal" and follow a And I wait until query?one=1&two=2 is loaded Then the query parameter two should be set to 2 diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature index f681012f1..5456b6739 100644 --- a/tests/end2end/features/keyinput.feature +++ b/tests/end2end/features/keyinput.feature @@ -18,6 +18,7 @@ Feature: Keyboard input # input.forward_unbound_keys + @qt<5.11.1 Scenario: Forwarding all keys When I open data/keyinput/log.html And I set input.forward_unbound_keys to all @@ -30,6 +31,7 @@ Feature: Keyboard input And the javascript message "key press: 112" should be logged And the javascript message "key release: 112" should be logged + @qt<5.11.1 Scenario: Forwarding special keys When I open data/keyinput/log.html And I set input.forward_unbound_keys to auto diff --git a/tests/end2end/features/test_prompts_bdd.py b/tests/end2end/features/test_prompts_bdd.py index 12d9cbeec..0d74700b4 100644 --- a/tests/end2end/features/test_prompts_bdd.py +++ b/tests/end2end/features/test_prompts_bdd.py @@ -17,9 +17,13 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. +import time + import pytest_bdd as bdd bdd.scenarios('prompts.feature') +from qutebrowser.utils import qtutils + @bdd.when("I load an SSL page") def load_ssl_page(quteproc, ssl_server): @@ -46,14 +50,21 @@ def no_prompt_shown(quteproc): @bdd.then("a SSL error page should be shown") def ssl_error_page(request, quteproc): - if not request.config.webengine: - line = quteproc.wait_for(message='Error while loading *: SSL ' - 'handshake failed') - line.expected = True - quteproc.wait_for(message="Changing title for idx * to 'Error " - "loading page: *'") - content = quteproc.get_content().strip() - assert "Unable to load page" in content + if request.config.webengine and qtutils.version_check('5.9'): + quteproc.wait_for(message="Certificate error: *") + time.sleep(0.5) # Wait for error page to appear + content = quteproc.get_content().strip() + assert ("ERR_INSECURE_RESPONSE" in content or # Qt <= 5.10 + "ERR_CERT_AUTHORITY_INVALID" in content) # Qt 5.11 + else: + if not request.config.webengine: + line = quteproc.wait_for(message='Error while loading *: SSL ' + 'handshake failed') + line.expected = True + quteproc.wait_for(message="Changing title for idx * to 'Error " + "loading page: *'") + content = quteproc.get_content().strip() + assert "Unable to load page" in content class AbstractCertificateErrorWrapper: diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 4101e6142..01cca1ee1 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -204,6 +204,10 @@ def is_ignored_chromium_message(line): # [30412:30412:0323/074933.387250:ERROR:node_channel.cc(899)] Dropping # message on closed channel. 'Dropping message on closed channel.', + # [2204:1408:0703/113804.788:ERROR: + # gpu_process_transport_factory.cc(1019)] Lost UI shared context. + 'Lost UI shared context.', + ] return any(testutils.pattern_match(pattern=pattern, value=message) for pattern in ignored_messages) diff --git a/tests/unit/browser/webengine/test_spell.py b/tests/unit/browser/webengine/test_spell.py index 1cc2c5d0f..14b343df5 100644 --- a/tests/unit/browser/webengine/test_spell.py +++ b/tests/unit/browser/webengine/test_spell.py @@ -92,3 +92,60 @@ def test_local_filename_installed_malformed(tmpdir, monkeypatch, caplog): (tmpdir / lang_file).ensure() with caplog.at_level(logging.WARNING): assert spell.local_filename('en-US') == 'en-US-11-0' + + +class TestInit: + + ENV = 'QTWEBENGINE_DICTIONARIES_PATH' + + @pytest.fixture(autouse=True) + def remove_envvar(self, monkeypatch): + monkeypatch.delenv(self.ENV, raising=False) + + @pytest.fixture + def patch_new_qt(self, monkeypatch): + monkeypatch.setattr(spell.qtutils, 'version_check', + lambda _ver, compiled: True) + + @pytest.fixture + def dict_dir(self, data_tmpdir): + return data_tmpdir / 'qtwebengine_dictionaries' + + @pytest.fixture + def old_dict_dir(self, monkeypatch, tmpdir): + data_dir = tmpdir / 'old' + dict_dir = data_dir / 'qtwebengine_dictionaries' + (dict_dir / 'somedict').ensure() + monkeypatch.setattr(spell.QLibraryInfo, 'location', + lambda _arg: str(data_dir)) + return dict_dir + + def test_old_qt(self, monkeypatch): + monkeypatch.setattr(spell.qtutils, 'version_check', + lambda _ver, compiled: False) + spell.init() + assert self.ENV not in os.environ + + def test_new_qt(self, dict_dir, patch_new_qt): + spell.init() + assert os.environ[self.ENV] == str(dict_dir) + + def test_moving(self, old_dict_dir, dict_dir, patch_new_qt): + spell.init() + assert (dict_dir / 'somedict').exists() + + def test_moving_oserror(self, mocker, caplog, + old_dict_dir, dict_dir, patch_new_qt): + mocker.patch('shutil.copytree', side_effect=OSError) + + with caplog.at_level(logging.ERROR): + spell.init() + + record = caplog.records[0] + assert record.message == 'Failed to copy old dictionaries' + + def test_moving_existing_destdir(self, old_dict_dir, dict_dir, + patch_new_qt): + dict_dir.ensure(dir=True) + spell.init() + assert not (dict_dir / 'somedict').exists() diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py index e1ef7ef94..bf4f7c02d 100644 --- a/tests/unit/config/test_config.py +++ b/tests/unit/config/test_config.py @@ -642,8 +642,8 @@ class TestConfig: meth = getattr(conf, method) with pytest.raises(configexc.BackendError): with qtbot.assert_not_emitted(conf.changed): - meth('content.cookies.accept', 'all') - assert not conf._values['content.cookies.accept'] + meth('hints.find_implementation', 'javascript') + assert not conf._values['hints.find_implementation'] @pytest.mark.parametrize('method, value', [ ('set_obj', {}), diff --git a/tests/unit/keyinput/key_data.py b/tests/unit/keyinput/key_data.py index bf1ccdede..48b5c8c56 100644 --- a/tests/unit/keyinput/key_data.py +++ b/tests/unit/keyinput/key_data.py @@ -374,37 +374,36 @@ KEYS = [ Key('Dead_Hook', 'Hook', qtest=False), Key('Dead_Horn', 'Horn', qtest=False), - # Not in Qt 5.10, so data may be wrong! - Key('Dead_Stroke', qtest=False), - Key('Dead_Abovecomma', qtest=False), - Key('Dead_Abovereversedcomma', qtest=False), - Key('Dead_Doublegrave', qtest=False), - Key('Dead_Belowring', qtest=False), - Key('Dead_Belowmacron', qtest=False), - Key('Dead_Belowcircumflex', qtest=False), - Key('Dead_Belowtilde', qtest=False), - Key('Dead_Belowbreve', qtest=False), - Key('Dead_Belowdiaeresis', qtest=False), - Key('Dead_Invertedbreve', qtest=False), - Key('Dead_Belowcomma', qtest=False), - Key('Dead_Currency', qtest=False), - Key('Dead_a', qtest=False), - Key('Dead_A', qtest=False), - Key('Dead_e', qtest=False), - Key('Dead_E', qtest=False), - Key('Dead_i', qtest=False), - Key('Dead_I', qtest=False), - Key('Dead_o', qtest=False), - Key('Dead_O', qtest=False), - Key('Dead_u', qtest=False), - Key('Dead_U', qtest=False), - Key('Dead_Small_Schwa', qtest=False), - Key('Dead_Capital_Schwa', qtest=False), - Key('Dead_Greek', qtest=False), - Key('Dead_Lowline', qtest=False), - Key('Dead_Aboveverticalline', qtest=False), - Key('Dead_Belowverticalline', qtest=False), - Key('Dead_Longsolidusoverlay', qtest=False), + Key('Dead_Stroke', '̵', qtest=False), + Key('Dead_Abovecomma', '̓', qtest=False), + Key('Dead_Abovereversedcomma', '̔', qtest=False), + Key('Dead_Doublegrave', '̏', qtest=False), + Key('Dead_Belowring', '̥', qtest=False), + Key('Dead_Belowmacron', '̱', qtest=False), + Key('Dead_Belowcircumflex', '̭', qtest=False), + Key('Dead_Belowtilde', '̰', qtest=False), + Key('Dead_Belowbreve', '̮', qtest=False), + Key('Dead_Belowdiaeresis', '̤', qtest=False), + Key('Dead_Invertedbreve', '̑', qtest=False), + Key('Dead_Belowcomma', '̦', qtest=False), + Key('Dead_Currency', '¤', qtest=False), + Key('Dead_a', 'a', qtest=False), + Key('Dead_A', 'A', qtest=False), + Key('Dead_e', 'e', qtest=False), + Key('Dead_E', 'E', qtest=False), + Key('Dead_i', 'i', qtest=False), + Key('Dead_I', 'I', qtest=False), + Key('Dead_o', 'o', qtest=False), + Key('Dead_O', 'O', qtest=False), + Key('Dead_u', 'u', qtest=False), + Key('Dead_U', 'U', qtest=False), + Key('Dead_Small_Schwa', 'ə', qtest=False), + Key('Dead_Capital_Schwa', 'Ə', qtest=False), + Key('Dead_Greek', 'Greek', qtest=False), + Key('Dead_Lowline', '̲', qtest=False), + Key('Dead_Aboveverticalline', '̍', qtest=False), + Key('Dead_Belowverticalline', '\u0329', qtest=False), + Key('Dead_Longsolidusoverlay', '̸', qtest=False), ### multimedia/internet keys - ignored by default - see QKeyEvent c'tor Key('Back'), diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py index 6de085b25..3a848474f 100644 --- a/tests/unit/utils/test_qtutils.py +++ b/tests/unit/utils/test_qtutils.py @@ -25,13 +25,6 @@ import os import os.path import unittest import unittest.mock -try: - # pylint: disable=no-name-in-module,useless-suppression - from test import test_file - # pylint: enable=no-name-in-module,useless-suppression -except ImportError: - # Debian patches Python to remove the tests... - test_file = None import pytest from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice, @@ -40,6 +33,20 @@ from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice, from qutebrowser.utils import qtutils, utils import overflow_test_cases +if utils.is_linux: + # Those are not run on macOS because that seems to cause a hang sometimes. + # On Windows, we don't run them either because of + # https://github.com/pytest-dev/pytest/issues/3650 + try: + # pylint: disable=no-name-in-module,useless-suppression + from test import test_file + # pylint: enable=no-name-in-module,useless-suppression + except ImportError: + # Debian patches Python to remove the tests... + test_file = None +else: + test_file = None + # pylint: disable=bad-continuation @pytest.mark.parametrize(['qversion', 'compiled', 'pyqt', 'version', 'exact', @@ -476,13 +483,11 @@ class TestSavefileOpen: assert data == b'foo\nbar\nbaz' -if test_file is not None and not utils.is_mac: +if test_file is not None: # If we were able to import Python's test_file module, we run some code # here which defines unittest TestCases to run the python tests over # PyQIODevice. - # Those are not run on macOS because that seems to cause a hang sometimes. - @pytest.fixture(scope='session', autouse=True) def clean_up_python_testfile(): """Clean up the python testfile after tests if tests didn't.""" diff --git a/tests/unit/utils/test_standarddir.py b/tests/unit/utils/test_standarddir.py index a483ede64..b9777f670 100644 --- a/tests/unit/utils/test_standarddir.py +++ b/tests/unit/utils/test_standarddir.py @@ -183,10 +183,13 @@ class TestStandardDir: @pytest.mark.qt_log_ignore(r'^QStandardPaths: ') def test_linux_invalid_runtimedir(self, monkeypatch, tmpdir): """With invalid XDG_RUNTIME_DIR, fall back to TempLocation.""" + tmpdir_env = tmpdir / 'temp' + tmpdir_env.ensure(dir=True) monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir / 'does-not-exist')) - monkeypatch.setenv('TMPDIR', str(tmpdir / 'temp')) + monkeypatch.setenv('TMPDIR', tmpdir_env) + standarddir._init_dirs() - assert standarddir.runtime() == str(tmpdir / 'temp' / APPNAME) + assert standarddir.runtime() == str(tmpdir_env / APPNAME) @pytest.mark.fake_os('windows') def test_runtimedir_empty_tempdir(self, monkeypatch, tmpdir): diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py index 683fba02e..e2fbf8f1c 100644 --- a/tests/unit/utils/test_version.py +++ b/tests/unit/utils/test_version.py @@ -659,7 +659,7 @@ class TestModuleVersions: The aim of this test is to fail if that gets missing in some future version of sip. """ - import sip + from qutebrowser.qt import sip assert isinstance(sip.SIP_VERSION_STR, str) @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36-pyqt59-cov,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint +envlist = py36-pyqt511-cov,misc,vulture,flake8,pylint,pyroma,check-manifest,eslint distshare = {toxworkdir} skipsdist = true @@ -13,14 +13,14 @@ skipsdist = true setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms PYTEST_QT_API=pyqt5 - pyqt{,56,571,59,510}: LINK_PYQT_SKIP=true - pyqt{,56,571,59,510}: QUTE_BDD_WEBENGINE=true + pyqt{,56,571,59,510,511}: LINK_PYQT_SKIP=true + pyqt{,56,571,59,510,511}: QUTE_BDD_WEBENGINE=true cov: PYTEST_ADDOPTS=--cov --cov-report xml --cov-report=html --cov-report= passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI TRAVIS XDG_* QUTE_* DOCKER basepython = - py35: python3.5 - py36: python3.6 - py37: python3.7 + py35: {env:PYTHON:python3.5} + py36: {env:PYTHON:python3.6} + py37: {env:PYTHON:python3.7} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/misc/requirements/requirements-tests.txt @@ -28,6 +28,7 @@ deps = pyqt571: PyQt5==5.7.1 pyqt59: PyQt5==5.9.2 pyqt510: PyQt5==5.10.1 + pyqt511: PyQt5==5.11.2 commands = {envpython} scripts/link_pyqt.py --tox {envdir} {envpython} -bb -m pytest {posargs:tests} @@ -59,7 +60,7 @@ commands = {envpython} -c "" usedevelop = true deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/misc/requirements/requirements-pyqt-old.txt + -r{toxinidir}/misc/requirements/requirements-pyqt.txt [testenv:misc] ignore_errors = true |