diff options
author | Kirill Burtsev <kirill.burtsev@qt.io> | 2019-01-31 13:08:21 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-06 15:18:18 +0000 |
commit | f4ca67aa7f70f58d39ef8689ddd5910e215ed6cd (patch) | |
tree | ebb9937672b14c725de14fd7da6e3eeac269e861 /tests | |
parent | 7aa06a1614b7ca6508d96ee2e8ef0f4c49038a6f (diff) |
Web Notifications API
Implements API for end-user notifications.
Co-authored by Allan Sandfeld Jensen
[ChangeLog][Profile] Support for Web Notifications API
for end-user notifications through QWebEngineNotification
Task-number: QTBUG-50995
Fixes: QTBUG-51191
Change-Id: Icebaaa05275a713e801f1f8ecdaaec725fa264c8
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/quick/publicapi/tst_publicapi.cpp | 18 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/data/tst_notification.qml | 116 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/qmltests.pro | 1 | ||||
-rw-r--r-- | tests/auto/shared/data/notification.html | 70 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp | 95 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc | 3 | ||||
-rw-r--r-- | tests/quicktestbrowser/ApplicationRoot.qml | 1 | ||||
-rw-r--r-- | tests/quicktestbrowser/BrowserWindow.qml | 12 | ||||
-rw-r--r-- | tests/quicktestbrowser/FeaturePermissionBar.qml | 29 |
9 files changed, 332 insertions, 13 deletions
diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp index 4cbadfdaa..b50a7d782 100644 --- a/tests/auto/quick/publicapi/tst_publicapi.cpp +++ b/tests/auto/quick/publicapi/tst_publicapi.cpp @@ -35,6 +35,7 @@ #include <QtTest/QtTest> #include <QtWebEngine/QQuickWebEngineProfile> #include <QtWebEngine/QQuickWebEngineScript> +#include <QtWebEngineCore/QWebEngineNotification> #include <QtWebEngineCore/QWebEngineQuotaRequest> #include <QtWebEngineCore/QWebEngineRegisterProtocolHandlerRequest> #include <private/qquickwebengineview_p.h> @@ -82,6 +83,7 @@ static const QList<const QMetaObject *> typesToCheck = QList<const QMetaObject * << &QQuickWebEngineContextMenuRequest::staticMetaObject << &QWebEngineQuotaRequest::staticMetaObject << &QWebEngineRegisterProtocolHandlerRequest::staticMetaObject + << &QWebEngineNotification::staticMetaObject ; static QList<const char *> knownEnumNames = QList<const char *>(); @@ -286,6 +288,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineProfile.clearHttpCache() --> void" << "QQuickWebEngineProfile.downloadFinished(QQuickWebEngineDownloadItem*) --> void" << "QQuickWebEngineProfile.downloadRequested(QQuickWebEngineDownloadItem*) --> void" + << "QQuickWebEngineProfile.userNotification(QWebEngineNotification*) --> void" << "QQuickWebEngineProfile.httpAcceptLanguage --> QString" << "QQuickWebEngineProfile.httpAcceptLanguageChanged() --> void" << "QQuickWebEngineProfile.httpCacheMaximumSize --> int" @@ -564,6 +567,7 @@ static const QStringList expectedAPI = QStringList() << "QQuickWebEngineView.NewViewInTab --> NewViewDestination" << "QQuickWebEngineView.NewViewInWindow --> NewViewDestination" << "QQuickWebEngineView.NoErrorDomain --> ErrorDomain" + << "QQuickWebEngineView.Notifications --> Feature" << "QQuickWebEngineView.NoWebAction --> WebAction" << "QQuickWebEngineView.NormalTerminationStatus --> RenderProcessTerminationStatus" << "QQuickWebEngineView.Note --> PrintedPageSizeId" @@ -703,6 +707,20 @@ static const QStringList expectedAPI = QStringList() << "QWebEngineRegisterProtocolHandlerRequest.origin --> QUrl" << "QWebEngineRegisterProtocolHandlerRequest.reject() --> void" << "QWebEngineRegisterProtocolHandlerRequest.scheme --> QString" + << "QWebEngineNotification.origin --> QUrl" + << "QWebEngineNotification.icon --> QIcon" + << "QWebEngineNotification.title --> QString" + << "QWebEngineNotification.message --> QString" + << "QWebEngineNotification.tag --> QString" + << "QWebEngineNotification.language --> QString" + << "QWebEngineNotification.direction --> Direction" + << "QWebEngineNotification.LeftToRight --> Direction" + << "QWebEngineNotification.RightToLeft --> Direction" + << "QWebEngineNotification.DirectionAuto --> Direction" + << "QWebEngineNotification.show() --> void" + << "QWebEngineNotification.click() --> void" + << "QWebEngineNotification.close() --> void" + << "QWebEngineNotification.closed() --> void" ; static bool isCheckedEnum(const QByteArray &typeName) diff --git a/tests/auto/quick/qmltests/data/tst_notification.qml b/tests/auto/quick/qmltests/data/tst_notification.qml new file mode 100644 index 000000000..609a04f61 --- /dev/null +++ b/tests/auto/quick/qmltests/data/tst_notification.qml @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtWebEngine 1.9 + +TestWebEngineView { + id: view + width: 320 + height: 320 + + property bool permissionRequested: false + property bool grantPermission: false + + signal consoleMessage(string message) + + SignalSpy { + id: spyRequest + target: view + signalName: 'featurePermissionRequested' + } + + onFeaturePermissionRequested: { + if (feature === WebEngineView.Notifications) { + permissionRequested = true + view.grantFeaturePermission(securityOrigin, feature, grantPermission) + } + } + + TestCase { + name: 'WebEngineNotification' + when: windowShown + + function resolverUrl(html) { + return Qt.resolvedUrl('../../../shared/data/' + html) + } + + function init() { + permissionRequested = false + spyRequest.clear() + } + + function test_request_data() { + return [ + { tag: 'grant', grant: true, permission: 'granted' }, + { tag: 'deny', grant: false, permission: 'denied' }, + ] + } + + function test_request(data) { + grantPermission = data.grant + + view.url = resolverUrl('notification.html') + verify(view.waitForLoadSucceeded()) + + view.runJavaScript('resetPermission()') + let result = {} + + view.runJavaScript('getPermission()', function (permission) { result.permission = permission }) + tryCompare(result, 'permission', 'default') + + view.runJavaScript('requestPermission()') + spyRequest.wait() + verify(permissionRequested) + compare(spyRequest.count, 1) + + view.runJavaScript('getPermission()', function (permission) { result.permission = permission }) + tryCompare(result, 'permission', data.permission) + } + + function test_notification() { + grantPermission = true + + view.url = resolverUrl('notification.html') + view.waitForLoadSucceeded() + + view.runJavaScript('requestPermission()') + spyRequest.wait() + verify(permissionRequested) + + let title = 'Title', message = 'Message', notification = null + view.profile.userNotification.connect(function (n) { notification = n }) + + view.runJavaScript('sendNotification("' + title + '", "' + message + '")') + tryVerify(function () { return notification !== null }) + compare(notification.title, title) + compare(notification.message, message) + } + } +} diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro index ad479cd31..00e884e11 100644 --- a/tests/auto/quick/qmltests/qmltests.pro +++ b/tests/auto/quick/qmltests/qmltests.pro @@ -67,6 +67,7 @@ OTHER_FILES += \ $$PWD/data/tst_navigationHistory.qml \ $$PWD/data/tst_navigationRequested.qml \ $$PWD/data/tst_newViewRequest.qml \ + $$PWD/data/tst_notification.qml \ $$PWD/data/tst_profile.qml \ $$PWD/data/tst_properties.qml \ $$PWD/data/tst_runJavaScript.qml \ diff --git a/tests/auto/shared/data/notification.html b/tests/auto/shared/data/notification.html new file mode 100644 index 000000000..cadcbd942 --- /dev/null +++ b/tests/auto/shared/data/notification.html @@ -0,0 +1,70 @@ +<!doctype html> +<html> +<head> +<title>Desktop Notifications Demo</title> +<script> + function resetPermission() { document.Notification = 'default' } + + function getPermission() { return document.Notification } + + function sendNotification(title, body) { + let notification = new Notification(title, { body: body }) + notification.onclick = function() { console.info('onclick') } + notification.onclose = function() { console.info('onclose') } + notification.onerror = function(error) { console.info('onerror: ' + error) } + notification.onshow = function() { console.info('onshow') } + } + + function makeNotification() { + let title = document.getElementById("title").value + let body = document.getElementById("body").value + console.log('making notification:', title) + sendNotification(title, body) + } + + function requestPermission(callback) { + Notification.requestPermission().then(function (permission) { + document.Notification = permission + if (callback) + callback(permission) + }) + } + + function displayNotification() { + console.info('notifications are ' + document.Notification) + + let state = document.getElementById('state') + + if (document.Notification === 'denied') { + state.innerHTML = 'Notifications disabled' + } else if (document.Notification === 'granted') { + makeNotification() + state.innerHTML = 'notification created' + } else { + state.innerHTML = 'requesting permission...' + requestPermission(function (permission) { + console.info('notifications request: ' + permission) + if (permission === 'granted') { + makeNotification() + state.innerHTML = 'permission granted, notification created' + } else if (permission === 'denied') + state.innerHTML = 'Notifications are disabled' + }) + } + } + + document.addEventListener("DOMContentLoaded", function() { + document.Notification = Notification.permission + }) +</script> +</head> +<body> + <form name="NotificationForm" id="notificationForm"> + Title: <input type="text" id="title" placeholder="Notification title" value='sample title'><br> + Body: <input type="text" id="body" placeholder="Notification body" value='default body'><br> + <input type="button" value="Display Notification" onclick="displayNotification()"><br> + <input type="button" value="Reset Permission" onclick="resetPermission()"> + </form> + <div id='state'></div> +</body> +</html> diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp index 0b519fe8a..0504d39fa 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp @@ -49,6 +49,7 @@ #include <qwebenginedownloaditem.h> #include <qwebenginefullscreenrequest.h> #include <qwebenginehistory.h> +#include <qwebenginenotification.h> #include <qwebenginepage.h> #include <qwebengineprofile.h> #include <qwebenginequotarequest.h> @@ -195,6 +196,10 @@ private Q_SLOTS: void triggerActionWithoutMenu(); void dynamicFrame(); + void notificationRequest_data(); + void notificationRequest(); + void sendNotification(); + private: static QPoint elementCenter(QWebEnginePage *page, const QString &id); @@ -3202,6 +3207,96 @@ void tst_QWebEnginePage::dynamicFrame() QCOMPARE(toPlainTextSync(&page).trimmed(), QStringLiteral("foo")); } +struct NotificationPage : ConsolePage { + Q_OBJECT + const QWebEnginePage::PermissionPolicy policy; + +public: + NotificationPage(QWebEnginePage::PermissionPolicy ppolicy) : policy(ppolicy) { + connect(this, &QWebEnginePage::loadFinished, [load = spyLoad.ref()] (bool result) mutable { load(result); }); + + connect(this, &QWebEnginePage::featurePermissionRequested, + [this] (const QUrl &origin, QWebEnginePage::Feature feature) { + if (feature != QWebEnginePage::Notifications) + return; + if (spyRequest.wasCalled()) + QFAIL("request executed twise!"); + setFeaturePermission(origin, feature, policy); + spyRequest.ref()(origin); + }); + + load(QStringLiteral("qrc:///shared/notification.html")); + } + + CallbackSpy<bool> spyLoad; + CallbackSpy<QUrl> spyRequest; + + QString getPermission() { return evaluateJavaScriptSync(this, "getPermission()").toString(); } + void requestPermission() { runJavaScript("requestPermission()"); } + void resetPermission() { runJavaScript("resetPermission()"); } + void sendNotification(const QString &title, const QString &body) { + runJavaScript("sendNotification('" + title + "', '" + body + "')"); + } +}; + +void tst_QWebEnginePage::notificationRequest_data() +{ + QTest::addColumn<QWebEnginePage::PermissionPolicy>("policy"); + QTest::addColumn<QString>("permission"); + QTest::newRow("deny") << QWebEnginePage::PermissionDeniedByUser << "denied"; + QTest::newRow("grant") << QWebEnginePage::PermissionGrantedByUser << "granted"; +} + +void tst_QWebEnginePage::notificationRequest() +{ + QFETCH(QWebEnginePage::PermissionPolicy, policy); + QFETCH(QString, permission); + + NotificationPage page(policy); + QVERIFY(page.spyLoad.waitForResult()); + + page.resetPermission(); + QCOMPARE(page.getPermission(), "default"); + + page.requestPermission(); + page.spyRequest.waitForResult(); + QVERIFY(page.spyRequest.wasCalled()); + + QCOMPARE(page.getPermission(), permission); +} + +void tst_QWebEnginePage::sendNotification() +{ + NotificationPage page(QWebEnginePage::PermissionGrantedByUser); + QVERIFY(page.spyLoad.waitForResult()); + + page.resetPermission(); + page.requestPermission(); + auto origin = page.spyRequest.waitForResult(); + QVERIFY(page.spyRequest.wasCalled()); + QCOMPARE(page.getPermission(), "granted"); + + CallbackSpy<QWebEngineNotification> presenter; + page.profile()->setNotificationPresenter([callback = presenter.ref()] (const QWebEngineNotification ¬ification) mutable { callback(notification); }); + + QString title("Title"), message("Message"); + page.sendNotification(title, message); + + auto notification = presenter.waitForResult(); + QVERIFY(presenter.wasCalled()); + QVERIFY(!notification.isNull()); + QCOMPARE(notification.title(), title); + QCOMPARE(notification.message(), message); + QCOMPARE(notification.origin(), origin); + + notification.show(); + QTRY_VERIFY2(page.messages.contains("onshow"), page.messages.join("\n").toLatin1().constData()); + notification.click(); + QTRY_VERIFY2(page.messages.contains("onclick"), page.messages.join("\n").toLatin1().constData()); + notification.close(); + QTRY_VERIFY2(page.messages.contains("onclose"), page.messages.join("\n").toLatin1().constData()); +} + static QByteArrayList params = {QByteArrayLiteral("--use-fake-device-for-media-stream")}; W_QTEST_MAIN(tst_QWebEnginePage, params) diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc index 3bb88cbe1..757e151c1 100644 --- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc +++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.qrc @@ -23,4 +23,7 @@ <file>resources/bar.txt</file> <file>resources/path with spaces.txt</file> </qresource> +<qresource prefix='/shared'> + <file alias='notification.html'>../../shared/data/notification.html</file> +</qresource> </RCC> diff --git a/tests/quicktestbrowser/ApplicationRoot.qml b/tests/quicktestbrowser/ApplicationRoot.qml index 980016535..e2248e350 100644 --- a/tests/quicktestbrowser/ApplicationRoot.qml +++ b/tests/quicktestbrowser/ApplicationRoot.qml @@ -53,6 +53,7 @@ QtObject { var newWindow = browserWindowComponent.createObject(root) newWindow.currentWebView.profile = profile profile.downloadRequested.connect(newWindow.onDownloadRequested) + profile.userNotification.connect(newWindow.onUserNotification) return newWindow } function createDialog(profile) { diff --git a/tests/quicktestbrowser/BrowserWindow.qml b/tests/quicktestbrowser/BrowserWindow.qml index 22f98e1c5..381e9c142 100644 --- a/tests/quicktestbrowser/BrowserWindow.qml +++ b/tests/quicktestbrowser/BrowserWindow.qml @@ -505,6 +505,18 @@ ApplicationWindow { download.accept() } + MessageDialog { + id: notificationDialog + width: 200 + standardButtons: StandardButton.Ok + } + + function onUserNotification(notification) { + notificationDialog.title = notification.title + notificationDialog.text = notification.origin.toString() + '\n' + notification.message + notificationDialog.open() + } + ZoomController { id: zoomController y: parent.mapFromItem(currentWebView, 0 , 0).y - 4 diff --git a/tests/quicktestbrowser/FeaturePermissionBar.qml b/tests/quicktestbrowser/FeaturePermissionBar.qml index 9c0b25966..500d13206 100644 --- a/tests/quicktestbrowser/FeaturePermissionBar.qml +++ b/tests/quicktestbrowser/FeaturePermissionBar.qml @@ -40,10 +40,24 @@ Rectangle { visible: false height: acceptButton.height + 4 - onRequestedFeatureChanged: { - message.text = securityOrigin + " wants to access " + message.textForFeature(requestedFeature); + + function textForFeature(feature) { + switch (feature) { + case WebEngineView.Geolocation: return 'Allow %1 to access your location information?' + case WebEngineView.MediaAudioCapture: return 'Allow %1 to access your microphone?' + case WebEngineView.MediaVideoCapture: return 'Allow %1 to access your webcam?' + case WebEngineView.MediaAudioVideoCapture: return 'Allow %1 to access your microphone and webcam?' + case WebEngineView.DesktopVideoCapture: return 'Allow %1 to capture video of your desktop?' + case WebEngineView.DesktopAudioVideoCapture: return 'Allow %1 to capture audio and video of your desktop?' + case WebEngineView.Notifications: return 'Allow %1 to show notification on your desktop?' + default: break + } + return 'Grant permission for %1 to unknown or unsupported feature [' + feature + ']?' } + onRequestedFeatureChanged: { + message.text = textForFeature(requestedFeature).arg(securityOrigin); + } RowLayout { anchors { @@ -54,17 +68,6 @@ Rectangle { Label { id: message Layout.fillWidth: true - - function textForFeature(feature) { - if (feature === WebEngineView.MediaAudioCapture) - return "your microphone" - if (feature === WebEngineView.MediaVideoCapture) - return "your camera" - if (feature === WebEngineView.MediaAudioVideoCapture) - return "your camera and microphone" - if (feature === WebEngineView.Geolocation) - return "your position" - } } Button { |