Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9b4c2a66e | ||
|
|
6a7ab7edb3 | ||
|
|
f7f96484e8 | ||
|
|
840d2e4423 | ||
|
|
e54f2a090a | ||
|
|
55ce4b7ed2 | ||
|
|
3daf823da8 | ||
|
|
72feb2c19f | ||
|
|
6d04508490 | ||
|
|
93ebd846ab | ||
|
|
9ee473a54c | ||
|
|
d1cced0da4 | ||
|
|
22644c41da | ||
|
|
6b5857ef7d | ||
|
|
b0f4cc6924 | ||
|
|
218637af84 | ||
|
|
dd84845f01 | ||
|
|
cf53f9042a | ||
|
|
f48266f72f | ||
|
|
5dac848968 | ||
|
|
341aa1e700 | ||
|
|
ebf81c06ae | ||
|
|
a3eb8d6561 | ||
|
|
af4b02bf46 |
@@ -15,6 +15,21 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.3.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- QtWebEngine: Improved workaround for a bug in Qt 5.11 where only the
|
||||
top/bottom half of the window is used.
|
||||
- QtWebEngine: Work around a bug in Qt 5.11 where an endless loading-loop is
|
||||
triggered when clicking a link with an unknown scheme.
|
||||
- QtWebEngine: When switching between pages with changed settings, less
|
||||
unnecessary reloads are done now.
|
||||
- QtWebEngine: It's now possible to open external links such as `magnet://` or
|
||||
`mailto:` via hints.
|
||||
|
||||
v1.3.1
|
||||
------
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2018 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 3, 1)
|
||||
__version_info__ = (1, 3, 2)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -307,6 +307,10 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
href_tags = ['a', 'area', 'link']
|
||||
return self.tag_name() in href_tags and 'href' in self
|
||||
|
||||
def _requires_user_interaction(self):
|
||||
"""Return True if clicking this element needs user interaction."""
|
||||
raise NotImplementedError
|
||||
|
||||
def _mouse_pos(self):
|
||||
"""Get the position to click/hover."""
|
||||
# Click the center of the largest square fitting into the top/left
|
||||
@@ -405,7 +409,7 @@ class AbstractWebElement(collections.abc.MutableMapping):
|
||||
return
|
||||
|
||||
if click_target == usertypes.ClickTarget.normal:
|
||||
if self.is_link():
|
||||
if self.is_link() and not self._requires_user_interaction():
|
||||
log.webelem.debug("Clicking via JS click()")
|
||||
self._click_js(click_target)
|
||||
elif self.is_editable(strict=True):
|
||||
|
||||
@@ -27,7 +27,7 @@ from PyQt5.QtGui import QMouseEvent
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineSettings
|
||||
|
||||
from qutebrowser.utils import log, javascript
|
||||
from qutebrowser.utils import log, javascript, urlutils
|
||||
from qutebrowser.browser import webelem
|
||||
|
||||
|
||||
@@ -198,6 +198,13 @@ class WebEngineElement(webelem.AbstractWebElement):
|
||||
if self.is_text_input() and self.is_editable():
|
||||
self._js_call('move_cursor_to_end')
|
||||
|
||||
def _requires_user_interaction(self):
|
||||
baseurl = self._tab.url()
|
||||
url = self.resolve_url(baseurl)
|
||||
if url is None:
|
||||
return True
|
||||
return url.scheme() not in urlutils.WEBENGINE_SCHEMES
|
||||
|
||||
def _click_editable(self, click_target):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58515
|
||||
ev = QMouseEvent(QMouseEvent.MouseButtonPress, QPoint(0, 0),
|
||||
|
||||
@@ -34,6 +34,9 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
def install(self, profile):
|
||||
"""Install the handler for qute:// URLs on the given profile."""
|
||||
profile.installUrlSchemeHandler(b'qute', self)
|
||||
if qtutils.version_check('5.11', compiled=False):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||
profile.installUrlSchemeHandler(b'chrome-error', self)
|
||||
|
||||
def requestStarted(self, job):
|
||||
"""Handle a request for a qute: scheme.
|
||||
@@ -45,6 +48,12 @@ class QuteSchemeHandler(QWebEngineUrlSchemeHandler):
|
||||
job: QWebEngineUrlRequestJob
|
||||
"""
|
||||
url = job.requestUrl()
|
||||
|
||||
if url.scheme() == 'chrome-error':
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-63378
|
||||
job.fail(QWebEngineUrlRequestJob.UrlInvalid)
|
||||
return
|
||||
|
||||
assert job.requestMethod() == b'GET'
|
||||
assert url.scheme() == 'qute'
|
||||
log.misc.debug("Got request for {}".format(url.toDisplayString()))
|
||||
|
||||
@@ -30,7 +30,7 @@ from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QEvent, QPoint, QPointF,
|
||||
QUrl, QTimer)
|
||||
from PyQt5.QtGui import QKeyEvent, QIcon
|
||||
from PyQt5.QtNetwork import QAuthenticator
|
||||
from PyQt5.QtWidgets import QApplication, QWidget
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtWebEngineWidgets import QWebEnginePage, QWebEngineScript
|
||||
|
||||
from qutebrowser.config import configdata, config
|
||||
@@ -1048,28 +1048,6 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
def _on_navigation_request(self, navigation):
|
||||
super()._on_navigation_request(navigation)
|
||||
|
||||
if qtutils.version_check('5.11.0', exact=True, compiled=False):
|
||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68224
|
||||
layout = self._widget.layout()
|
||||
count = layout.count()
|
||||
children = self._widget.findChildren(QWidget)
|
||||
if not count and children:
|
||||
log.webview.warning("Found children not in layout: {}, "
|
||||
"focus proxy {} (QTBUG-68224)".format(
|
||||
children, self._widget.focusProxy()))
|
||||
if count > 1:
|
||||
log.webview.debug("Found {} widgets! (QTBUG-68224)"
|
||||
.format(count))
|
||||
for i in range(count):
|
||||
item = layout.itemAt(i)
|
||||
if item is None:
|
||||
continue
|
||||
widget = item.widget()
|
||||
if widget is not self._widget.focusProxy():
|
||||
log.webview.debug("Removing widget {} (QTBUG-68224)"
|
||||
.format(widget))
|
||||
layout.removeWidget(widget)
|
||||
|
||||
if not navigation.accepted or not navigation.is_main_frame:
|
||||
return
|
||||
|
||||
@@ -1092,7 +1070,7 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
# TODO on Qt > 5.11.0, we hopefully never need a reload:
|
||||
# https://codereview.qt-project.org/#/c/229525/1
|
||||
if not qtutils.version_check('5.11.0', exact=True, compiled=False):
|
||||
if navigation.navigation_type != navigation.Type.link_clicked:
|
||||
if navigation.navigation_type == navigation.Type.link_clicked:
|
||||
reload_needed = False
|
||||
|
||||
if reload_needed:
|
||||
@@ -1137,4 +1115,4 @@ class WebEngineTab(browsertab.AbstractTab):
|
||||
self.predicted_navigation.connect(self._on_predicted_navigation)
|
||||
|
||||
def event_target(self):
|
||||
return self._widget.focusProxy()
|
||||
return self._widget.render_widget()
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
import functools
|
||||
|
||||
import sip
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QUrl, PYQT_VERSION
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtQuickWidgets import QQuickWidget
|
||||
from PyQt5.QtWebEngineWidgets import (QWebEngineView, QWebEnginePage,
|
||||
QWebEngineScript)
|
||||
|
||||
@@ -30,6 +32,7 @@ from qutebrowser.browser import shared
|
||||
from qutebrowser.browser.webengine import certificateerror, webenginesettings
|
||||
from qutebrowser.config import config
|
||||
from qutebrowser.utils import log, debug, usertypes, jinja, objreg, qtutils
|
||||
from qutebrowser.misc import miscwidgets
|
||||
|
||||
|
||||
class WebEngineView(QWebEngineView):
|
||||
@@ -51,6 +54,34 @@ class WebEngineView(QWebEngineView):
|
||||
parent=self)
|
||||
self.setPage(page)
|
||||
|
||||
if qtutils.version_check('5.11', compiled=False):
|
||||
# Set a PseudoLayout as a WORKAROUND for
|
||||
# https://bugreports.qt.io/browse/QTBUG-68224
|
||||
# and other related issues.
|
||||
sip.delete(self.layout())
|
||||
self._layout = miscwidgets.PseudoLayout(self)
|
||||
|
||||
def render_widget(self):
|
||||
"""Get the RenderWidgetHostViewQt for this view.
|
||||
|
||||
Normally, this would always be the focusProxy().
|
||||
However, it sometimes isn't, so we use this as a WORKAROUND for
|
||||
https://bugreports.qt.io/browse/QTBUG-68727
|
||||
"""
|
||||
proxy = self.focusProxy()
|
||||
if proxy is not None:
|
||||
return proxy
|
||||
|
||||
# This should only find the RenderWidgetHostViewQtDelegateWidget,
|
||||
# but not e.g. a QMenu
|
||||
children = self.findChildren(QQuickWidget)
|
||||
|
||||
if not children:
|
||||
return None
|
||||
|
||||
assert len(children) == 1, children
|
||||
return children[0]
|
||||
|
||||
def shutdown(self):
|
||||
self.page().shutdown()
|
||||
|
||||
|
||||
@@ -305,6 +305,9 @@ class WebKitElement(webelem.AbstractWebElement):
|
||||
if self.is_text_input() and self.is_editable():
|
||||
self._tab.caret.move_to_end_of_document()
|
||||
|
||||
def _requires_user_interaction(self):
|
||||
return False
|
||||
|
||||
def _click_editable(self, click_target):
|
||||
ok = self._elem.evaluateJavaScript('this.focus(); true;')
|
||||
if ok:
|
||||
|
||||
@@ -74,7 +74,7 @@ function tryagain()
|
||||
</td>
|
||||
<td style="padding-left: 40px;">
|
||||
<h1>Unable to load page</h1>
|
||||
Error while opening {{ url }}<br>
|
||||
Error while opening {{ url | default('page', true) }}<br>
|
||||
<p id="error-message-text" style="color: #a31a1a;">{{ error }}</p><br><br>
|
||||
|
||||
<form name="bl">
|
||||
|
||||
@@ -266,6 +266,47 @@ class WrapperLayout(QLayout):
|
||||
self._widget.deleteLater()
|
||||
|
||||
|
||||
class PseudoLayout(QLayout):
|
||||
|
||||
"""A layout which isn't actually a real layout.
|
||||
|
||||
This is used to replace QWebEngineView's internal layout, as a WORKAROUND
|
||||
for https://bugreports.qt.io/browse/QTBUG-68224 and other related issues.
|
||||
|
||||
This is partly inspired by https://codereview.qt-project.org/#/c/230894/
|
||||
which does something similar as part of Qt.
|
||||
"""
|
||||
|
||||
def addItem(self, item):
|
||||
assert self.parent() is not None
|
||||
item.widget().setParent(self.parent())
|
||||
|
||||
def removeItem(self, item):
|
||||
item.widget().setParent(None)
|
||||
|
||||
def count(self):
|
||||
return 0
|
||||
|
||||
def itemAt(self, _pos):
|
||||
return None
|
||||
|
||||
def widget(self):
|
||||
return self.parent().render_widget()
|
||||
|
||||
def setGeometry(self, rect):
|
||||
"""Resize the render widget when the view is resized."""
|
||||
widget = self.widget()
|
||||
if widget is not None:
|
||||
widget.setGeometry(rect)
|
||||
|
||||
def sizeHint(self):
|
||||
"""Make sure the view has the sizeHint of the render widget."""
|
||||
widget = self.widget()
|
||||
if widget is not None:
|
||||
return widget.sizeHint()
|
||||
return QSize()
|
||||
|
||||
|
||||
class FullscreenNotification(QLabel):
|
||||
|
||||
"""A label telling the user this page is now fullscreen."""
|
||||
|
||||
@@ -39,6 +39,21 @@ from qutebrowser.browser.network import pac
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/108
|
||||
|
||||
|
||||
# URL schemes supported by QtWebEngine
|
||||
WEBENGINE_SCHEMES = [
|
||||
'about',
|
||||
'data',
|
||||
'file',
|
||||
'filesystem',
|
||||
'ftp',
|
||||
'http',
|
||||
'https',
|
||||
'javascript',
|
||||
'ws',
|
||||
'wss',
|
||||
]
|
||||
|
||||
|
||||
class InvalidUrlError(ValueError):
|
||||
|
||||
"""Error raised if a function got an invalid URL.
|
||||
|
||||
1
setup.py
1
setup.py
@@ -77,6 +77,7 @@ try:
|
||||
version='.'.join(str(e) for e in _get_constant('version_info')),
|
||||
description=_get_constant('description'),
|
||||
long_description=read_file('README.asciidoc'),
|
||||
long_description_content_type='text/plain',
|
||||
url='https://www.qutebrowser.org/',
|
||||
author=_get_constant('author'),
|
||||
author_email=_get_constant('email'),
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<a href="what://::">I'm broken</a>
|
||||
<a href="foo://bar">Unknown scheme</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -201,8 +201,8 @@ Feature: Downloading things from a website.
|
||||
And I run :download-retry
|
||||
Then the error "Retrying downloads is unsupported *" should be shown
|
||||
|
||||
@qtwebkit_skip @qt>=5.10
|
||||
Scenario: Retrying a failed download with QtWebEngine (Qt >= 5.10)
|
||||
@qtwebkit_skip @qt==5.10.1
|
||||
Scenario: Retrying a failed download with QtWebEngine (Qt 5.10)
|
||||
When I open data/downloads/issue2298.html
|
||||
And I run :click-element id download
|
||||
And I wait for "Download error: *" in the log
|
||||
|
||||
@@ -156,11 +156,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
|
||||
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
|
||||
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
|
||||
|
||||
@@ -361,6 +361,9 @@ class QuteProc(testprocess.Process):
|
||||
"Focus object changed: "
|
||||
"<qutebrowser.browser.webengine.webview.WebEngineView object "
|
||||
"at *>",
|
||||
# Qt >= 5.11 with workarounds
|
||||
"Focus object changed: "
|
||||
"<PyQt5.QtQuickWidgets.QQuickWidget object at *>",
|
||||
]
|
||||
|
||||
if (log_line.category == 'ipc' and
|
||||
|
||||
Reference in New Issue
Block a user