From e4457c8995d12beb6a1d1cdbac922a24e1824333 Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Sun, 7 Feb 2021 19:12:18 +0100 Subject: [PATCH 1/9] add semicolons --- src/app/webbrowser/morph-browser.qml | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/app/webbrowser/morph-browser.qml b/src/app/webbrowser/morph-browser.qml index 119aae939..89f8038be 100644 --- a/src/app/webbrowser/morph-browser.qml +++ b/src/app/webbrowser/morph-browser.qml @@ -115,9 +115,9 @@ QtObject { onActiveChanged: { if (active) { - var index = allWindows.indexOf(this) + var index = allWindows.indexOf(this); if (index > -1) { - allWindows.push(allWindows.splice(index, 1)[0]) + allWindows.push(allWindows.splice(index, 1)[0]); } } } @@ -125,45 +125,43 @@ QtObject { onClosing: { if (allWindows.length == 1) { if (tabsModel.count > 0) { - session.save() + session.save(); } else { - session.clear() + session.clear(); } } if (incognito && (allWindows.length > 1)) { // If the last incognito window is being closed, // prune incognito entries from the downloads model - var incognitoWindows = 0 + var incognitoWindows = 0; for (var w in allWindows) { - var window = allWindows[w] + var window = allWindows[w]; if ((window !== this) && window.incognito) { - ++incognitoWindows + ++incognitoWindows; } } - if (incognitoWindows == 0) { - DownloadsModel.pruneIncognitoDownloads() + if (incognitoWindows === 0) { + DownloadsModel.pruneIncognitoDownloads(); } } - - if (allWindows.length > 1) { for (var win in allWindows) { if (this === allWindows[win]) { - var tabs = allWindows[win].tabsModel + var tabs = allWindows[win].tabsModel; for (var t = tabs.count - 1; t >= 0; --t) { - //console.log("remove tab with url " + tabs.get(t).url) - tabs.removeTab(t) + //console.log("remove tab with url " + tabs.get(t).url); + tabs.removeTab(t); } - allWindows.splice(win, 1) - return + allWindows.splice(win, 1); + return; } } } - destroy() + destroy(); } Shortcut { From f0728b9e5f5120ab975ec10171cea7aff65eee45 Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Sun, 7 Feb 2021 19:18:18 +0100 Subject: [PATCH 2/9] implement web notifications support --- CMakeLists.txt | 3 + debian/morph-browser-apparmor.manifest | 2 +- debian/morph-browser.install | 1 + src/app/CMakeLists.txt | 3 + src/app/NotificationsAccessDialog.qml | 95 +++++++++++ src/app/WebViewImpl.qml | 6 + src/app/browserapplication.cpp | 3 + src/app/notifications-proxy.cpp | 77 +++++++++ src/app/notifications-proxy.h | 44 +++++ src/app/pushclient/pushclient.cpp | 189 ++++++++++++++++++++++ src/app/pushclient/pushclient.h | 54 +++++++ src/app/webbrowser/Browser.qml | 5 + src/app/webbrowser/morph-browser.qml | 9 ++ src/app/webcontainer/WebApp.qml | 5 + src/app/webcontainer/webapp-container.qml | 16 ++ 15 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 src/app/NotificationsAccessDialog.qml create mode 100644 src/app/notifications-proxy.cpp create mode 100644 src/app/notifications-proxy.h create mode 100644 src/app/pushclient/pushclient.cpp create mode 100644 src/app/pushclient/pushclient.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 60b052893..7c8ff1853 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(DESKTOP_FILE morph-browser.desktop) +set(PUSH_HELPER_DIR "lib/ubuntu-push-client/legacy-helpers") # uninstall target configure_file( @@ -94,4 +95,6 @@ add_subdirectory(doc) install(FILES morph-browser.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/morph-browser) install(FILES morph-browser-splash.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/morph-browser) +install(PROGRAMS push-helper/morph-browser-helper.sh DESTINATION ${PUSH_HELPER_DIR} RENAME morph-browser) + add_subdirectory(click-hooks) diff --git a/debian/morph-browser-apparmor.manifest b/debian/morph-browser-apparmor.manifest index 20da2280f..2d90b2219 100644 --- a/debian/morph-browser-apparmor.manifest +++ b/debian/morph-browser-apparmor.manifest @@ -26,7 +26,7 @@ ], "template_variables": { "APP_ID_DBUS": "morph_2dbrowser", - "APP_PKGNAME_DBUS": "morph_2dbrowser", + "APP_PKGNAME_DBUS": "_", "APP_PKGNAME": "morph-browser", "CLICK_DIR": "/usr/share/morph-browser" }, diff --git a/debian/morph-browser.install b/debian/morph-browser.install index eae71a260..de61bddc8 100644 --- a/debian/morph-browser.install +++ b/debian/morph-browser.install @@ -1,4 +1,5 @@ usr/bin/morph-browser +usr/lib/ubuntu-push-client/legacy-helpers usr/share/morph-browser/*.qml usr/share/morph-browser/qmldir usr/share/morph-browser/*.js diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index d6f653425..a22366661 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -30,6 +30,8 @@ set(COMMONLIB_SRC input-method-handler.cpp meminfo.cpp mime-database.cpp + notifications-proxy.cpp + pushclient/pushclient.cpp session-storage.cpp single-instance-manager.cpp ) @@ -44,6 +46,7 @@ target_link_libraries(${COMMONLIB} Qt5::Qml Qt5::Quick Qt5::Widgets + Qt5DBus Qt5WebEngine Qt5WebEngineCore ${LIBAPPARMOR_LDFLAGS} diff --git a/src/app/NotificationsAccessDialog.qml b/src/app/NotificationsAccessDialog.qml new file mode 100644 index 000000000..926f806e6 --- /dev/null +++ b/src/app/NotificationsAccessDialog.qml @@ -0,0 +1,95 @@ +/* + * Copyright 2020 UBports Foundation + * + * This file is part of morph-browser. + * + * morph-browser 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; version 3. + * + * morph-browser 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 this program. If not, see . + */ + +import QtQuick 2.4 +import QtWebEngine 1.5 +import Ubuntu.Components 1.3 +import Ubuntu.Components.Popups 1.3 + +Dialog { + property url origin + modal: true + + signal accept() + signal reject() + + onAccept: { hide(); grantFeaturePermission(origin, WebEngineView.Notifications, true); } + onReject: { hide(); grantFeaturePermission(origin, WebEngineView.Notifications, false); } + + Label { + elide: Text.ElideRight + textSize: Label.Large + color: theme.palette.normal.overlayText + text: i18n.tr("Permission") + } + + Label { + color: theme.palette.normal.baseText + wrapMode: Text.Wrap + text: i18n.tr("Allow this domain to create notifications?") + } + + Label { + color: theme.palette.normal.baseText + wrapMode: Text.Wrap + text: origin + } + + Item { + height: units.gu(2) + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: units.dp(1) + color: theme.palette.normal.base + } + } + + Row { + height: units.gu(4) + spacing: units.gu(2) + layoutDirection: Qt.RightToLeft + + Button { + objectName: "notificationsAccessDialog.allowButton" + text: i18n.tr("Yes") + color: theme.palette.normal.positive + width: units.gu(10) + onClicked: accept() + } + + Button { + objectName: "notificationsAccessDialog.denyButton" + text: i18n.tr("No") + width: units.gu(10) + onClicked: reject() + } + } + + // adjust default dialog visuals to custom design requirements + // (should not be needed when updated dialog implementation lands in UITK) + Binding { + target: __foreground + property: "margins" + value: units.gu(2) + } +} + diff --git a/src/app/WebViewImpl.qml b/src/app/WebViewImpl.qml index c49a47009..11e75ee6c 100644 --- a/src/app/WebViewImpl.qml +++ b/src/app/WebViewImpl.qml @@ -231,6 +231,12 @@ WebView { mediaAccessDialog.origin = securityOrigin; mediaAccessDialog.feature = feature; break; + + case WebEngineView.Notifications: + var notificationsAccessDialog = PopupUtils.open(Qt.resolvedUrl("NotificationsAccessDialog.qml"), this); + notificationsAccessDialog.origin = securityOrigin; + + break; } } diff --git a/src/app/browserapplication.cpp b/src/app/browserapplication.cpp index 0c44835e4..5faf827d7 100644 --- a/src/app/browserapplication.cpp +++ b/src/app/browserapplication.cpp @@ -45,6 +45,7 @@ #include "input-method-handler.h" #include "meminfo.h" #include "mime-database.h" +#include "notifications-proxy.h" #include "session-storage.h" BrowserApplication::BrowserApplication(int& argc, char** argv) @@ -107,6 +108,7 @@ MAKE_SINGLETON_FACTORY(DownloadsModel) MAKE_SINGLETON_FACTORY(FileOperations) MAKE_SINGLETON_FACTORY(MemInfo) MAKE_SINGLETON_FACTORY(MimeDatabase) +MAKE_SINGLETON_FACTORY(NotificationsProxy) MAKE_SINGLETON_FACTORY(UserAgentsModel) bool BrowserApplication::initialize(const QString& qmlFileSubPath @@ -202,6 +204,7 @@ bool BrowserApplication::initialize(const QString& qmlFileSubPath qmlRegisterSingletonType(uri, 0, 1, "FileOperations", FileOperations_singleton_factory); qmlRegisterSingletonType(uri, 0, 1, "MemInfo", MemInfo_singleton_factory); qmlRegisterSingletonType(uri, 0, 1, "MimeDatabase", MimeDatabase_singleton_factory); + qmlRegisterSingletonType(uri, 0, 1, "NotificationsProxy", NotificationsProxy_singleton_factory); qmlRegisterType(uri, 0, 1, "SessionStorage"); qmlRegisterSingletonType(uri, 0, 1, "UserAgentsModel", UserAgentsModel_singleton_factory); diff --git a/src/app/notifications-proxy.cpp b/src/app/notifications-proxy.cpp new file mode 100644 index 000000000..7c0b9185f --- /dev/null +++ b/src/app/notifications-proxy.cpp @@ -0,0 +1,77 @@ +/* + * Copyright 2020 UBports Foundation + * + * This file is part of morph-browser. + * + * morph-browser 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; version 3. + * + * morph-browser 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 this program. If not, see . + */ + +#include "notifications-proxy.h" +#include "pushclient/pushclient.h" + +#include +#include +#include + +NotificationsProxy::NotificationsProxy(QObject* parent) : QObject(parent) +{ + m_appId = QString(); + m_isWebApp = false; +} + +void NotificationsProxy::setAppId(const QString &appId) +{ + m_appId = appId; + PushClient::instance()->setAppId(m_appId); + m_isWebApp = ! m_appId.startsWith("_"); +} + +void NotificationsProxy::sendNotification(QObject * notificationObject) const +{ + QWebEngineNotification * notification = qobject_cast(notificationObject); + QJsonObject message = buildMessage(notification->tag(), notification->origin(), notification->title(), notification->message()); + // send notification, update it if tag does already exist + PushClient::instance()->update(notification->tag(), message); +} + +void NotificationsProxy::updateCount() const +{ + PushClient::instance()->updateCount(); +} + +QJsonObject NotificationsProxy::buildMessage(const QString & tag, const QUrl & origin, const QString & title, const QString & body) const +{ + QJsonObject notification; + notification["tag"] = tag; + notification["card"] = buildCard(origin, title, body); + notification["sound"] = false; + //notification["vibrate"] = vibrate(); + QJsonObject message; + message["notification"] = notification; + return message; +} + +QJsonObject NotificationsProxy::buildCard(const QUrl & origin, const QString & title, const QString & body) const +{ + QJsonObject card; + card["summary"] = title; + card["body"] = body; + card["popup"] = true; + card["persist"] = true; + + QJsonArray actions = QJsonArray(); + QString actionUri = m_isWebApp ? QString("appid://%1/%2/current-user-version").arg(m_appId.split("_").at(0), m_appId.split("_").at(1)) : origin.toString(); + actions.append(actionUri); + card["actions"] = actions; + return card; +} diff --git a/src/app/notifications-proxy.h b/src/app/notifications-proxy.h new file mode 100644 index 000000000..4819f2af1 --- /dev/null +++ b/src/app/notifications-proxy.h @@ -0,0 +1,44 @@ +/* + * Copyright 2020 UBports Foundation + * + * This file is part of morph-browser. + * + * morph-browser 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; version 3. + * + * morph-browser 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 this program. If not, see . + */ + +#ifndef __NOTIFICATIONS_PROXY_H__ +#define __NOTIFICATIONS_PROXY_H__ + +#include +#include + +class NotificationsProxy : public QObject +{ + Q_OBJECT + +public: + explicit NotificationsProxy(QObject* parent=0); + + Q_INVOKABLE void setAppId(const QString & appId); + Q_INVOKABLE void sendNotification(QObject * notificationObject) const; + Q_INVOKABLE void updateCount() const; + +private: + QJsonObject buildMessage(const QString & tag, const QUrl & origin, const QString & title, const QString & body) const; + QJsonObject buildCard(const QUrl & origin, const QString & title, const QString & body) const; + + QString m_appId; + bool m_isWebApp; +}; + +#endif // __NOTIFICATIONS_PROXY_H__ diff --git a/src/app/pushclient/pushclient.cpp b/src/app/pushclient/pushclient.cpp new file mode 100644 index 000000000..f60fd2ef7 --- /dev/null +++ b/src/app/pushclient/pushclient.cpp @@ -0,0 +1,189 @@ +/* Copyright (C) 2017 Dan Chapman (used pushclient.cpp of dekko as base) + * Copyright 2020 UBports Foundation + * + * This file is part of morph-browser + * + * morph-browser 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; version 3. + * + * morph-browser 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 this program. If not, see . +*/ +#include "pushclient.h" +#include +#include +#include +#include +#include + +#define POSTAL_SERVICE "com.ubuntu.Postal" +#define POSTAL_PATH "/com/ubuntu/Postal" +#define POSTAL_IFACE "com.ubuntu.Postal" + +static QPointer s_client; +PushClient *PushClient::instance() +{ + if (s_client.isNull()) { + s_client = new PushClient(); + } + return s_client; +} + +PushClient::PushClient(QObject *parent) : QObject(parent), + m_conn(QDBusConnection::sessionBus()) +{ +} + +void PushClient::setAppId(const QString & appId) +{ + m_appId = appId; + updateCount(); +} + +//shamelessly stolen from accounts-polld +bool PushClient::send(const QJsonObject &message) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(POSTAL_SERVICE, + makePath(), + POSTAL_IFACE, + "Post"); + msg << m_appId; + QByteArray data = QJsonDocument(message).toJson(QJsonDocument::Compact); + msg << QString::fromUtf8(data); + + qDebug() << "[POST] >> " << msg; + + QDBusMessage reply = m_conn.call(msg); + if (reply.type() == QDBusMessage::ErrorMessage) { + qDebug() << "[POST ERROR] " << reply.errorMessage(); + return false; + } + qDebug() << "[POST SUCCESS] >> Message posted."; + QJsonObject n = message.value("notification").toObject(); + QString tag = n.value("tag").toString(); + updateCount(tag); + return true; +} + +bool PushClient::update(const QString &tag, const QJsonObject &message) +{ + if (hasTag(tag)) { + clearPersistent(tag); + } + return send(message); +} + +bool PushClient::hasTag(const QString &tag) +{ + return m_tags.contains(tag); +} + +bool PushClient::clearPersistent(const QString &tag) +{ + if (m_tags.contains(tag)) { + qDebug() << "[REMOVE] >> Removing message: " << tag; + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, + makePath(), + POSTAL_IFACE, + "ClearPersistent"); + message << m_appId; + message << tag; + + QDBusMessage reply = m_conn.call(message); + if (reply.type() == QDBusMessage::ErrorMessage) { + qDebug() << "[REMOVE ERROR] " << reply.errorMessage(); + return false; + } + qDebug() << "[REMOVE SUCCESS] Notification removed"; + return updateCount(tag, true); + } + return false; +} + +bool PushClient::updateCount(const QString &tag, const bool remove) +{ + qDebug() << "[COUNT] >> Updating launcher count"; + if (!tag.isEmpty()) { + if (!remove && !m_tags.contains(tag)) { + qDebug() << "[COUNT] >> Tag not yet in persistent list. adding it now: " << tag; + m_tags << tag; + } + + if (remove && m_tags.contains(tag)) { + qDebug() << "[COUNT] >> Removing tag from persistent list: " << tag; + m_tags.removeAll(tag); + } + } + else { + m_tags = getPersistent(); + } + + bool visible = m_tags.count() != 0; + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, + makePath(), + POSTAL_IFACE, + "SetCounter"); + message << m_appId << m_tags.count() << visible; + bool result = m_conn.send(message); + if (result) { + qDebug() << "[COUNT] >> Updated."; + } + return result; +} + +//shamelessly stolen from accounts-polld +QByteArray PushClient::makePath() +{ + QByteArray path(QByteArrayLiteral("/com/ubuntu/Postal/")); + + QByteArray pkg = m_appId.split('_').first().toUtf8(); + + // legacy apps have _ as path + if (pkg.count() == 0) { + path += '_'; + } + + // path for click apps + for (int i = 0; i < pkg.count(); i++) { + char buffer[10]; + char c = pkg[i]; + switch (c) { + case '+': + case '.': + case '-': + case ':': + case '~': + case '_': + sprintf(buffer, "_%.2x", c); + path += buffer; + break; + default: + path += c; + } + } + qDebug() << "[PATH] >> " << path; + return path; +} + +QStringList PushClient::getPersistent() +{ + QDBusMessage message = QDBusMessage::createMethodCall(POSTAL_SERVICE, + makePath(), + POSTAL_IFACE, + "ListPersistent"); + message << m_appId; + QDBusMessage reply = m_conn.call(message); + if (reply.type() == QDBusMessage::ErrorMessage) { + qDebug() << reply.errorMessage(); + return QStringList(); + } + QStringList tags = reply.arguments()[0].toStringList(); + qDebug() << "[TAGS] >> " << tags; + return tags; +} diff --git a/src/app/pushclient/pushclient.h b/src/app/pushclient/pushclient.h new file mode 100644 index 000000000..d4d68b462 --- /dev/null +++ b/src/app/pushclient/pushclient.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2017 Dan Chapman (used pushclient.h of Dekko as base) + * Copyright 2020 UBports Foundation + * + * This file is part of morph-browser + * + * morph-browser 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; version 3. + * + * morph-browser 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 this program. If not, see . +*/ +#ifndef PUSHCLIENT_H +#define PUSHCLIENT_H + +#include +#include +#include + +class PushClient : public QObject +{ + Q_OBJECT +public: + explicit PushClient(QObject *parent = 0); + + static PushClient *instance(); + + // set the app id + void setAppId(const QString & appId); + + // Send a notification + bool send(const QJsonObject &message); + // Update a notification + bool update(const QString &tag, const QJsonObject &message); + + bool hasTag(const QString &tag); + bool clearPersistent(const QString &tag); + bool updateCount(const QString &tag = QString(), const bool remove = false); + +private: + QByteArray makePath(); + QStringList getPersistent(); + + QDBusConnection m_conn; + QStringList m_tags; + QString m_appId; +}; + +#endif // PUSHCLIENT_H diff --git a/src/app/webbrowser/Browser.qml b/src/app/webbrowser/Browser.qml index 7402b4424..cba454d9d 100644 --- a/src/app/webbrowser/Browser.qml +++ b/src/app/webbrowser/Browser.qml @@ -1970,6 +1970,11 @@ Common.BrowserView { internal.closeTabsWithUrl(download.url); } } + + onPresentNotification: { + NotificationsProxy.updateCount(); + NotificationsProxy.sendNotification(notification); + } } Connections { diff --git a/src/app/webbrowser/morph-browser.qml b/src/app/webbrowser/morph-browser.qml index 89f8038be..6f7f0fae0 100644 --- a/src/app/webbrowser/morph-browser.qml +++ b/src/app/webbrowser/morph-browser.qml @@ -61,6 +61,12 @@ QtObject { // create path for pages printed to PDF FileOperations.mkpath(Qt.resolvedUrl(cacheLocation) + "/pdf_tmp"); + + // set appId of NotificationsProxy + NotificationsProxy.setAppId("_morph-browser"); + + // update notification count + NotificationsProxy.updateCount(); } // Array of all windows, sorted chronologically (most recently active last) @@ -119,6 +125,7 @@ QtObject { if (index > -1) { allWindows.push(allWindows.splice(index, 1)[0]); } + NotificationsProxy.updateCount(); } } @@ -161,6 +168,8 @@ QtObject { } } + NotificationsProxy.updateCount(); + destroy(); } diff --git a/src/app/webcontainer/WebApp.qml b/src/app/webcontainer/WebApp.qml index 3235af014..bc4b25c56 100644 --- a/src/app/webcontainer/WebApp.qml +++ b/src/app/webcontainer/WebApp.qml @@ -512,6 +512,11 @@ Common.BrowserView { console.log("a download was finished with path %1.".arg(download.path)) webapp.setDownloadComplete(download) } + + onPresentNotification: { + NotificationsProxy.updateCount(); + NotificationsProxy.sendNotification(notification); + } } Connections { diff --git a/src/app/webcontainer/webapp-container.qml b/src/app/webcontainer/webapp-container.qml index 949a3c815..4e55b579a 100644 --- a/src/app/webcontainer/webapp-container.qml +++ b/src/app/webcontainer/webapp-container.qml @@ -63,6 +63,16 @@ BrowserWindow { // Used for testing signal schemeUriHandleFilterResult(string uri) + onActiveChanged: { + if (active) { + NotificationsProxy.updateCount(); + } + } + + onClosing: { + NotificationsProxy.updateCount(); + } + function getWindowTitle() { var webappViewTitle = webappViewLoader.item @@ -187,6 +197,12 @@ BrowserWindow { // create path for pages printed to PDF FileOperations.mkpath(Qt.resolvedUrl(cacheLocation) + "/pdf_tmp"); + + // set appId for NotificationsProxy + NotificationsProxy.setAppId(unversionedAppId); + + // update notifications count + NotificationsProxy.updateCount(); } function loadCustomUserScripts() { From d108a000ed1699bb659da41129122352bb142a8e Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Sun, 7 Feb 2021 19:19:16 +0100 Subject: [PATCH 3/9] add push helper --- push-helper/morph-browser-helper.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 push-helper/morph-browser-helper.sh diff --git a/push-helper/morph-browser-helper.sh b/push-helper/morph-browser-helper.sh new file mode 100644 index 000000000..5ebbc4dfe --- /dev/null +++ b/push-helper/morph-browser-helper.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cp $1 $2 From 68f7e98f87d86e4feeec4dab7350e1288877058e Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Tue, 9 Feb 2021 17:14:47 +0100 Subject: [PATCH 4/9] add notifications preference (allow / deny) to domain settings --- src/app/DomainSettingsPage.qml | 21 +++++- src/app/NotificationsAccessDialog.qml | 94 +++++++++------------------ src/app/WebViewImpl.qml | 28 +++++++- src/app/domain-settings-model.cpp | 73 +++++++++++++++++++-- src/app/domain-settings-model.h | 11 ++++ 5 files changed, 155 insertions(+), 72 deletions(-) diff --git a/src/app/DomainSettingsPage.qml b/src/app/DomainSettingsPage.qml index a19c33c9e..a86a34d22 100644 --- a/src/app/DomainSettingsPage.qml +++ b/src/app/DomainSettingsPage.qml @@ -171,6 +171,7 @@ FocusScope { readonly property string domain: model.domain readonly property int userAgentId: model.userAgentId readonly property int locationPreference: model.allowLocation + readonly property int notificationsPreference: model.allowNotifications height: isCurrentItem ? layout.height : units.gu(5) color: isCurrentItem ? ((theme.palette.selected.background.hslLightness > 0.5) ? Qt.darker(theme.palette.selected.background, 1.05) : Qt.lighter(theme.palette.selected.background, 1.5)) : theme.palette.normal.background @@ -215,7 +216,6 @@ FocusScope { } } - Row { spacing: units.gu(1.5) height: units.gu(1) @@ -234,6 +234,25 @@ FocusScope { anchors.verticalCenter: parent.verticalCenter } } + + Row { + spacing: units.gu(1.5) + height: units.gu(1) + visible: item.ListView.isCurrentItem + + Label { + width: parent.width * 0.5 + text: i18n.tr("send notifications") + anchors.verticalCenter: parent.verticalCenter + } + + ComboBox { + model: [ i18n.tr("Ask each time"), i18n.tr("Allowed"), i18n.tr("Denied") ] + currentIndex: item.notificationsPreference + onCurrentIndexChanged: DomainSettingsModel.setNotificationsPreference(item.domain, currentIndex) + anchors.verticalCenter: parent.verticalCenter + } + } Row { spacing: units.gu(1.5) diff --git a/src/app/NotificationsAccessDialog.qml b/src/app/NotificationsAccessDialog.qml index 926f806e6..a16ab35b0 100644 --- a/src/app/NotificationsAccessDialog.qml +++ b/src/app/NotificationsAccessDialog.qml @@ -17,79 +17,45 @@ */ import QtQuick 2.4 -import QtWebEngine 1.5 import Ubuntu.Components 1.3 import Ubuntu.Components.Popups 1.3 Dialog { - property url origin + id: dialog + + property string securityOrigin + property bool showRememberDecisionCheckBox modal: true + + title: i18n.tr("Permission Request") + text: securityOrigin + "
" + i18n.tr("This page wants to know create notifications.") - signal accept() + signal allow() + signal allowPermanently() signal reject() + signal rejectPermanently() - onAccept: { hide(); grantFeaturePermission(origin, WebEngineView.Notifications, true); } - onReject: { hide(); grantFeaturePermission(origin, WebEngineView.Notifications, false); } - - Label { - elide: Text.ElideRight - textSize: Label.Large - color: theme.palette.normal.overlayText - text: i18n.tr("Permission") + onAllow: { PopupUtils.close(dialog); } + onAllowPermanently: { PopupUtils.close(dialog); } + onReject: { PopupUtils.close(dialog); } + onRejectPermanently: { PopupUtils.close(dialog); } + + ListItemLayout { + visible: showRememberDecisionCheckBox + title.text: i18n.tr("Remember decision") + CheckBox { + id: rememberDecisionCheckBox + } } - - Label { - color: theme.palette.normal.baseText - wrapMode: Text.Wrap - text: i18n.tr("Allow this domain to create notifications?") + Button { + objectName: "allow" + text: i18n.tr("Allow") + color: theme.palette.normal.positive + onClicked: rememberDecisionCheckBox.checked ? allowPermanently() : allow() } - - Label { - color: theme.palette.normal.baseText - wrapMode: Text.Wrap - text: origin - } - - Item { - height: units.gu(2) - Rectangle { - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - height: units.dp(1) - color: theme.palette.normal.base - } - } - - Row { - height: units.gu(4) - spacing: units.gu(2) - layoutDirection: Qt.RightToLeft - - Button { - objectName: "notificationsAccessDialog.allowButton" - text: i18n.tr("Yes") - color: theme.palette.normal.positive - width: units.gu(10) - onClicked: accept() - } - - Button { - objectName: "notificationsAccessDialog.denyButton" - text: i18n.tr("No") - width: units.gu(10) - onClicked: reject() - } - } - - // adjust default dialog visuals to custom design requirements - // (should not be needed when updated dialog implementation lands in UITK) - Binding { - target: __foreground - property: "margins" - value: units.gu(2) + Button { + objectName: "deny" + text: i18n.tr("Deny") + onClicked: rememberDecisionCheckBox.checked ? rejectPermanently() : reject() } } - diff --git a/src/app/WebViewImpl.qml b/src/app/WebViewImpl.qml index 11e75ee6c..d682c13af 100644 --- a/src/app/WebViewImpl.qml +++ b/src/app/WebViewImpl.qml @@ -233,9 +233,33 @@ WebView { break; case WebEngineView.Notifications: - var notificationsAccessDialog = PopupUtils.open(Qt.resolvedUrl("NotificationsAccessDialog.qml"), this); - notificationsAccessDialog.origin = securityOrigin; + + var domain = UrlUtils.extractHost(securityOrigin); + var notificationsPreference = DomainSettingsModel.getNotificationsPreference(domain); + if (notificationsPreference === DomainSettingsModel.AllowNotificationsAccess) + { + grantFeaturePermission(securityOrigin, feature, true); + return; + } + + if (notificationsPreference === DomainSettingsModel.DenyNotificationsAccess) + { + grantFeaturePermission(securityOrigin, feature, false); + return; + } + + var notificationsAccessDialog = PopupUtils.open(Qt.resolvedUrl("NotificationsAccessDialog.qml"), this); + notificationsAccessDialog.securityOrigin = securityOrigin; + notificationsAccessDialog.showRememberDecisionCheckBox = (domain !== "") && ! incognito + notificationsAccessDialog.allow.connect(function() { grantFeaturePermission(securityOrigin, feature, true); }); + notificationsAccessDialog.allowPermanently.connect(function() { grantFeaturePermission(securityOrigin, feature, true); + DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.AllowNotificationsAccess); + }); + notificationsAccessDialog.reject.connect(function() { grantFeaturePermission(securityOrigin, feature, false); }); + notificationsAccessDialog.rejectPermanently.connect(function() { grantFeaturePermission(securityOrigin, feature, false); + DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.DenyNotificationsAccess); + }); break; } } diff --git a/src/app/domain-settings-model.cpp b/src/app/domain-settings-model.cpp index 95cd1cb33..14b9621ac 100644 --- a/src/app/domain-settings-model.cpp +++ b/src/app/domain-settings-model.cpp @@ -71,6 +71,7 @@ QHash DomainSettingsModel::roleNames() const roles[DomainWithoutSubdomain] = "domainWithoutSubdomain"; roles[AllowCustomUrlSchemes] = "allowCustomUrlSchemes"; roles[AllowLocation] = "allowLocation"; + roles[AllowNotifications] = "allowNotifications"; roles[UserAgentId] = "userAgentId"; roles[ZoomFactor] = "zoomFactor"; } @@ -98,6 +99,8 @@ QVariant DomainSettingsModel::data(const QModelIndex& index, int role) const return entry.allowCustomUrlSchemes; case AllowLocation: return entry.allowLocation; + case AllowNotifications: + return entry.allowNotifications; case UserAgentId: return entry.userAgentId; case ZoomFactor: @@ -111,16 +114,40 @@ void DomainSettingsModel::createOrAlterDatabaseSchema() { QSqlQuery createQuery(m_database); QString query = QLatin1String("CREATE TABLE IF NOT EXISTS domainsettings " - "(domain VARCHAR NOT NULL UNIQUE, domainWithoutSubdomain VARCHAR, allowCustomUrlSchemes BOOL, allowLocation INTEGER, " + "(domain VARCHAR NOT NULL UNIQUE, domainWithoutSubdomain VARCHAR, allowCustomUrlSchemes BOOL, allowLocation INTEGER, allowNotifications INTEGER" "userAgentId INTEGER, zoomFactor REAL, PRIMARY KEY(domain), FOREIGN KEY(userAgentId) REFERENCES useragents(id)); "); createQuery.prepare(query); createQuery.exec(); + + // Older version of the database schema didn’t have the column 'allowNotifications' + QSqlQuery tableInfoQuery(m_database); + query = QLatin1String("PRAGMA TABLE_INFO(domainsettings);"); + tableInfoQuery.prepare(query); + tableInfoQuery.exec(); + + bool missingAllowNotificationsColumn = true; + + while (tableInfoQuery.next()) { + if (tableInfoQuery.value("name").toString() == "allowNotifications") { + missingAllowNotificationsColumn = false; + } + if (!missingAllowNotificationsColumn) { + break; + } + } + + if (missingAllowNotificationsColumn) { + QSqlQuery addFolderColumnQuery(m_database); + query = QLatin1String("ALTER TABLE domainsettings ADD COLUMN allowNotifications INTEGER;"); + addFolderColumnQuery.prepare(query); + addFolderColumnQuery.exec(); + } } void DomainSettingsModel::populateFromDatabase() { QSqlQuery populateQuery(m_database); - QString query = QLatin1String("SELECT domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, userAgentId, zoomFactor " + QString query = QLatin1String("SELECT domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, allowNotifications, userAgentId, zoomFactor " "FROM domainsettings;"); populateQuery.prepare(query); populateQuery.exec(); @@ -131,6 +158,7 @@ void DomainSettingsModel::populateFromDatabase() entry.domainWithoutSubdomain = populateQuery.value("domainWithoutSubdomain").toString(); entry.allowCustomUrlSchemes = populateQuery.value("allowCustomUrlSchemes").toBool(); entry.allowLocation = static_cast(populateQuery.value("allowLocation").toInt()); + entry.allowNotifications = static_cast(populateQuery.value("allowNotifications").toInt()); entry.userAgentId = populateQuery.value("userAgentId").toInt(); entry.zoomFactor = populateQuery.value("zoomFactor").isNull() ? std::numeric_limits::quiet_NaN() : populateQuery.value("zoomFactor").toDouble(); @@ -247,6 +275,38 @@ void DomainSettingsModel::setLocationPreference(const QString& domain, DomainSet } } +DomainSettingsModel::NotificationsPreference DomainSettingsModel::getNotificationsPreference(const QString& domain) const +{ + int index = getIndexForDomain(domain); + if (index == -1) + { + return NotificationsPreference::AskForNotificationsAccess; + } + + return m_entries[index].allowNotifications; +} + +void DomainSettingsModel::setNotificationsPreference(const QString& domain, DomainSettingsModel::NotificationsPreference preference) +{ + insertEntry(domain); + + int index = getIndexForDomain(domain); + if (index != -1) { + DomainSetting& entry = m_entries[index]; + if (entry.allowNotifications == preference) { + return; + } + entry.allowNotifications = preference; + Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector() << AllowNotifications); + QSqlQuery query(m_database); + static QString updateStatement = QLatin1String("UPDATE domainsettings SET allowNotifications=? WHERE domain=?;"); + query.prepare(updateStatement); + query.addBindValue(entry.allowNotifications); + query.addBindValue(domain); + query.exec(); + } +} + int DomainSettingsModel::getUserAgentId(const QString& domain) const { int index = getIndexForDomain(domain); @@ -347,6 +407,7 @@ void DomainSettingsModel::insertEntry(const QString &domain) entry.domainWithoutSubdomain = DomainUtils::getDomainWithoutSubdomain(domain); entry.allowCustomUrlSchemes = false; entry.allowLocation = AllowLocationPreference::AskForLocationAccess; + entry.allowNotifications = NotificationsPreference::AskForNotificationsAccess; entry.userAgentId = 0; entry.zoomFactor = std::numeric_limits::quiet_NaN(); m_entries.append(entry); @@ -354,13 +415,14 @@ void DomainSettingsModel::insertEntry(const QString &domain) Q_EMIT rowCountChanged(); QSqlQuery query(m_database); - static QString insertStatement = QLatin1String("INSERT INTO domainsettings (domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, userAgentId, zoomFactor)" + static QString insertStatement = QLatin1String("INSERT INTO domainsettings (domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, allowNotifications, userAgentId, zoomFactor)" " VALUES (?, ?, ?, ?, ?, ?);"); query.prepare(insertStatement); query.addBindValue(entry.domain); query.addBindValue(entry.domainWithoutSubdomain); query.addBindValue(entry.allowCustomUrlSchemes); query.addBindValue(entry.allowLocation); + query.addBindValue(entry.allowNotifications); query.addBindValue((entry.userAgentId > 0) ? entry.userAgentId : QVariant()); query.addBindValue(entry.zoomFactor); query.exec(); @@ -390,10 +452,11 @@ void DomainSettingsModel::removeEntry(const QString &domain) void DomainSettingsModel::removeObsoleteEntries() { QSqlQuery query(m_database); - static QString deleteStatement = QLatin1String("DELETE FROM domainsettings WHERE allowCustomUrlSchemes=? AND allowLocation=? AND userAgentId IS NULL AND zoomFactor IS NULL;"); + static QString deleteStatement = QLatin1String("DELETE FROM domainsettings WHERE allowCustomUrlSchemes=? AND allowLocation=? AND allowNotifications=? AND userAgentId IS NULL AND zoomFactor IS NULL;"); query.prepare(deleteStatement); query.addBindValue(false); - query.addBindValue(false); + query.addBindValue(AllowLocationPreference::AskForLocationAccess); + query.addBindValue(NotificationsPreference::AskForNotificationsAccess); query.exec(); } diff --git a/src/app/domain-settings-model.h b/src/app/domain-settings-model.h index d3271584c..61c173b04 100644 --- a/src/app/domain-settings-model.h +++ b/src/app/domain-settings-model.h @@ -33,6 +33,7 @@ class DomainSettingsModel : public QAbstractListModel Q_PROPERTY(double defaultZoomFactor READ defaultZoomFactor WRITE setDefaultZoomFactor) Q_ENUMS(AllowLocationPreference) + Q_ENUMS(NotificationsPreference) Q_ENUMS(Roles) public: @@ -45,11 +46,18 @@ class DomainSettingsModel : public QAbstractListModel DenyLocationAccess = 2 }; + enum NotificationsPreference { + AskForNotificationsAccess = 0, + AllowNotificationsAccess = 1, + DenyNotificationsAccess = 2 + }; + enum Roles { Domain = Qt::UserRole + 1, DomainWithoutSubdomain, AllowCustomUrlSchemes, AllowLocation, + AllowNotifications, UserAgentId, ZoomFactor }; @@ -71,6 +79,8 @@ class DomainSettingsModel : public QAbstractListModel Q_INVOKABLE void allowCustomUrlSchemes(const QString& domain, bool allow); Q_INVOKABLE AllowLocationPreference getLocationPreference(const QString& domain) const; Q_INVOKABLE void setLocationPreference(const QString& domain, AllowLocationPreference preference); + Q_INVOKABLE NotificationsPreference getNotificationsPreference(const QString& domain) const; + Q_INVOKABLE void setNotificationsPreference(const QString& domain, NotificationsPreference); Q_INVOKABLE int getUserAgentId(const QString& domain) const; Q_INVOKABLE void setUserAgentId(const QString& domain, int userAgentId); Q_INVOKABLE void removeUserAgentIdFromAllDomains(int userAgentId); @@ -93,6 +103,7 @@ class DomainSettingsModel : public QAbstractListModel QString domainWithoutSubdomain; bool allowCustomUrlSchemes; AllowLocationPreference allowLocation; + NotificationsPreference allowNotifications; int userAgentId; double zoomFactor; }; From b9705cd181176ae0d2611cc99e8d35b2d6e86964 Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Tue, 9 Feb 2021 21:20:50 +0100 Subject: [PATCH 5/9] sql command corrections --- src/app/domain-settings-model.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/domain-settings-model.cpp b/src/app/domain-settings-model.cpp index 14b9621ac..be5fd07cb 100644 --- a/src/app/domain-settings-model.cpp +++ b/src/app/domain-settings-model.cpp @@ -114,7 +114,7 @@ void DomainSettingsModel::createOrAlterDatabaseSchema() { QSqlQuery createQuery(m_database); QString query = QLatin1String("CREATE TABLE IF NOT EXISTS domainsettings " - "(domain VARCHAR NOT NULL UNIQUE, domainWithoutSubdomain VARCHAR, allowCustomUrlSchemes BOOL, allowLocation INTEGER, allowNotifications INTEGER" + "(domain VARCHAR NOT NULL UNIQUE, domainWithoutSubdomain VARCHAR, allowCustomUrlSchemes BOOL, allowLocation INTEGER, allowNotifications INTEGER, " "userAgentId INTEGER, zoomFactor REAL, PRIMARY KEY(domain), FOREIGN KEY(userAgentId) REFERENCES useragents(id)); "); createQuery.prepare(query); createQuery.exec(); @@ -416,7 +416,7 @@ void DomainSettingsModel::insertEntry(const QString &domain) QSqlQuery query(m_database); static QString insertStatement = QLatin1String("INSERT INTO domainsettings (domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, allowNotifications, userAgentId, zoomFactor)" - " VALUES (?, ?, ?, ?, ?, ?);"); + " VALUES (?, ?, ?, ?, ?, ?, ?);"); query.prepare(insertStatement); query.addBindValue(entry.domain); query.addBindValue(entry.domainWithoutSubdomain); From f6b5c72dcbdc95773cd766420fcc32e41fa130a2 Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Wed, 26 May 2021 20:10:27 +0200 Subject: [PATCH 6/9] Update NotificationsAccessDialog.qml --- src/app/NotificationsAccessDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/NotificationsAccessDialog.qml b/src/app/NotificationsAccessDialog.qml index a16ab35b0..ac15fceff 100644 --- a/src/app/NotificationsAccessDialog.qml +++ b/src/app/NotificationsAccessDialog.qml @@ -28,7 +28,7 @@ Dialog { modal: true title: i18n.tr("Permission Request") - text: securityOrigin + "
" + i18n.tr("This page wants to know create notifications.") + text: securityOrigin + "
" + i18n.tr("This page wants to create notifications.") signal allow() signal allowPermanently() From fbd3dc637486658468bb04886f6811dca0af4770 Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Wed, 26 May 2021 20:11:52 +0200 Subject: [PATCH 7/9] Update notifications-proxy.cpp remove commented code line --- src/app/notifications-proxy.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/notifications-proxy.cpp b/src/app/notifications-proxy.cpp index 7c0b9185f..7c9340750 100644 --- a/src/app/notifications-proxy.cpp +++ b/src/app/notifications-proxy.cpp @@ -55,7 +55,6 @@ QJsonObject NotificationsProxy::buildMessage(const QString & tag, const QUrl & o notification["tag"] = tag; notification["card"] = buildCard(origin, title, body); notification["sound"] = false; - //notification["vibrate"] = vibrate(); QJsonObject message; message["notification"] = notification; return message; From ca01e97c53e2db798e073a1f3a67d5658fa7536c Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Sun, 30 May 2021 11:50:46 +0200 Subject: [PATCH 8/9] add incognito flag for custom user agents / notification settings in DomainSettings --- src/app/DomainSettingsPage.qml | 4 +- src/app/WebViewImpl.qml | 24 +++++--- src/app/domain-settings-model.cpp | 74 +++++++++++++---------- src/app/domain-settings-model.h | 6 +- src/app/webbrowser/Browser.qml | 47 +++----------- src/app/webcontainer/WebViewImplOxide.qml | 47 +++----------- 6 files changed, 81 insertions(+), 121 deletions(-) diff --git a/src/app/DomainSettingsPage.qml b/src/app/DomainSettingsPage.qml index a86a34d22..b74b60a10 100644 --- a/src/app/DomainSettingsPage.qml +++ b/src/app/DomainSettingsPage.qml @@ -211,7 +211,7 @@ FocusScope { CheckBox { checked: model.allowCustomUrlSchemes - onTriggered: DomainSettingsModel.allowCustomUrlSchemes(model.domain, checked) + onTriggered: DomainSettingsModel.allowCustomUrlSchemes(model.domain, checked, false) anchors.verticalCenter: parent.verticalCenter } } @@ -249,7 +249,7 @@ FocusScope { ComboBox { model: [ i18n.tr("Ask each time"), i18n.tr("Allowed"), i18n.tr("Denied") ] currentIndex: item.notificationsPreference - onCurrentIndexChanged: DomainSettingsModel.setNotificationsPreference(item.domain, currentIndex) + onCurrentIndexChanged: DomainSettingsModel.setNotificationsPreference(item.domain, currentIndex, false) anchors.verticalCenter: parent.verticalCenter } } diff --git a/src/app/WebViewImpl.qml b/src/app/WebViewImpl.qml index d682c13af..ff5aa9248 100644 --- a/src/app/WebViewImpl.qml +++ b/src/app/WebViewImpl.qml @@ -252,14 +252,22 @@ WebView { var notificationsAccessDialog = PopupUtils.open(Qt.resolvedUrl("NotificationsAccessDialog.qml"), this); notificationsAccessDialog.securityOrigin = securityOrigin; notificationsAccessDialog.showRememberDecisionCheckBox = (domain !== "") && ! incognito - notificationsAccessDialog.allow.connect(function() { grantFeaturePermission(securityOrigin, feature, true); }); - notificationsAccessDialog.allowPermanently.connect(function() { grantFeaturePermission(securityOrigin, feature, true); - DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.AllowNotificationsAccess); - }); - notificationsAccessDialog.reject.connect(function() { grantFeaturePermission(securityOrigin, feature, false); }); - notificationsAccessDialog.rejectPermanently.connect(function() { grantFeaturePermission(securityOrigin, feature, false); - DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.DenyNotificationsAccess); - }); + notificationsAccessDialog.allow.connect(function() { + grantFeaturePermission(securityOrigin, feature, true); + DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.AllowNotificationsAccess, true); + }); + notificationsAccessDialog.allowPermanently.connect(function() { + grantFeaturePermission(securityOrigin, feature, true); + DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.AllowNotificationsAccess, false); + }); + notificationsAccessDialog.reject.connect(function() { + grantFeaturePermission(securityOrigin, feature, false); + DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.DenyNotificationsAccess, true); + }); + notificationsAccessDialog.rejectPermanently.connect(function() { + grantFeaturePermission(securityOrigin, feature, false); + DomainSettingsModel.setNotificationsPreference(domain, DomainSettingsModel.DenyNotificationsAccess, false); + }); break; } } diff --git a/src/app/domain-settings-model.cpp b/src/app/domain-settings-model.cpp index be5fd07cb..f53b235fa 100644 --- a/src/app/domain-settings-model.cpp +++ b/src/app/domain-settings-model.cpp @@ -222,9 +222,9 @@ bool DomainSettingsModel::areCustomUrlSchemesAllowed(const QString& domain) return m_entries[index].allowCustomUrlSchemes; } -void DomainSettingsModel::allowCustomUrlSchemes(const QString& domain, bool allow) +void DomainSettingsModel::allowCustomUrlSchemes(const QString& domain, bool allow, bool incognito) { - insertEntry(domain); + insertEntry(domain, incognito); int index = getIndexForDomain(domain); if (index != -1) { @@ -234,12 +234,15 @@ void DomainSettingsModel::allowCustomUrlSchemes(const QString& domain, bool allo } entry.allowCustomUrlSchemes = allow; Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector() << AllowCustomUrlSchemes); - QSqlQuery query(m_database); - static QString updateStatement = QLatin1String("UPDATE domainsettings SET allowCustomUrlSchemes=? WHERE domain=?;"); - query.prepare(updateStatement); - query.addBindValue(allow); - query.addBindValue(domain); - query.exec(); + if (!incognito) + { + QSqlQuery query(m_database); + static QString updateStatement = QLatin1String("UPDATE domainsettings SET allowCustomUrlSchemes=? WHERE domain=?;"); + query.prepare(updateStatement); + query.addBindValue(allow); + query.addBindValue(domain); + query.exec(); + } } } @@ -256,7 +259,7 @@ DomainSettingsModel::AllowLocationPreference DomainSettingsModel::getLocationPre void DomainSettingsModel::setLocationPreference(const QString& domain, DomainSettingsModel::AllowLocationPreference preference) { - insertEntry(domain); + insertEntry(domain, false); int index = getIndexForDomain(domain); if (index != -1) { @@ -286,9 +289,9 @@ DomainSettingsModel::NotificationsPreference DomainSettingsModel::getNotificatio return m_entries[index].allowNotifications; } -void DomainSettingsModel::setNotificationsPreference(const QString& domain, DomainSettingsModel::NotificationsPreference preference) +void DomainSettingsModel::setNotificationsPreference(const QString& domain, DomainSettingsModel::NotificationsPreference preference, bool incognito) { - insertEntry(domain); + insertEntry(domain, incognito); int index = getIndexForDomain(domain); if (index != -1) { @@ -298,12 +301,16 @@ void DomainSettingsModel::setNotificationsPreference(const QString& domain, Doma } entry.allowNotifications = preference; Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector() << AllowNotifications); - QSqlQuery query(m_database); - static QString updateStatement = QLatin1String("UPDATE domainsettings SET allowNotifications=? WHERE domain=?;"); - query.prepare(updateStatement); - query.addBindValue(entry.allowNotifications); - query.addBindValue(domain); - query.exec(); + + if (!incognito) + { + QSqlQuery query(m_database); + static QString updateStatement = QLatin1String("UPDATE domainsettings SET allowNotifications=? WHERE domain=?;"); + query.prepare(updateStatement); + query.addBindValue(entry.allowNotifications); + query.addBindValue(domain); + query.exec(); + } } } @@ -320,7 +327,7 @@ int DomainSettingsModel::getUserAgentId(const QString& domain) const void DomainSettingsModel::setUserAgentId(const QString& domain, int userAgentId) { - insertEntry(domain); + insertEntry(domain, false); int index = getIndexForDomain(domain); if (index != -1) { @@ -374,7 +381,7 @@ double DomainSettingsModel::getZoomFactor(const QString& domain) const void DomainSettingsModel::setZoomFactor(const QString& domain, double zoomFactor) { - insertEntry(domain); + insertEntry(domain, false); int index = getIndexForDomain(domain); if (index != -1) { @@ -394,7 +401,7 @@ void DomainSettingsModel::setZoomFactor(const QString& domain, double zoomFactor } } -void DomainSettingsModel::insertEntry(const QString &domain) +void DomainSettingsModel::insertEntry(const QString &domain, bool incognito) { if (contains(domain)) { @@ -414,18 +421,21 @@ void DomainSettingsModel::insertEntry(const QString &domain) endInsertRows(); Q_EMIT rowCountChanged(); - QSqlQuery query(m_database); - static QString insertStatement = QLatin1String("INSERT INTO domainsettings (domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, allowNotifications, userAgentId, zoomFactor)" - " VALUES (?, ?, ?, ?, ?, ?, ?);"); - query.prepare(insertStatement); - query.addBindValue(entry.domain); - query.addBindValue(entry.domainWithoutSubdomain); - query.addBindValue(entry.allowCustomUrlSchemes); - query.addBindValue(entry.allowLocation); - query.addBindValue(entry.allowNotifications); - query.addBindValue((entry.userAgentId > 0) ? entry.userAgentId : QVariant()); - query.addBindValue(entry.zoomFactor); - query.exec(); + if (!incognito) + { + QSqlQuery query(m_database); + static QString insertStatement = QLatin1String("INSERT INTO domainsettings (domain, domainWithoutSubdomain, allowCustomUrlSchemes, allowLocation, allowNotifications, userAgentId, zoomFactor)" + " VALUES (?, ?, ?, ?, ?, ?, ?);"); + query.prepare(insertStatement); + query.addBindValue(entry.domain); + query.addBindValue(entry.domainWithoutSubdomain); + query.addBindValue(entry.allowCustomUrlSchemes); + query.addBindValue(entry.allowLocation); + query.addBindValue(entry.allowNotifications); + query.addBindValue((entry.userAgentId > 0) ? entry.userAgentId : QVariant()); + query.addBindValue(entry.zoomFactor); + query.exec(); + } } void DomainSettingsModel::removeEntry(const QString &domain) diff --git a/src/app/domain-settings-model.h b/src/app/domain-settings-model.h index 61c173b04..e84fc8019 100644 --- a/src/app/domain-settings-model.h +++ b/src/app/domain-settings-model.h @@ -76,17 +76,17 @@ class DomainSettingsModel : public QAbstractListModel Q_INVOKABLE bool contains(const QString& domain) const; Q_INVOKABLE void deleteAndResetDataBase(); Q_INVOKABLE bool areCustomUrlSchemesAllowed(const QString& domain); - Q_INVOKABLE void allowCustomUrlSchemes(const QString& domain, bool allow); + Q_INVOKABLE void allowCustomUrlSchemes(const QString& domain, bool allow, bool incognito); Q_INVOKABLE AllowLocationPreference getLocationPreference(const QString& domain) const; Q_INVOKABLE void setLocationPreference(const QString& domain, AllowLocationPreference preference); Q_INVOKABLE NotificationsPreference getNotificationsPreference(const QString& domain) const; - Q_INVOKABLE void setNotificationsPreference(const QString& domain, NotificationsPreference); + Q_INVOKABLE void setNotificationsPreference(const QString& domain, NotificationsPreference, bool incognito); Q_INVOKABLE int getUserAgentId(const QString& domain) const; Q_INVOKABLE void setUserAgentId(const QString& domain, int userAgentId); Q_INVOKABLE void removeUserAgentIdFromAllDomains(int userAgentId); Q_INVOKABLE double getZoomFactor(const QString& domain) const; Q_INVOKABLE void setZoomFactor(const QString& domain, double zoomFactor); - Q_INVOKABLE void insertEntry(const QString& domain); + Q_INVOKABLE void insertEntry(const QString& domain, bool incognito); Q_INVOKABLE void removeEntry(const QString& domain); Q_SIGNALS: diff --git a/src/app/webbrowser/Browser.qml b/src/app/webbrowser/Browser.qml index cba454d9d..b9eaba187 100644 --- a/src/app/webbrowser/Browser.qml +++ b/src/app/webbrowser/Browser.qml @@ -185,7 +185,7 @@ Common.BrowserView { // handle custom schemes if (UrlUtils.hasCustomScheme(url)) { - if (! internal.areCustomUrlSchemesAllowed(currentDomain)) + if (! DomainSettingsModel.areCustomUrlSchemesAllowed(currentDomain)) { request.action = WebEngineNavigationRequest.IgnoreRequest; @@ -193,14 +193,14 @@ Common.BrowserView { allowCustomSchemesDialog.url = url; allowCustomSchemesDialog.domain = currentDomain; allowCustomSchemesDialog.showAllowPermanentlyCheckBox = ! browser.incognito; - allowCustomSchemesDialog.allow.connect(function() {internal.allowCustomUrlSchemes(currentDomain, false); - internal.navigateToUrlAsync(url); - } - ); - allowCustomSchemesDialog.allowPermanently.connect(function() {internal.allowCustomUrlSchemes(currentDomain, true); - internal.navigateToUrlAsync(url); - } - ); + allowCustomSchemesDialog.allow.connect(function() { + DomainSettingsModel.allowCustomUrlSchemes(currentDomain, true, true); + internal.navigateToUrlAsync(url); + }); + allowCustomSchemesDialog.allowPermanently.connect(function() { + DomainSettingsModel.allowCustomUrlSchemes(currentDomain, true, false); + internal.navigateToUrlAsync(url); + }); } return; } @@ -1531,35 +1531,6 @@ Common.BrowserView { return false } - // domains the user has allowed custom protocols for this (incognito) session - property var domainsWithCustomUrlSchemesAllowed: [] - - function allowCustomUrlSchemes(domain, allowPermanently) { - domainsWithCustomUrlSchemesAllowed.push(domain); - - if (allowPermanently) - { - DomainSettingsModel.allowCustomUrlSchemes(domain, true); - } - } - - function areCustomUrlSchemesAllowed(domain) { - - for (var i in domainsWithCustomUrlSchemesAllowed) { - if (domain === domainsWithCustomUrlSchemesAllowed[i]) { - return true; - } - } - - if (DomainSettingsModel.areCustomUrlSchemesAllowed(domain)) - { - domainsWithCustomUrlSchemesAllowed.push(domain); - return true; - } - - return false; - } - function historyGoBack() { if (currentWebview && currentWebview.canGoBack) { internal.resetFocus() diff --git a/src/app/webcontainer/WebViewImplOxide.qml b/src/app/webcontainer/WebViewImplOxide.qml index ba96ef33e..f17aa4c3b 100644 --- a/src/app/webcontainer/WebViewImplOxide.qml +++ b/src/app/webcontainer/WebViewImplOxide.qml @@ -215,35 +215,6 @@ WebappWebview { currentWebview.runJavaScript("window.location.href = '%1';".arg(targetUrl)); } - // domains the user has allowed custom protocols for this (incognito) session - property var domainsWithCustomUrlSchemesAllowed: [] - - function allowCustomUrlSchemes(domain, allowPermanently) { - domainsWithCustomUrlSchemesAllowed.push(domain); - - if (allowPermanently) - { - DomainSettingsModel.allowCustomUrlSchemes(domain, true); - } - } - - function areCustomUrlSchemesAllowed(domain) { - - for (var i in domainsWithCustomUrlSchemesAllowed) { - if (domain === domainsWithCustomUrlSchemesAllowed[i]) { - return true; - } - } - - if (DomainSettingsModel.areCustomUrlSchemesAllowed(domain)) - { - domainsWithCustomUrlSchemesAllowed.push(domain); - return true; - } - - return false; - } - function navigationRequestedDelegate(request) { var url = request.url.toString(); @@ -255,7 +226,7 @@ WebappWebview { // handle custom schemes if (UrlUtils.hasCustomScheme(url)) { - if (! areCustomUrlSchemesAllowed(currentDomain)) + if (! DomainSettingsModel.areCustomUrlSchemesAllowed(currentDomain)) { request.action = WebEngineNavigationRequest.IgnoreRequest; @@ -263,14 +234,14 @@ WebappWebview { allowCustomSchemesDialog.url = url; allowCustomSchemesDialog.domain = currentDomain; allowCustomSchemesDialog.showAllowPermanentlyCheckBox = true; - allowCustomSchemesDialog.allow.connect(function() {allowCustomUrlSchemes(currentDomain, false); - navigateToUrlAsync(url); - } - ); - allowCustomSchemesDialog.allowPermanently.connect(function() {allowCustomUrlSchemes(currentDomain, true); - navigateToUrlAsync(url); - } - ); + allowCustomSchemesDialog.allow.connect(function() { + DomainSettingsModel.allowCustomUrlSchemes(currentDomain, true, true); + navigateToUrlAsync(url); + }); + allowCustomSchemesDialog.allowPermanently.connect(function() { + DomainSettingsModel.allowCustomUrlSchemes(currentDomain, true, false); + navigateToUrlAsync(url); + }); } return; } From 1b6d8ca0d18e96a56e45f913a0109cf9d5eceb62 Mon Sep 17 00:00:00 2001 From: Chris Clime Date: Sun, 8 Aug 2021 18:43:37 +0200 Subject: [PATCH 9/9] openUrls() if a tab with that url is already open, switch to it instead of opening a new one --- src/app/webbrowser/Browser.qml | 13 +++++++++++++ src/app/webbrowser/morph-browser.qml | 20 ++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/app/webbrowser/Browser.qml b/src/app/webbrowser/Browser.qml index b9eaba187..81d6be797 100644 --- a/src/app/webbrowser/Browser.qml +++ b/src/app/webbrowser/Browser.qml @@ -63,6 +63,10 @@ Common.BrowserView { browser.bindExistingTab(tab); } + function findTabIndexWithUrl(url) { + return internal.findTabIndexWithUrl(url); + } + function moveTab(from, to) { if (from === to || from < 0 || from >= count @@ -1432,6 +1436,15 @@ Common.BrowserView { } } + function findTabIndexWithUrl(url) { + var i; + for (i = 0; i < tabsModel.count; i++) { + if (tabsModel.get(i).url.toString() === url.toString()) + return i; + } + return -1; + } + function undoCloseTab() { if (!incognito && closedTabHistory.length > 0) { // For triggering property change on closedTabHistory diff --git a/src/app/webbrowser/morph-browser.qml b/src/app/webbrowser/morph-browser.qml index 6f7f0fae0..db500c91e 100644 --- a/src/app/webbrowser/morph-browser.qml +++ b/src/app/webbrowser/morph-browser.qml @@ -85,17 +85,25 @@ QtObject { function openUrls(urls, newWindow, incognito) { var window = getLastActiveWindow(incognito) if (!window || newWindow) { - window = windowFactory.createObject(null, {"incognito": incognito}) + window = windowFactory.createObject(null, {"incognito": incognito}); } for (var i in urls) { - window.addTab(urls[i]).load() + var tabIndexWithUrl = window.tabsModel.findTabIndexWithUrl(urls[i]); + + if (tabIndexWithUrl >= 0) { + window.tabsModel.selectTab(tabIndexWithUrl); + } + else { + window.addTab(urls[i]).load(); + window.tabsModel.currentIndex = window.tabsModel.count - 1; + } } if (window.tabsModel.count === 0) { - window.addTab().load() + window.addTab().load(); + window.tabsModel.currentIndex = window.tabsModel.count - 1; } - window.tabsModel.currentIndex = window.tabsModel.count - 1 - window.show() - window.requestActivate() + window.show(); + window.requestActivate(); } property var windowFactory: Component {