Drop PyQt < 5.7.1 support for QtWebEngine

This commit is contained in:
Florian Bruhin 2017-02-17 14:11:45 +01:00
parent 7a4a34c374
commit a86170f45d
18 changed files with 61 additions and 186 deletions

View File

@ -21,10 +21,6 @@ matrix:
- os: linux
env: DOCKER=ubuntu-xenial
services: docker
- os: linux
language: python
python: 3.5
env: TESTENV=py35-pyqt56
- os: linux
language: python
python: 3.6

View File

@ -36,6 +36,7 @@ Added
Changed
~~~~~~~
- PyQt/Qt 5.7.1 is now required for the QtWebEngine backend
- Scrolling with the scrollwheel while holding shift now scrolls sideways
- New way of clicking hints with which solves various small issues
- When yanking a mailto: link via hints, the mailto: prefix is now stripped

View File

@ -158,7 +158,7 @@
|<<content-allow-images,allow-images>>|Whether images are automatically loaded in web pages.
|<<content-allow-javascript,allow-javascript>>|Enables or disables the running of JavaScript programs.
|<<content-allow-plugins,allow-plugins>>|Enables or disables plugins in Web pages.
|<<content-webgl,webgl>>|Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is required for this setting.
|<<content-webgl,webgl>>|Enables or disables WebGL.
|<<content-css-regions,css-regions>>|Enable or disable support for CSS regions.
|<<content-hyperlink-auditing,hyperlink-auditing>>|Enable or disable hyperlink auditing (<a ping>).
|<<content-geolocation,geolocation>>|Allow websites to request geolocations.
@ -1426,7 +1426,7 @@ Default: +pass:[false]+
[[content-webgl]]
=== webgl
Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is required for this setting.
Enables or disables WebGL.
Valid values:

View File

@ -49,12 +49,8 @@ class DownloadItem(downloads.AbstractDownloadItem):
def _is_page_download(self):
"""Check if this item is a page (i.e. mhtml) download."""
try:
return (self._qt_item.savePageFormat() !=
QWebEngineDownloadItem.UnknownSaveFormat)
except AttributeError:
# Added in Qt 5.7
return False
return (self._qt_item.savePageFormat() !=
QWebEngineDownloadItem.UnknownSaveFormat)
@pyqtSlot(QWebEngineDownloadItem.DownloadState)
def _on_state_changed(self, state):
@ -209,9 +205,6 @@ class DownloadManager(downloads.AbstractDownloadManager):
def get_mhtml(self, tab, target):
"""Download the given tab as mhtml to the given target."""
assert tab.backend == usertypes.Backend.QtWebEngine
# Raises browsertab.UnsupportedOperationError on older Qt versions
# but we let the caller handle that.
tab.action.check_save_page_supported()
assert self._mhtml_target is None, self._mhtml_target
self._mhtml_target = target
tab.action.save_page()

View File

@ -25,7 +25,7 @@
from PyQt5.QtCore import QRect, Qt, QPoint
from PyQt5.QtGui import QMouseEvent
from qutebrowser.utils import log, javascript, qtutils
from qutebrowser.utils import log, javascript
from qutebrowser.browser import webelem
@ -159,15 +159,11 @@ class WebEngineElement(webelem.AbstractWebElement):
# because it was added in Qt 5.6, but we can be sure we use that with
# QtWebEngine.
# pylint: disable=no-member
# This also seems to break stuff on Qt 5.6 for some reason...
if qtutils.version_check('5.7'):
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
QPoint(0, 0), QPoint(0, 0), Qt.NoButton,
Qt.NoButton, Qt.NoModifier,
Qt.MouseEventSynthesizedBySystem)
# pylint: enable=no-member
self._tab.send_event(ev)
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
QPoint(0, 0), QPoint(0, 0), Qt.NoButton, Qt.NoButton,
Qt.NoModifier, Qt.MouseEventSynthesizedBySystem)
# pylint: enable=no-member
self._tab.send_event(ev)
# This actually "clicks" the element by calling focus() on it in JS.
js_code = javascript.assemble('webelem', 'focus', self._id)
self._tab.run_js_async(js_code)

View File

@ -176,16 +176,16 @@ def shutdown():
# Missing QtWebEngine attributes:
# - ScreenCaptureEnabled (5.7)
# - Accelerated2dCanvasEnabled (5.7)
# - AutoLoadIconsForPage (5.7)
# - TouchIconsEnabled (5.7)
# - ScreenCaptureEnabled
# - Accelerated2dCanvasEnabled
# - AutoLoadIconsForPage
# - TouchIconsEnabled
# - FocusOnNavigationEnabled (5.8)
# - AllowRunningInsecureContent (5.8)
#
# Missing QtWebEngine fonts:
# - FantasyFont
# - PictographFont (5.7)
# - PictographFont
MAPPINGS = {
@ -209,6 +209,8 @@ MAPPINGS = {
# https://bugreports.qt.io/browse/QTBUG-58650
# 'cookies-store':
# PersistentCookiePolicy(),
'webgl':
Attribute(QWebEngineSettings.WebGLEnabled),
},
'input': {
'spatial-navigation':
@ -278,12 +280,6 @@ MAPPINGS = {
}
}
try:
MAPPINGS['content']['webgl'] = Attribute(QWebEngineSettings.WebGLEnabled)
except AttributeError:
# Added in Qt 5.7
pass
try:
MAPPINGS['general']['print-element-backgrounds'] = Attribute(
QWebEngineSettings.PrintElementBackgrounds)

View File

@ -90,11 +90,6 @@ class WebEngineAction(browsertab.AbstractAction):
def exit_fullscreen(self):
self._action(QWebEnginePage.ExitFullScreen)
def check_save_page_supported(self):
if not hasattr(QWebEnginePage, 'SavePage'):
raise browsertab.UnsupportedOperationError(
"Saving as mhtml is unsupported with Qt < 5.7")
def save_page(self):
"""Save the current page."""
self._action(QWebEnginePage.SavePage)
@ -105,9 +100,7 @@ class WebEnginePrinting(browsertab.AbstractPrinting):
"""QtWebEngine implementations related to printing."""
def check_pdf_support(self):
if not hasattr(self._widget.page(), 'printToPdf'):
raise browsertab.WebTabError(
"Printing to PDF is unsupported with QtWebEngine on Qt < 5.7")
return True
def check_printer_support(self):
if not hasattr(self._widget.page(), 'print'):
@ -261,11 +254,7 @@ class WebEngineScroller(browsertab.AbstractScroller):
def _init_widget(self, widget):
super()._init_widget(widget)
page = widget.page()
try:
page.scrollPositionChanged.connect(self._update_pos)
except AttributeError:
log.stub('scrollPositionChanged, on Qt < 5.7')
self._pos_perc = (None, None)
page.scrollPositionChanged.connect(self._update_pos)
def _key_press(self, key, count=1):
for _ in range(min(count, 5000)):
@ -501,7 +490,6 @@ class WebEngineTab(browsertab.AbstractTab):
self.backend = usertypes.Backend.QtWebEngine
self._init_js()
self._child_event_filter = None
self.needs_qtbug54419_workaround = False
self._saved_zoom = None
def _init_js(self):
@ -516,13 +504,7 @@ class WebEngineTab(browsertab.AbstractTab):
script.setSourceCode(js_code)
page = self._widget.page()
try:
page.runJavaScript("", QWebEngineScript.ApplicationWorld)
except TypeError:
# We're unable to pass a world to runJavaScript
script.setWorldId(QWebEngineScript.MainWorld)
else:
script.setWorldId(QWebEngineScript.ApplicationWorld)
script.setWorldId(QWebEngineScript.ApplicationWorld)
# FIXME:qtwebengine what about runsOnSubFrames?
page.scripts().insert(script)
@ -567,19 +549,10 @@ class WebEngineTab(browsertab.AbstractTab):
else:
world_id = _JS_WORLD_MAP[world]
try:
if callback is None:
self._widget.page().runJavaScript(code, world_id)
else:
self._widget.page().runJavaScript(code, world_id, callback)
except TypeError:
if world is not None and world != usertypes.JsWorld.jseval:
log.webview.warning("Ignoring world ID on Qt < 5.7")
# Qt < 5.7
if callback is None:
self._widget.page().runJavaScript(code)
else:
self._widget.page().runJavaScript(code, callback)
if callback is None:
self._widget.page().runJavaScript(code, world_id)
else:
self._widget.page().runJavaScript(code, world_id, callback)
def shutdown(self):
self.shutting_down.emit()
@ -602,11 +575,7 @@ class WebEngineTab(browsertab.AbstractTab):
return self._widget.title()
def icon(self):
try:
return self._widget.icon()
except AttributeError:
log.stub('on Qt < 5.7')
return QIcon()
return self._widget.icon()
def set_html(self, html, base_url=None):
# FIXME:qtwebengine
@ -712,19 +681,13 @@ class WebEngineTab(browsertab.AbstractTab):
page.certificate_error.connect(self._on_ssl_errors)
page.authenticationRequired.connect(self._on_authentication_required)
page.fullScreenRequested.connect(self._on_fullscreen_requested)
try:
page.contentsSizeChanged.connect(self.contents_size_changed)
except AttributeError:
log.stub('contentsSizeChanged, on Qt < 5.7')
page.contentsSizeChanged.connect(self.contents_size_changed)
view.titleChanged.connect(self.title_changed)
view.urlChanged.connect(self._on_url_changed)
view.renderProcessTerminated.connect(
self._on_render_process_terminated)
try:
view.iconChanged.connect(self.icon_changed)
except AttributeError:
log.stub('iconChanged, on Qt < 5.7')
view.iconChanged.connect(self.icon_changed)
def event_target(self):
return self._widget.focusProxy()

View File

@ -71,7 +71,7 @@ class WebEngineView(QWebEngineView):
A window without decoration.
QWebEnginePage::WebBrowserBackgroundTab:
A web browser tab without hiding the current visible
WebEngineView. (Added in Qt 5.7)
WebEngineView.
Return:
The new QWebEngineView object.
@ -82,13 +82,6 @@ class WebEngineView(QWebEngineView):
log.webview.debug("createWindow with type {}, background_tabs "
"{}".format(debug_type, background_tabs))
try:
background_tab_wintype = QWebEnginePage.WebBrowserBackgroundTab
except AttributeError:
# This is unavailable with an older PyQt, but we still might get
# this with a newer Qt...
background_tab_wintype = 0x0003
if wintype == QWebEnginePage.WebBrowserWindow:
# Shift-Alt-Click
target = usertypes.ClickTarget.window
@ -103,7 +96,7 @@ class WebEngineView(QWebEngineView):
target = usertypes.ClickTarget.tab
else:
target = usertypes.ClickTarget.tab_bg
elif wintype == background_tab_wintype:
elif wintype == QWebEnginePage.WebBrowserBackgroundTab:
# Middle-click / Ctrl-Click
if background_tabs:
target = usertypes.ClickTarget.tab_bg
@ -113,15 +106,6 @@ class WebEngineView(QWebEngineView):
raise ValueError("Invalid wintype {}".format(debug_type))
tab = shared.get_tab(self._win_id, target)
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-54419
vercheck = qtutils.version_check
qtbug54419_fixed = ((vercheck('5.6.2') and not vercheck('5.7.0')) or
qtutils.version_check('5.7.1') or
os.environ.get('QUTE_QTBUG54419_PATCHED', ''))
if not qtbug54419_fixed:
tab.needs_qtbug54419_workaround = True
return tab._widget # pylint: disable=protected-access
@ -261,20 +245,16 @@ class WebEnginePage(QWebEnginePage):
except shared.CallSuper:
return super().javaScriptConfirm(url, js_msg)
if PYQT_VERSION > 0x050700:
# WORKAROUND
# Can't override javaScriptPrompt with older PyQt versions
# https://www.riverbankcomputing.com/pipermail/pyqt/2016-November/038293.html
def javaScriptPrompt(self, url, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts."""
if self._is_shutting_down:
return (False, "")
try:
return shared.javascript_prompt(url, js_msg, default,
abort_on=[self.loadStarted,
self.shutting_down])
except shared.CallSuper:
return super().javaScriptPrompt(url, js_msg, default)
def javaScriptPrompt(self, url, js_msg, default):
"""Override javaScriptPrompt to use qutebrowser prompts."""
if self._is_shutting_down:
return (False, "")
try:
return shared.javascript_prompt(url, js_msg, default,
abort_on=[self.loadStarted,
self.shutting_down])
except shared.CallSuper:
return super().javaScriptPrompt(url, js_msg, default)
def javaScriptAlert(self, url, js_msg):
"""Override javaScriptAlert to use qutebrowser prompts."""

View File

@ -823,8 +823,7 @@ def data(readonly=False):
('webgl',
SettingValue(typ.Bool(), 'true'),
"Enables or disables WebGL. For QtWebEngine, Qt/PyQt >= 5.7 is "
"required for this setting."),
"Enables or disables WebGL."),
('css-regions',
SettingValue(typ.Bool(), 'true',

View File

@ -539,22 +539,6 @@ class TabbedBrowser(tabwidget.TabWidget):
# We can get signals for tabs we already deleted...
return
# If needed, re-open the tab as a workaround for QTBUG-54419.
# See https://bugreports.qt.io/browse/QTBUG-54419
if (tab.backend == usertypes.Backend.QtWebEngine and
tab.needs_qtbug54419_workaround and url.isValid()):
log.misc.debug("Doing QTBUG-54419 workaround for {}, "
"url {}".format(tab, url))
background = self.currentIndex() != idx
self.setUpdatesEnabled(False)
try:
self.tabopen(url, background=background, idx=idx,
ignore_tabs_are_windows=True)
self.close_tab(tab, add_undo=False)
finally:
self.setUpdatesEnabled(True)
tab.needs_qtbug54419_workaround = False
@pyqtSlot(browsertab.AbstractTab, QIcon)
def on_icon_changed(self, tab, icon):
"""Set the icon of a tab.

View File

@ -262,15 +262,16 @@ def get_backend(args):
def check_qt_version(backend):
"""Check if the Qt version is recent enough."""
from PyQt5.QtCore import qVersion
from PyQt5.QtCore import qVersion, PYQT_VERSION
from qutebrowser.utils import qtutils
if qtutils.version_check('5.2.0', operator.lt):
text = ("Fatal error: Qt and PyQt >= 5.2.0 are required, but {} is "
"installed.".format(qVersion()))
_die(text)
elif backend == 'webengine' and qtutils.version_check('5.6.0',
operator.lt):
text = ("Fatal error: Qt and PyQt >= 5.6.0 are required for "
elif (backend == 'webengine' and (
qtutils.version_check('5.7.0', operator.lt) or
PYQT_VERSION < 0x050701)):
text = ("Fatal error: Qt and PyQt >= 5.7.1 are required for "
"QtWebEngine support, but {} is installed.".format(qVersion()))
_die(text)

View File

@ -154,15 +154,6 @@ def link_pyqt(executable, venv_path):
copy_or_link(path, dest)
def patch_pypi_pyqt(venv_path):
executable = get_venv_executable(venv_path)
pyqt_dir = os.path.dirname(get_lib_path(executable, 'PyQt5.QtCore'))
qt_conf = os.path.join(pyqt_dir, 'Qt', 'libexec', 'qt.conf')
with open(qt_conf, 'w', encoding='utf-8') as f:
f.write('[Paths]\n')
f.write('Prefix = ..\n')
def copy_or_link(source, dest):
"""Copy or symlink source to dest."""
if os.name == 'nt':
@ -186,15 +177,11 @@ def remove(filename):
os.unlink(filename)
def get_venv_executable(path):
"""Get the Python executable in a virtualenv."""
subdir = 'Scripts' if os.name == 'nt' else 'bin'
return os.path.join(path, subdir, 'python')
def get_venv_lib_path(path):
"""Get the library path of a virtualenv."""
return run_py(get_venv_executable(path),
subdir = 'Scripts' if os.name == 'nt' else 'bin'
executable = os.path.join(path, subdir, 'python')
return run_py(executable,
'from distutils.sysconfig import get_python_lib',
'print(get_python_lib())')
@ -213,19 +200,15 @@ def main():
parser.add_argument('path', help="Base path to the venv.")
parser.add_argument('--tox', help="Add when called via tox.",
action='store_true')
parser.add_argument('--pypi', help="Patch a PyPI-installed PyQt 5.6",
action='store_true')
args = parser.parse_args()
if args.pypi:
patch_pypi_pyqt(args.path)
if args.tox:
executable = get_tox_syspython(args.path)
else:
if args.tox:
executable = get_tox_syspython(args.path)
else:
executable = sys.executable
venv_path = get_venv_lib_path(args.path)
link_pyqt(executable, venv_path)
executable = sys.executable
venv_path = get_venv_lib_path(args.path)
link_pyqt(executable, venv_path)
if __name__ == '__main__':

View File

@ -120,13 +120,9 @@ def pytest_collection_modifyitems(config, items):
_apply_platform_markers(item)
if item.get_marker('xfail_norun'):
item.add_marker(pytest.mark.xfail(run=False))
if item.get_marker('js_prompt'):
if config.webengine:
js_prompt_pyqt_version = 0x050700
else:
js_prompt_pyqt_version = 0x050300
if item.get_marker('js_prompt') and not config.webengine:
item.add_marker(pytest.mark.skipif(
PYQT_VERSION <= js_prompt_pyqt_version,
PYQT_VERSION <= 0x050300,
reason='JS prompts are not supported with this PyQt version'))
if deselected:

View File

@ -197,7 +197,6 @@ Feature: Downloading things from a website.
## mhtml downloads
@qt>=5.8
Scenario: Downloading as mhtml is available
When I open data/title.html
And I run :download --mhtml
@ -227,7 +226,6 @@ Feature: Downloading things from a website.
And I wait for "File successfully written." in the log
Then the downloaded file Test title.mhtml should exist
@qt>=5.8
Scenario: Opening a mhtml download directly
When I set storage -> prompt-download-directory to true
And I open html

View File

@ -80,7 +80,7 @@ Feature: Various utility commands.
And I wait for the javascript message "Hello from JS!"
Then "Ignoring world ID 1" should be logged
@qtwebkit_skip @pyqt>=5.7.0
@qtwebkit_skip
Scenario: :jseval uses separate world without --world
When I set general -> log-javascript-console to info
And I open data/misc/jseval.html
@ -88,14 +88,14 @@ Feature: Various utility commands.
Then the javascript message "Hello from the page!" should not be logged
And the javascript message "Uncaught ReferenceError: do_log is not defined" should be logged
@qtwebkit_skip @pyqt>=5.7.0
@qtwebkit_skip
Scenario: :jseval using the main world
When I set general -> log-javascript-console to info
And I open data/misc/jseval.html
And I run :jseval --world 0 do_log()
Then the javascript message "Hello from the page!" should be logged
@qtwebkit_skip @pyqt>=5.7.0
@qtwebkit_skip
Scenario: :jseval using the main world as name
When I set general -> log-javascript-console to info
And I open data/misc/jseval.html

View File

@ -334,9 +334,6 @@ class QuteProc(testprocess.Process):
# pylint: disable=no-name-in-module,useless-suppression
from PyQt5.QtWebEngineWidgets import QWebEnginePage
# pylint: enable=no-name-in-module,useless-suppression
if not hasattr(QWebEnginePage, 'scrollPositionChanged'):
# Qt < 5.7
pytest.skip("QWebEnginePage.scrollPositionChanged missing")
if x is None and y is None:
point = 'PyQt5.QtCore.QPoint(*, *)' # not counting 0/0 here
elif x == '0' and y == '0':

View File

@ -26,8 +26,6 @@ import collections
import pytest
from qutebrowser.utils import qtutils
def collect_tests():
basedir = os.path.dirname(__file__)
@ -106,9 +104,6 @@ def _test_mhtml_requests(test_dir, test_path, httpbin):
@pytest.mark.parametrize('test_name', collect_tests())
def test_mhtml(request, test_name, download_dir, quteproc, httpbin):
if not qtutils.version_check('5.7'):
pytest.skip("mhtml is unsupported with Qt < 5.7")
quteproc.set_setting('storage', 'download-directory',
download_dir.location)
quteproc.set_setting('storage', 'prompt-download-directory', 'false')

View File

@ -66,10 +66,7 @@ passenv = {[testenv]passenv}
deps =
{[testenv]deps}
PyQt5==5.6
sip==4.18.1
commands =
{envpython} scripts/link_pyqt.py --pypi {envdir}
{envpython} scripts/dev/run_pytest.py {posargs:tests}
commands = {envpython} scripts/dev/run_pytest.py {posargs:tests}
[testenv:py35-pyqt571]
basepython = python3.5