Add support for onclose callbacks on notifications.

This commit is contained in:
Ash 2020-06-03 22:21:22 -07:00
parent 755fafc9ce
commit 77d4d3f4d4
6 changed files with 65 additions and 8 deletions

View File

@ -23,6 +23,7 @@ markers =
qtwebkit_skip: Tests not applicable with QtWebKit
qtwebengine_flaky: Tests which are flaky (and currently skipped) with QtWebEngine
qtwebengine_mac_xfail: Tests which fail on macOS with QtWebEngine
qtwebengine_py_5_15: Tests which require PyQtWebEngine 5.15.
js_prompt: Tests needing to display a javascript prompt
this: Used to mark tests during development
no_invalid_lines: Don't fail on unparseable lines in end2end tests

View File

@ -24,8 +24,9 @@ import typing
from qutebrowser.utils import log
from PyQt5.QtGui import QImage
from PyQt5.QtCore import QVariant, QMetaType, QByteArray, PYQT_VERSION
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBus, QDBusArgument
from PyQt5.QtCore import QObject, QVariant, QMetaType, QByteArray, pyqtSlot, PYQT_VERSION
from PyQt5.QtDBus import QDBusConnection, QDBusInterface, QDBus, QDBusArgument, QDBusMessage
from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION
from PyQt5.QtWebEngineCore import QWebEngineNotification
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
@ -34,7 +35,7 @@ class DBusException(Exception):
"""Raised when something goes wrong with talking to DBus."""
class DBusNotificationPresenter:
class DBusNotificationPresenter(QObject):
"""Manages notifications that are sent over DBus."""
SERVICE = "org.freedesktop.Notifications"
@ -43,17 +44,29 @@ class DBusNotificationPresenter:
INTERFACE = "org.freedesktop.Notifications"
def __init__(self, test_service: bool = False):
super().__init__()
self._active_notifications = {} # type: typing.Dict[int, QWebEngineNotification]
bus = QDBusConnection.sessionBus()
if not bus.isConnected():
raise DBusException("Failed to connect to DBus session bus")
service = self.TEST_SERVICE if test_service else self.SERVICE
self.interface = QDBusInterface(
self.TEST_SERVICE if test_service else self.SERVICE,
service,
self.PATH,
self.INTERFACE,
bus,
)
bus.connect(
service,
self.PATH,
self.INTERFACE,
"NotificationClosed",
self._handle_close
)
if not self.interface:
raise DBusException("Could not construct a DBus interface")
@ -114,6 +127,7 @@ class DBusNotificationPresenter:
)
notification_id = reply.arguments()[0]
self._active_notifications[notification_id] = qt_notification
log.webview.debug("Sent out notification {}".format(notification_id))
def _convert_image(self, qimage: QImage) -> QDBusArgument:
@ -141,3 +155,13 @@ class DBusNotificationPresenter:
image_data.add(QByteArray(bits))
image_data.endStructure()
return image_data
@pyqtSlot(QDBusMessage)
def _handle_close(self, message: QDBusMessage) -> None:
notification_id = message.arguments()[0]
if notification_id in self._active_notifications:
try:
self._active_notifications[notification_id].close()
except RuntimeError:
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html
pass

View File

@ -112,6 +112,7 @@ def _get_backend_tag(tag):
'qtwebengine_todo': pytest.mark.qtwebengine_todo,
'qtwebengine_skip': pytest.mark.qtwebengine_skip,
'qtwebengine_notifications': pytest.mark.qtwebengine_notifications,
'qtwebengine_py_5_15': pytest.mark.qtwebengine_py_5_15,
'qtwebkit_skip': pytest.mark.qtwebkit_skip,
}
if not any(tag.startswith(t + ':') for t in pytest_marks):
@ -136,6 +137,13 @@ if not getattr(sys, 'frozen', False):
return None
def _pyqt_webengine_at_least_5_15() -> bool:
try:
from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION
return PYQT_WEBENGINE_VERSION >= 0x050F00
except ImportError:
return False
def pytest_collection_modifyitems(config, items):
"""Apply @qtwebengine_* markers; skip unittests with QUTE_BDD_WEBENGINE."""
markers = [
@ -153,6 +161,10 @@ def pytest_collection_modifyitems(config, items):
config.webengine),
('qtwebengine_mac_xfail', 'Fails on macOS with QtWebEngine',
pytest.mark.xfail, config.webengine and utils.is_mac),
# WORKAROUND for https://www.riverbankcomputing.com/pipermail/pyqt/2020-May/042918.html
('qtwebengine_py_5_15', 'Skipped with PyQtWebEngine < 5.15',
pytest.mark.skipif,
config.webengine and not _pyqt_webengine_at_least_5_15())
]
for item in items:

View File

@ -4,7 +4,8 @@ Feature: Notifications
HTML5 notification API interaction
Background:
Given I open data/prompt/notifications.html
Given I have a fresh instance
And I open data/prompt/notifications.html
And I set content.notifications to true
And I run :click-element id button
@ -14,3 +15,14 @@ Feature: Notifications
Then the javascript message "notification shown" should be logged
And a notification with id 1 is presented
@qtwebengine_notifications @qtwebengine_py_5_15
Scenario: User closes presented notification
When I run :click-element id show-button
And I close the notification with id 1
Then the javascript message "notification closed" should be logged
@qtwebengine_notifications @qtwebengine_py_5_15
Scenario: User closes some other application's notification
When I run :click-element id show-button
And I close the notification with id 1234
Then the javascript message "notification closed" should not be logged

View File

@ -27,3 +27,7 @@ from qutebrowser.utils import qtutils
@bdd.then(bdd.parsers.cfparse('a notification with id {id_:d} is presented'))
def notification_presented(notification_server, id_):
assert id_ in notification_server.messages
@bdd.when(bdd.parsers.cfparse('I close the notification with id {id_:d}'))
def close_notification(notification_server, id_):
notification_server.close(id_)

View File

@ -23,6 +23,7 @@ class TestNotificationServer(QObject):
super().__init__()
self._service = service
self._bus = QDBusConnection.sessionBus()
assert self._bus.isConnected()
self._message_id = 0
# A dict mapping notification IDs to currently-displayed notifications.
self.messages = {} # type: typing.Dict[int, QDBusMessage]
@ -35,6 +36,7 @@ class TestNotificationServer(QObject):
QDBusConnection.ExportAllSlots)
def unregister(self) -> None:
self._bus.unregisterObject(DBusNotificationPresenter.PATH)
assert self._bus.unregisterService(self._service)
@pyqtSlot(QDBusMessage, result="uint")
@ -58,9 +60,11 @@ class TestNotificationServer(QObject):
@pytest.fixture
def notification_server(qapp):
server = TestNotificationServer(DBusNotificationPresenter.TEST_SERVICE)
server.register()
yield server
server.unregister()
try:
server.register()
yield server
finally:
server.unregister()
def _as_uint32(x: int) -> QVariant: